29 files changed
+2139
-526
No files matched your search
+1
-1
@@ -4,7 +4,7 @@ members = [
|
|||||||
"packages/dioxus",
|
"packages/dioxus",
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/web",
|
# "packages/web",
|
||||||
# "packages/router",
|
# "packages/router",
|
||||||
# "packages/ssr",
|
# "packages/ssr",
|
||||||
# "packages/webview",
|
# "packages/webview",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
|
|||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
#[fc]
|
||||||
static Example: FC<()> = |ctx, props| {
|
static Example: FC<()> = |ctx, props| {
|
||||||
let (selection, set_selection) = use_state(&ctx, || "...?");
|
let (selection, set_selection) = use_state(&ctx, || "...?");
|
||||||
|
|
||||||
|
|||||||
@@ -360,3 +360,37 @@ The DomTree object purely represents a viewable "key". It also forces components
|
|||||||
## Events
|
## Events
|
||||||
|
|
||||||
Events are finally in! To do events properly, we are abstracting over the event source with synthetic events. This forces 3rd party renderers to create the appropriate cross-platform event
|
Events are finally in! To do events properly, we are abstracting over the event source with synthetic events. This forces 3rd party renderers to create the appropriate cross-platform event
|
||||||
|
|
||||||
|
|
||||||
|
## Optional Props on Components
|
||||||
|
|
||||||
|
A major goal here is ergonomics. Any field that is Option<T> should default to none.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
Example { /* args go here */ a: 10, b: 20 }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Properties)]
|
||||||
|
struct Props {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Component: FC<Props> = |ctx, props| {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[fc]
|
||||||
|
static Component: FC = |ctx, name: &str| {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -12,6 +12,7 @@ description = "Core macro for Dioxus Virtual DOM"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
once_cell = "1.7.2"
|
||||||
proc-macro-hack = "0.5.19"
|
proc-macro-hack = "0.5.19"
|
||||||
proc-macro2 = "1.0.6"
|
proc-macro2 = "1.0.6"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
use dioxus_core_macro::fc;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
+120
-103
@@ -9,80 +9,13 @@ use syn::{
|
|||||||
parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
|
parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn function_component_impl(
|
|
||||||
// name: FunctionComponentName,
|
|
||||||
component: FunctionComponent,
|
|
||||||
) -> syn::Result<TokenStream> {
|
|
||||||
// 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
|
/// A parsed version of the user's input
|
||||||
pub struct FunctionComponent {
|
pub struct FunctionComponent {
|
||||||
// The actual contents of the function
|
// The actual contents of the function
|
||||||
block: Box<Block>,
|
block: Box<Block>,
|
||||||
|
|
||||||
// The user's props type
|
// // The user's props type
|
||||||
props_type: Box<Type>,
|
// props_type: Box<Type>,
|
||||||
|
|
||||||
arg: FnArg,
|
arg: FnArg,
|
||||||
vis: Visibility,
|
vis: Visibility,
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
@@ -117,7 +50,7 @@ impl Parse for FunctionComponent {
|
|||||||
.unwrap_or_else(|| syn::parse_quote! { _: &() });
|
.unwrap_or_else(|| syn::parse_quote! { _: &() });
|
||||||
|
|
||||||
// Extract the "context" object
|
// 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
|
Extract the rest of the function arguments into a struct body
|
||||||
@@ -159,7 +92,7 @@ impl Parse for FunctionComponent {
|
|||||||
let name = sig.ident;
|
let name = sig.ident;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
props_type,
|
// props_type,
|
||||||
block,
|
block,
|
||||||
arg: first_arg,
|
arg: first_arg,
|
||||||
vis,
|
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
|
/// Ensure the user's input is actually a functional component
|
||||||
pub fn ensure_fn_block(item: Item) -> syn::Result<ItemFn> {
|
pub fn ensure_fn_block(item: Item) -> syn::Result<ItemFn> {
|
||||||
@@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
|
|||||||
match output {
|
match output {
|
||||||
ReturnType::Default => Err(syn::Error::new_spanned(
|
ReturnType::Default => Err(syn::Error::new_spanned(
|
||||||
output,
|
output,
|
||||||
"function components must return `dioxus::VNode`",
|
"function components must return a `DomTree`",
|
||||||
)),
|
)),
|
||||||
ReturnType::Type(_, ty) => Ok(ty),
|
ReturnType::Type(_, ty) => Ok(ty),
|
||||||
}
|
}
|
||||||
@@ -268,39 +284,40 @@ pub fn validate_signature(sig: Signature) -> syn::Result<Signature> {
|
|||||||
Ok(sig)
|
Ok(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
|
// pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
|
||||||
if let FnArg::Typed(arg) = first_arg {
|
// if let FnArg::Typed(arg) = first_arg {
|
||||||
// Input arg is a reference to an &mut Context
|
// // if let Type::R
|
||||||
if let Type::Reference(ty) = &*arg.ty {
|
// // Input arg is a reference to an &mut Context
|
||||||
if ty.lifetime.is_some() {
|
// // if let Type::Reference(ty) = &*arg.ty {
|
||||||
return Err(syn::Error::new_spanned(
|
// // if ty.lifetime.is_some() {
|
||||||
&ty.lifetime,
|
// // return Err(syn::Error::new_spanned(
|
||||||
"reference must not have a lifetime",
|
// // &ty.lifetime,
|
||||||
));
|
// // "reference must not have a lifetime",
|
||||||
}
|
// // ));
|
||||||
|
// // }
|
||||||
|
|
||||||
if ty.mutability.is_some() {
|
// // if ty.mutability.is_some() {
|
||||||
return Err(syn::Error::new_spanned(
|
// // return Err(syn::Error::new_spanned(
|
||||||
&ty.mutability,
|
// // &ty.mutability,
|
||||||
"reference must not be mutable",
|
// // "reference must not be mutable",
|
||||||
));
|
// // ));
|
||||||
}
|
// // }
|
||||||
|
|
||||||
Ok(ty.elem.clone())
|
// // Ok(ty.elem.clone())
|
||||||
} else {
|
// // } else {
|
||||||
let msg = format!(
|
// // let msg = format!(
|
||||||
"expected a reference to a `Context` object (try: `&mut {}`)",
|
// // "expected a reference to a `Context` object (try: `&mut {}`)",
|
||||||
arg.ty.to_token_stream()
|
// // arg.ty.to_token_stream()
|
||||||
);
|
// // );
|
||||||
return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
|
// // return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
|
||||||
}
|
// // }
|
||||||
} else {
|
// } else {
|
||||||
return Err(syn::Error::new_spanned(
|
// return Err(syn::Error::new_spanned(
|
||||||
first_arg,
|
// first_arg,
|
||||||
"function components can't accept a receiver",
|
// "function components can't accept a receiver",
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn collect_inline_args() {}
|
pub fn collect_inline_args() {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use ::proc_macro::TokenStream;
|
|
||||||
use ::quote::{quote, ToTokens};
|
use ::quote::{quote, ToTokens};
|
||||||
use ::std::ops::Not;
|
use ::std::ops::Not;
|
||||||
use ::syn::{
|
use ::syn::{
|
||||||
@@ -6,10 +5,7 @@ use ::syn::{
|
|||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
// #[macro_use]
|
|
||||||
// mod macros {
|
|
||||||
|
|
||||||
// #[cfg(not(feature = "verbose-expansions"))]
|
// #[cfg(not(feature = "verbose-expansions"))]
|
||||||
macro_rules! debug_input {
|
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"))]
|
// #[cfg(not(feature = "verbose-expansions"))]
|
||||||
macro_rules! debug_output {
|
macro_rules! debug_output {
|
||||||
($expr:expr) => {
|
($expr:expr) => {
|
||||||
@@ -38,19 +22,30 @@ macro_rules! debug_output {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(feature = "verbose-expansions")]
|
// #[cfg(feature = "verbose-expansions")]
|
||||||
macro_rules! debug_output {
|
// macro_rules! debug_input {
|
||||||
($expr:expr) => {
|
// ($expr:expr) => {
|
||||||
match $expr {
|
// match $expr {
|
||||||
expr => {
|
// expr => {
|
||||||
eprintln!("=>\n{}\n-------------------\n", expr);
|
// eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr);
|
||||||
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<TokenStream> {
|
||||||
let IfmtInput {
|
let IfmtInput {
|
||||||
mut format_literal,
|
mut format_literal,
|
||||||
mut positional_args,
|
mut positional_args,
|
||||||
@@ -121,7 +116,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
|
|||||||
arg,
|
arg,
|
||||||
) {
|
) {
|
||||||
Ok(segments) => segments.into_iter().collect(),
|
Ok(segments) => segments.into_iter().collect(),
|
||||||
Err(err) => return err.to_compile_error().into(),
|
Err(err) => return Err(err),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match segments.len() {
|
match segments.len() {
|
||||||
@@ -151,7 +146,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
|
|||||||
format_args!("{}", positional_args.len()),
|
format_args!("{}", positional_args.len()),
|
||||||
)
|
)
|
||||||
.expect("`usize` or `char` Display impl cannot panic");
|
.expect("`usize` or `char` Display impl cannot panic");
|
||||||
let segments: Punctuated<TokenStream2, Token![.]> = segments
|
let segments: Punctuated<TokenStream, Token![.]> = segments
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|it| match it {
|
.map(|it| match it {
|
||||||
Segment::Ident(ident) => ident.into_token_stream(),
|
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());
|
format_literal = LitStr::new(out_format_literal, format_literal.span());
|
||||||
|
|
||||||
TokenStream::from(debug_output!(quote! {
|
Ok(TokenStream::from(debug_output!(quote! {
|
||||||
format_args!(
|
format_args!(
|
||||||
#format_literal
|
#format_literal
|
||||||
#(, #positional_args)*
|
#(, #positional_args)*
|
||||||
#(, #named_args)*
|
#(, #named_args)*
|
||||||
)
|
)
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use syn::parse_macro_input;
|
|||||||
mod fc;
|
mod fc;
|
||||||
mod htm;
|
mod htm;
|
||||||
mod ifmt;
|
mod ifmt;
|
||||||
|
mod props;
|
||||||
mod rsxt;
|
mod rsxt;
|
||||||
|
mod util;
|
||||||
|
|
||||||
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
|
/// 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.
|
/// We aim to keep functional parity with html templates.
|
||||||
@@ -27,31 +29,43 @@ 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.
|
/// Label a function or static closure as a functional component.
|
||||||
/// This macro reduces the need to create a separate properties struct.
|
/// 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]
|
#[proc_macro_attribute]
|
||||||
pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
use fc::{function_component_impl, FunctionComponent};
|
match syn::parse::<fc::FunctionComponent>(item) {
|
||||||
|
Err(e) => e.to_compile_error().into(),
|
||||||
let item = parse_macro_input!(item as FunctionComponent);
|
Ok(s) => s.to_token_stream().into(),
|
||||||
|
}
|
||||||
function_component_impl(item)
|
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
||||||
use ifmt::*;
|
use ifmt::*;
|
||||||
|
|
||||||
let item = parse_macro_input!(input as IfmtInput);
|
let item = parse_macro_input!(input as IfmtInput);
|
||||||
|
format_args_f_impl(item)
|
||||||
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
// #[allow(unused)]
|
#[proc_macro_derive(Props, attributes(builder))]
|
||||||
// const FUNCTION_NAME: &str = "format_args_f";
|
pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input = parse_macro_input!(input as syn::DeriveInput);
|
||||||
// debug_input!(&input);
|
match props::impl_my_derive(&input) {
|
||||||
|
Ok(output) => output.into(),
|
||||||
ifmt::format_args_f_impl(item)
|
Err(error) => error.to_compile_error().into(),
|
||||||
// .unwrap_or_else(|err| err.to_compile_error())
|
}
|
||||||
// .into()
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
+216
-87
@@ -88,6 +88,7 @@ impl ToTokens for RsxRender {
|
|||||||
enum Node {
|
enum Node {
|
||||||
Element(Element),
|
Element(Element),
|
||||||
Text(TextNode),
|
Text(TextNode),
|
||||||
|
Component(Component),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<&Node> {
|
impl ToTokens for ToToksCtx<&Node> {
|
||||||
@@ -95,28 +96,29 @@ impl ToTokens for ToToksCtx<&Node> {
|
|||||||
match &self.inner {
|
match &self.inner {
|
||||||
Node::Element(el) => self.recurse(el).to_tokens(tokens),
|
Node::Element(el) => self.recurse(el).to_tokens(tokens),
|
||||||
Node::Text(txt) => self.recurse(txt).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 {
|
impl Parse for Node {
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
fn parse(s: ParseStream) -> Result<Self> {
|
||||||
let fork = s.fork();
|
let fork1 = s.fork();
|
||||||
|
let fork2 = s.fork();
|
||||||
|
|
||||||
let ret = if let Ok(text) = fork.parse::<TextNode>() {
|
// todo: map issues onto the second fork if any arise
|
||||||
s.advance_to(&fork);
|
// it's unlikely that textnodes or components would fail?
|
||||||
Ok(Self::Text(text))
|
|
||||||
} else if let Ok(el) = s.parse::<Element>() {
|
let ret = if let Ok(text) = fork1.parse::<TextNode>() {
|
||||||
Ok(Self::Element(el))
|
s.advance_to(&fork1);
|
||||||
|
Self::Text(text)
|
||||||
|
} else if let Ok(element) = fork2.parse::<Element>() {
|
||||||
|
s.advance_to(&fork2);
|
||||||
|
Self::Element(element)
|
||||||
|
} else if let Ok(comp) = s.parse::<Component>() {
|
||||||
|
Self::Component(comp)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Span information
|
return Err(Error::new(s.span(), "Failed to parse as a valid child"));
|
||||||
panic!("Not a valid child node");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// consume comma if it exists
|
// consume comma if it exists
|
||||||
@@ -124,7 +126,153 @@ impl Parse for Node {
|
|||||||
if s.peek(Token![,]) {
|
if s.peek(Token![,]) {
|
||||||
let _ = s.parse::<Token![,]>();
|
let _ = s.parse::<Token![,]>();
|
||||||
}
|
}
|
||||||
ret
|
Ok(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Component {
|
||||||
|
name: Ident,
|
||||||
|
body: Vec<ComponentField>,
|
||||||
|
// attrs: Vec<Attr>,
|
||||||
|
children: MaybeExpr<Vec<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mut builder = quote! {
|
||||||
|
fc_to_builder(#name)
|
||||||
|
};
|
||||||
|
|
||||||
|
for field in &self.inner.body {
|
||||||
|
builder.append_all(quote! {#field});
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append_all(quote! {
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
// panic!("tokens are {:#?}", toks);
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
// });
|
||||||
|
let toks = tokens.append_all(quote! {
|
||||||
|
dioxus::builder::virtual_child(ctx, #name, #builder)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Component {
|
||||||
|
fn parse(s: ParseStream) -> Result<Self> {
|
||||||
|
// 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 body: Vec<ComponentField> = Vec::new();
|
||||||
|
let mut children: Vec<Node> = Vec::new();
|
||||||
|
|
||||||
|
// parse_element_content(content, &mut attrs, &mut children);
|
||||||
|
|
||||||
|
'parsing: loop {
|
||||||
|
// [1] Break if empty
|
||||||
|
if content.is_empty() {
|
||||||
|
break 'parsing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(field) = content.parse::<ComponentField>() {
|
||||||
|
body.push(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let body = content.parse()?;
|
||||||
|
|
||||||
|
// eventually we'll parse the attrs
|
||||||
|
// let mut attrs: Vec<Attr> = vec![];
|
||||||
|
let mut children: Vec<Node> = 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 ComponentField {
|
||||||
|
name: Ident,
|
||||||
|
content: Expr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ComponentField {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let mut name = Ident::parse_any(input)?;
|
||||||
|
let name_str = name.to_string();
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
let content = input.parse()?;
|
||||||
|
|
||||||
|
// consume comma if it exists
|
||||||
|
// we don't actually care if there *are* commas between attrs
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
let _ = input.parse::<Token![,]>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { name, content })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for &ComponentField {
|
||||||
|
// impl ToTokens for ToToksCtx<&ComponentField> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let name = &self.name;
|
||||||
|
let content = &self.content;
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
.#name(#content)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,12 +290,12 @@ struct Element {
|
|||||||
|
|
||||||
impl ToTokens for ToToksCtx<&Element> {
|
impl ToTokens for ToToksCtx<&Element> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
// todo!()
|
|
||||||
// // let ctx = self.ctx;
|
|
||||||
let name = &self.inner.name.to_string();
|
let name = &self.inner.name.to_string();
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
dioxus::builder::ElementBuilder::new(ctx, #name)
|
dioxus::builder::ElementBuilder::new(ctx, #name)
|
||||||
});
|
});
|
||||||
|
|
||||||
for attr in self.inner.attrs.iter() {
|
for attr in self.inner.attrs.iter() {
|
||||||
self.recurse(attr).to_tokens(tokens);
|
self.recurse(attr).to_tokens(tokens);
|
||||||
}
|
}
|
||||||
@@ -182,84 +330,20 @@ impl Parse for Element {
|
|||||||
// we want names *only*
|
// we want names *only*
|
||||||
let name = Ident::parse_any(s)?;
|
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
|
// parse the guts
|
||||||
let content: ParseBuffer;
|
let content: ParseBuffer;
|
||||||
syn::braced!(content in s);
|
syn::braced!(content in s);
|
||||||
|
|
||||||
let mut attrs = vec![];
|
let mut attrs: Vec<Attr> = vec![];
|
||||||
let mut children: Vec<Node> = vec![];
|
let mut children: Vec<Node> = vec![];
|
||||||
|
parse_element_content(content, &mut attrs, &mut children);
|
||||||
'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<Node>
|
|
||||||
// [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::<Attr>() {
|
|
||||||
// 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::<Node>() {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
let children = MaybeExpr::Literal(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::<Token![<]>()?;
|
|
||||||
// s.parse::<Token![/]>()?;
|
|
||||||
// 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::<Token![>]>()?;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
attrs,
|
attrs,
|
||||||
@@ -268,6 +352,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<Attr>, children: &mut Vec<Node>) {
|
||||||
|
'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<Node>
|
||||||
|
// [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::<Attr>() {
|
||||||
|
// 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::<Node>() {
|
||||||
|
// 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
|
/// Parse a VElement's Attributes
|
||||||
/// =======================================
|
/// =======================================
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
// use lazy_static::lazy_static;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::hash_set::HashSet;
|
||||||
|
|
||||||
|
static VALID_TAGS: Lazy<HashSet<&'static str>> = 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)
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@
|
|||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
struct Props {
|
struct Props {
|
||||||
items: Vec<ListItem>,
|
items: Vec<ListItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
struct ListItem {
|
struct ListItem {
|
||||||
name: String,
|
name: String,
|
||||||
age: u32,
|
age: u32,
|
||||||
@@ -21,19 +24,20 @@ struct ListItem {
|
|||||||
fn app(ctx: Context, props: &Props) -> DomTree {
|
fn app(ctx: Context, props: &Props) -> DomTree {
|
||||||
let (f, setter) = use_state(&ctx, || 0);
|
let (f, setter) = use_state(&ctx, || 0);
|
||||||
|
|
||||||
ctx.render(move |b| {
|
ctx.render(move |c| {
|
||||||
let mut root = builder::ElementBuilder::new(b, "div");
|
let mut root = builder::ElementBuilder::new(c, "div");
|
||||||
for child in &props.items {
|
for child in &props.items {
|
||||||
// notice that the child directly borrows from our vec
|
// notice that the child directly borrows from our vec
|
||||||
// this makes lists very fast (simply views reusing lifetimes)
|
// this makes lists very fast (simply views reusing lifetimes)
|
||||||
|
// <ChildItem item=child hanldler=setter />
|
||||||
root = root.child(builder::virtual_child(
|
root = root.child(builder::virtual_child(
|
||||||
b.bump,
|
c,
|
||||||
ChildProps {
|
ChildItem,
|
||||||
item: child,
|
// create the props with nothing but the fc<T>
|
||||||
item_handler: setter,
|
fc_to_builder(ChildItem)
|
||||||
},
|
.item(child)
|
||||||
// <ChildItem item=child hanldler=setter />
|
.item_handler(setter)
|
||||||
child_item,
|
.build(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
root.finish()
|
root.finish()
|
||||||
@@ -41,7 +45,16 @@ fn app(ctx: Context, props: &Props) -> DomTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StateSetter<T> = dyn Fn(T);
|
type StateSetter<T> = dyn Fn(T);
|
||||||
|
// struct StateSetter<T>(dyn Fn(T));
|
||||||
|
|
||||||
|
// impl<T> PartialEq for StateSetter<T> {
|
||||||
|
// fn eq(&self, other: &Self) -> bool {
|
||||||
|
// self as *const _ == other as *const _
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// props should derive a partialeq implementation automatically, but implement ptr compare for & fields
|
||||||
|
#[derive(Props)]
|
||||||
struct ChildProps<'a> {
|
struct ChildProps<'a> {
|
||||||
// Pass down complex structs
|
// Pass down complex structs
|
||||||
item: &'a ListItem,
|
item: &'a ListItem,
|
||||||
@@ -50,7 +63,22 @@ struct ChildProps<'a> {
|
|||||||
item_handler: &'a StateSetter<i32>,
|
item_handler: &'a StateSetter<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child_item(ctx: Context, props: &ChildProps) -> DomTree {
|
impl PartialEq for ChildProps<'_> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
// assume the dyn fn is never stable -
|
||||||
|
// wrap with use_callback if it's an issue for you
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Properties for ChildProps<'a> {
|
||||||
|
type Builder = ChildPropsBuilder<'a, ((), ())>;
|
||||||
|
fn builder() -> <Self as Properties>::Builder {
|
||||||
|
ChildProps::builder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ChildItem(ctx: Context, props: &ChildProps) -> DomTree {
|
||||||
todo!()
|
todo!()
|
||||||
// ctx.render(rsx! {
|
// ctx.render(rsx! {
|
||||||
// div {
|
// div {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use dioxus_core::component::fc_to_builder;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use dioxus_core_macro::fc;
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
static BLAH: FC<()> = |ctx, props| {
|
||||||
|
let g = "asd".to_string();
|
||||||
|
ctx.render(rsx! {
|
||||||
|
div {
|
||||||
|
SomeComponent {
|
||||||
|
some_field: g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
pub struct ExampleProps {
|
||||||
|
some_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
static SomeComponent: FC<ExampleProps> = |ctx, props| {
|
||||||
|
ctx.render(rsx! {
|
||||||
|
div { }
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
impl Properties for ExampleProps {
|
||||||
|
type Builder = ExamplePropsBuilder<((),)>;
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
ExampleProps::builder()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,20 @@
|
|||||||
use std::marker::PhantomData;
|
use dioxus_core_macro::Props;
|
||||||
|
|
||||||
fn main() {}
|
#[derive(Debug, Props)]
|
||||||
|
struct SomeProps {
|
||||||
|
a: i32,
|
||||||
|
|
||||||
trait Props<'parent> {}
|
// automatically do the default (none) and automatically Into<T>
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
struct SomeProps<'p> {
|
b: Option<i32>,
|
||||||
text: &'p str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p> Props<'p> for SomeProps<'p> {}
|
// have we committed to the trait style yet?
|
||||||
|
|
||||||
struct OutputNode<'a> {
|
fn main() {
|
||||||
_p: PhantomData<&'a ()>,
|
let g: SomeProps = SomeProps::builder().a(10).b(10).build();
|
||||||
|
|
||||||
|
let r = g.b.unwrap_or_else(|| 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// combine reference to self (borrowed from self) and referenfce to parent (borrowed from parent)
|
fn auto_into_some() {}
|
||||||
// borrow chain looks like 'p + 's -> 'p + 's -> 'p + 's
|
|
||||||
// always adding new lifetimes from self into the mix
|
|
||||||
// what does a "self" lifetime mean?
|
|
||||||
// a "god" gives us our data
|
|
||||||
// the god's lifetime is tied to Context, and the borrowed props object
|
|
||||||
// for the sake of simplicity, we just clobber lifetimes.
|
|
||||||
// user functions are just lies and we abuse lifetimes.
|
|
||||||
// everything is managed at runtime because that's how we make something ergonomc
|
|
||||||
// lifetime management in dioxus is just cheating around the rules
|
|
||||||
// our kind god manages lifetimes for us so we don't have to, thanks god
|
|
||||||
fn something<'s>(_props: &'s SomeProps<'s>) -> OutputNode<'s> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
// type BC<'p, P: Props<'p>> = for<'a, 'b, 'c> fn(&'a P<'b>) -> OutputNode<'c>;
|
|
||||||
@@ -3,33 +3,53 @@ use dioxus_core::prelude::*;
|
|||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
|
trait SProps {}
|
||||||
|
|
||||||
|
trait Comp {
|
||||||
|
type Props;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Comp for FC<T> {
|
||||||
|
type Props = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test() {
|
||||||
|
// let g: FC<ButtonProps> = CustomButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Render {
|
||||||
|
fn render(ctx: Context, props: &Self) -> DomTree;
|
||||||
|
}
|
||||||
// include as much as you might accept
|
// include as much as you might accept
|
||||||
struct ButtonProps<'a> {
|
struct Button<'a> {
|
||||||
onhover: Option<&'a dyn Fn()>,
|
onhover: Option<&'a dyn Fn()>,
|
||||||
// // A list of any attrs
|
|
||||||
// attrs: AttrList<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree {
|
impl Render for Button<'_> {
|
||||||
let onfocus = move |evt: ()| log::debug!("Focused");
|
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||||
|
let onfocus = move |evt: ()| log::debug!("Focused");
|
||||||
|
|
||||||
// todo!()
|
// todo!()
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
button {
|
button {
|
||||||
// ..props.attrs,
|
// ..props.attrs,
|
||||||
class: "abc123",
|
class: "abc123",
|
||||||
// style: { a: 2, b: 3, c: 4 },
|
// style: { a: 2, b: 3, c: 4 },
|
||||||
onclick: move |evt| {
|
onclick: move |evt| {
|
||||||
// log::info("hello world");
|
// log::info("hello world");
|
||||||
},
|
},
|
||||||
// Custom1 { a: 123 }
|
// Custom1 { a: 123 }
|
||||||
// Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
|
// Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
|
||||||
// Custom3 { a: "sometext goes here" }
|
// Custom3 { a: "sometext goes here" }
|
||||||
// Custom4 { onclick: |evt| log::info("click") }
|
// Custom4 { onclick: |evt| log::info("click") }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[fc]
|
||||||
|
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {}
|
||||||
|
|
||||||
// h1 {
|
// h1 {
|
||||||
// tag: "type", abc: 123, class: "big small wide short",
|
// tag: "type", abc: 123, class: "big small wide short",
|
||||||
// "title1"
|
// "title1"
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::{component::Properties, prelude::*};
|
||||||
|
|
||||||
fn main() -> Result<(), ()> {
|
fn main() -> Result<(), ()> {
|
||||||
let p1 = Props { name: "bob".into() };
|
let p1 = SomeProps { name: "bob".into() };
|
||||||
|
|
||||||
let mut vdom = VirtualDom::new_with_props(Example, p1);
|
let mut vdom = VirtualDom::new_with_props(Example, p1);
|
||||||
vdom.update_props(|p: &mut Props| {});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Props)]
|
||||||
struct Props {
|
struct SomeProps {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<Props> = |ctx, _props| {
|
static Example: FC<SomeProps> = |ctx, _props| {
|
||||||
ctx.render(html! {
|
ctx.render(html! {
|
||||||
<div>
|
<div>
|
||||||
<h1> "hello world!" </h1>
|
<h1> "hello world!" </h1>
|
||||||
@@ -24,3 +23,11 @@ static Example: FC<Props> = |ctx, _props| {
|
|||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// toodo: derive this
|
||||||
|
impl Properties for SomeProps {
|
||||||
|
type Builder = SomePropsBuilder<((),)>;
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
SomeProps::builder()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,69 +1,33 @@
|
|||||||
//! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
|
//! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
|
||||||
//! for components to be used within Nodes.
|
//! for components to be used within Nodes.
|
||||||
|
//!
|
||||||
|
//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is
|
||||||
|
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||||
|
//! that ensures compile-time required and optional fields on props.
|
||||||
|
|
||||||
|
use crate::innerlude::FC;
|
||||||
pub type ScopeIdx = generational_arena::Index;
|
pub type ScopeIdx = generational_arena::Index;
|
||||||
|
|
||||||
/// The `Component` trait refers to any struct or funciton that can be used as a component
|
pub trait Properties: PartialEq {
|
||||||
/// We automatically implement Component for FC<T>
|
type Builder;
|
||||||
// pub trait Component {
|
fn builder() -> Self::Builder;
|
||||||
// type Props: Properties<'static>;
|
}
|
||||||
// fn builder(&'static self) -> Self::Props;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Auto implement component for a FC
|
pub struct EmptyBuilder;
|
||||||
// // Calling the FC is the same as "rendering" it
|
impl EmptyBuilder {
|
||||||
// impl<P: Properties<'static>> Component for FC<P> {
|
pub fn build(self) -> () {
|
||||||
// type Props = P;
|
()
|
||||||
|
|
||||||
// fn builder(&self) -> Self::Props {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields.
|
|
||||||
/// Components take a "properties" object
|
|
||||||
// pub trait Properties<'a>
|
|
||||||
// where
|
|
||||||
// Self: Debug,
|
|
||||||
// {
|
|
||||||
// fn call(&self, ptr: *const ()) {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Auto implement for no-prop components
|
|
||||||
// impl<'a> Properties<'a> for () {
|
|
||||||
// fn call(&self, ptr: *const ()) {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::prelude::bumpalo::Bump;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
fn test_static_fn<'a, P>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
static TestComponent: FC<()> = |ctx, props| {
|
|
||||||
//
|
|
||||||
|
|
||||||
ctx.render(html! {
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
static TestComponent2: FC<()> = |ctx, props| {
|
|
||||||
//
|
|
||||||
ctx.render(|ctx| VNode::text("blah"))
|
|
||||||
};
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Properties for () {
|
||||||
|
type Builder = EmptyBuilder;
|
||||||
|
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
EmptyBuilder {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fc_to_builder<T: Properties>(f: FC<T>) -> T::Builder {
|
||||||
|
T::builder()
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ impl<'a> Context<'a> {
|
|||||||
/// ctx.render(lazy_tree)
|
/// ctx.render(lazy_tree)
|
||||||
/// }
|
/// }
|
||||||
///```
|
///```
|
||||||
pub fn render(self, lazy_nodes: impl FnOnce(&NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
|
pub fn render(self, lazy_nodes: impl FnOnce(&'_ NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
|
||||||
let ctx = NodeCtx {
|
let ctx = NodeCtx {
|
||||||
bump: self.bump,
|
bump: self.bump,
|
||||||
scope: self.scope,
|
scope: self.scope,
|
||||||
|
|||||||
@@ -21,19 +21,25 @@ impl DebugRenderer {
|
|||||||
pub fn log_dom(&self) {}
|
pub fn log_dom(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(old)]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::scope::Properties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_creation() -> Result<(), ()> {
|
fn ensure_creation() -> Result<(), ()> {
|
||||||
let mut dom = VirtualDom::new(|ctx, props| {
|
#[derive(PartialEq)]
|
||||||
//
|
struct Creation {}
|
||||||
ctx.render(html! { <div>"hello world" </div> })
|
impl FC for Creation {
|
||||||
});
|
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||||
|
ctx.render(html! { <div>"hello world" </div> })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new_with_props(Creation {});
|
||||||
|
|
||||||
// dom.progress()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
/// 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
|
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
|
||||||
/// subscriptions and props changes.
|
/// subscriptions and props changes.
|
||||||
pub struct DiffMachine<'a, 'b> {
|
pub struct DiffMachine<'a> {
|
||||||
pub change_list: EditMachine<'a>,
|
pub change_list: EditMachine<'a>,
|
||||||
|
|
||||||
pub vdom: &'b VirtualDom,
|
|
||||||
pub cur_idx: ScopeIdx,
|
|
||||||
pub diffed: FxHashSet<ScopeIdx>,
|
pub diffed: FxHashSet<ScopeIdx>,
|
||||||
pub need_to_diff: FxHashSet<ScopeIdx>,
|
pub need_to_diff: FxHashSet<ScopeIdx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> DiffMachine<'a, 'b> {
|
impl<'a> DiffMachine<'a> {
|
||||||
pub fn new(vdom: &'b VirtualDom, bump: &'a Bump, idx: ScopeIdx) -> Self {
|
pub fn new(bump: &'a Bump) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cur_idx: idx,
|
|
||||||
change_list: EditMachine::new(bump),
|
change_list: EditMachine::new(bump),
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
need_to_diff: FxHashSet::default(),
|
need_to_diff: FxHashSet::default(),
|
||||||
vdom,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,10 +114,10 @@ impl<'a, 'b> DiffMachine<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Component(cold), VNode::Component(cnew)) => {
|
(VNode::Component(cold), VNode::Component(cnew)) => {
|
||||||
if cold.comp != cnew.comp {
|
// if cold.comp != cnew.comp {
|
||||||
// queue an event to mount this new component
|
// // queue an event to mount this new component
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// compare props.... somehow.....
|
// compare props.... somehow.....
|
||||||
|
|
||||||
@@ -843,23 +839,9 @@ impl<'a, 'b> DiffMachine<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listeners.iter().enumerate().for_each(|(id, listener)| {
|
listeners.iter().enumerate().for_each(|(id, listener)| {
|
||||||
// if let Some(index) = self.current_idx {
|
self.change_list
|
||||||
self.change_list.new_event_listener(
|
.new_event_listener(listener.event, listener.scope, listener.id)
|
||||||
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");
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
// for l in listeners {
|
|
||||||
// unsafe {
|
|
||||||
// registry.add(l);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
for attr in attributes {
|
for attr in attributes {
|
||||||
self.change_list
|
self.change_list
|
||||||
|
|||||||
+24
-24
@@ -183,30 +183,30 @@ mod use_reducer_def {
|
|||||||
Decr,
|
Decr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
// #[allow(unused)]
|
||||||
static Example: FC<()> = |ctx, props| {
|
// static Example: FC<()> = |ctx, props| {
|
||||||
let (count, reduce) = use_reducer(
|
// let (count, reduce) = use_reducer(
|
||||||
&ctx,
|
// &ctx,
|
||||||
|| 0,
|
// || 0,
|
||||||
|count, action| match action {
|
// |count, action| match action {
|
||||||
Actions::Incr => *count += 1,
|
// Actions::Incr => *count += 1,
|
||||||
Actions::Decr => *count -= 1,
|
// Actions::Decr => *count -= 1,
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
ctx.render(rsx! {
|
// ctx.render(rsx! {
|
||||||
div {
|
// div {
|
||||||
h1 {"Count: {count}"}
|
// h1 {"Count: {count}"}
|
||||||
button {
|
// button {
|
||||||
"Increment"
|
// "Increment"
|
||||||
onclick: move |_| reduce(Actions::Incr)
|
// onclick: move |_| reduce(Actions::Incr)
|
||||||
}
|
// }
|
||||||
button {
|
// button {
|
||||||
"Decrement"
|
// "Decrement"
|
||||||
onclick: move |_| reduce(Actions::Decr)
|
// onclick: move |_| reduce(Actions::Decr)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
};
|
// };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,7 @@ pub mod builder {
|
|||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
// pub(crate) use crate::component::Properties;
|
// pub(crate) use crate::component::Properties;
|
||||||
|
|
||||||
|
pub(crate) use crate::component::Properties;
|
||||||
pub(crate) use crate::context::Context;
|
pub(crate) use crate::context::Context;
|
||||||
pub(crate) use crate::error::{Error, Result};
|
pub(crate) use crate::error::{Error, Result};
|
||||||
use crate::nodes;
|
use crate::nodes;
|
||||||
@@ -122,13 +123,14 @@ pub(crate) mod innerlude {
|
|||||||
// Re-export the FC macro
|
// Re-export the FC macro
|
||||||
pub use crate as dioxus;
|
pub use crate as dioxus;
|
||||||
pub use crate::nodebuilder as builder;
|
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.
|
/// Re-export common types for ease of development use.
|
||||||
/// Essential when working with the html! macro
|
/// Essential when working with the html! macro
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
// pub use crate::component::Properties;
|
pub use crate::component::{fc_to_builder, Properties};
|
||||||
pub use crate::context::Context;
|
pub use crate::context::Context;
|
||||||
use crate::nodes;
|
use crate::nodes;
|
||||||
pub use crate::virtual_dom::VirtualDom;
|
pub use crate::virtual_dom::VirtualDom;
|
||||||
@@ -150,8 +152,8 @@ pub mod prelude {
|
|||||||
pub use crate as dioxus;
|
pub use crate as dioxus;
|
||||||
pub use crate::nodebuilder as builder;
|
pub use crate::nodebuilder as builder;
|
||||||
// pub use dioxus_core_macro::fc;
|
// 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::{format_args_f, html, rsx, Props};
|
||||||
|
|
||||||
pub use crate::component::ScopeIdx;
|
pub use crate::component::ScopeIdx;
|
||||||
pub use crate::diff::DiffMachine;
|
pub use crate::diff::DiffMachine;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{borrow::BorrowMut, ops::Deref};
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::NodeCtx,
|
context::NodeCtx,
|
||||||
events::VirtualEvent,
|
events::VirtualEvent,
|
||||||
innerlude::VComponent,
|
innerlude::{VComponent, FC},
|
||||||
nodes::{Attribute, Listener, NodeKey, VNode},
|
nodes::{Attribute, Listener, NodeKey, VNode},
|
||||||
prelude::VElement,
|
prelude::VElement,
|
||||||
};
|
};
|
||||||
@@ -524,7 +524,14 @@ 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<T>) -> VNode<'a> {
|
// _f: crate::innerlude::FC<T>,
|
||||||
|
// _props: T
|
||||||
|
pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>(
|
||||||
|
ctx: &'b NodeCtx<'a>,
|
||||||
|
f: FC<T>,
|
||||||
|
p: T,
|
||||||
|
) -> VNode<'a> {
|
||||||
|
// crate::nodes::VComponent
|
||||||
todo!()
|
todo!()
|
||||||
// VNode::Component()
|
// VNode::Component()
|
||||||
}
|
}
|
||||||
+40
-10
@@ -296,8 +296,13 @@ mod vtext {
|
|||||||
/// Virtual Components for custom user-defined components
|
/// Virtual Components for custom user-defined components
|
||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
mod vcomponent {
|
mod vcomponent {
|
||||||
use crate::innerlude::{Context, ScopeIdx, FC};
|
use crate::innerlude::{Context, Properties, ScopeIdx, FC};
|
||||||
use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc};
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
cell::RefCell,
|
||||||
|
marker::PhantomData,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use super::DomTree;
|
use super::DomTree;
|
||||||
|
|
||||||
@@ -306,16 +311,18 @@ mod vcomponent {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VComponent<'src> {
|
pub struct VComponent<'src> {
|
||||||
_p: PhantomData<&'src ()>,
|
_p: PhantomData<&'src ()>,
|
||||||
pub(crate) props: Box<dyn std::any::Any>,
|
// pub(crate) props: Box<dyn std::any::Any>,
|
||||||
pub(crate) props_type: TypeId,
|
// pub(crate) props_type: TypeId,
|
||||||
pub(crate) comp: *const (),
|
// pub(crate) comp: *const (),
|
||||||
pub(crate) caller: Caller,
|
// pub(crate) caller: Caller,
|
||||||
|
pub(crate) comparator: Comparator,
|
||||||
// once a component gets mounted, its parent gets a stable address.
|
// once a component gets mounted, its parent gets a stable address.
|
||||||
// this way we can carry the scope index from between renders
|
// this way we can carry the scope index from between renders
|
||||||
// genius, really!
|
// genius, really!
|
||||||
pub assigned_scope: StableScopeAddres,
|
// pub assigned_scope: StableScopeAddres,
|
||||||
}
|
}
|
||||||
|
pub struct Comparator(Box<dyn Fn(&dyn Any) -> bool>);
|
||||||
|
// pub struct Comparator<'src>(&'src dyn Fn(&dyn Any) -> bool);
|
||||||
|
|
||||||
pub struct Caller(Box<dyn Fn(Context) -> DomTree>);
|
pub struct Caller(Box<dyn Fn(Context) -> DomTree>);
|
||||||
impl std::fmt::Debug for Caller {
|
impl std::fmt::Debug for Caller {
|
||||||
@@ -324,12 +331,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> {
|
impl<'a> VComponent<'a> {
|
||||||
// use the type parameter on props creation and move it into a portable context
|
// 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:
|
// this lets us keep scope generic *and* downcast its props when we need to:
|
||||||
// - perform comparisons when diffing (memoization)
|
// - perform comparisons when diffing (memoization)
|
||||||
// -
|
// -
|
||||||
pub fn new<P>(comp: FC<P>, props: P) -> Self {
|
pub fn new<P: Properties + 'static>(caller: FC<P>, comp: P) -> Self {
|
||||||
|
todo!()
|
||||||
|
// let p = Rc::new(props);
|
||||||
|
|
||||||
|
// let props_comparator = move |new_props: &dyn Any| -> bool {
|
||||||
|
// new_props
|
||||||
|
// .downcast_ref::<P>()
|
||||||
|
// .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 caller = move |ctx: Context| {
|
||||||
// let t = comp(ctx, &props);
|
// let t = comp(ctx, &props);
|
||||||
// t
|
// t
|
||||||
@@ -355,7 +385,7 @@ mod vcomponent {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
todo!()
|
// todo!()
|
||||||
// Self {
|
// Self {
|
||||||
// _p: PhantomData {},
|
// _p: PhantomData {},
|
||||||
// props,
|
// props,
|
||||||
|
|||||||
+72
-73
@@ -11,10 +11,6 @@ use std::{
|
|||||||
ops::Deref,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Properties: PartialEq {}
|
|
||||||
// just for now
|
|
||||||
impl<T: PartialEq> Properties for T {}
|
|
||||||
|
|
||||||
pub trait Scoped {
|
pub trait Scoped {
|
||||||
fn run(&mut self);
|
fn run(&mut self);
|
||||||
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
|
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
|
||||||
@@ -37,10 +33,12 @@ pub struct Scope<P: Properties> {
|
|||||||
// our own index
|
// our own index
|
||||||
pub myidx: ScopeIdx,
|
pub myidx: ScopeIdx,
|
||||||
|
|
||||||
pub caller: FC<P>,
|
// the props
|
||||||
|
|
||||||
pub props: P,
|
pub props: P,
|
||||||
|
|
||||||
|
// and the actual render function
|
||||||
|
pub caller: FC<P>,
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// slightly unsafe stuff
|
// slightly unsafe stuff
|
||||||
// ==========================
|
// ==========================
|
||||||
@@ -87,10 +85,10 @@ pub fn create_scoped<P: Properties + 'static>(
|
|||||||
let frames = ActiveFrame::from_frames(old_frame, new_frame);
|
let frames = ActiveFrame::from_frames(old_frame, new_frame);
|
||||||
|
|
||||||
Box::new(Scope {
|
Box::new(Scope {
|
||||||
|
caller,
|
||||||
myidx,
|
myidx,
|
||||||
hook_arena,
|
hook_arena,
|
||||||
hooks,
|
hooks,
|
||||||
caller,
|
|
||||||
frames,
|
frames,
|
||||||
listeners,
|
listeners,
|
||||||
parent,
|
parent,
|
||||||
@@ -125,6 +123,7 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
|
|||||||
|
|
||||||
// Note that the actual modification of the vnode head element occurs during this call
|
// Note that the actual modification of the vnode head element occurs during this call
|
||||||
// let _: DomTree = caller(ctx, props);
|
// let _: DomTree = caller(ctx, props);
|
||||||
|
// let _: DomTree = P::render (ctx, &self.props);
|
||||||
let _: DomTree = (self.caller)(ctx, &self.props);
|
let _: DomTree = (self.caller)(ctx, &self.props);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -259,95 +258,95 @@ impl ActiveFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(old)]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
static ListenerTest: FC<()> = |ctx, props| {
|
// static ListenerTest: FC<()> = |ctx, props| {
|
||||||
ctx.render(html! {
|
// ctx.render(html! {
|
||||||
<div onclick={|_| println!("Hell owlrld")}>
|
// <div onclick={|_| println!("Hell owlrld")}>
|
||||||
"hello"
|
// "hello"
|
||||||
</div>
|
// </div>
|
||||||
})
|
// })
|
||||||
};
|
// };
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope() {
|
fn test_scope() {
|
||||||
let example: FC<()> = |ctx, props| {
|
#[derive(PartialEq)]
|
||||||
use crate::builder::*;
|
struct Example {}
|
||||||
ctx.render(|ctx| {
|
impl FC for Example {
|
||||||
builder::ElementBuilder::new(ctx, "div")
|
fn render(ctx: Context<'_>, _: &Self) -> DomTree {
|
||||||
.child(text("a"))
|
use crate::builder::*;
|
||||||
.finish()
|
ctx.render(|ctx| {
|
||||||
})
|
builder::ElementBuilder::new(ctx, "div")
|
||||||
};
|
.child(text("a"))
|
||||||
|
.finish()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let props = ();
|
|
||||||
let parent = None;
|
|
||||||
let mut nodes = generational_arena::Arena::new();
|
let mut nodes = generational_arena::Arena::new();
|
||||||
nodes.insert_with(|myidx| {
|
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};
|
use crate::{builder::*, hooks::use_ref};
|
||||||
|
|
||||||
fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps) -> DomTree {
|
#[derive(Debug, PartialEq)]
|
||||||
let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
|
struct EmptyProps<'src> {
|
||||||
|
name: &'src String,
|
||||||
let childprops: ExampleProps<'a> = ExampleProps { name: content };
|
|
||||||
ctx.render(move |c| {
|
|
||||||
builder::ElementBuilder::new(c, "div")
|
|
||||||
.child(text(props.name))
|
|
||||||
.child(virtual_child::<ExampleProps>(
|
|
||||||
c.bump,
|
|
||||||
childprops,
|
|
||||||
child_example,
|
|
||||||
))
|
|
||||||
.finish()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
|
impl FC for EmptyProps<'_> {
|
||||||
ctx.render(move |ctx| {
|
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||||
builder::ElementBuilder::new(ctx, "div")
|
let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
|
||||||
.child(text(props.name))
|
|
||||||
.finish()
|
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<ExampleProps> = |ctx, props: &'_ ExampleProps| {
|
#[derive(Debug, PartialEq)]
|
||||||
ctx.render(move |ctx| {
|
struct ExampleProps<'src> {
|
||||||
builder::ElementBuilder::new(ctx, "div")
|
name: &'src String,
|
||||||
.child(text(props.name))
|
}
|
||||||
.finish()
|
|
||||||
})
|
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]
|
#[test]
|
||||||
fn test_borrowed_scope() {
|
fn test_borrowed_scope() {
|
||||||
let example: FC<EmptyProps> = |ctx, props| {
|
#[derive(Debug, PartialEq)]
|
||||||
ctx.render(move |b| {
|
struct Example {
|
||||||
builder::ElementBuilder::new(b, "div")
|
name: String,
|
||||||
.child(virtual_child(
|
}
|
||||||
b.bump,
|
|
||||||
ExampleProps { name: props.name },
|
impl FC for Example {
|
||||||
CHILD,
|
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||||
))
|
todo!()
|
||||||
.finish()
|
// 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 source_text = "abcd123".to_string();
|
||||||
let props = ExampleProps { name: &source_text };
|
let props = ExampleProps { name: &source_text };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// use crate::{changelist::EditList, nodes::VNode};
|
// use crate::{changelist::EditList, nodes::VNode};
|
||||||
|
|
||||||
use crate::{innerlude::*, scope::Properties};
|
use crate::innerlude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
patch::Edit,
|
patch::Edit,
|
||||||
scope::{create_scoped, Scoped},
|
scope::{create_scoped, Scoped},
|
||||||
@@ -63,19 +63,19 @@ impl VirtualDom {
|
|||||||
pub fn new(root: FC<()>) -> Self {
|
pub fn new(root: FC<()>) -> Self {
|
||||||
Self::new_with_props(root, ())
|
Self::new_with_props(root, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a new VirtualDom instance with a dependent props.
|
/// Start a new VirtualDom instance with a dependent props.
|
||||||
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||||
///
|
///
|
||||||
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
/// 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.
|
/// to toss out the entire tree.
|
||||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||||
let mut components = Arena::new();
|
// let mut components = Arena::new();
|
||||||
|
// let mut components = Arena::new();
|
||||||
|
|
||||||
// Create a reference to the component in the arena
|
// 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
|
// 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
|
// 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!()
|
todo!()
|
||||||
// Self {
|
// Self {
|
||||||
@@ -106,19 +106,16 @@ impl VirtualDom {
|
|||||||
|
|
||||||
let b = Bump::new();
|
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);
|
// let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope);
|
||||||
|
|
||||||
todo!()
|
// 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();
|
|
||||||
|
|
||||||
|
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;
|
// 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.
|
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||||
@@ -187,6 +184,9 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// struct LockedEdits<'src> {
|
enum LifeCycleEvent {
|
||||||
// edits:
|
// Mount {
|
||||||
// }
|
// props: &dyn Properties,
|
||||||
|
// // f: FC<dyn Properties>,
|
||||||
|
// },
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ pub mod prelude {
|
|||||||
pub use dioxus_core_macro::fc;
|
pub use dioxus_core_macro::fc;
|
||||||
}
|
}
|
||||||
|
|
||||||
use dioxus_core::prelude::FC;
|
// use dioxus_core::prelude::FC;
|
||||||
|
|
||||||
// Re-export core completely
|
// Re-export core completely
|
||||||
pub use dioxus_core as core;
|
pub use dioxus_core as core;
|
||||||
@@ -22,6 +22,7 @@ pretty_env_logger = "0.4.0"
|
|||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
generational-arena = "0.2.8"
|
generational-arena = "0.2.8"
|
||||||
wasm-bindgen-test = "0.3.21"
|
wasm-bindgen-test = "0.3.21"
|
||||||
|
once_cell = "1.7.2"
|
||||||
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ fn main() {
|
|||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is a component
|
||||||
static Example: FC<()> = |ctx, _props| {
|
static Example: FC<()> = |ctx, _props| {
|
||||||
let (event, set_event) = use_state(&ctx, || None);
|
let (event, set_event) = use_state(&ctx, || None);
|
||||||
let event = format!("{:#?}", event);
|
let event = format!("{:#?}", event);
|
||||||
@@ -21,14 +22,12 @@ static Example: FC<()> = |ctx, _props| {
|
|||||||
div {
|
div {
|
||||||
|
|
||||||
class: "py-12 px-4 w-full max-w-2xl mx-auto bg-red-100"
|
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 {
|
span {
|
||||||
class: "text-sm font-semibold"
|
class: "text-sm font-semibold"
|
||||||
"Dioxus Example: Synthetic Events"
|
"Dioxus Example: Synthetic Events"
|
||||||
}
|
}
|
||||||
button {
|
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"
|
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"
|
"press me"
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
@@ -36,6 +35,21 @@ static Example: FC<()> = |ctx, _props| {
|
|||||||
id: "json"
|
id: "json"
|
||||||
"{event}"
|
"{event}"
|
||||||
}
|
}
|
||||||
|
Example2 { name: "{event}" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Props {
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
static Example2: FC<Props> = |ctx, props| {
|
||||||
|
ctx.render(rsx!{
|
||||||
|
div {
|
||||||
|
h1 {"hello {props.name}"}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user