mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Merge #589
589: Allow components to accept children elements r=jstarry a=jstarry Fixes: https://github.com/yewstack/yew/issues/537 #### Terminology - (B) Base component that renders components nested inside each other - (P) Parent component that has a `children` property and can render those children - (C) Child component that is nested inside parent and included inside the Parent's `children` #### Todo - [x] Add example for nested components - [x] Support arbitrary html nested inside component tags - [x] Support nested components inside component tags - [x] Allow modifying & accessing (C) props when rendering (P) - [x] Allow filtering (C) components when rendering (P) - [x] Children prop be required or optional - [x] Clean up nested component example - [x] Fix parser for generic component type - [x] Write tests - [x] Update documentation and README - [x] ~~Investigate passing required properties from (P) -> (C)~~ - [x] ~~Allow sending messages from (C) -> (B)~~ Co-authored-by: Justin Starry <jstarry@users.noreply.github.com>
This commit is contained in:
commit
24d39f97e3
@ -72,6 +72,7 @@ members = [
|
|||||||
"examples/minimal",
|
"examples/minimal",
|
||||||
"examples/mount_point",
|
"examples/mount_point",
|
||||||
"examples/multi_thread",
|
"examples/multi_thread",
|
||||||
|
"examples/nested_list",
|
||||||
"examples/npm_and_rest",
|
"examples/npm_and_rest",
|
||||||
"examples/routing",
|
"examples/routing",
|
||||||
"examples/server",
|
"examples/server",
|
||||||
|
|||||||
@ -218,6 +218,9 @@ html! {
|
|||||||
<nav class="menu">
|
<nav class="menu">
|
||||||
<MyButton title="First Button" />
|
<MyButton title="First Button" />
|
||||||
<MyButton title="Second Button "/>
|
<MyButton title="Second Button "/>
|
||||||
|
<MyList name="Grocery List">
|
||||||
|
<MyListItem text="Apples" />
|
||||||
|
</MyList>
|
||||||
</nav>
|
</nav>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -170,13 +170,29 @@ impl TryFrom<Field> for PropField {
|
|||||||
|
|
||||||
impl PartialOrd for PropField {
|
impl PartialOrd for PropField {
|
||||||
fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
|
||||||
self.name.partial_cmp(&other.name)
|
if self.name == other.name {
|
||||||
|
Some(Ordering::Equal)
|
||||||
|
} else if self.name == "children" {
|
||||||
|
Some(Ordering::Greater)
|
||||||
|
} else if other.name == "children" {
|
||||||
|
Some(Ordering::Less)
|
||||||
|
} else {
|
||||||
|
self.name.partial_cmp(&other.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for PropField {
|
impl Ord for PropField {
|
||||||
fn cmp(&self, other: &PropField) -> Ordering {
|
fn cmp(&self, other: &PropField) -> Ordering {
|
||||||
self.name.cmp(&other.name)
|
if self.name == other.name {
|
||||||
|
Ordering::Equal
|
||||||
|
} else if self.name == "children" {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if other.name == "children" {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
self.name.cmp(&other.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,53 +1,88 @@
|
|||||||
use super::HtmlProp;
|
use super::HtmlProp;
|
||||||
use super::HtmlPropSuffix;
|
use super::HtmlPropSuffix;
|
||||||
|
use super::HtmlTreeNested;
|
||||||
use crate::PeekValue;
|
use crate::PeekValue;
|
||||||
use boolinator::Boolinator;
|
use boolinator::Boolinator;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use std::cmp::Ordering;
|
||||||
use syn::buffer::Cursor;
|
use syn::buffer::Cursor;
|
||||||
use syn::parse;
|
use syn::parse;
|
||||||
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Ident, Token, Type};
|
use syn::{Ident, Path, PathArguments, PathSegment, Token, Type, TypePath};
|
||||||
|
|
||||||
pub struct HtmlComponent(HtmlComponentInner);
|
pub struct HtmlComponent {
|
||||||
|
ty: Type,
|
||||||
|
props: Option<Props>,
|
||||||
|
children: Vec<HtmlTreeNested>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PeekValue<()> for HtmlComponent {
|
impl PeekValue<()> for HtmlComponent {
|
||||||
fn peek(cursor: Cursor) -> Option<()> {
|
fn peek(cursor: Cursor) -> Option<()> {
|
||||||
let (punct, cursor) = cursor.punct()?;
|
let (punct, cursor) = cursor.punct()?;
|
||||||
(punct.as_char() == '<').as_option()?;
|
(punct.as_char() == '<').as_option()?;
|
||||||
|
|
||||||
HtmlComponent::peek_type(cursor)
|
HtmlComponent::peek_type(cursor)?;
|
||||||
|
Some(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for HtmlComponent {
|
impl Parse for HtmlComponent {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
let lt = input.parse::<Token![<]>()?;
|
if HtmlComponentClose::peek(input.cursor()).is_some() {
|
||||||
let HtmlPropSuffix { stream, div, gt } = input.parse()?;
|
return match input.parse::<HtmlComponentClose>() {
|
||||||
if div.is_none() {
|
Ok(close) => Err(syn::Error::new_spanned(
|
||||||
return Err(syn::Error::new_spanned(
|
close,
|
||||||
HtmlComponentTag { lt, gt },
|
"this close tag has no corresponding open tag",
|
||||||
"expected component tag be of form `< .. />`",
|
)),
|
||||||
));
|
Err(err) => Err(err),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
match parse(stream) {
|
let open = input.parse::<HtmlComponentOpen>()?;
|
||||||
Ok(comp) => Ok(HtmlComponent(comp)),
|
// Return early if it's a self-closing tag
|
||||||
Err(err) => {
|
if open.div.is_some() {
|
||||||
if err.to_string().starts_with("unexpected end of input") {
|
return Ok(HtmlComponent {
|
||||||
Err(syn::Error::new_spanned(div, err.to_string()))
|
ty: open.ty,
|
||||||
} else {
|
props: open.props,
|
||||||
Err(err)
|
children: Vec::new(),
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut children: Vec<HtmlTreeNested> = vec![];
|
||||||
|
loop {
|
||||||
|
if input.is_empty() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
open,
|
||||||
|
"this open tag has no corresponding close tag",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if HtmlComponentClose::peek(input.cursor()).is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.parse::<HtmlComponentClose>()?;
|
||||||
|
|
||||||
|
Ok(HtmlComponent {
|
||||||
|
ty: open.ty,
|
||||||
|
props: open.props,
|
||||||
|
children,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for HtmlComponent {
|
impl ToTokens for HtmlComponent {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
let HtmlComponentInner { ty, props } = &self.0;
|
let Self {
|
||||||
|
ty,
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
} = self;
|
||||||
let vcomp_scope = Ident::new("__yew_vcomp_scope", Span::call_site());
|
let vcomp_scope = Ident::new("__yew_vcomp_scope", Span::call_site());
|
||||||
|
|
||||||
let validate_props = if let Some(Props::List(ListProps(vec_props))) = props {
|
let validate_props = if let Some(Props::List(ListProps(vec_props))) = props {
|
||||||
@ -56,6 +91,12 @@ impl ToTokens for HtmlComponent {
|
|||||||
quote! { #prop_ref.#label; }
|
quote! { #prop_ref.#label; }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let check_children = if !children.is_empty() {
|
||||||
|
quote! { #prop_ref.children; }
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
// This is a hack to avoid allocating memory but still have a reference to a props
|
// This is a hack to avoid allocating memory but still have a reference to a props
|
||||||
// struct so that attributes can be checked against it
|
// struct so that attributes can be checked against it
|
||||||
|
|
||||||
@ -71,12 +112,30 @@ impl ToTokens for HtmlComponent {
|
|||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#unallocated_prop_ref
|
#unallocated_prop_ref
|
||||||
|
#check_children
|
||||||
#(#check_props)*
|
#(#check_props)*
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
let init_props = if let Some(props) = props {
|
let init_props = if let Some(props) = props {
|
||||||
match props {
|
match props {
|
||||||
Props::List(ListProps(vec_props)) => {
|
Props::List(ListProps(vec_props)) => {
|
||||||
@ -89,6 +148,7 @@ impl ToTokens for HtmlComponent {
|
|||||||
quote! {
|
quote! {
|
||||||
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
|
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
|
||||||
#(#set_props)*
|
#(#set_props)*
|
||||||
|
#set_children
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +156,9 @@ impl ToTokens for HtmlComponent {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder().build()
|
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
|
||||||
|
#set_children
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,9 +179,7 @@ impl ToTokens for HtmlComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default();
|
let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default();
|
||||||
::yew::virtual_dom::VNode::VComp(
|
::yew::virtual_dom::VChild::<#ty, _>::new(#init_props, #vcomp_scope)
|
||||||
::yew::virtual_dom::VComp::new::<#ty>(#init_props, #vcomp_scope)
|
|
||||||
)
|
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,13 +195,18 @@ impl HtmlComponent {
|
|||||||
Some(cursor)
|
Some(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek_type(mut cursor: Cursor) -> Option<()> {
|
fn peek_type(mut cursor: Cursor) -> Option<Type> {
|
||||||
let mut colons_optional = true;
|
let mut colons_optional = true;
|
||||||
let mut last_ident = None;
|
let mut last_ident = None;
|
||||||
|
let mut leading_colon = None;
|
||||||
|
let mut segments = Punctuated::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut post_colons_cursor = cursor;
|
let mut post_colons_cursor = cursor;
|
||||||
if let Some(c) = Self::double_colon(post_colons_cursor) {
|
if let Some(c) = Self::double_colon(post_colons_cursor) {
|
||||||
|
if colons_optional {
|
||||||
|
leading_colon = Some(Token));
|
||||||
|
}
|
||||||
post_colons_cursor = c;
|
post_colons_cursor = c;
|
||||||
} else if !colons_optional {
|
} else if !colons_optional {
|
||||||
break;
|
break;
|
||||||
@ -149,7 +214,11 @@ impl HtmlComponent {
|
|||||||
|
|
||||||
if let Some((ident, c)) = post_colons_cursor.ident() {
|
if let Some((ident, c)) = post_colons_cursor.ident() {
|
||||||
cursor = c;
|
cursor = c;
|
||||||
last_ident = Some(ident);
|
last_ident = Some(ident.clone());
|
||||||
|
segments.push(PathSegment {
|
||||||
|
ident,
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -160,46 +229,96 @@ impl HtmlComponent {
|
|||||||
|
|
||||||
let type_str = last_ident?.to_string();
|
let type_str = last_ident?.to_string();
|
||||||
type_str.is_ascii().as_option()?;
|
type_str.is_ascii().as_option()?;
|
||||||
type_str.bytes().next()?.is_ascii_uppercase().as_option()
|
type_str.bytes().next()?.is_ascii_uppercase().as_option()?;
|
||||||
|
|
||||||
|
Some(Type::Path(TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: Path {
|
||||||
|
leading_colon,
|
||||||
|
segments,
|
||||||
|
},
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HtmlComponentInner {
|
struct HtmlComponentOpen {
|
||||||
|
lt: Token![<],
|
||||||
ty: Type,
|
ty: Type,
|
||||||
props: Option<Props>,
|
props: Option<Props>,
|
||||||
}
|
div: Option<Token![/]>,
|
||||||
|
|
||||||
impl Parse for HtmlComponentInner {
|
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
|
||||||
let ty = input.parse()?;
|
|
||||||
// backwards compat
|
|
||||||
let _ = input.parse::<Token![:]>();
|
|
||||||
|
|
||||||
let props = if let Some(prop_type) = Props::peek(input.cursor()) {
|
|
||||||
match prop_type {
|
|
||||||
PropType::List => input.parse().map(Props::List).map(Some)?,
|
|
||||||
PropType::With => input.parse().map(Props::With).map(Some)?,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(HtmlComponentInner { ty, props })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HtmlComponentTag {
|
|
||||||
lt: Token![<],
|
|
||||||
gt: Token![>],
|
gt: Token![>],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for HtmlComponentTag {
|
impl PeekValue<Type> for HtmlComponentOpen {
|
||||||
|
fn peek(cursor: Cursor) -> Option<Type> {
|
||||||
|
let (punct, cursor) = cursor.punct()?;
|
||||||
|
(punct.as_char() == '<').as_option()?;
|
||||||
|
HtmlComponent::peek_type(cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for HtmlComponentOpen {
|
||||||
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
|
let lt = input.parse::<Token![<]>()?;
|
||||||
|
let ty = input.parse()?;
|
||||||
|
// backwards compat
|
||||||
|
let _ = input.parse::<Token![:]>();
|
||||||
|
let HtmlPropSuffix { stream, div, gt } = input.parse()?;
|
||||||
|
let props: Option<Props> = parse(stream).ok();
|
||||||
|
|
||||||
|
Ok(HtmlComponentOpen {
|
||||||
|
lt,
|
||||||
|
ty,
|
||||||
|
props,
|
||||||
|
div,
|
||||||
|
gt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for HtmlComponentOpen {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
let HtmlComponentTag { lt, gt } = self;
|
let HtmlComponentOpen { lt, gt, .. } = self;
|
||||||
tokens.extend(quote! {#lt#gt});
|
tokens.extend(quote! {#lt#gt});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HtmlComponentClose {
|
||||||
|
lt: Token![<],
|
||||||
|
div: Token![/],
|
||||||
|
ty: Type,
|
||||||
|
gt: Token![>],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeekValue<Type> for HtmlComponentClose {
|
||||||
|
fn peek(cursor: Cursor) -> Option<Type> {
|
||||||
|
let (punct, cursor) = cursor.punct()?;
|
||||||
|
(punct.as_char() == '<').as_option()?;
|
||||||
|
|
||||||
|
let (punct, cursor) = cursor.punct()?;
|
||||||
|
(punct.as_char() == '/').as_option()?;
|
||||||
|
|
||||||
|
HtmlComponent::peek_type(cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Parse for HtmlComponentClose {
|
||||||
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
|
Ok(HtmlComponentClose {
|
||||||
|
lt: input.parse()?,
|
||||||
|
div: input.parse()?,
|
||||||
|
ty: input.parse()?,
|
||||||
|
gt: input.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for HtmlComponentClose {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
let HtmlComponentClose { lt, div, ty, gt } = self;
|
||||||
|
tokens.extend(quote! {#lt#div#ty#gt});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum PropType {
|
enum PropType {
|
||||||
List,
|
List,
|
||||||
With,
|
With,
|
||||||
@ -223,6 +342,17 @@ impl PeekValue<PropType> for Props {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Parse for Props {
|
||||||
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
|
let prop_type = Props::peek(input.cursor())
|
||||||
|
.ok_or_else(|| syn::Error::new(Span::call_site(), "ignore - no props found"))?;
|
||||||
|
match prop_type {
|
||||||
|
PropType::List => input.parse().map(Props::List),
|
||||||
|
PropType::With => input.parse().map(Props::With),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ListProps(Vec<HtmlProp>);
|
struct ListProps(Vec<HtmlProp>);
|
||||||
impl Parse for ListProps {
|
impl Parse for ListProps {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
@ -242,10 +372,18 @@ impl Parse for ListProps {
|
|||||||
|
|
||||||
// alphabetize
|
// alphabetize
|
||||||
props.sort_by(|a, b| {
|
props.sort_by(|a, b| {
|
||||||
a.label
|
if a.label == b.label {
|
||||||
.to_string()
|
Ordering::Equal
|
||||||
.partial_cmp(&b.label.to_string())
|
} else if a.label.to_string() == "children" {
|
||||||
.unwrap()
|
Ordering::Greater
|
||||||
|
} else if b.label.to_string() == "children" {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
a.label
|
||||||
|
.to_string()
|
||||||
|
.partial_cmp(&b.label.to_string())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(ListProps(props))
|
Ok(ListProps(props))
|
||||||
|
|||||||
@ -18,7 +18,7 @@ use html_prop::HtmlProp;
|
|||||||
use html_prop::HtmlPropSuffix;
|
use html_prop::HtmlPropSuffix;
|
||||||
use html_tag::HtmlTag;
|
use html_tag::HtmlTag;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens;
|
use quote::{quote, ToTokens};
|
||||||
use syn::buffer::Cursor;
|
use syn::buffer::Cursor;
|
||||||
use syn::parse::{Parse, ParseStream, Result};
|
use syn::parse::{Parse, ParseStream, Result};
|
||||||
|
|
||||||
@ -105,17 +105,36 @@ impl PeekValue<HtmlType> for HtmlTree {
|
|||||||
|
|
||||||
impl ToTokens for HtmlTree {
|
impl ToTokens for HtmlTree {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
let empty_html_el = HtmlList(Vec::new());
|
let node = self.token_stream();
|
||||||
let html_tree_el: &dyn ToTokens = match self {
|
tokens.extend(quote! {
|
||||||
HtmlTree::Empty => &empty_html_el,
|
::yew::virtual_dom::VNode::from(#node)
|
||||||
HtmlTree::Component(comp) => comp,
|
});
|
||||||
HtmlTree::Tag(tag) => tag,
|
}
|
||||||
HtmlTree::List(list) => list,
|
}
|
||||||
HtmlTree::Node(node) => node,
|
|
||||||
HtmlTree::Iterable(iterable) => iterable,
|
impl HtmlTree {
|
||||||
HtmlTree::Block(block) => block,
|
fn token_stream(&self) -> proc_macro2::TokenStream {
|
||||||
};
|
match self {
|
||||||
|
HtmlTree::Empty => HtmlList(Vec::new()).into_token_stream(),
|
||||||
html_tree_el.to_tokens(tokens);
|
HtmlTree::Component(comp) => comp.into_token_stream(),
|
||||||
|
HtmlTree::Tag(tag) => tag.into_token_stream(),
|
||||||
|
HtmlTree::List(list) => list.into_token_stream(),
|
||||||
|
HtmlTree::Node(node) => node.into_token_stream(),
|
||||||
|
HtmlTree::Iterable(iterable) => iterable.into_token_stream(),
|
||||||
|
HtmlTree::Block(block) => block.into_token_stream(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HtmlTreeNested(HtmlTree);
|
||||||
|
impl Parse for HtmlTreeNested {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Ok(HtmlTreeNested(HtmlTree::parse(input)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for HtmlTreeNested {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
tokens.extend(self.0.token_stream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
examples/nested_list/Cargo.toml
Normal file
8
examples/nested_list/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "nested_list"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Justin Starry <justin.starry@icloud.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yew = { path = "../.." }
|
||||||
46
examples/nested_list/src/header.rs
Normal file
46
examples/nested_list/src/header.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::list::Hovered;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct ListHeader {
|
||||||
|
props: Props,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
#[props(required)]
|
||||||
|
pub on_hover: Callback<Hovered>,
|
||||||
|
#[props(required)]
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
Hover,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ListHeader {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
ListHeader { props }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
|
match msg {
|
||||||
|
Msg::Hover => {
|
||||||
|
self.props.on_hover.emit(Hovered::Header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<ListHeader> for ListHeader {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
html! {
|
||||||
|
<div class="list-header" onmouseover=|_| Msg::Hover>
|
||||||
|
{ &self.props.text }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
examples/nested_list/src/item.rs
Normal file
66
examples/nested_list/src/item.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use crate::list::Hovered;
|
||||||
|
use yew::html::Children;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct ListItem {
|
||||||
|
props: Props,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
pub hide: bool,
|
||||||
|
#[props(required)]
|
||||||
|
pub on_hover: Callback<Hovered>,
|
||||||
|
#[props(required)]
|
||||||
|
pub name: String,
|
||||||
|
pub children: Children<ListItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
Hover,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ListItem {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
ListItem { props }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
|
match msg {
|
||||||
|
Msg::Hover => {
|
||||||
|
self.props
|
||||||
|
.on_hover
|
||||||
|
.emit(Hovered::Item(self.props.name.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<ListItem> for ListItem {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
html! {
|
||||||
|
<div class="list-item" onmouseover=|_| Msg::Hover>
|
||||||
|
{ &self.props.name }
|
||||||
|
{ self.view_details() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListItem {
|
||||||
|
fn view_details(&self) -> Html<Self> {
|
||||||
|
if self.props.children.is_empty() {
|
||||||
|
return html! {};
|
||||||
|
}
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="list-item-details">
|
||||||
|
{ self.props.children.view() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
examples/nested_list/src/lib.rs
Normal file
43
examples/nested_list/src/lib.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#![recursion_limit = "128"]
|
||||||
|
|
||||||
|
mod header;
|
||||||
|
mod item;
|
||||||
|
mod list;
|
||||||
|
|
||||||
|
use header::ListHeader;
|
||||||
|
use item::ListItem;
|
||||||
|
use list::{List, Msg as ListMsg};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct Model;
|
||||||
|
|
||||||
|
impl Component for Model {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
Model
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<Model> for Model {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
html! {
|
||||||
|
<div class="main">
|
||||||
|
<h1>{ "Nested List Demo" }</h1>
|
||||||
|
<List>
|
||||||
|
<ListHeader text="Calling all Rusties!" on_hover=ListMsg::Hover />
|
||||||
|
<ListItem name="Rustin" on_hover=ListMsg::Hover />
|
||||||
|
<ListItem hide={true} name="Rustaroo" on_hover=ListMsg::Hover />
|
||||||
|
<ListItem name="Rustifer" on_hover=ListMsg::Hover>
|
||||||
|
<span>{"Hello!"}</span>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
164
examples/nested_list/src/list.rs
Normal file
164
examples/nested_list/src/list.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
use crate::{header::Props as HeaderProps, ListHeader};
|
||||||
|
use crate::{item::Props as ItemProps, ListItem};
|
||||||
|
use std::fmt;
|
||||||
|
use yew::html::ChildrenRenderer;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew::virtual_dom::vcomp::ScopeHolder;
|
||||||
|
use yew::virtual_dom::{VChild, VComp, VNode};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Hovered {
|
||||||
|
Header,
|
||||||
|
Item(String),
|
||||||
|
List,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
Hover(Hovered),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Variants {
|
||||||
|
Item(<ListItem as Component>::Properties),
|
||||||
|
Header(<ListHeader as Component>::Properties),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ItemProps> for Variants {
|
||||||
|
fn from(props: ItemProps) -> Self {
|
||||||
|
Variants::Item(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HeaderProps> for Variants {
|
||||||
|
fn from(props: HeaderProps) -> Self {
|
||||||
|
Variants::Header(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListVariant {
|
||||||
|
props: Variants,
|
||||||
|
scope: ScopeHolder<List>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
#[props(required)]
|
||||||
|
pub children: ChildrenRenderer<ListVariant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct List {
|
||||||
|
props: Props,
|
||||||
|
hovered: Hovered,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for List {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
List {
|
||||||
|
props,
|
||||||
|
hovered: Hovered::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
|
match msg {
|
||||||
|
Msg::Hover(hovered) => self.hovered = hovered,
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<List> for List {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
html! {
|
||||||
|
<div
|
||||||
|
class="list-container"
|
||||||
|
onmouseout=|_| Msg::Hover(Hovered::None)
|
||||||
|
onmouseover=|_| Msg::Hover(Hovered::List)
|
||||||
|
>
|
||||||
|
<div class="list">
|
||||||
|
{self.view_header()}
|
||||||
|
<div class="items">
|
||||||
|
{self.view_items()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{self.view_last_hovered()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
fn view_header(&self) -> Html<Self> {
|
||||||
|
html! {{
|
||||||
|
for self.props.children.iter().filter(|c| match c.props {
|
||||||
|
Variants::Header(_) => true,
|
||||||
|
_ => false
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_items(&self) -> Html<Self> {
|
||||||
|
html! {{
|
||||||
|
for self.props.children.iter().filter(|c| match &c.props {
|
||||||
|
Variants::Item(props) => !props.hide,
|
||||||
|
_ => false,
|
||||||
|
}).enumerate().map(|(i, mut c)| {
|
||||||
|
if let Variants::Item(ref mut props) = c.props {
|
||||||
|
props.name = format!("#{} - {}", i + 1, props.name);
|
||||||
|
}
|
||||||
|
c
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_last_hovered(&self) -> Html<Self> {
|
||||||
|
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!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Hovered::Header => "Header",
|
||||||
|
Hovered::Item(name) => name,
|
||||||
|
Hovered::List => "List container",
|
||||||
|
Hovered::None => "Nothing",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CHILD> From<VChild<CHILD, List>> for ListVariant
|
||||||
|
where
|
||||||
|
CHILD: Component + Renderable<CHILD>,
|
||||||
|
CHILD::Properties: Into<Variants>,
|
||||||
|
{
|
||||||
|
fn from(vchild: VChild<CHILD, List>) -> Self {
|
||||||
|
ListVariant {
|
||||||
|
props: vchild.props.into(),
|
||||||
|
scope: vchild.scope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<VNode<List>> for ListVariant {
|
||||||
|
fn into(self) -> VNode<List> {
|
||||||
|
match self.props {
|
||||||
|
Variants::Header(props) => VComp::new::<ListHeader>(props, self.scope).into(),
|
||||||
|
Variants::Item(props) => VComp::new::<ListItem>(props, self.scope).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
examples/nested_list/src/main.rs
Normal file
3
examples/nested_list/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
yew::start_app::<nested_list::Model>();
|
||||||
|
}
|
||||||
11
examples/nested_list/static/index.html
Normal file
11
examples/nested_list/static/index.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Yew • Nested List</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="/nested_list.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
77
examples/nested_list/static/styles.css
Normal file
77
examples/nested_list/static/styles.css
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
background: #FAFAFA;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 40px;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container:hover {
|
||||||
|
background: #EAEAEA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #666;
|
||||||
|
min-width: 30vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-header {
|
||||||
|
background: #FEECAA;
|
||||||
|
border-bottom: 1px solid #666;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-header:hover {
|
||||||
|
background: #FEE3A0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #666;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item:hover {
|
||||||
|
background: #FAFAFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item:last-child {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-details {
|
||||||
|
background: #EEE;
|
||||||
|
border: 1px solid #666;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-hovered {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-hovered-text {
|
||||||
|
color: #666;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
124
src/html/mod.rs
124
src/html/mod.rs
@ -11,7 +11,7 @@ pub(crate) use scope::ComponentUpdate;
|
|||||||
pub use scope::{NodeCell, Scope};
|
pub use scope::{NodeCell, Scope};
|
||||||
|
|
||||||
use crate::callback::Callback;
|
use crate::callback::Callback;
|
||||||
use crate::virtual_dom::VNode;
|
use crate::virtual_dom::{VChild, VList, VNode};
|
||||||
|
|
||||||
/// This type indicates that component should be rendered again.
|
/// This type indicates that component should be rendered again.
|
||||||
pub type ShouldRender = bool;
|
pub type ShouldRender = bool;
|
||||||
@ -48,6 +48,128 @@ pub trait Component: Sized + 'static {
|
|||||||
/// A type which expected as a result of `view` function implementation.
|
/// A type which expected as a result of `view` function implementation.
|
||||||
pub type Html<MSG> = VNode<MSG>;
|
pub type Html<MSG> = VNode<MSG>;
|
||||||
|
|
||||||
|
/// A type used for accepting children elements in Component::Properties.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// **`model.rs`**
|
||||||
|
///
|
||||||
|
/// In this example, the Wrapper component is used to wrap other elements.
|
||||||
|
/// ```
|
||||||
|
/// html!{
|
||||||
|
/// <Wrapper>
|
||||||
|
/// <h4> {"Hi"} </h4>
|
||||||
|
/// <div> {"Hello"} </div>
|
||||||
|
/// </Wrapper>
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **`wrapper.rs`**
|
||||||
|
///
|
||||||
|
/// The Wrapper component must define a `children` property in order to wrap other elements. The
|
||||||
|
/// children property can be used to render the wrapped elements.
|
||||||
|
/// ```
|
||||||
|
/// #[derive(Properties)]
|
||||||
|
/// struct WrapperProps {
|
||||||
|
/// children: Children<Wrapper>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// html!{
|
||||||
|
/// <div id="container">
|
||||||
|
/// { self.props.children.view() }
|
||||||
|
/// </div>
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub type Children<T> = ChildrenRenderer<Html<T>>;
|
||||||
|
|
||||||
|
/// A type used for accepting children elements in Component::Properties and accessing their props.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// **`model.rs`**
|
||||||
|
///
|
||||||
|
/// In this example, the `List` component can wrap `ListItem` components.
|
||||||
|
/// ```
|
||||||
|
/// html!{
|
||||||
|
/// <List>
|
||||||
|
/// <ListItem value="a" />
|
||||||
|
/// <ListItem value="b" />
|
||||||
|
/// <ListItem value="c" />
|
||||||
|
/// </List>
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **`list.rs`**
|
||||||
|
///
|
||||||
|
/// The `List` component must define a `children` property in order to wrap the list items. The
|
||||||
|
/// `children` property can be used to filter, mutate, and render the items.
|
||||||
|
/// ```
|
||||||
|
/// #[derive(Properties)]
|
||||||
|
/// struct ListProps {
|
||||||
|
/// children: ChildrenWithProps<ListItem, List>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// html!{{
|
||||||
|
/// for self.props.children.iter().map(|mut item| {
|
||||||
|
/// item.props.value = format!("item-{}", item.props.value);
|
||||||
|
/// item
|
||||||
|
/// })
|
||||||
|
/// }}
|
||||||
|
/// ```
|
||||||
|
pub type ChildrenWithProps<C, P> = ChildrenRenderer<VChild<C, P>>;
|
||||||
|
|
||||||
|
/// A type used for rendering children html.
|
||||||
|
pub struct ChildrenRenderer<T> {
|
||||||
|
len: usize,
|
||||||
|
boxed_render: Box<dyn Fn() -> Vec<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ChildrenRenderer<T> {
|
||||||
|
/// Create children
|
||||||
|
pub fn new(len: usize, boxed_render: Box<dyn Fn() -> Vec<T>>) -> Self {
|
||||||
|
Self { len, boxed_render }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Children list is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of children elements
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build children components and return `Vec`
|
||||||
|
pub fn to_vec(&self) -> Vec<T> {
|
||||||
|
(&self.boxed_render)()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render children components and return `Iterator`
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = T> {
|
||||||
|
(&self.boxed_render)().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for ChildrenRenderer<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
len: 0,
|
||||||
|
boxed_render: Box::new(|| Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, COMP: Component> Renderable<COMP> for ChildrenRenderer<T>
|
||||||
|
where
|
||||||
|
T: Into<VNode<COMP>>,
|
||||||
|
{
|
||||||
|
fn view(&self) -> Html<COMP> {
|
||||||
|
VList {
|
||||||
|
childs: self.iter().map(|c| c.into()).collect(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Should be rendered relative to context and component environment.
|
/// Should be rendered relative to context and component environment.
|
||||||
pub trait Renderable<COMP: Component> {
|
pub trait Renderable<COMP: Component> {
|
||||||
/// Called by rendering loop.
|
/// Called by rendering loop.
|
||||||
|
|||||||
@ -149,7 +149,8 @@ pub mod prelude {
|
|||||||
pub use crate::callback::Callback;
|
pub use crate::callback::Callback;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::html::{
|
pub use crate::html::{
|
||||||
Component, ComponentLink, Href, Html, Properties, Renderable, ShouldRender,
|
Children, ChildrenWithProps, Component, ComponentLink, Href, Html, Properties, Renderable,
|
||||||
|
ShouldRender,
|
||||||
};
|
};
|
||||||
pub use crate::macros::*;
|
pub use crate::macros::*;
|
||||||
pub use crate::virtual_dom::Classes;
|
pub use crate::virtual_dom::Classes;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use std::collections::HashMap;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use stdweb::web::{Element, EventListenerHandle, Node};
|
use stdweb::web::{Element, EventListenerHandle, Node};
|
||||||
|
|
||||||
pub use self::vcomp::VComp;
|
pub use self::vcomp::{VChild, VComp};
|
||||||
pub use self::vlist::VList;
|
pub use self::vlist::VList;
|
||||||
pub use self::vnode::VNode;
|
pub use self::vnode::VNode;
|
||||||
pub use self::vtag::VTag;
|
pub use self::vtag::VTag;
|
||||||
|
|||||||
@ -31,6 +31,35 @@ pub struct VComp<COMP: Component> {
|
|||||||
state: Rc<RefCell<MountState<COMP>>>,
|
state: Rc<RefCell<MountState<COMP>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A virtual child component.
|
||||||
|
pub struct VChild<SELF: Component, PARENT: Component> {
|
||||||
|
/// The component properties
|
||||||
|
pub props: SELF::Properties,
|
||||||
|
/// The parent component scope
|
||||||
|
pub scope: ScopeHolder<PARENT>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SELF, PARENT> VChild<SELF, PARENT>
|
||||||
|
where
|
||||||
|
SELF: Component,
|
||||||
|
PARENT: Component,
|
||||||
|
{
|
||||||
|
/// Creates a child component that can be accessed and modified by its parent.
|
||||||
|
pub fn new(props: SELF::Properties, scope: ScopeHolder<PARENT>) -> Self {
|
||||||
|
Self { props, scope }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<COMP, CHILD> From<VChild<CHILD, COMP>> for VComp<COMP>
|
||||||
|
where
|
||||||
|
COMP: Component,
|
||||||
|
CHILD: Component + Renderable<CHILD>,
|
||||||
|
{
|
||||||
|
fn from(vchild: VChild<CHILD, COMP>) -> Self {
|
||||||
|
VComp::new::<CHILD>(vchild.props, vchild.scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum MountState<COMP: Component> {
|
enum MountState<COMP: Component> {
|
||||||
Unmounted(Unmounted<COMP>),
|
Unmounted(Unmounted<COMP>),
|
||||||
Mounted(Mounted),
|
Mounted(Mounted),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! This module contains the implementation of abstract virtual node.
|
//! This module contains the implementation of abstract virtual node.
|
||||||
|
|
||||||
use super::{VComp, VDiff, VList, VTag, VText};
|
use super::{VChild, VComp, VDiff, VList, VTag, VText};
|
||||||
use crate::html::{Component, Renderable, Scope};
|
use crate::html::{Component, Renderable, Scope};
|
||||||
use std::cmp::PartialEq;
|
use std::cmp::PartialEq;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@ -96,6 +96,16 @@ impl<COMP: Component> From<VComp<COMP>> for VNode<COMP> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<COMP, CHILD> From<VChild<CHILD, COMP>> for VNode<COMP>
|
||||||
|
where
|
||||||
|
COMP: Component,
|
||||||
|
CHILD: Component + Renderable<CHILD>,
|
||||||
|
{
|
||||||
|
fn from(vchild: VChild<CHILD, COMP>) -> Self {
|
||||||
|
VNode::VComp(VComp::from(vchild))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<COMP: Component, T: ToString> From<T> for VNode<COMP> {
|
impl<COMP: Component, T: ToString> From<T> for VNode<COMP> {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
VNode::VText(VText::new(value.to_string()))
|
VNode::VText(VText::new(value.to_string()))
|
||||||
|
|||||||
@ -12,7 +12,6 @@ macro_rules! pass_helper {
|
|||||||
( $($content:tt)* ) => {
|
( $($content:tt)* ) => {
|
||||||
mod test_component;
|
mod test_component;
|
||||||
use test_component::TestComponent;
|
use test_component::TestComponent;
|
||||||
// #[allow(unused_imports)]
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
impl Renderable<TestComponent> for TestComponent {
|
impl Renderable<TestComponent> for TestComponent {
|
||||||
fn view(&self) -> Html<Self> {
|
fn view(&self) -> Html<Self> {
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
fn compile_fail() {
|
fn compile_fail() {
|
||||||
html! { () };
|
html! {
|
||||||
|
<>
|
||||||
|
{ () }
|
||||||
|
</>
|
||||||
|
};
|
||||||
|
|
||||||
let not_tree = || ();
|
let not_tree = || ();
|
||||||
html! {
|
html! {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||||
--> $DIR/html-block-fail.rs:4:13
|
--> $DIR/html-block-fail.rs:6:15
|
||||||
|
|
|
|
||||||
4 | html! { () };
|
6 | { () }
|
||||||
| ^^ `()` cannot be formatted with the default formatter
|
| ^^ `()` cannot be formatted with the default formatter
|
||||||
|
|
|
|
||||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||||
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
||||||
@ -11,21 +11,21 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
|||||||
= note: required by `std::convert::From::from`
|
= note: required by `std::convert::From::from`
|
||||||
|
|
||||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||||
--> $DIR/html-block-fail.rs:8:16
|
--> $DIR/html-block-fail.rs:12:16
|
||||||
|
|
|
||||||
8 | <div>{ not_tree() }</div>
|
|
||||||
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
|
||||||
|
|
|
||||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
|
||||||
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
|
||||||
= note: required because of the requirements on the impl of `std::string::ToString` for `()`
|
|
||||||
= note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>`
|
|
||||||
= note: required by `std::convert::From::from`
|
|
||||||
|
|
||||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
|
||||||
--> $DIR/html-block-fail.rs:11:17
|
|
||||||
|
|
|
|
||||||
11 | <>{ for (0..3).map(|_| not_tree()) }</>
|
12 | <div>{ not_tree() }</div>
|
||||||
|
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
||||||
|
|
|
||||||
|
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||||
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
||||||
|
= note: required because of the requirements on the impl of `std::string::ToString` for `()`
|
||||||
|
= note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>`
|
||||||
|
= note: required by `std::convert::From::from`
|
||||||
|
|
||||||
|
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||||
|
--> $DIR/html-block-fail.rs:15:17
|
||||||
|
|
|
||||||
|
15 | <>{ for (0..3).map(|_| not_tree()) }</>
|
||||||
| ^^^^^^ `()` cannot be formatted with the default formatter
|
| ^^^^^^ `()` cannot be formatted with the default formatter
|
||||||
|
|
|
|
||||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||||
|
|||||||
@ -9,13 +9,13 @@ pub struct ChildProperties {
|
|||||||
pub int: i32,
|
pub int: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChildComponent;
|
pub struct Child;
|
||||||
impl Component for ChildComponent {
|
impl Component for Child {
|
||||||
type Message = ();
|
type Message = ();
|
||||||
type Properties = ChildProperties;
|
type Properties = ChildProperties;
|
||||||
|
|
||||||
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
ChildComponent
|
Child
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
@ -23,29 +23,63 @@ impl Component for ChildComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderable<ChildComponent> for ChildComponent {
|
impl Renderable<Self> for Child {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties)]
|
||||||
|
pub struct ChildContainerProperties {
|
||||||
|
pub children: ChildrenWithProps<Child, ChildContainer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChildContainer;
|
||||||
|
impl Component for ChildContainer {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ChildContainerProperties;
|
||||||
|
|
||||||
|
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
ChildContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<Self> for ChildContainer {
|
||||||
fn view(&self) -> Html<Self> {
|
fn view(&self) -> Html<Self> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_fail() {
|
fn compile_fail() {
|
||||||
html! { <ChildComponent> };
|
html! { <Child> };
|
||||||
html! { <ChildComponent:: /> };
|
html! { <Child:: /> };
|
||||||
html! { <ChildComponent with /> };
|
html! { <Child with /> };
|
||||||
html! { <ChildComponent props /> };
|
html! { <Child props /> };
|
||||||
html! { <ChildComponent with props > };
|
html! { <Child with props > };
|
||||||
html! { <ChildComponent with blah /> };
|
html! { <Child with blah /> };
|
||||||
html! { <ChildComponent with props () /> };
|
html! { <Child with props () /> };
|
||||||
html! { <ChildComponent type=0 /> };
|
html! { <Child type=0 /> };
|
||||||
html! { <ChildComponent invalid-prop-name=0 /> };
|
html! { <Child invalid-prop-name=0 /> };
|
||||||
html! { <ChildComponent unknown="unknown" /> };
|
html! { <Child unknown="unknown" /> };
|
||||||
html! { <ChildComponent string= /> };
|
html! { <Child string= /> };
|
||||||
html! { <ChildComponent int=1 string={} /> };
|
html! { <Child int=1 string={} /> };
|
||||||
html! { <ChildComponent int=1 string=3 /> };
|
html! { <Child int=1 string=3 /> };
|
||||||
html! { <ChildComponent int=1 string={3} /> };
|
html! { <Child int=1 string={3} /> };
|
||||||
html! { <ChildComponent int=0u32 /> };
|
html! { <Child int=0u32 /> };
|
||||||
html! { <ChildComponent string="abc" /> };
|
html! { <Child string="abc" /> };
|
||||||
|
html! { </Child> };
|
||||||
|
html! { <Child><Child></Child> };
|
||||||
|
html! { <Child></Child><Child></Child> };
|
||||||
|
html! { <Child>{ "Not allowed" }</Child> };
|
||||||
|
html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
|
||||||
|
html! { <ChildContainer><></></ChildContainer> };
|
||||||
|
html! { <ChildContainer><ChildContainer /></ChildContainer> };
|
||||||
|
html! { <ChildContainer><ChildContainer /></ChildContainer> };
|
||||||
|
html! { <ChildContainer><Child int=1 /><other /></ChildContainer> };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|||||||
@ -1,127 +1,231 @@
|
|||||||
error: expected component tag be of form `< .. />`
|
error: this open tag has no corresponding close tag
|
||||||
--> $DIR/html-component-fail.rs:33:13
|
--> $DIR/html-component-fail.rs:58:13
|
||||||
|
|
|
|
||||||
33 | html! { <ChildComponent> };
|
58 | html! { <Child> };
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
error: unexpected end of input, expected identifier
|
|
||||||
--> $DIR/html-component-fail.rs:34:31
|
|
||||||
|
|
|
||||||
34 | html! { <ChildComponent:: /> };
|
|
||||||
| ^
|
|
||||||
|
|
||||||
error: unexpected end of input, expected identifier
|
|
||||||
--> $DIR/html-component-fail.rs:35:34
|
|
||||||
|
|
|
||||||
35 | html! { <ChildComponent with /> };
|
|
||||||
| ^
|
|
||||||
|
|
||||||
error: unexpected token
|
|
||||||
--> $DIR/html-component-fail.rs:36:29
|
|
||||||
|
|
|
||||||
36 | html! { <ChildComponent props /> };
|
|
||||||
| ^^^^^
|
|
||||||
|
|
||||||
error: expected component tag be of form `< .. />`
|
|
||||||
--> $DIR/html-component-fail.rs:37:13
|
|
||||||
|
|
|
||||||
37 | html! { <ChildComponent with props > };
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
error: unexpected token
|
|
||||||
--> $DIR/html-component-fail.rs:39:40
|
|
||||||
|
|
|
||||||
39 | html! { <ChildComponent with props () /> };
|
|
||||||
| ^^
|
|
||||||
|
|
||||||
error: expected identifier
|
error: expected identifier
|
||||||
--> $DIR/html-component-fail.rs:40:29
|
--> $DIR/html-component-fail.rs:59:22
|
||||||
|
|
|
|
||||||
40 | html! { <ChildComponent type=0 /> };
|
59 | html! { <Child:: /> };
|
||||||
| ^^^^
|
| ^
|
||||||
|
|
||||||
error: expected identifier
|
error: this open tag has no corresponding close tag
|
||||||
--> $DIR/html-component-fail.rs:41:29
|
--> $DIR/html-component-fail.rs:62:13
|
||||||
|
|
|
|
||||||
41 | html! { <ChildComponent invalid-prop-name=0 /> };
|
62 | html! { <Child with props > };
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: unexpected end of input, expected expression
|
error: expected type, found `/`
|
||||||
--> $DIR/html-component-fail.rs:43:37
|
--> $DIR/html-component-fail.rs:74:14
|
||||||
|
|
|
|
||||||
43 | html! { <ChildComponent string= /> };
|
74 | html! { </Child> };
|
||||||
| ^
|
| ^
|
||||||
|
|
||||||
|
error: this open tag has no corresponding close tag
|
||||||
|
--> $DIR/html-component-fail.rs:75:13
|
||||||
|
|
|
||||||
|
75 | html! { <Child><Child></Child> };
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: only one root html element allowed
|
||||||
|
--> $DIR/html-component-fail.rs:76:28
|
||||||
|
|
|
||||||
|
76 | html! { <Child></Child><Child></Child> };
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error[E0425]: cannot find value `blah` in this scope
|
error[E0425]: cannot find value `blah` in this scope
|
||||||
--> $DIR/html-component-fail.rs:38:34
|
--> $DIR/html-component-fail.rs:63:25
|
||||||
|
|
|
|
||||||
38 | html! { <ChildComponent with blah /> };
|
63 | html! { <Child with blah /> };
|
||||||
| ^^^^ not found in this scope
|
| ^^^^ not found in this scope
|
||||||
|
|
||||||
|
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
|
--> $DIR/html-component-fail.rs:60:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `build` not found for this
|
||||||
|
...
|
||||||
|
60 | html! { <Child with /> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
|
--> $DIR/html-component-fail.rs:61:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `build` not found for this
|
||||||
|
...
|
||||||
|
61 | html! { <Child props /> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
|
--> $DIR/html-component-fail.rs:64:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `build` not found for this
|
||||||
|
...
|
||||||
|
64 | html! { <Child with props () /> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
|
--> $DIR/html-component-fail.rs:65:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `build` not found for this
|
||||||
|
...
|
||||||
|
65 | html! { <Child type=0 /> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
|
--> $DIR/html-component-fail.rs:66:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `build` not found for this
|
||||||
|
...
|
||||||
|
66 | html! { <Child invalid-prop-name=0 /> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0609]: no field `unknown` on type `ChildProperties`
|
error[E0609]: no field `unknown` on type `ChildProperties`
|
||||||
--> $DIR/html-component-fail.rs:42:29
|
--> $DIR/html-component-fail.rs:67:20
|
||||||
|
|
|
|
||||||
42 | html! { <ChildComponent unknown="unknown" /> };
|
67 | html! { <Child unknown="unknown" /> };
|
||||||
| ^^^^^^^ unknown field
|
| ^^^^^^^ unknown field
|
||||||
|
|
|
|
||||||
= note: available fields are: `string`, `int`
|
= note: available fields are: `string`, `int`
|
||||||
|
|
||||||
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
--> $DIR/html-component-fail.rs:42:29
|
--> $DIR/html-component-fail.rs:67:20
|
||||||
|
|
|
|
||||||
5 | #[derive(Properties, PartialEq)]
|
5 | #[derive(Properties, PartialEq)]
|
||||||
| - method `unknown` not found for this
|
| - method `unknown` not found for this
|
||||||
...
|
...
|
||||||
42 | html! { <ChildComponent unknown="unknown" /> };
|
67 | html! { <Child unknown="unknown" /> };
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error[E0599]: no method named `build` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
|
--> $DIR/html-component-fail.rs:68:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `build` not found for this
|
||||||
|
...
|
||||||
|
68 | html! { <Child string= /> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0308]: mismatched types
|
error[E0308]: mismatched types
|
||||||
--> $DIR/html-component-fail.rs:44:42
|
--> $DIR/html-component-fail.rs:69:33
|
||||||
|
|
|
|
||||||
44 | html! { <ChildComponent int=1 string={} /> };
|
69 | html! { <Child int=1 string={} /> };
|
||||||
| ^^ expected struct `std::string::String`, found ()
|
| ^^ expected struct `std::string::String`, found ()
|
||||||
|
|
|
|
||||||
= note: expected type `std::string::String`
|
= note: expected type `std::string::String`
|
||||||
found type `()`
|
found type `()`
|
||||||
|
|
||||||
error[E0308]: mismatched types
|
error[E0308]: mismatched types
|
||||||
--> $DIR/html-component-fail.rs:45:42
|
--> $DIR/html-component-fail.rs:70:33
|
||||||
|
|
|
|
||||||
45 | html! { <ChildComponent int=1 string=3 /> };
|
70 | html! { <Child int=1 string=3 /> };
|
||||||
| ^
|
| ^
|
||||||
| |
|
| |
|
||||||
| expected struct `std::string::String`, found integer
|
| expected struct `std::string::String`, found integer
|
||||||
| help: try using a conversion method: `3.to_string()`
|
| help: try using a conversion method: `3.to_string()`
|
||||||
|
|
|
|
||||||
= note: expected type `std::string::String`
|
= note: expected type `std::string::String`
|
||||||
found type `{integer}`
|
found type `{integer}`
|
||||||
|
|
||||||
error[E0308]: mismatched types
|
error[E0308]: mismatched types
|
||||||
--> $DIR/html-component-fail.rs:46:42
|
--> $DIR/html-component-fail.rs:71:33
|
||||||
|
|
|
|
||||||
46 | html! { <ChildComponent int=1 string={3} /> };
|
71 | html! { <Child int=1 string={3} /> };
|
||||||
| ^^^
|
| ^^^
|
||||||
| |
|
| |
|
||||||
| expected struct `std::string::String`, found integer
|
| expected struct `std::string::String`, found integer
|
||||||
| help: try using a conversion method: `{3}.to_string()`
|
| help: try using a conversion method: `{3}.to_string()`
|
||||||
|
|
|
|
||||||
= note: expected type `std::string::String`
|
= note: expected type `std::string::String`
|
||||||
found type `{integer}`
|
found type `{integer}`
|
||||||
|
|
||||||
error[E0308]: mismatched types
|
error[E0308]: mismatched types
|
||||||
--> $DIR/html-component-fail.rs:47:33
|
--> $DIR/html-component-fail.rs:72:24
|
||||||
|
|
|
|
||||||
47 | html! { <ChildComponent int=0u32 /> };
|
72 | html! { <Child int=0u32 /> };
|
||||||
| ^^^^ expected i32, found u32
|
| ^^^^ expected i32, found u32
|
||||||
|
|
||||||
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
--> $DIR/html-component-fail.rs:48:29
|
--> $DIR/html-component-fail.rs:73:20
|
||||||
|
|
|
|
||||||
5 | #[derive(Properties, PartialEq)]
|
5 | #[derive(Properties, PartialEq)]
|
||||||
| - method `string` not found for this
|
| - method `string` not found for this
|
||||||
...
|
...
|
||||||
48 | html! { <ChildComponent string="abc" /> };
|
73 | html! { <Child string="abc" /> };
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
||||||
Some errors occurred: E0308, E0425, E0599, E0609.
|
error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<ChildPropertiesBuilderStep_missing_required_prop_int>` in the current scope
|
||||||
For more information about an error, try `rustc --explain E0308`.
|
--> $DIR/html-component-fail.rs:77:5
|
||||||
|
|
|
||||||
|
5 | #[derive(Properties, PartialEq)]
|
||||||
|
| - method `children` not found for this
|
||||||
|
...
|
||||||
|
77 | html! { <Child>{ "Not allowed" }</Child> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not satisfied
|
||||||
|
--> $DIR/html-component-fail.rs:78:5
|
||||||
|
|
|
||||||
|
78 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
||||||
|
|
|
||||||
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vnode::VNode<_>`
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not satisfied
|
||||||
|
--> $DIR/html-component-fail.rs:79:5
|
||||||
|
|
|
||||||
|
79 | html! { <ChildContainer><></></ChildContainer> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
||||||
|
|
|
||||||
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vnode::VNode<_>`
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not satisfied
|
||||||
|
--> $DIR/html-component-fail.rs:80:5
|
||||||
|
|
|
||||||
|
80 | html! { <ChildContainer><ChildContainer /></ChildContainer> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
||||||
|
|
|
||||||
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vcomp::VChild<ChildContainer, _>`
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not satisfied
|
||||||
|
--> $DIR/html-component-fail.rs:81:5
|
||||||
|
|
|
||||||
|
81 | html! { <ChildContainer><ChildContainer /></ChildContainer> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vcomp::VChild<ChildContainer, _>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
||||||
|
|
|
||||||
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vcomp::VChild<ChildContainer, _>`
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>: std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not satisfied
|
||||||
|
--> $DIR/html-component-fail.rs:82:5
|
||||||
|
|
|
||||||
|
82 | html! { <ChildContainer><Child int=1 /><other /></ChildContainer> };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode<_>>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child, ChildContainer>`
|
||||||
|
|
|
||||||
|
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child, ChildContainer>>` for `yew::virtual_dom::vnode::VNode<_>`
|
||||||
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||||
|
|
||||||
|
Some errors occurred: E0277, E0308, E0425, E0599, E0609.
|
||||||
|
For more information about an error, try `rustc --explain E0277`.
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
#![recursion_limit = "128"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
|
use yew::html::ChildrenRenderer;
|
||||||
|
|
||||||
#[derive(Properties, Default, PartialEq)]
|
#[derive(Properties, Default, PartialEq)]
|
||||||
pub struct ChildProperties {
|
pub struct ChildProperties {
|
||||||
pub string: String,
|
pub string: String,
|
||||||
@ -12,13 +14,13 @@ pub struct ChildProperties {
|
|||||||
pub optional_callback: Option<Callback<()>>,
|
pub optional_callback: Option<Callback<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChildComponent;
|
pub struct Child;
|
||||||
impl Component for ChildComponent {
|
impl Component for Child {
|
||||||
type Message = ();
|
type Message = ();
|
||||||
type Properties = ChildProperties;
|
type Properties = ChildProperties;
|
||||||
|
|
||||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
ChildComponent
|
Child
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
@ -26,66 +28,159 @@ impl Component for ChildComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderable<ChildComponent> for ChildComponent {
|
impl Renderable<Child> for Child {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, Default)]
|
||||||
|
pub struct ContainerProperties {
|
||||||
|
#[props(required)]
|
||||||
|
pub int: i32,
|
||||||
|
pub children: Children<Container>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Container;
|
||||||
|
impl Component for Container {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ContainerProperties;
|
||||||
|
|
||||||
|
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
Container
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<Self> for Container {
|
||||||
|
fn view(&self) -> Html<Self> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, Default)]
|
||||||
|
pub struct ChildContainerProperties {
|
||||||
|
#[props(required)]
|
||||||
|
pub int: i32,
|
||||||
|
pub children: ChildrenWithProps<Child, ChildContainer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChildContainer;
|
||||||
|
impl Component for ChildContainer {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ChildContainerProperties;
|
||||||
|
|
||||||
|
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||||
|
ChildContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable<Self> for ChildContainer {
|
||||||
fn view(&self) -> Html<Self> {
|
fn view(&self) -> Html<Self> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod scoped {
|
mod scoped {
|
||||||
pub use super::ChildComponent;
|
pub use super::Child;
|
||||||
|
pub use super::Container;
|
||||||
}
|
}
|
||||||
|
|
||||||
pass_helper! {
|
pass_helper! {
|
||||||
html! { <ChildComponent int=1 /> };
|
html! { <Child int=1 /> };
|
||||||
|
|
||||||
// backwards compat
|
// backwards compat
|
||||||
html! { <ChildComponent: int=1 /> };
|
html! { <Child: int=1 /> };
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<ChildComponent int=1 />
|
<Child int=1 />
|
||||||
<scoped::ChildComponent int=1 />
|
<scoped::Child int=1 />
|
||||||
|
|
||||||
// backwards compat
|
// backwards compat
|
||||||
<ChildComponent: int=1 />
|
<Child: int=1 />
|
||||||
<scoped::ChildComponent: int=1 />
|
<scoped::Child: int=1 />
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
|
|
||||||
let props = <ChildComponent as Component>::Properties::default();
|
let props = <Child as Component>::Properties::default();
|
||||||
let props2 = <ChildComponent as Component>::Properties::default();
|
let props2 = <Child as Component>::Properties::default();
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<ChildComponent with props />
|
<Child with props />
|
||||||
|
|
||||||
// backwards compat
|
// backwards compat
|
||||||
<ChildComponent: with props2, />
|
<Child: with props2, />
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<ChildComponent int=1 string="child" />
|
<Child int=1 string="child" />
|
||||||
<ChildComponent int=1 />
|
<Child int=1 />
|
||||||
<ChildComponent int={1+1} />
|
<Child int={1+1} />
|
||||||
<ChildComponent int=1 vec={vec![1]} />
|
<Child int=1 vec={vec![1]} />
|
||||||
<ChildComponent string={String::from("child")} int=1 />
|
<Child string={String::from("child")} int=1 />
|
||||||
|
|
||||||
// backwards compat
|
// backwards compat
|
||||||
<ChildComponent: string="child", int=3, />
|
<Child: string="child", int=3, />
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
|
|
||||||
let name_expr = "child";
|
let name_expr = "child";
|
||||||
html! {
|
html! {
|
||||||
<ChildComponent int=1 string=name_expr />
|
<Child int=1 string=name_expr />
|
||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<ChildComponent int=1 />
|
<Child int=1 />
|
||||||
<ChildComponent int=1 optional_callback=|_| () />
|
<Child int=1 optional_callback=|_| () />
|
||||||
|
</>
|
||||||
|
};
|
||||||
|
|
||||||
|
let props = <Container as Component>::Properties::default();
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<Container int=1 />
|
||||||
|
<Container int=1></Container>
|
||||||
|
|
||||||
|
<Container with props>
|
||||||
|
<></>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<Container int=1>
|
||||||
|
<Child int=2 />
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<scoped::Container int=1>
|
||||||
|
<scoped::Container int=2/>
|
||||||
|
</scoped::Container>
|
||||||
|
|
||||||
|
<Container int=1 children=ChildrenRenderer::new(
|
||||||
|
1,
|
||||||
|
::std::boxed::Box::new(move || {
|
||||||
|
|| -> ::std::vec::Vec<_> {
|
||||||
|
vec![html!{ "String" }]
|
||||||
|
}
|
||||||
|
}()),
|
||||||
|
) />
|
||||||
|
</>
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<ChildContainer int=1 />
|
||||||
|
<ChildContainer int=1></ChildContainer>
|
||||||
|
<ChildContainer int=1><Child int = 2 /></ChildContainer>
|
||||||
|
<ChildContainer int=1><Child int = 2 /><Child int = 2 /></ChildContainer>
|
||||||
</>
|
</>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user