diff --git a/Cargo.toml b/Cargo.toml index 44d161884..0841d0b62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "packages/dioxus", "packages/core-macro", "packages/core", - "packages/web", + # "packages/web", # "packages/router", # "packages/ssr", # "packages/webview", diff --git a/README.md b/README.md index 60b7d80d2..83bb39720 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla ```rust +#[fc] static Example: FC<()> = |ctx, props| { let (selection, set_selection) = use_state(&ctx, || "...?"); diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index fe1de60f3..7a2b6b574 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -12,6 +12,7 @@ description = "Core macro for Dioxus Virtual DOM" proc-macro = true [dependencies] +once_cell = "1.7.2" proc-macro-hack = "0.5.19" proc-macro2 = "1.0.6" quote = "1.0" diff --git a/packages/core-macro/examples/fc.rs b/packages/core-macro/examples/fc.rs new file mode 100644 index 000000000..033a454fb --- /dev/null +++ b/packages/core-macro/examples/fc.rs @@ -0,0 +1,3 @@ +use dioxus_core_macro::fc; + +fn main() {} diff --git a/packages/core-macro/src/fc.rs b/packages/core-macro/src/fc.rs index c7b87ce62..055efd38f 100644 --- a/packages/core-macro/src/fc.rs +++ b/packages/core-macro/src/fc.rs @@ -9,80 +9,13 @@ use syn::{ parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility, }; -pub fn function_component_impl( - // name: FunctionComponentName, - component: FunctionComponent, -) -> syn::Result { - // let FunctionComponentName { component_name } = name; - - let FunctionComponent { - block, - props_type, - arg, - vis, - attrs, - name: function_name, - return_type, - } = component; - - // if function_name == component_name { - // return Err(syn::Error::new_spanned( - // component_name, - // "the component must not have the same name as the function", - // )); - // } - - let quoted = quote! { - #[doc(hidden)] - #[allow(non_camel_case_types)] - mod __component_blah { - use super::*; - - #[derive(PartialEq)] - pub struct Props<'a> { - name: &'a str - } - - pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> { - // Destructure the props into the parent scope - // todo: handle expansion of lifetimes - let Props { - name - } = ctx.props; - - #block - } - } - #[allow(non_snake_case)] - pub use __component_blah::component as #function_name; - }; - // let quoted = quote! { - // #[doc(hidden)] - // #[allow(non_camel_case_types)] - // #vis struct #function_name; - - // impl ::yew_functional::FunctionProvider for #function_name { - // type TProps = #props_type; - - // fn run(#arg) -> #ret_type { - // #block - // } - // } - - // #(#attrs)* - // #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>; - // }; - Ok(quoted) -} - /// A parsed version of the user's input pub struct FunctionComponent { // The actual contents of the function block: Box, - // The user's props type - props_type: Box, - + // // The user's props type + // props_type: Box, arg: FnArg, vis: Visibility, attrs: Vec, @@ -117,7 +50,7 @@ impl Parse for FunctionComponent { .unwrap_or_else(|| syn::parse_quote! { _: &() }); // Extract the "context" object - let props_type = validate_context_arg(&first_arg)?; + // let props_type = validate_context_arg(&first_arg)?; /* Extract the rest of the function arguments into a struct body @@ -159,7 +92,7 @@ impl Parse for FunctionComponent { let name = sig.ident; Ok(Self { - props_type, + // props_type, block, arg: first_arg, vis, @@ -169,6 +102,89 @@ impl Parse for FunctionComponent { }) } } +impl ToTokens for FunctionComponent { + fn to_tokens(&self, tokens: &mut TokenStream) { + // let FunctionComponentName { component_name } = name; + + let FunctionComponent { + block, + // props_type, + arg, + vis, + attrs, + name: function_name, + return_type, + } = self; + + // if function_name == component_name { + // return Err(syn::Error::new_spanned( + // component_name, + // "the component must not have the same name as the function", + // )); + // } + + let quoted = quote! { + + + #[doc(hidden)] + #[allow(non_camel_case_types)] + #[derive(PartialEq)] + pub struct #function_name<'a> { + // and some other attrs + ___p: std::marker::PhantomData<&'a ()> + } + + impl<'a> FC for #function_name<'a> { + fn render(ctx: Context<'_>, props: &#function_name<'a>) -> DomTree { + let #function_name { + .. + } = props; + + #block + } + } + + // mod __component_blah { + // use super::*; + + // #[derive(PartialEq)] + // pub struct Props<'a> { + // name: &'a str + // } + + // pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> { + // // Destructure the props into the parent scope + // // todo: handle expansion of lifetimes + // let Props { + // name + // } = ctx.props; + + // #block + // } + // } + // #[allow(non_snake_case)] + // pub use __component_blah::component as #function_name; + }; + + quoted.to_tokens(tokens); + // let quoted = quote! { + // #[doc(hidden)] + // #[allow(non_camel_case_types)] + // #vis struct #function_name; + + // impl ::yew_functional::FunctionProvider for #function_name { + // type TProps = #props_type; + + // fn run(#arg) -> #ret_type { + // #block + // } + // } + + // #(#attrs)* + // #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>; + // }; + } +} /// Ensure the user's input is actually a functional component pub fn ensure_fn_block(item: Item) -> syn::Result { @@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result> { match output { ReturnType::Default => Err(syn::Error::new_spanned( output, - "function components must return `dioxus::VNode`", + "function components must return a `DomTree`", )), ReturnType::Type(_, ty) => Ok(ty), } @@ -268,39 +284,40 @@ pub fn validate_signature(sig: Signature) -> syn::Result { Ok(sig) } -pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result> { - if let FnArg::Typed(arg) = first_arg { - // Input arg is a reference to an &mut Context - if let Type::Reference(ty) = &*arg.ty { - if ty.lifetime.is_some() { - return Err(syn::Error::new_spanned( - &ty.lifetime, - "reference must not have a lifetime", - )); - } +// pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result> { +// if let FnArg::Typed(arg) = first_arg { +// // if let Type::R +// // Input arg is a reference to an &mut Context +// // if let Type::Reference(ty) = &*arg.ty { +// // if ty.lifetime.is_some() { +// // return Err(syn::Error::new_spanned( +// // &ty.lifetime, +// // "reference must not have a lifetime", +// // )); +// // } - if ty.mutability.is_some() { - return Err(syn::Error::new_spanned( - &ty.mutability, - "reference must not be mutable", - )); - } +// // if ty.mutability.is_some() { +// // return Err(syn::Error::new_spanned( +// // &ty.mutability, +// // "reference must not be mutable", +// // )); +// // } - Ok(ty.elem.clone()) - } else { - let msg = format!( - "expected a reference to a `Context` object (try: `&mut {}`)", - arg.ty.to_token_stream() - ); - return Err(syn::Error::new_spanned(arg.ty.clone(), msg)); - } - } else { - return Err(syn::Error::new_spanned( - first_arg, - "function components can't accept a receiver", - )); - } -} +// // Ok(ty.elem.clone()) +// // } else { +// // let msg = format!( +// // "expected a reference to a `Context` object (try: `&mut {}`)", +// // arg.ty.to_token_stream() +// // ); +// // return Err(syn::Error::new_spanned(arg.ty.clone(), msg)); +// // } +// } else { +// return Err(syn::Error::new_spanned( +// first_arg, +// "function components can't accept a receiver", +// )); +// } +// } pub fn collect_inline_args() {} diff --git a/packages/core-macro/src/ifmt.rs b/packages/core-macro/src/ifmt.rs index d0e341eb3..816c1f400 100644 --- a/packages/core-macro/src/ifmt.rs +++ b/packages/core-macro/src/ifmt.rs @@ -1,4 +1,3 @@ -use ::proc_macro::TokenStream; use ::quote::{quote, ToTokens}; use ::std::ops::Not; use ::syn::{ @@ -6,10 +5,7 @@ use ::syn::{ punctuated::Punctuated, *, }; -use proc_macro2::TokenStream as TokenStream2; - -// #[macro_use] -// mod macros { +use proc_macro2::TokenStream; // #[cfg(not(feature = "verbose-expansions"))] macro_rules! debug_input { @@ -18,18 +14,6 @@ macro_rules! debug_input { }; } -// #[cfg(feature = "verbose-expansions")] -macro_rules! debug_input { - ($expr:expr) => { - match $expr { - expr => { - eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr); - expr - } - } - }; -} - // #[cfg(not(feature = "verbose-expansions"))] macro_rules! debug_output { ($expr:expr) => { @@ -38,19 +22,30 @@ macro_rules! debug_output { } // #[cfg(feature = "verbose-expansions")] -macro_rules! debug_output { - ($expr:expr) => { - match $expr { - expr => { - eprintln!("=>\n{}\n-------------------\n", expr); - expr - } - } - }; -} +// macro_rules! debug_input { +// ($expr:expr) => { +// match $expr { +// expr => { +// eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr); +// expr +// } +// } +// }; // } -pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { +// #[cfg(feature = "verbose-expansions")] +// macro_rules! debug_output { +// ($expr:expr) => { +// match $expr { +// expr => { +// eprintln!("=>\n{}\n-------------------\n", expr); +// expr +// } +// } +// }; +// } + +pub fn format_args_f_impl(input: IfmtInput) -> Result { let IfmtInput { mut format_literal, mut positional_args, @@ -121,7 +116,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { arg, ) { Ok(segments) => segments.into_iter().collect(), - Err(err) => return err.to_compile_error().into(), + Err(err) => return Err(err), } }; match segments.len() { @@ -151,7 +146,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { format_args!("{}", positional_args.len()), ) .expect("`usize` or `char` Display impl cannot panic"); - let segments: Punctuated = segments + let segments: Punctuated = segments .into_iter() .map(|it| match it { Segment::Ident(ident) => ident.into_token_stream(), @@ -172,13 +167,13 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { }); format_literal = LitStr::new(out_format_literal, format_literal.span()); - TokenStream::from(debug_output!(quote! { + Ok(TokenStream::from(debug_output!(quote! { format_args!( #format_literal #(, #positional_args)* #(, #named_args)* ) - })) + }))) } #[allow(dead_code)] // dumb compiler does not see the struct being used... diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index e9b24c0be..67669f873 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -6,6 +6,7 @@ mod fc; mod htm; mod ifmt; mod rsxt; +mod util; /// The html! macro makes it easy for developers to write jsx-style markup in their components. /// We aim to keep functional parity with html templates. @@ -27,31 +28,34 @@ pub fn rsx(s: TokenStream) -> TokenStream { } } +// #[proc_macro_attribute] +// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream { + /// Label a function or static closure as a functional component. /// This macro reduces the need to create a separate properties struct. +/// +/// Using this macro is fun and simple +/// +/// ```ignore +/// +/// #[fc] +/// fn Example(ctx: Context, name: &str) -> DomTree { +/// ctx.render(rsx! { h1 {"hello {name}"} }) +/// } +/// ``` #[proc_macro_attribute] pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream { - use fc::{function_component_impl, FunctionComponent}; - - let item = parse_macro_input!(item as FunctionComponent); - - function_component_impl(item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() + match syn::parse::(item) { + Err(e) => e.to_compile_error().into(), + Ok(s) => s.to_token_stream().into(), + } } #[proc_macro] pub fn format_args_f(input: TokenStream) -> TokenStream { use ifmt::*; - let item = parse_macro_input!(input as IfmtInput); - - // #[allow(unused)] - // const FUNCTION_NAME: &str = "format_args_f"; - - // debug_input!(&input); - - ifmt::format_args_f_impl(item) - // .unwrap_or_else(|err| err.to_compile_error()) - // .into() + format_args_f_impl(item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } diff --git a/packages/core-macro/src/rsxt.rs b/packages/core-macro/src/rsxt.rs index 61be92ef4..b5dd31cab 100644 --- a/packages/core-macro/src/rsxt.rs +++ b/packages/core-macro/src/rsxt.rs @@ -88,6 +88,7 @@ impl ToTokens for RsxRender { enum Node { Element(Element), Text(TextNode), + Component(Component), } impl ToTokens for ToToksCtx<&Node> { @@ -95,28 +96,29 @@ impl ToTokens for ToToksCtx<&Node> { match &self.inner { Node::Element(el) => self.recurse(el).to_tokens(tokens), Node::Text(txt) => self.recurse(txt).to_tokens(tokens), + Node::Component(c) => self.recurse(c).to_tokens(tokens), } } } -// impl Node { -// fn peek(s: ParseStream) -> bool { -// (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr) -// } -// } - impl Parse for Node { fn parse(s: ParseStream) -> Result { - let fork = s.fork(); + let fork1 = s.fork(); + let fork2 = s.fork(); - let ret = if let Ok(text) = fork.parse::() { - s.advance_to(&fork); - Ok(Self::Text(text)) - } else if let Ok(el) = s.parse::() { - Ok(Self::Element(el)) + // todo: map issues onto the second fork if any arise + // it's unlikely that textnodes or components would fail? + + let ret = if let Ok(text) = fork1.parse::() { + s.advance_to(&fork1); + Self::Text(text) + } else if let Ok(element) = fork2.parse::() { + s.advance_to(&fork2); + Self::Element(element) + } else if let Ok(comp) = s.parse::() { + Self::Component(comp) } else { - // TODO: Span information - panic!("Not a valid child node"); + return Err(Error::new(s.span(), "Failed to parse as a valid child")); }; // consume comma if it exists @@ -124,7 +126,120 @@ impl Parse for Node { if s.peek(Token![,]) { let _ = s.parse::(); } - ret + Ok(ret) + } +} + +struct Component { + name: Ident, + body: Vec, + // attrs: Vec, + children: MaybeExpr>, +} + +impl ToTokens for ToToksCtx<&Component> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let name = &self.inner.name; + + // let mut toks = quote! {}; + + // for attr in self.inner.attrs.iter() { + // self.recurse(attr).to_tokens(&mut toks); + // } + + let body = &self.inner.body; + // panic!("tokens are {:#?}", toks); + + // tokens.append_all(quote! { + // dioxus::builder::virtual_child(ctx, #name{ #body, ___p: PhantomData{} }) + // // dioxus::builder::ElementBuilder::new(ctx, #name) + // }); + + // no children right now + + // match &self.inner.children { + // MaybeExpr::Expr(expr) => tokens.append_all(quote! { + // .children(#expr) + // }), + // MaybeExpr::Literal(nodes) => { + // let mut children = nodes.iter(); + // if let Some(child) = children.next() { + // let mut inner_toks = TokenStream2::new(); + // self.recurse(child).to_tokens(&mut inner_toks); + // while let Some(child) = children.next() { + // quote!(,).to_tokens(&mut inner_toks); + // self.recurse(child).to_tokens(&mut inner_toks); + // } + // tokens.append_all(quote! { + // .children([#inner_toks]) + // }); + // } + // } + // } + // tokens.append_all(quote! { + // .finish() + // }); + } +} + +impl Parse for Component { + fn parse(s: ParseStream) -> Result { + // TODO: reject anything weird/nonstandard + // we want names *only* + let name = Ident::parse_any(s)?; + + if crate::util::is_valid_tag(&name.to_string()) { + return Err(Error::new( + name.span(), + "Components cannot share names with valid HTML tags", + )); + } + + // parse the guts + let content: ParseBuffer; + syn::braced!(content in s); + let mut fields: Vec = Vec::new(); + + 'parsing: loop { + // [1] Break if empty + if content.is_empty() { + break 'parsing; + } + } + + // let body = content.parse()?; + + // eventually we'll parse the attrs + // let mut attrs: Vec = vec![]; + let mut children: Vec = vec![]; + // parse_element_content(content, &mut attrs, &mut children); + + let children = MaybeExpr::Literal(children); + + Ok(Self { + name, + body, + // attrs, + children, + }) + } +} + +// the struct's fields info +pub struct Field { + name: Ident, + content: Ident, +} + +impl Parse for Field { + fn parse(input: ParseStream) -> Result { + todo!() + } +} + +impl ToTokens for ToToksCtx<&Field> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + todo!() } } @@ -142,12 +257,12 @@ struct Element { impl ToTokens for ToToksCtx<&Element> { fn to_tokens(&self, tokens: &mut TokenStream2) { - // todo!() - // // let ctx = self.ctx; let name = &self.inner.name.to_string(); + tokens.append_all(quote! { dioxus::builder::ElementBuilder::new(ctx, #name) }); + for attr in self.inner.attrs.iter() { self.recurse(attr).to_tokens(tokens); } @@ -182,84 +297,20 @@ impl Parse for Element { // we want names *only* let name = Ident::parse_any(s)?; + if !crate::util::is_valid_tag(&name.to_string()) { + return Err(Error::new(name.span(), "Not a valid Html tag")); + } + // parse the guts let content: ParseBuffer; syn::braced!(content in s); - let mut attrs = vec![]; + let mut attrs: Vec = vec![]; let mut children: Vec = vec![]; - - 'parsing: loop { - // todo move this around into a more functional style - // [1] Break if empty - // [2] Try to consume an attr (with comma) - // [3] Try to consume a child node (with comma) - // [4] Try to consume brackets as anything thats Into - // [last] Fail if none worked - - // [1] Break if empty - if content.is_empty() { - break 'parsing; - } - - // [2] Try to consume an attr - let fork = content.fork(); - if let Ok(attr) = fork.parse::() { - // make sure to advance or your computer will become a spaceheater :) - content.advance_to(&fork); - attrs.push(attr); - continue 'parsing; - } - - // [3] Try to consume a child node - let fork = content.fork(); - if let Ok(node) = fork.parse::() { - // make sure to advance or your computer will become a spaceheater :) - content.advance_to(&fork); - children.push(node); - continue 'parsing; - } - - // [4] TODO: Parsing brackets - // let fork = content.fork(); - // if let Ok(el) = fork.parse() { - // children.push(el); - // continue 'parsing; - // } - - // todo: pass a descriptive error onto the offending tokens - panic!("Entry is not an attr or element\n {}", content) - } + parse_element_content(content, &mut attrs, &mut children); let children = MaybeExpr::Literal(children); - // let children = MaybeExpr::Literal(Vec::new()); - // // Contents of an element can either be a brace (in which case we just copy verbatim), or a - // // sequence of nodes. - // let children = if s.peek(token::Brace) { - // // expr - // let content; - // syn::braced!(content in s); - // MaybeExpr::Expr(content.parse()?) - // } else { - // // nodes - // let mut children = vec![]; - // while !(s.peek(Token![<]) && s.peek2(Token![/])) { - // children.push(s.parse()?); - // } - // MaybeExpr::Literal(children) - // }; - // // closing element - // s.parse::()?; - // s.parse::()?; - // let close = Ident::parse_any(s)?; - // if close.to_string() != name.to_string() { - // return Err(Error::new_spanned( - // close, - // "closing element does not match opening", - // )); - // } - // s.parse::]>()?; Ok(Self { name, attrs, @@ -268,6 +319,51 @@ impl Parse for Element { } } +// used by both vcomponet and velement to parse the contents of the elements into attras and children +fn parse_element_content(content: ParseBuffer, attrs: &mut Vec, children: &mut Vec) { + 'parsing: loop { + // todo move this around into a more functional style + // [1] Break if empty + // [2] Try to consume an attr (with comma) + // [3] Try to consume a child node (with comma) + // [4] Try to consume brackets as anything thats Into + // [last] Fail if none worked + + // [1] Break if empty + if content.is_empty() { + break 'parsing; + } + + // [2] Try to consume an attr + let fork = content.fork(); + if let Ok(attr) = fork.parse::() { + // make sure to advance or your computer will become a spaceheater :) + content.advance_to(&fork); + attrs.push(attr); + continue 'parsing; + } + + // [3] Try to consume a child node + let fork = content.fork(); + if let Ok(node) = fork.parse::() { + // make sure to advance or your computer will become a spaceheater :) + content.advance_to(&fork); + children.push(node); + continue 'parsing; + } + + // [4] TODO: Parsing brackets + // let fork = content.fork(); + // if let Ok(el) = fork.parse() { + // children.push(el); + // continue 'parsing; + // } + + // todo: pass a descriptive error onto the offending tokens + panic!("Entry is not an attr or element\n {}", content) + } +} + /// ======================================= /// Parse a VElement's Attributes /// ======================================= diff --git a/packages/core-macro/src/util.rs b/packages/core-macro/src/util.rs new file mode 100644 index 000000000..f3d26e1b9 --- /dev/null +++ b/packages/core-macro/src/util.rs @@ -0,0 +1,137 @@ +// use lazy_static::lazy_static; +use once_cell::sync::Lazy; +use std::collections::hash_set::HashSet; + +static VALID_TAGS: Lazy> = Lazy::new(|| { + [ + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "big", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "command", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "keygen", + "label", + "legend", + "li", + "link", + "main", + "map", + "mark", + "menu", + "menuitem", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "picture", + "pre", + "progress", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", + ] + .iter() + .cloned() + .collect() +}); + +/// Whether or not this tag is valid +/// +/// ``` +/// use html_validation::is_valid_tag; +/// +/// assert_eq!(is_valid_tag("br"), true); +/// +/// assert_eq!(is_valid_tag("random"), false); +/// ``` +pub fn is_valid_tag(tag: &str) -> bool { + VALID_TAGS.contains(tag) +} diff --git a/packages/core/examples/fc.rs b/packages/core/examples/fc.rs new file mode 100644 index 000000000..4e0bd8f95 --- /dev/null +++ b/packages/core/examples/fc.rs @@ -0,0 +1,41 @@ +use dioxus_core::prelude::*; +use dioxus_core::scope::FC; +use dioxus_core_macro::fc; + +use std::marker::PhantomData; +// #[derive(PartialEq)] +// pub struct Example<'a> { +// b: &'a str, +// ___p: std::marker::PhantomData<&'a ()>, +// } + +// impl<'a> FC for Example<'a> { +// fn render(ctx: Context<'_>, props: &Example<'a>) -> DomTree { +// let Example { b, .. } = props; +// { +// ctx.render(rsx! { +// div { "abcd {b}" } +// }) +// } +// } +// } + +// always try to fill in with Default + +#[fc] +fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree { + ctx.render(rsx! { + div { + SomeComponent { + a: "123" + } + } + }) +} + +#[fc] +fn SomeComponent(ctx: Context, a: &str, b: &str) -> DomTree { + todo!() +} + +fn main() {} diff --git a/packages/core/examples/rsx_usage.rs b/packages/core/examples/rsx_usage.rs index 7ed471a69..4001fc29c 100644 --- a/packages/core/examples/rsx_usage.rs +++ b/packages/core/examples/rsx_usage.rs @@ -3,33 +3,53 @@ use dioxus_core::prelude::*; fn main() {} +trait SProps {} + +trait Comp { + type Props; +} + +impl Comp for FC { + type Props = T; +} + +fn test() { + // let g: FC = CustomButton; +} + +trait Render { + fn render(ctx: Context, props: &Self) -> DomTree; +} // include as much as you might accept -struct ButtonProps<'a> { +struct Button<'a> { onhover: Option<&'a dyn Fn()>, - // // A list of any attrs - // attrs: AttrList<'a>, } -fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree { - let onfocus = move |evt: ()| log::debug!("Focused"); +impl Render for Button<'_> { + fn render(ctx: Context, props: &Self) -> DomTree { + let onfocus = move |evt: ()| log::debug!("Focused"); - // todo!() - ctx.render(rsx! { - button { - // ..props.attrs, - class: "abc123", - // style: { a: 2, b: 3, c: 4 }, - onclick: move |evt| { - // log::info("hello world"); - }, - // Custom1 { a: 123 } - // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} } - // Custom3 { a: "sometext goes here" } - // Custom4 { onclick: |evt| log::info("click") } - } - }) + // todo!() + ctx.render(rsx! { + button { + // ..props.attrs, + class: "abc123", + // style: { a: 2, b: 3, c: 4 }, + onclick: move |evt| { + // log::info("hello world"); + }, + // Custom1 { a: 123 } + // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} } + // Custom3 { a: "sometext goes here" } + // Custom4 { onclick: |evt| log::info("click") } + } + }) + } } +// #[fc] +// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {} + // h1 { // tag: "type", abc: 123, class: "big small wide short", // "title1" diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index a80951f6a..2b173345e 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -4,7 +4,7 @@ fn main() -> Result<(), ()> { let p1 = Props { name: "bob".into() }; let mut vdom = VirtualDom::new_with_props(Example, p1); - vdom.update_props(|p: &mut Props| {}); + // vdom.update_props(|p: &mut Props| {}); Ok(()) } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 8dc0b69a1..6b8fe0173 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -39,31 +39,31 @@ mod tests { use crate::prelude::bumpalo::Bump; use crate::prelude::*; - fn test_static_fn<'a, P>(b: &'a Bump, r: FC

) -> VNode<'a> { - todo!() - } + // fn test_static_fn<'a, P>(b: &'a Bump, r: FC

) -> VNode<'a> { + // todo!() + // } - static TestComponent: FC<()> = |ctx, props| { - // + // static TestComponent: FC<()> = |ctx, props| { + // // - ctx.render(html! { -

