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/mount_point",
|
||||
"examples/multi_thread",
|
||||
"examples/nested_list",
|
||||
"examples/npm_and_rest",
|
||||
"examples/routing",
|
||||
"examples/server",
|
||||
|
||||
@ -218,6 +218,9 @@ html! {
|
||||
<nav class="menu">
|
||||
<MyButton title="First Button" />
|
||||
<MyButton title="Second Button "/>
|
||||
<MyList name="Grocery List">
|
||||
<MyListItem text="Apples" />
|
||||
</MyList>
|
||||
</nav>
|
||||
}
|
||||
```
|
||||
|
||||
@ -170,14 +170,30 @@ impl TryFrom<Field> for PropField {
|
||||
|
||||
impl PartialOrd for PropField {
|
||||
fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
|
||||
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 {
|
||||
fn cmp(&self, other: &PropField) -> Ordering {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PropField {
|
||||
|
||||
@ -1,53 +1,88 @@
|
||||
use super::HtmlProp;
|
||||
use super::HtmlPropSuffix;
|
||||
use super::HtmlTreeNested;
|
||||
use crate::PeekValue;
|
||||
use boolinator::Boolinator;
|
||||
use proc_macro2::Span;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use std::cmp::Ordering;
|
||||
use syn::buffer::Cursor;
|
||||
use syn::parse;
|
||||
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||
use syn::punctuated::Punctuated;
|
||||
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 {
|
||||
fn peek(cursor: Cursor) -> Option<()> {
|
||||
let (punct, cursor) = cursor.punct()?;
|
||||
(punct.as_char() == '<').as_option()?;
|
||||
|
||||
HtmlComponent::peek_type(cursor)
|
||||
HtmlComponent::peek_type(cursor)?;
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for HtmlComponent {
|
||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||
let lt = input.parse::<Token![<]>()?;
|
||||
let HtmlPropSuffix { stream, div, gt } = input.parse()?;
|
||||
if div.is_none() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
HtmlComponentTag { lt, gt },
|
||||
"expected component tag be of form `< .. />`",
|
||||
));
|
||||
if HtmlComponentClose::peek(input.cursor()).is_some() {
|
||||
return match input.parse::<HtmlComponentClose>() {
|
||||
Ok(close) => Err(syn::Error::new_spanned(
|
||||
close,
|
||||
"this close tag has no corresponding open tag",
|
||||
)),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
}
|
||||
|
||||
match parse(stream) {
|
||||
Ok(comp) => Ok(HtmlComponent(comp)),
|
||||
Err(err) => {
|
||||
if err.to_string().starts_with("unexpected end of input") {
|
||||
Err(syn::Error::new_spanned(div, err.to_string()))
|
||||
} else {
|
||||
Err(err)
|
||||
let open = input.parse::<HtmlComponentOpen>()?;
|
||||
// Return early if it's a self-closing tag
|
||||
if open.div.is_some() {
|
||||
return Ok(HtmlComponent {
|
||||
ty: open.ty,
|
||||
props: open.props,
|
||||
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 {
|
||||
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 validate_props = if let Some(Props::List(ListProps(vec_props))) = props {
|
||||
@ -56,6 +91,12 @@ impl ToTokens for HtmlComponent {
|
||||
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
|
||||
// struct so that attributes can be checked against it
|
||||
|
||||
@ -71,12 +112,30 @@ impl ToTokens for HtmlComponent {
|
||||
|
||||
quote! {
|
||||
#unallocated_prop_ref
|
||||
#check_children
|
||||
#(#check_props)*
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
match props {
|
||||
Props::List(ListProps(vec_props)) => {
|
||||
@ -89,6 +148,7 @@ impl ToTokens for HtmlComponent {
|
||||
quote! {
|
||||
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
|
||||
#(#set_props)*
|
||||
#set_children
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@ -96,7 +156,9 @@ impl ToTokens for HtmlComponent {
|
||||
}
|
||||
} else {
|
||||
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();
|
||||
::yew::virtual_dom::VNode::VComp(
|
||||
::yew::virtual_dom::VComp::new::<#ty>(#init_props, #vcomp_scope)
|
||||
)
|
||||
::yew::virtual_dom::VChild::<#ty, _>::new(#init_props, #vcomp_scope)
|
||||
}});
|
||||
}
|
||||
}
|
||||
@ -135,13 +195,18 @@ impl HtmlComponent {
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
fn peek_type(mut cursor: Cursor) -> Option<()> {
|
||||
fn peek_type(mut cursor: Cursor) -> Option<Type> {
|
||||
let mut colons_optional = true;
|
||||
let mut last_ident = None;
|
||||
let mut leading_colon = None;
|
||||
let mut segments = Punctuated::new();
|
||||
|
||||
loop {
|
||||
let mut post_colons_cursor = cursor;
|
||||
if let Some(c) = Self::double_colon(post_colons_cursor) {
|
||||
if colons_optional {
|
||||
leading_colon = Some(Token));
|
||||
}
|
||||
post_colons_cursor = c;
|
||||
} else if !colons_optional {
|
||||
break;
|
||||
@ -149,7 +214,11 @@ impl HtmlComponent {
|
||||
|
||||
if let Some((ident, c)) = post_colons_cursor.ident() {
|
||||
cursor = c;
|
||||
last_ident = Some(ident);
|
||||
last_ident = Some(ident.clone());
|
||||
segments.push(PathSegment {
|
||||
ident,
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -160,46 +229,96 @@ impl HtmlComponent {
|
||||
|
||||
let type_str = last_ident?.to_string();
|
||||
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,
|
||||
props: Option<Props>,
|
||||
}
|
||||
|
||||
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![<],
|
||||
div: Option<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) {
|
||||
let HtmlComponentTag { lt, gt } = self;
|
||||
let HtmlComponentOpen { lt, gt, .. } = self;
|
||||
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 {
|
||||
List,
|
||||
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>);
|
||||
impl Parse for ListProps {
|
||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||
@ -242,10 +372,18 @@ impl Parse for ListProps {
|
||||
|
||||
// alphabetize
|
||||
props.sort_by(|a, b| {
|
||||
if a.label == b.label {
|
||||
Ordering::Equal
|
||||
} else if a.label.to_string() == "children" {
|
||||
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))
|
||||
|
||||
@ -18,7 +18,7 @@ use html_prop::HtmlProp;
|
||||
use html_prop::HtmlPropSuffix;
|
||||
use html_tag::HtmlTag;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::buffer::Cursor;
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
|
||||
@ -105,17 +105,36 @@ impl PeekValue<HtmlType> for HtmlTree {
|
||||
|
||||
impl ToTokens for HtmlTree {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let empty_html_el = HtmlList(Vec::new());
|
||||
let html_tree_el: &dyn ToTokens = match self {
|
||||
HtmlTree::Empty => &empty_html_el,
|
||||
HtmlTree::Component(comp) => comp,
|
||||
HtmlTree::Tag(tag) => tag,
|
||||
HtmlTree::List(list) => list,
|
||||
HtmlTree::Node(node) => node,
|
||||
HtmlTree::Iterable(iterable) => iterable,
|
||||
HtmlTree::Block(block) => block,
|
||||
};
|
||||
|
||||
html_tree_el.to_tokens(tokens);
|
||||
let node = self.token_stream();
|
||||
tokens.extend(quote! {
|
||||
::yew::virtual_dom::VNode::from(#node)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl HtmlTree {
|
||||
fn token_stream(&self) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
HtmlTree::Empty => HtmlList(Vec::new()).into_token_stream(),
|
||||
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};
|
||||
|
||||
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.
|
||||
pub type ShouldRender = bool;
|
||||
@ -48,6 +48,128 @@ pub trait Component: Sized + 'static {
|
||||
/// A type which expected as a result of `view` function implementation.
|
||||
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.
|
||||
pub trait Renderable<COMP: Component> {
|
||||
/// Called by rendering loop.
|
||||
|
||||
@ -149,7 +149,8 @@ pub mod prelude {
|
||||
pub use crate::callback::Callback;
|
||||
pub use crate::events::*;
|
||||
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::virtual_dom::Classes;
|
||||
|
||||
@ -11,7 +11,7 @@ use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
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::vnode::VNode;
|
||||
pub use self::vtag::VTag;
|
||||
|
||||
@ -31,6 +31,35 @@ pub struct VComp<COMP: Component> {
|
||||
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> {
|
||||
Unmounted(Unmounted<COMP>),
|
||||
Mounted(Mounted),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! 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 std::cmp::PartialEq;
|
||||
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> {
|
||||
fn from(value: T) -> Self {
|
||||
VNode::VText(VText::new(value.to_string()))
|
||||
|
||||
@ -12,7 +12,6 @@ macro_rules! pass_helper {
|
||||
( $($content:tt)* ) => {
|
||||
mod test_component;
|
||||
use test_component::TestComponent;
|
||||
// #[allow(unused_imports)]
|
||||
use yew::prelude::*;
|
||||
impl Renderable<TestComponent> for TestComponent {
|
||||
fn view(&self) -> Html<Self> {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
fn compile_fail() {
|
||||
html! { () };
|
||||
html! {
|
||||
<>
|
||||
{ () }
|
||||
</>
|
||||
};
|
||||
|
||||
let not_tree = || ();
|
||||
html! {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
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
|
||||
|
|
||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||
@ -11,9 +11,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
= note: required by `std::convert::From::from`
|
||||
|
||||
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>
|
||||
12 | <div>{ not_tree() }</div>
|
||||
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
||||
|
|
||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||
@ -23,9 +23,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
= note: required by `std::convert::From::from`
|
||||
|
||||
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||
--> $DIR/html-block-fail.rs:11:17
|
||||
--> $DIR/html-block-fail.rs:15:17
|
||||
|
|
||||
11 | <>{ for (0..3).map(|_| not_tree()) }</>
|
||||
15 | <>{ for (0..3).map(|_| not_tree()) }</>
|
||||
| ^^^^^^ `()` cannot be formatted with the default formatter
|
||||
|
|
||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||
|
||||
@ -9,13 +9,13 @@ pub struct ChildProperties {
|
||||
pub int: i32,
|
||||
}
|
||||
|
||||
pub struct ChildComponent;
|
||||
impl Component for ChildComponent {
|
||||
pub struct Child;
|
||||
impl Component for Child {
|
||||
type Message = ();
|
||||
type Properties = ChildProperties;
|
||||
|
||||
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
ChildComponent
|
||||
Child
|
||||
}
|
||||
|
||||
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> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_fail() {
|
||||
html! { <ChildComponent> };
|
||||
html! { <ChildComponent:: /> };
|
||||
html! { <ChildComponent with /> };
|
||||
html! { <ChildComponent props /> };
|
||||
html! { <ChildComponent with props > };
|
||||
html! { <ChildComponent with blah /> };
|
||||
html! { <ChildComponent with props () /> };
|
||||
html! { <ChildComponent type=0 /> };
|
||||
html! { <ChildComponent invalid-prop-name=0 /> };
|
||||
html! { <ChildComponent unknown="unknown" /> };
|
||||
html! { <ChildComponent string= /> };
|
||||
html! { <ChildComponent int=1 string={} /> };
|
||||
html! { <ChildComponent int=1 string=3 /> };
|
||||
html! { <ChildComponent int=1 string={3} /> };
|
||||
html! { <ChildComponent int=0u32 /> };
|
||||
html! { <ChildComponent string="abc" /> };
|
||||
html! { <Child> };
|
||||
html! { <Child:: /> };
|
||||
html! { <Child with /> };
|
||||
html! { <Child props /> };
|
||||
html! { <Child with props > };
|
||||
html! { <Child with blah /> };
|
||||
html! { <Child with props () /> };
|
||||
html! { <Child type=0 /> };
|
||||
html! { <Child invalid-prop-name=0 /> };
|
||||
html! { <Child unknown="unknown" /> };
|
||||
html! { <Child string= /> };
|
||||
html! { <Child int=1 string={} /> };
|
||||
html! { <Child int=1 string=3 /> };
|
||||
html! { <Child int=1 string={3} /> };
|
||||
html! { <Child int=0u32 /> };
|
||||
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() {}
|
||||
|
||||
@ -1,93 +1,141 @@
|
||||
error: expected component tag be of form `< .. />`
|
||||
--> $DIR/html-component-fail.rs:33:13
|
||||
error: this open tag has no corresponding close tag
|
||||
--> $DIR/html-component-fail.rs:58:13
|
||||
|
|
||||
33 | html! { <ChildComponent> };
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
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 () /> };
|
||||
| ^^
|
||||
58 | html! { <Child> };
|
||||
| ^^^^^^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/html-component-fail.rs:40:29
|
||||
--> $DIR/html-component-fail.rs:59:22
|
||||
|
|
||||
40 | html! { <ChildComponent type=0 /> };
|
||||
| ^^^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/html-component-fail.rs:41:29
|
||||
|
|
||||
41 | html! { <ChildComponent invalid-prop-name=0 /> };
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unexpected end of input, expected expression
|
||||
--> $DIR/html-component-fail.rs:43:37
|
||||
|
|
||||
43 | html! { <ChildComponent string= /> };
|
||||
59 | html! { <Child:: /> };
|
||||
| ^
|
||||
|
||||
error: this open tag has no corresponding close tag
|
||||
--> $DIR/html-component-fail.rs:62:13
|
||||
|
|
||||
62 | html! { <Child with props > };
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: expected type, found `/`
|
||||
--> $DIR/html-component-fail.rs:74:14
|
||||
|
|
||||
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
|
||||
--> $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
|
||||
|
||||
error[E0609]: no field `unknown` on type `ChildProperties`
|
||||
--> $DIR/html-component-fail.rs:42:29
|
||||
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
|
||||
|
|
||||
42 | html! { <ChildComponent unknown="unknown" /> };
|
||||
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`
|
||||
--> $DIR/html-component-fail.rs:67:20
|
||||
|
|
||||
67 | html! { <Child unknown="unknown" /> };
|
||||
| ^^^^^^^ unknown field
|
||||
|
|
||||
= 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
|
||||
--> $DIR/html-component-fail.rs:42:29
|
||||
--> $DIR/html-component-fail.rs:67:20
|
||||
|
|
||||
5 | #[derive(Properties, PartialEq)]
|
||||
| - method `unknown` not found for this
|
||||
...
|
||||
42 | html! { <ChildComponent unknown="unknown" /> };
|
||||
67 | html! { <Child unknown="unknown" /> };
|
||||
| ^^^^^^^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-component-fail.rs:44:42
|
||||
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
|
||||
|
|
||||
44 | html! { <ChildComponent int=1 string={} /> };
|
||||
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
|
||||
--> $DIR/html-component-fail.rs:69:33
|
||||
|
|
||||
69 | html! { <Child int=1 string={} /> };
|
||||
| ^^ expected struct `std::string::String`, found ()
|
||||
|
|
||||
= note: expected type `std::string::String`
|
||||
found type `()`
|
||||
|
||||
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
|
||||
@ -97,9 +145,9 @@ error[E0308]: mismatched types
|
||||
found type `{integer}`
|
||||
|
||||
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
|
||||
@ -109,19 +157,75 @@ error[E0308]: mismatched types
|
||||
found type `{integer}`
|
||||
|
||||
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
|
||||
|
||||
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)]
|
||||
| - method `string` not found for this
|
||||
...
|
||||
48 | html! { <ChildComponent string="abc" /> };
|
||||
73 | html! { <Child string="abc" /> };
|
||||
| ^^^^^^
|
||||
|
||||
Some errors occurred: E0308, E0425, E0599, E0609.
|
||||
For more information about an error, try `rustc --explain E0308`.
|
||||
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)]
|
||||
| - 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]
|
||||
mod helpers;
|
||||
|
||||
use yew::html::ChildrenRenderer;
|
||||
|
||||
#[derive(Properties, Default, PartialEq)]
|
||||
pub struct ChildProperties {
|
||||
pub string: String,
|
||||
@ -12,13 +14,13 @@ pub struct ChildProperties {
|
||||
pub optional_callback: Option<Callback<()>>,
|
||||
}
|
||||
|
||||
pub struct ChildComponent;
|
||||
impl Component for ChildComponent {
|
||||
pub struct Child;
|
||||
impl Component for Child {
|
||||
type Message = ();
|
||||
type Properties = ChildProperties;
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
ChildComponent
|
||||
Child
|
||||
}
|
||||
|
||||
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> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
mod scoped {
|
||||
pub use super::ChildComponent;
|
||||
pub use super::Child;
|
||||
pub use super::Container;
|
||||
}
|
||||
|
||||
pass_helper! {
|
||||
html! { <ChildComponent int=1 /> };
|
||||
html! { <Child int=1 /> };
|
||||
|
||||
// backwards compat
|
||||
html! { <ChildComponent: int=1 /> };
|
||||
html! { <Child: int=1 /> };
|
||||
|
||||
html! {
|
||||
<>
|
||||
<ChildComponent int=1 />
|
||||
<scoped::ChildComponent int=1 />
|
||||
<Child int=1 />
|
||||
<scoped::Child int=1 />
|
||||
|
||||
// backwards compat
|
||||
<ChildComponent: int=1 />
|
||||
<scoped::ChildComponent: int=1 />
|
||||
<Child: int=1 />
|
||||
<scoped::Child: int=1 />
|
||||
</>
|
||||
};
|
||||
|
||||
let props = <ChildComponent as Component>::Properties::default();
|
||||
let props2 = <ChildComponent as Component>::Properties::default();
|
||||
let props = <Child as Component>::Properties::default();
|
||||
let props2 = <Child as Component>::Properties::default();
|
||||
html! {
|
||||
<>
|
||||
<ChildComponent with props />
|
||||
<Child with props />
|
||||
|
||||
// backwards compat
|
||||
<ChildComponent: with props2, />
|
||||
<Child: with props2, />
|
||||
</>
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<ChildComponent int=1 string="child" />
|
||||
<ChildComponent int=1 />
|
||||
<ChildComponent int={1+1} />
|
||||
<ChildComponent int=1 vec={vec![1]} />
|
||||
<ChildComponent string={String::from("child")} int=1 />
|
||||
<Child int=1 string="child" />
|
||||
<Child int=1 />
|
||||
<Child int={1+1} />
|
||||
<Child int=1 vec={vec![1]} />
|
||||
<Child string={String::from("child")} int=1 />
|
||||
|
||||
// backwards compat
|
||||
<ChildComponent: string="child", int=3, />
|
||||
<Child: string="child", int=3, />
|
||||
</>
|
||||
};
|
||||
|
||||
let name_expr = "child";
|
||||
html! {
|
||||
<ChildComponent int=1 string=name_expr />
|
||||
<Child int=1 string=name_expr />
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<ChildComponent int=1 />
|
||||
<ChildComponent int=1 optional_callback=|_| () />
|
||||
<Child int=1 />
|
||||
<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