mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Make virtual dom cloneable (#786)
This commit is contained in:
parent
3ffde502a7
commit
84a9ed0e09
@ -240,7 +240,7 @@ Properties are also pure Rust types with strict type-checking during the compila
|
||||
```rust
|
||||
// my_button.rs
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
#[derive(Clone, Properties, PartialEq)]
|
||||
pub struct Properties {
|
||||
pub hidden: bool,
|
||||
#[props(required)]
|
||||
|
||||
@ -119,16 +119,9 @@ impl ToTokens for HtmlComponent {
|
||||
};
|
||||
|
||||
let set_children = if !children.is_empty() {
|
||||
let children_len = children.len();
|
||||
quote! {
|
||||
.children(::yew::html::ChildrenRenderer::new(
|
||||
#children_len,
|
||||
::std::boxed::Box::new(move || {
|
||||
#[allow(unused_must_use)]
|
||||
|| -> ::std::vec::Vec<_> {
|
||||
vec![#(#children.into(),)*]
|
||||
}
|
||||
}()),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -172,7 +172,7 @@ impl ToTokens for HtmlTag {
|
||||
#(#set_classes)*
|
||||
#(#set_node_ref)*
|
||||
#vtag.add_attributes(vec![#(#attr_pairs),*]);
|
||||
#vtag.add_listeners(vec![#(::std::boxed::Box::new(#listeners)),*]);
|
||||
#vtag.add_listeners(vec![#(::std::rc::Rc::new(#listeners)),*]);
|
||||
#vtag.add_children(vec![#(#children),*]);
|
||||
::yew::virtual_dom::VNode::from(#vtag)
|
||||
}});
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
//! link: ComponentLink<Self>,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Properties)]
|
||||
//! #[derive(Clone, Properties)]
|
||||
//! struct Props {
|
||||
//! #[props(required)]
|
||||
//! prop: String,
|
||||
|
||||
@ -12,7 +12,7 @@ pub enum Msg {
|
||||
ChildClicked,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub limit: u32,
|
||||
#[props(required)]
|
||||
|
||||
@ -10,7 +10,7 @@ pub enum Msg {
|
||||
Clicked,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub title: String,
|
||||
#[props(required)]
|
||||
|
||||
@ -24,7 +24,7 @@ pub enum Msg {
|
||||
Increase,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub initial: u32,
|
||||
pub color: Color,
|
||||
|
||||
@ -5,4 +5,6 @@ authors = ["Justin Starry <justin.starry@icloud.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
web_logger = "0.2"
|
||||
yew = { path = "../.." }
|
||||
|
||||
77
examples/nested_list/src/app.rs
Normal file
77
examples/nested_list/src/app.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use super::header::ListHeader;
|
||||
use super::item::ListItem;
|
||||
use super::list::List;
|
||||
use super::{Hovered, WeakComponentLink};
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct App {
|
||||
link: ComponentLink<Self>,
|
||||
hovered: Hovered,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Hover(Hovered),
|
||||
}
|
||||
|
||||
impl Component for App {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
App {
|
||||
link,
|
||||
hovered: Hovered::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Hover(hovered) => self.hovered = hovered,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let on_hover = &self.link.callback(Msg::Hover);
|
||||
let onmouseenter = &self.link.callback(|_| Msg::Hover(Hovered::None));
|
||||
let list_link = &WeakComponentLink::<List>::default();
|
||||
let sub_list_link = &WeakComponentLink::<List>::default();
|
||||
html! {
|
||||
<div class="main" onmouseenter=onmouseenter>
|
||||
<h1>{ "Nested List Demo" }</h1>
|
||||
<List on_hover=on_hover weak_link=list_link>
|
||||
<ListHeader text="Calling all Rusties!" on_hover=on_hover list_link=list_link />
|
||||
<ListItem name="Rustin" on_hover=on_hover />
|
||||
<ListItem hide={true} name="Rustaroo" on_hover=on_hover />
|
||||
<ListItem name="Rustifer" on_hover=on_hover>
|
||||
<div class="sublist">{"Sublist!"}</div>
|
||||
{
|
||||
html! {
|
||||
<List on_hover=on_hover weak_link=sub_list_link>
|
||||
<ListHeader text="Sub Rusties!" on_hover=on_hover list_link=sub_list_link/>
|
||||
<ListItem name="Sub Rustin" on_hover=on_hover />
|
||||
<ListItem hide={true} name="Sub Rustaroo" on_hover=on_hover />
|
||||
<ListItem name="Sub Rustifer" on_hover=on_hover />
|
||||
</List>
|
||||
}
|
||||
}
|
||||
</ListItem>
|
||||
</List>
|
||||
{self.view_last_hovered()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn view_last_hovered(&self) -> Html {
|
||||
html! {
|
||||
<div class="last-hovered">
|
||||
{ "Last hovered:"}
|
||||
<span class="last-hovered-text">
|
||||
{ &self.hovered }
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,19 @@
|
||||
use crate::Hovered;
|
||||
use super::list::{List, Msg as ListMsg};
|
||||
use super::{Hovered, WeakComponentLink};
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct ListHeader {
|
||||
props: Props,
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
pub on_hover: Callback<Hovered>,
|
||||
#[props(required)]
|
||||
pub text: String,
|
||||
#[props(required)]
|
||||
pub list_link: WeakComponentLink<List>,
|
||||
}
|
||||
|
||||
impl Component for ListHeader {
|
||||
@ -26,9 +29,11 @@ impl Component for ListHeader {
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let list_link = self.props.list_link.borrow().clone().unwrap();
|
||||
let onclick = list_link.callback(|_| ListMsg::HeaderClick);
|
||||
let onmouseover = self.props.on_hover.reform(|_| Hovered::Header);
|
||||
html! {
|
||||
<div class="list-header" onmouseover=onmouseover>
|
||||
<div class="list-header" onmouseover=onmouseover onclick=onclick>
|
||||
{ &self.props.text }
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ pub struct ListItem {
|
||||
props: Props,
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
pub hide: bool,
|
||||
#[props(required)]
|
||||
@ -30,7 +30,10 @@ impl Component for ListItem {
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let name = self.props.name.clone();
|
||||
let onmouseover = self.props.on_hover.reform(move |_| Hovered::Item(name.clone()));
|
||||
let onmouseover = self
|
||||
.props
|
||||
.on_hover
|
||||
.reform(move |_| Hovered::Item(name.clone()));
|
||||
html! {
|
||||
<div class="list-item" onmouseover=onmouseover>
|
||||
{ &self.props.name }
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
#![recursion_limit = "128"]
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
mod app;
|
||||
mod header;
|
||||
mod item;
|
||||
mod list;
|
||||
|
||||
use header::ListHeader;
|
||||
use item::ListItem;
|
||||
use list::List;
|
||||
use yew::prelude::*;
|
||||
pub use app::App;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
|
||||
pub struct Model {
|
||||
link: ComponentLink<Self>,
|
||||
hovered: Hovered,
|
||||
}
|
||||
use std::rc::Rc;
|
||||
use yew::html::ComponentLink;
|
||||
pub type WeakComponentLink<COMP> = Rc<RefCell<Option<ComponentLink<COMP>>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Hovered {
|
||||
@ -23,60 +20,6 @@ pub enum Hovered {
|
||||
None,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Hover(Hovered),
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Model { link,
|
||||
hovered: Hovered::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Hover(hovered) => self.hovered = hovered,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let on_hover = self.link.callback(Msg::Hover);
|
||||
|
||||
html! {
|
||||
<div class="main">
|
||||
<h1>{ "Nested List Demo" }</h1>
|
||||
<List on_hover=on_hover.clone()>
|
||||
<ListHeader text="Calling all Rusties!" on_hover=on_hover.clone() />
|
||||
<ListItem name="Rustin" on_hover=on_hover.clone() />
|
||||
<ListItem hide={true} name="Rustaroo" on_hover=on_hover.clone() />
|
||||
<ListItem name="Rustifer" on_hover=on_hover.clone()>
|
||||
<span>{"Hello!"}</span>
|
||||
</ListItem>
|
||||
</List>
|
||||
{self.view_last_hovered()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_last_hovered(&self) -> Html {
|
||||
html! {
|
||||
<div class="last-hovered">
|
||||
{ "Last hovered:"}
|
||||
<span class="last-hovered-text">
|
||||
{ &self.hovered }
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Hovered {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
use crate::{header::Props as HeaderProps, ListHeader};
|
||||
use crate::{item::Props as ItemProps, ListItem};
|
||||
use crate::Hovered;
|
||||
use super::{Hovered, WeakComponentLink};
|
||||
use crate::{header::ListHeader, header::Props as HeaderProps};
|
||||
use crate::{item::ListItem, item::Props as ItemProps};
|
||||
use yew::html::{ChildrenRenderer, NodeRef};
|
||||
use yew::prelude::*;
|
||||
use yew::virtual_dom::{VChild, VComp, VNode};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Variants {
|
||||
Item(<ListItem as Component>::Properties),
|
||||
Header(<ListHeader as Component>::Properties),
|
||||
@ -22,40 +23,58 @@ impl From<HeaderProps> for Variants {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ListVariant {
|
||||
props: Variants,
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
pub children: ChildrenRenderer<ListVariant>,
|
||||
#[props(required)]
|
||||
pub on_hover: Callback<Hovered>,
|
||||
#[props(required)]
|
||||
pub weak_link: WeakComponentLink<List>,
|
||||
}
|
||||
|
||||
pub struct List {
|
||||
props: Props,
|
||||
inactive: bool,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
HeaderClick,
|
||||
}
|
||||
|
||||
impl Component for List {
|
||||
type Message = ();
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
||||
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
List { props }
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
*props.weak_link.borrow_mut() = Some(link);
|
||||
List {
|
||||
props,
|
||||
inactive: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
||||
false
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::HeaderClick => {
|
||||
self.inactive = !self.inactive;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let inactive = if self.inactive { "inactive" } else { "" };
|
||||
let onmouseover = self.props.on_hover.reform(|_| Hovered::List);
|
||||
let onmouseout = self.props.on_hover.reform(|_| Hovered::None);
|
||||
html! {
|
||||
<div class="list-container" onmouseout=onmouseout onmouseover=onmouseover>
|
||||
<div class="list">
|
||||
<div class=vec!["list", inactive]>
|
||||
{self.view_header()}
|
||||
<div class="items">
|
||||
{self.view_items()}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
fn main() {
|
||||
yew::start_app::<nested_list::Model>();
|
||||
web_logger::init();
|
||||
yew::start_app::<nested_list::App>();
|
||||
}
|
||||
|
||||
@ -35,10 +35,15 @@ html, body {
|
||||
min-width: 30vw;
|
||||
}
|
||||
|
||||
.list.inactive {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
background: #FEECAA;
|
||||
border-bottom: 1px solid #666;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-header:hover {
|
||||
@ -65,6 +70,7 @@ html, body {
|
||||
border-radius: 3px;
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.last-hovered {
|
||||
@ -75,3 +81,17 @@ html, body {
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.list-item-details .list-container {
|
||||
margin-top: 0px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.sublist {
|
||||
display: inline-block;
|
||||
background: #FFF;
|
||||
border: 1px solid #666;
|
||||
border-radius: 3px;
|
||||
margin: 5px;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ pub struct InputComponent {
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
pub on_hover: Callback<()>,
|
||||
|
||||
@ -64,7 +64,7 @@ pub type Html = VNode;
|
||||
/// In this example, the `Wrapper` component is used to wrap other elements.
|
||||
/// ```
|
||||
///# use yew::{Children, Html, Properties, Component, ComponentLink, html};
|
||||
///# #[derive(Properties)]
|
||||
///# #[derive(Clone, Properties)]
|
||||
///# struct WrapperProps {
|
||||
///# children: Children,
|
||||
///# }
|
||||
@ -92,7 +92,7 @@ pub type Html = VNode;
|
||||
/// children property can be used to render the wrapped elements.
|
||||
/// ```
|
||||
///# use yew::{Children, Html, Properties, Renderable, Component, ComponentLink, html};
|
||||
/// #[derive(Properties)]
|
||||
/// #[derive(Clone, Properties)]
|
||||
/// struct WrapperProps {
|
||||
/// children: Children,
|
||||
/// }
|
||||
@ -124,7 +124,7 @@ pub type Children = ChildrenRenderer<Html>;
|
||||
/// ```
|
||||
///# use yew::{html, Component, Renderable, Html, ComponentLink, ChildrenWithProps, Properties};
|
||||
///#
|
||||
///# #[derive(Properties)]
|
||||
///# #[derive(Clone, Properties)]
|
||||
///# struct ListProps {
|
||||
///# children: ChildrenWithProps<ListItem>,
|
||||
///# }
|
||||
@ -136,7 +136,7 @@ pub type Children = ChildrenRenderer<Html>;
|
||||
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# #[derive(Properties)]
|
||||
///# #[derive(Clone, Properties)]
|
||||
///# struct ListItemProps {
|
||||
///# value: String
|
||||
///# }
|
||||
@ -166,7 +166,7 @@ pub type Children = ChildrenRenderer<Html>;
|
||||
/// ```
|
||||
///# use yew::{html, Component, Html, ChildrenWithProps, ComponentLink, Properties};
|
||||
///#
|
||||
/// #[derive(Properties)]
|
||||
/// #[derive(Clone, Properties)]
|
||||
/// struct ListProps {
|
||||
/// children: ChildrenWithProps<ListItem>,
|
||||
/// }
|
||||
@ -188,7 +188,7 @@ pub type Children = ChildrenRenderer<Html>;
|
||||
/// }
|
||||
/// }
|
||||
///#
|
||||
///# #[derive(Properties)]
|
||||
///# #[derive(Clone, Properties)]
|
||||
///# struct ListItemProps {
|
||||
///# value: String
|
||||
///# }
|
||||
@ -205,30 +205,33 @@ pub type Children = ChildrenRenderer<Html>;
|
||||
pub type ChildrenWithProps<CHILD> = ChildrenRenderer<VChild<CHILD>>;
|
||||
|
||||
/// A type used for rendering children html.
|
||||
#[derive(Clone)]
|
||||
pub struct ChildrenRenderer<T> {
|
||||
len: usize,
|
||||
boxed_render: Box<dyn Fn() -> Vec<T>>,
|
||||
children: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> ChildrenRenderer<T> {
|
||||
impl<T> ChildrenRenderer<T>
|
||||
where
|
||||
T: Clone + Into<VNode>,
|
||||
{
|
||||
/// Create children
|
||||
pub fn new(len: usize, boxed_render: Box<dyn Fn() -> Vec<T>>) -> Self {
|
||||
Self { len, boxed_render }
|
||||
pub fn new(children: Vec<T>) -> Self {
|
||||
Self { children }
|
||||
}
|
||||
|
||||
/// Children list is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
self.children.is_empty()
|
||||
}
|
||||
|
||||
/// Number of children elements
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
self.children.len()
|
||||
}
|
||||
|
||||
/// Build children components and return `Vec`
|
||||
pub fn to_vec(&self) -> Vec<T> {
|
||||
(&self.boxed_render)()
|
||||
self.children.clone()
|
||||
}
|
||||
|
||||
/// Render children components and return `Iterator`
|
||||
@ -239,13 +242,8 @@ impl<T> ChildrenRenderer<T> {
|
||||
|
||||
impl<T> Default for ChildrenRenderer<T> {
|
||||
fn default() -> Self {
|
||||
// False positive: https://github.com/rust-lang/rust-clippy/issues/4002
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let boxed_render = Box::new(|| Vec::new());
|
||||
|
||||
Self {
|
||||
len: 0,
|
||||
boxed_render,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -258,7 +256,7 @@ impl<T> fmt::Debug for ChildrenRenderer<T> {
|
||||
|
||||
impl<T> Renderable for ChildrenRenderer<T>
|
||||
where
|
||||
T: Into<VNode>,
|
||||
T: Clone + Into<VNode>,
|
||||
{
|
||||
fn render(&self) -> Html {
|
||||
VList::new_with_children(self.iter().map(|c| c.into()).collect()).into()
|
||||
@ -338,7 +336,7 @@ impl<COMP: Component> Renderable for COMP {
|
||||
}
|
||||
|
||||
/// Trait for building properties for a component
|
||||
pub trait Properties {
|
||||
pub trait Properties: Clone {
|
||||
/// Builder that will be used to construct properties
|
||||
type Builder;
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ pub mod vtext;
|
||||
use indexmap::set::IndexSet;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use stdweb::web::{Element, EventListenerHandle, Node};
|
||||
|
||||
pub use self::vcomp::{VChild, VComp};
|
||||
@ -33,7 +34,7 @@ impl fmt::Debug for dyn Listener {
|
||||
}
|
||||
|
||||
/// A list of event listeners.
|
||||
type Listeners = Vec<Box<dyn Listener>>;
|
||||
type Listeners = Vec<Rc<dyn Listener>>;
|
||||
|
||||
/// A map of attributes.
|
||||
type Attributes = HashMap<String, String>;
|
||||
|
||||
@ -9,7 +9,7 @@ use std::rc::Rc;
|
||||
use stdweb::web::{document, Element, INode, Node, TextNode};
|
||||
|
||||
/// The method generates an instance of a component.
|
||||
type Generator = dyn FnOnce(GeneratorType) -> Mounted;
|
||||
type Generator = dyn Fn(GeneratorType) -> Mounted;
|
||||
|
||||
/// Components can be generated by mounting or by overwriting an old component.
|
||||
enum GeneratorType {
|
||||
@ -23,6 +23,15 @@ pub struct VComp {
|
||||
state: Rc<RefCell<MountState>>,
|
||||
}
|
||||
|
||||
impl Clone for VComp {
|
||||
fn clone(&self) -> Self {
|
||||
VComp {
|
||||
type_id: self.type_id,
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A virtual child component.
|
||||
pub struct VChild<COMP: Component> {
|
||||
/// The component properties
|
||||
@ -31,6 +40,15 @@ pub struct VChild<COMP: Component> {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl<COMP: Component> Clone for VChild<COMP> {
|
||||
fn clone(&self) -> Self {
|
||||
VChild {
|
||||
props: self.props.clone(),
|
||||
node_ref: self.node_ref.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<COMP> VChild<COMP>
|
||||
where
|
||||
COMP: Component,
|
||||
@ -83,21 +101,21 @@ impl VComp {
|
||||
element,
|
||||
Some(VNode::VRef(dummy_node.into())),
|
||||
node_ref.clone(),
|
||||
props,
|
||||
props.clone(),
|
||||
);
|
||||
|
||||
Mounted {
|
||||
node_ref,
|
||||
node_ref: node_ref.clone(),
|
||||
scope: scope.clone().into(),
|
||||
destroyer: Box::new(move || scope.destroy()),
|
||||
}
|
||||
}
|
||||
GeneratorType::Overwrite(hidden_scope) => {
|
||||
let mut scope: Scope<COMP> = hidden_scope.into();
|
||||
scope.update(ComponentUpdate::Properties(props));
|
||||
scope.update(ComponentUpdate::Properties(props.clone()));
|
||||
|
||||
Mounted {
|
||||
node_ref,
|
||||
node_ref: node_ref.clone(),
|
||||
scope: scope.clone().into(),
|
||||
destroyer: Box::new(move || scope.destroy()),
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ use std::ops::{Deref, DerefMut};
|
||||
use stdweb::web::{Element, Node};
|
||||
|
||||
/// This struct represents a fragment of the Virtual DOM tree.
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct VList {
|
||||
/// The list of children nodes.
|
||||
pub children: Vec<VNode>,
|
||||
|
||||
@ -8,6 +8,7 @@ use std::iter::FromIterator;
|
||||
use stdweb::web::{Element, INode, Node};
|
||||
|
||||
/// Bind virtual element to a DOM reference.
|
||||
#[derive(Clone)]
|
||||
pub enum VNode {
|
||||
/// A bind between `VTag` and `Element`.
|
||||
VTag(Box<VTag>),
|
||||
|
||||
@ -8,6 +8,7 @@ use log::warn;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::PartialEq;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use stdweb::unstable::TryFrom;
|
||||
use stdweb::web::html_element::InputElement;
|
||||
use stdweb::web::html_element::TextAreaElement;
|
||||
@ -56,6 +57,24 @@ pub struct VTag {
|
||||
captured: Vec<EventListenerHandle>,
|
||||
}
|
||||
|
||||
impl Clone for VTag {
|
||||
fn clone(&self) -> Self {
|
||||
VTag {
|
||||
tag: self.tag.clone(),
|
||||
reference: None,
|
||||
listeners: self.listeners.clone(),
|
||||
attributes: self.attributes.clone(),
|
||||
children: self.children.clone(),
|
||||
classes: self.classes.clone(),
|
||||
value: self.value.clone(),
|
||||
kind: self.kind.clone(),
|
||||
checked: self.checked,
|
||||
node_ref: self.node_ref.clone(),
|
||||
captured: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VTag {
|
||||
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
|
||||
pub fn new<S: Into<Cow<'static, str>>>(tag: S) -> Self {
|
||||
@ -161,14 +180,14 @@ impl VTag {
|
||||
/// Adds new listener to the node.
|
||||
/// It's boxed because we want to keep it in a single list.
|
||||
/// Later `Listener::attach` will attach an actual listener to a DOM node.
|
||||
pub fn add_listener(&mut self, listener: Box<dyn Listener>) {
|
||||
pub fn add_listener(&mut self, listener: Rc<dyn Listener>) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
|
||||
/// Adds new listeners to the node.
|
||||
/// They are boxed because we want to keep them in a single list.
|
||||
/// Later `Listener::attach` will attach an actual listener to a DOM node.
|
||||
pub fn add_listeners(&mut self, listeners: Vec<Box<dyn Listener>>) {
|
||||
pub fn add_listeners(&mut self, listeners: Vec<Rc<dyn Listener>>) {
|
||||
for listener in listeners {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ use stdweb::web::{document, Element, INode, Node, TextNode};
|
||||
/// A type for a virtual
|
||||
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
|
||||
/// representation.
|
||||
#[derive(Clone)]
|
||||
pub struct VText {
|
||||
/// Contains a text of the node.
|
||||
pub text: String,
|
||||
|
||||
@ -4,8 +4,9 @@ use yew::prelude::*;
|
||||
|
||||
mod t1 {
|
||||
use super::*;
|
||||
#[derive(Clone)]
|
||||
struct Value;
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
// ERROR: optional params must implement default
|
||||
value: Value,
|
||||
@ -14,7 +15,7 @@ mod t1 {
|
||||
|
||||
mod t2 {
|
||||
use super::*;
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
// ERROR: optional is not a tag
|
||||
#[props(optional)]
|
||||
@ -24,7 +25,7 @@ mod t2 {
|
||||
|
||||
mod t3 {
|
||||
use super::*;
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
value: String,
|
||||
@ -37,7 +38,7 @@ mod t3 {
|
||||
|
||||
mod t4 {
|
||||
use super::*;
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
b: i32,
|
||||
#[props(required)]
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
error: expected `props(required)`
|
||||
--> $DIR/fail.rs:20:11
|
||||
--> $DIR/fail.rs:21:11
|
||||
|
|
||||
20 | #[props(optional)]
|
||||
21 | #[props(optional)]
|
||||
| ^^^^^
|
||||
|
||||
error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfied
|
||||
--> $DIR/fail.rs:8:14
|
||||
--> $DIR/fail.rs:9:21
|
||||
|
|
||||
8 | #[derive(Properties)]
|
||||
9 | #[derive(Clone, Properties)]
|
||||
| ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `t1::Value`
|
||||
|
|
||||
= note: required by `std::default::Default::default`
|
||||
|
||||
error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>` in the current scope
|
||||
--> $DIR/fail.rs:34:26
|
||||
--> $DIR/fail.rs:35:26
|
||||
|
|
||||
27 | #[derive(Properties)]
|
||||
28 | #[derive(Clone, Properties)]
|
||||
| - method `build` not found for this
|
||||
...
|
||||
34 | Props::builder().build();
|
||||
35 | Props::builder().build();
|
||||
| ^^^^^ method not found in `t3::PropsBuilder<t3::PropsBuilderStep_missing_required_prop_value>`
|
||||
|
||||
error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::PropsBuilderStep_missing_required_prop_a>` in the current scope
|
||||
--> $DIR/fail.rs:48:26
|
||||
--> $DIR/fail.rs:49:26
|
||||
|
|
||||
40 | #[derive(Properties)]
|
||||
41 | #[derive(Clone, Properties)]
|
||||
| - method `b` not found for this
|
||||
...
|
||||
48 | Props::builder().b(1).a(2).build();
|
||||
49 | Props::builder().b(1).a(2).build();
|
||||
| ^ help: there is a method with a similar name: `a`
|
||||
|
||||
@ -5,8 +5,8 @@ use yew::prelude::*;
|
||||
mod t1 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props<T: Default> {
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props<T: Clone + Default> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
@ -19,9 +19,10 @@ mod t1 {
|
||||
mod t2 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Value;
|
||||
#[derive(Properties)]
|
||||
pub struct Props<T> {
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props<T: Clone> {
|
||||
#[props(required)]
|
||||
value: T,
|
||||
}
|
||||
@ -34,7 +35,7 @@ mod t2 {
|
||||
mod t3 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
b: i32,
|
||||
@ -50,10 +51,10 @@ mod t3 {
|
||||
mod t4 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props<T>
|
||||
where
|
||||
T: Default,
|
||||
T: Clone + Default,
|
||||
{
|
||||
value: T,
|
||||
}
|
||||
@ -67,8 +68,8 @@ mod t4 {
|
||||
mod t5 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props<'a, T: Default + 'a> {
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct Props<'a, T: Clone + Default + 'a> {
|
||||
static_value: &'static str,
|
||||
#[props(required)]
|
||||
value: &'a T,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
#[derive(Clone, Properties, PartialEq)]
|
||||
pub struct ChildProperties {
|
||||
pub string: String,
|
||||
#[props(required)]
|
||||
@ -27,7 +27,7 @@ impl Component for Child {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[derive(Clone, Properties)]
|
||||
pub struct ChildContainerProperties {
|
||||
pub children: ChildrenWithProps<Child>,
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ error[E0609]: no field `unknown` on type `ChildProperties`
|
||||
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||
--> $DIR/html-component-fail.rs:65:20
|
||||
|
|
||||
5 | #[derive(Properties, PartialEq)]
|
||||
5 | #[derive(Clone, Properties, PartialEq)]
|
||||
| - method `unknown` not found for this
|
||||
...
|
||||
65 | html! { <Child unknown="unknown" /> };
|
||||
@ -170,7 +170,7 @@ help: you can convert an `u32` to `i32` and panic if the converted value wouldn'
|
||||
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||
--> $DIR/html-component-fail.rs:73:20
|
||||
|
|
||||
5 | #[derive(Properties, PartialEq)]
|
||||
5 | #[derive(Clone, Properties, PartialEq)]
|
||||
| - method `string` not found for this
|
||||
...
|
||||
73 | html! { <Child string="abc" /> };
|
||||
@ -179,7 +179,7 @@ error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<Ch
|
||||
error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||
--> $DIR/html-component-fail.rs:77:5
|
||||
|
|
||||
5 | #[derive(Properties, PartialEq)]
|
||||
5 | #[derive(Clone, Properties, PartialEq)]
|
||||
| - method `children` not found for this
|
||||
...
|
||||
77 | html! { <Child>{ "Not allowed" }</Child> };
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
use yew::prelude::*;
|
||||
use yew::html::ChildrenRenderer;
|
||||
|
||||
#[derive(Properties, Default, PartialEq)]
|
||||
#[derive(Clone, Properties, Default, PartialEq)]
|
||||
pub struct ChildProperties {
|
||||
pub string: String,
|
||||
#[props(required)]
|
||||
@ -30,7 +30,7 @@ impl Component for Child {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Default)]
|
||||
#[derive(Clone, Properties, Default)]
|
||||
pub struct ContainerProperties {
|
||||
#[props(required)]
|
||||
pub int: i32,
|
||||
@ -55,7 +55,7 @@ impl Component for Container {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Default)]
|
||||
#[derive(Clone, Properties, Default)]
|
||||
pub struct ChildContainerProperties {
|
||||
#[props(required)]
|
||||
pub int: i32,
|
||||
@ -165,12 +165,7 @@ fn compile_pass() {
|
||||
</scoped::Container>
|
||||
|
||||
<Container int=1 children=ChildrenRenderer::new(
|
||||
1,
|
||||
::std::boxed::Box::new(move || {
|
||||
|| -> ::std::vec::Vec<_> {
|
||||
vec![html!{ "String" }]
|
||||
}
|
||||
}()),
|
||||
) />
|
||||
</>
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
#[derive(Clone, Properties, PartialEq)]
|
||||
pub struct TestProperties {
|
||||
pub string: String,
|
||||
pub int: i32,
|
||||
|
||||
@ -8,7 +8,7 @@ wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
struct Comp;
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
struct Props {
|
||||
field_1: u32,
|
||||
field_2: u32,
|
||||
@ -33,19 +33,19 @@ impl Component for Comp {
|
||||
|
||||
#[test]
|
||||
fn set_properties_to_component() {
|
||||
let _ = html! {
|
||||
html! {
|
||||
<Comp />
|
||||
};
|
||||
|
||||
let _ = html! {
|
||||
html! {
|
||||
<Comp field_1=1 />
|
||||
};
|
||||
|
||||
let _ = html! {
|
||||
html! {
|
||||
<Comp field_2=2 />
|
||||
};
|
||||
|
||||
let _ = html! {
|
||||
html! {
|
||||
<Comp field_1=1 field_2=2 />
|
||||
};
|
||||
|
||||
@ -54,7 +54,7 @@ fn set_properties_to_component() {
|
||||
field_2: 1,
|
||||
};
|
||||
|
||||
let _ = html! {
|
||||
html! {
|
||||
<Comp with props />
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user