+ // ctx.render(html! { + //
-
- }) - }; + //
+ // }) + // }; - static TestComponent2: FC<()> = |ctx, props| { - // - ctx.render(|ctx| VNode::text("blah")) - }; + // static TestComponent2: FC<()> = |ctx, props| { + // // + // ctx.render(|ctx| VNode::text("blah")) + // }; - #[test] - fn ensure_types_work() { - let bump = Bump::new(); + // #[test] + // fn ensure_types_work() { + // let bump = Bump::new(); - // Happiness! The VNodes are now allocated onto the bump vdom - let _ = test_static_fn(&bump, TestComponent); - let _ = test_static_fn(&bump, TestComponent2); - } + // // Happiness! The VNodes are now allocated onto the bump vdom + // let _ = test_static_fn(&bump, TestComponent); + // let _ = test_static_fn(&bump, TestComponent2); + // } } diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 0d08e8db7..5acc1543d 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -25,15 +25,20 @@ impl DebugRenderer { mod tests { use super::*; use crate::prelude::*; + use crate::scope::FC; #[test] fn ensure_creation() -> Result<(), ()> { - let mut dom = VirtualDom::new(|ctx, props| { - // - ctx.render(html! {
"hello world"
}) - }); + #[derive(PartialEq)] + struct Creation {} + impl FC for Creation { + fn render(ctx: Context, props: &Self) -> DomTree { + ctx.render(html! {
"hello world"
}) + } + } + + let mut dom = VirtualDom::new_with_props(Creation {}); - // dom.progress()?; Ok(()) } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2199f7755..936568145 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -53,23 +53,19 @@ use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components /// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via /// subscriptions and props changes. -pub struct DiffMachine<'a, 'b> { +pub struct DiffMachine<'a> { pub change_list: EditMachine<'a>, - pub vdom: &'b VirtualDom, - pub cur_idx: ScopeIdx, pub diffed: FxHashSet, pub need_to_diff: FxHashSet, } -impl<'a, 'b> DiffMachine<'a, 'b> { - pub fn new(vdom: &'b VirtualDom, bump: &'a Bump, idx: ScopeIdx) -> Self { +impl<'a> DiffMachine<'a> { + pub fn new(bump: &'a Bump) -> Self { Self { - cur_idx: idx, change_list: EditMachine::new(bump), diffed: FxHashSet::default(), need_to_diff: FxHashSet::default(), - vdom, } } @@ -118,10 +114,10 @@ impl<'a, 'b> DiffMachine<'a, 'b> { } (VNode::Component(cold), VNode::Component(cnew)) => { - if cold.comp != cnew.comp { - // queue an event to mount this new component - return; - } + // if cold.comp != cnew.comp { + // // queue an event to mount this new component + // return; + // } // compare props.... somehow..... @@ -843,23 +839,9 @@ impl<'a, 'b> DiffMachine<'a, 'b> { } listeners.iter().enumerate().for_each(|(id, listener)| { - // if let Some(index) = self.current_idx { - self.change_list.new_event_listener( - listener.event, - listener.scope, - listener.id, - ); - // } else { - // Don't panic - // Used for testing - // log::trace!("Failed to set listener, create was not called in the context of the virtual dom"); - // } + self.change_list + .new_event_listener(listener.event, listener.scope, listener.id) }); - // for l in listeners { - // unsafe { - // registry.add(l); - // } - // } for attr in attributes { self.change_list diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 3bd36db23..c03c99bff 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -183,30 +183,30 @@ mod use_reducer_def { Decr, } - #[allow(unused)] - static Example: FC<()> = |ctx, props| { - let (count, reduce) = use_reducer( - &ctx, - || 0, - |count, action| match action { - Actions::Incr => *count += 1, - Actions::Decr => *count -= 1, - }, - ); + // #[allow(unused)] + // static Example: FC<()> = |ctx, props| { + // let (count, reduce) = use_reducer( + // &ctx, + // || 0, + // |count, action| match action { + // Actions::Incr => *count += 1, + // Actions::Decr => *count -= 1, + // }, + // ); - ctx.render(rsx! { - div { - h1 {"Count: {count}"} - button { - "Increment" - onclick: move |_| reduce(Actions::Incr) - } - button { - "Decrement" - onclick: move |_| reduce(Actions::Decr) - } - } - }) - }; + // ctx.render(rsx! { + // div { + // h1 {"Count: {count}"} + // button { + // "Increment" + // onclick: move |_| reduce(Actions::Incr) + // } + // button { + // "Decrement" + // onclick: move |_| reduce(Actions::Decr) + // } + // } + // }) + // }; } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8748a0146..bbaf4a160 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -106,7 +106,7 @@ pub(crate) mod innerlude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; + // pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; mod fc2 {} // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; @@ -122,7 +122,8 @@ pub(crate) mod innerlude { // Re-export the FC macro pub use crate as dioxus; pub use crate::nodebuilder as builder; - pub use dioxus_core_macro::{fc, html, rsx}; + pub use dioxus_core_macro::{html, rsx}; + // pub use dioxus_core_macro::{fc, html, rsx}; } /// Re-export common types for ease of development use. @@ -136,7 +137,7 @@ pub mod prelude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub use crate::innerlude::FC; + // pub use crate::innerlude::FC; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate @@ -151,7 +152,7 @@ pub mod prelude { pub use crate::nodebuilder as builder; // pub use dioxus_core_macro::fc; pub use dioxus_core_macro::format_args_f; - pub use dioxus_core_macro::{fc, html, rsx}; + pub use dioxus_core_macro::{html, rsx}; pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 6cba5f135..22b72645e 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -524,7 +524,9 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { // } // } -pub fn virtual_child<'a, T>(_bump: &'a Bump, _props: T, _f: crate::innerlude::FC) -> VNode<'a> { +// _f: crate::innerlude::FC, +// _props: T +pub fn virtual_child<'a, T: crate::scope::FC>(ctx: &'a NodeCtx<'a>, p: T) -> VNode<'a> { todo!() // VNode::Component() } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 8b04e95ae..9075e3b06 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -296,8 +296,16 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::innerlude::{Context, ScopeIdx, FC}; - use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc}; + use crate::{ + innerlude::{Context, ScopeIdx}, + scope::FC, + }; + use std::{ + any::{Any, TypeId}, + cell::RefCell, + marker::PhantomData, + rc::Rc, + }; use super::DomTree; @@ -306,16 +314,18 @@ mod vcomponent { #[derive(Debug)] pub struct VComponent<'src> { _p: PhantomData<&'src ()>, - pub(crate) props: Box, - pub(crate) props_type: TypeId, - pub(crate) comp: *const (), - pub(crate) caller: Caller, - + // pub(crate) props: Box, + // pub(crate) props_type: TypeId, + // pub(crate) comp: *const (), + // pub(crate) caller: Caller, + pub(crate) comparator: Comparator, // once a component gets mounted, its parent gets a stable address. // this way we can carry the scope index from between renders // genius, really! - pub assigned_scope: StableScopeAddres, + // pub assigned_scope: StableScopeAddres, } + pub struct Comparator(Box bool>); + // pub struct Comparator<'src>(&'src dyn Fn(&dyn Any) -> bool); pub struct Caller(Box DomTree>); impl std::fmt::Debug for Caller { @@ -324,12 +334,35 @@ mod vcomponent { } } + impl std::fmt::Debug for Comparator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + impl<'a> VComponent<'a> { // use the type parameter on props creation and move it into a portable context // this lets us keep scope generic *and* downcast its props when we need to: // - perform comparisons when diffing (memoization) // - - pub fn new

(comp: FC

, props: P) -> Self { + pub fn new(comp: P) -> Self { + todo!() + // let p = Rc::new(props); + + // let props_comparator = move |new_props: &dyn Any| -> bool { + // new_props + // .downcast_ref::

() + // .map(|new_p| p.as_ref() == new_p) + // .unwrap_or_else(|| { + // log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); + // false + // }) + // }; + + // Self { + // _p: PhantomData, + // comparator: Comparator(Box::new(props_comparator)), + // } // let caller = move |ctx: Context| { // let t = comp(ctx, &props); // t @@ -355,7 +388,7 @@ mod vcomponent { // } // } - todo!() + // todo!() // Self { // _p: PhantomData {}, // props, diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 7de06a73e..a7e43aff7 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -11,9 +11,9 @@ use std::{ ops::Deref, }; -pub trait Properties: PartialEq {} -// just for now -impl Properties for T {} +pub trait FC: PartialEq { + fn render(ctx: Context, props: &Self) -> DomTree; +} pub trait Scoped { fn run(&mut self); @@ -30,15 +30,13 @@ pub trait Scoped { /// /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. -pub struct Scope { +pub struct Scope { // Map to the parent pub parent: Option, // our own index pub myidx: ScopeIdx, - pub caller: FC

, - pub props: P, // ========================== @@ -63,8 +61,7 @@ pub struct Scope { // instead of having it as a trait method, we use a single function // todo: do the unsafety magic stuff to erase the type of p -pub fn create_scoped( - caller: FC

, +pub fn create_scoped( props: P, myidx: ScopeIdx, parent: Option, @@ -90,7 +87,6 @@ pub fn create_scoped( myidx, hook_arena, hooks, - caller, frames, listeners, parent, @@ -98,7 +94,7 @@ pub fn create_scoped( }) } -impl Scoped for Scope

{ +impl Scoped for Scope

{ /// Create a new context and run the component with references from the Virtual Dom /// This function downcasts the function pointer based on the stored props_type /// @@ -125,7 +121,8 @@ impl Scoped for Scope

{ // Note that the actual modification of the vnode head element occurs during this call // let _: DomTree = caller(ctx, props); - let _: DomTree = (self.caller)(ctx, &self.props); + let _: DomTree = P::render(ctx, &self.props); + // let _: DomTree = (self.caller)(ctx, &self.props); /* SAFETY ALERT @@ -264,90 +261,89 @@ mod tests { use super::*; use crate::prelude::*; - static ListenerTest: FC<()> = |ctx, props| { - ctx.render(html! { -

- "hello" -
- }) - }; + // static ListenerTest: FC<()> = |ctx, props| { + // ctx.render(html! { + //
+ // "hello" + //
+ // }) + // }; #[test] fn test_scope() { - let example: FC<()> = |ctx, props| { - use crate::builder::*; - ctx.render(|ctx| { - builder::ElementBuilder::new(ctx, "div") - .child(text("a")) - .finish() - }) - }; + #[derive(PartialEq)] + struct Example {} + impl FC for Example { + fn render(ctx: Context<'_>, _: &Self) -> DomTree { + use crate::builder::*; + ctx.render(|ctx| { + builder::ElementBuilder::new(ctx, "div") + .child(text("a")) + .finish() + }) + } + } - let props = (); - let parent = None; let mut nodes = generational_arena::Arena::new(); nodes.insert_with(|myidx| { - let scope = create_scoped(example, props, myidx, parent); + let scope = create_scoped(Example {}, myidx, None); }); } - #[derive(Debug)] - struct ExampleProps<'src> { - name: &'src String, - } - - #[derive(Debug)] - struct EmptyProps<'src> { - name: &'src String, - } - use crate::{builder::*, hooks::use_ref}; - fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps) -> DomTree { - let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string()); - - let childprops: ExampleProps<'a> = ExampleProps { name: content }; - ctx.render(move |c| { - builder::ElementBuilder::new(c, "div") - .child(text(props.name)) - .child(virtual_child::( - c.bump, - childprops, - child_example, - )) - .finish() - }) + #[derive(Debug, PartialEq)] + struct EmptyProps<'src> { + name: &'src String, } - fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree { - ctx.render(move |ctx| { - builder::ElementBuilder::new(ctx, "div") - .child(text(props.name)) - .finish() - }) + impl FC for EmptyProps<'_> { + fn render(ctx: Context, props: &Self) -> DomTree { + let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string()); + + let childprops: ExampleProps<'_> = ExampleProps { name: content }; + todo!() + // ctx.render(move |c| { + // builder::ElementBuilder::new(c, "div") + // .child(text(props.name)) + // .child(virtual_child(c, childprops)) + // .finish() + // }) + } } - static CHILD: FC = |ctx, props: &'_ ExampleProps| { - ctx.render(move |ctx| { - builder::ElementBuilder::new(ctx, "div") - .child(text(props.name)) - .finish() - }) - }; + #[derive(Debug, PartialEq)] + struct ExampleProps<'src> { + name: &'src String, + } + + impl FC for ExampleProps<'_> { + fn render(ctx: Context, props: &Self) -> DomTree { + ctx.render(move |ctx| { + builder::ElementBuilder::new(ctx, "div") + .child(text(props.name)) + .finish() + }) + } + } #[test] fn test_borrowed_scope() { - let example: FC = |ctx, props| { - ctx.render(move |b| { - builder::ElementBuilder::new(b, "div") - .child(virtual_child( - b.bump, - ExampleProps { name: props.name }, - CHILD, - )) - .finish() - }) - }; + #[derive(Debug, PartialEq)] + struct Example { + name: String, + } + + impl FC for Example { + fn render(ctx: Context, props: &Self) -> DomTree { + todo!() + // ctx.render(move |c| { + // builder::ElementBuilder::new(c, "div") + // .child(virtual_child(c, ExampleProps { name: &props.name })) + // .finish() + // }) + } + } let source_text = "abcd123".to_string(); let props = ExampleProps { name: &source_text }; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 10b1d2d95..909193d28 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,6 +1,6 @@ // use crate::{changelist::EditList, nodes::VNode}; -use crate::{innerlude::*, scope::Properties}; +use crate::{innerlude::*, scope::FC}; use crate::{ patch::Edit, scope::{create_scoped, Scoped}, @@ -60,8 +60,9 @@ impl VirtualDom { /// /// This means that the root component must either consumes its own context, or statics are used to generate the page. /// The root component can access things like routing in its context. - pub fn new(root: FC<()>) -> Self { - Self::new_with_props(root, ()) + pub fn new(root: impl Fn(Context) -> DomTree) -> Self { + todo!() + // Self::new_with_props(root) } /// Start a new VirtualDom instance with a dependent props. @@ -69,13 +70,13 @@ impl VirtualDom { /// /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// to toss out the entire tree. - pub fn new_with_props(root: FC

, root_props: P) -> Self { - let mut components = Arena::new(); + pub fn new_with_props(root: P) -> Self { + // let mut components = Arena::new(); // Create a reference to the component in the arena // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist // This puts the dom in a usable state on creation, rather than being potentially invalid - let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); + // let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); todo!() // Self { @@ -106,19 +107,16 @@ impl VirtualDom { let b = Bump::new(); - let mut diff_machine = DiffMachine::new(self, &b, self.base_scope); + let mut diff_machine = DiffMachine::new(&self.diff_bump); // let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope); - todo!() - // let component = self.components.get(self.base_scope).unwrap(); - - // diff_machine.diff_node(component.old_frame(), component.new_frame()); - - // let edits = diff_machine.consume(); + // todo!() + let component = self.components.get(self.base_scope).unwrap(); + diff_machine.diff_node(component.old_frame(), component.new_frame()); + let edits = diff_machine.consume(); // self.diff_bump = b; - - // Ok(edits) + Ok(edits) } /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. @@ -187,6 +185,9 @@ impl VirtualDom { } } -// struct LockedEdits<'src> { -// edits: -// } +enum LifeCycleEvent { + // Mount { +// props: &dyn Properties, +// // f: FC, +// }, +} diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index cb1b61434..5706e406b 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -3,7 +3,7 @@ pub mod prelude { pub use dioxus_core_macro::fc; } -use dioxus_core::prelude::FC; +// use dioxus_core::prelude::FC; // Re-export core completely pub use dioxus_core as core; diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index b5f276723..23f183621 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -22,6 +22,7 @@ pretty_env_logger = "0.4.0" console_error_panic_hook = "0.1.6" generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" +once_cell = "1.7.2" # html-validation = { path = "../html-validation", version = "0.1.1" } [dependencies.web-sys] diff --git a/packages/web/examples/infer.rs b/packages/web/examples/infer.rs index b1eb91ad0..fa9cd1529 100644 --- a/packages/web/examples/infer.rs +++ b/packages/web/examples/infer.rs @@ -9,6 +9,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } +// this is a component static Example: FC<()> = |ctx, _props| { let (event, set_event) = use_state(&ctx, || None); let event = format!("{:#?}", event); @@ -21,14 +22,12 @@ static Example: FC<()> = |ctx, _props| { div { class: "py-12 px-4 w-full max-w-2xl mx-auto bg-red-100" - // class: "py-12 px-4 text-center w-full max-w-2xl mx-auto bg-red-100" span { class: "text-sm font-semibold" "Dioxus Example: Synthetic Events" } button { class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" - "press me" } pre { @@ -36,6 +35,21 @@ static Example: FC<()> = |ctx, _props| { id: "json" "{event}" } + Example2 { name: "{event}" } + } + }) +}; + + +#[derive(Debug, PartialEq)] +struct Props { + name: String +} + +static Example2: FC = |ctx, props| { + ctx.render(rsx!{ + div { + h1 {"hello {props.name}"} } }) };