From d573f5dfd5c25f82b009933968bd70b46d7ef77b Mon Sep 17 00:00:00 2001 From: Koji AGAWA Date: Fri, 11 Aug 2023 11:59:52 +0900 Subject: [PATCH 01/26] feat: props spread --- Cargo.toml | 2 + packages/autofmt/src/element.rs | 1 + packages/core/src/lib.rs | 4 +- packages/core/src/nodes.rs | 31 +++++ packages/dioxus/tests/props_spread.rs | 121 ++++++++++++++++++ packages/html-internal-macro/Cargo.toml | 28 ++++ packages/html-internal-macro/src/lib.rs | 83 ++++++++++++ .../html-internal-macro/tests/01-simple.rs | 1 + .../html-internal-macro/tests/progress.rs | 5 + packages/html/Cargo.toml | 1 + packages/html/src/elements.rs | 12 +- packages/html/src/global_attributes.rs | 7 + packages/rsx-rosetta/src/lib.rs | 1 + packages/rsx/src/element.rs | 8 ++ 14 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 packages/dioxus/tests/props_spread.rs create mode 100644 packages/html-internal-macro/Cargo.toml create mode 100644 packages/html-internal-macro/src/lib.rs create mode 100644 packages/html-internal-macro/tests/01-simple.rs create mode 100644 packages/html-internal-macro/tests/progress.rs diff --git a/Cargo.toml b/Cargo.toml index 5b55f4628..0072a608e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "packages/extension", "packages/router", "packages/html", + "packages/html-internal-macro", "packages/hooks", "packages/web", "packages/ssr", @@ -60,6 +61,7 @@ dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0" } dioxus-router = { path = "packages/router", version = "0.4.1" } dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" } dioxus-html = { path = "packages/html", version = "0.4.0" } +dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.4.0" } dioxus-hooks = { path = "packages/hooks", version = "0.4.0" } dioxus-web = { path = "packages/web", version = "0.4.0" } dioxus-ssr = { path = "packages/ssr", version = "0.4.0" } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index dbc336401..44f527337 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -49,6 +49,7 @@ impl Writer<'_> { attributes, children, brace, + extra_attributes, } = el; /* diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 62e142f60..b9e514a7c 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -73,8 +73,8 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, - CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, + fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeBox, AttributeValue, BorrowedAttributeValue, + CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index a108f3577..0e3a9708b 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -832,3 +832,34 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { } } } + +pub struct AttributeBox<'a> { + /// The name of the attribute. + pub name: &'a str, + + /// The value of the attribute + pub value: Box + 'static>, + + /// The namespace of the attribute. + /// + /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups. + pub namespace: Option<&'static str>, + + /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated + pub volatile: bool, +} + +impl<'a> AttributeBox<'a> { + pub fn new(name: &'a str, value: impl IntoAttributeValue<'a> + 'static, namespace: Option<&'static str>, volatile: bool) -> Self { + Self { + name, + value: Box::new(value), + namespace, + volatile, + } + } +} + +pub trait HasAttributesBox<'a, T> { + fn push_attribute(self, attr: AttributeBox<'a>) -> T; +} diff --git a/packages/dioxus/tests/props_spread.rs b/packages/dioxus/tests/props_spread.rs new file mode 100644 index 000000000..a183e11da --- /dev/null +++ b/packages/dioxus/tests/props_spread.rs @@ -0,0 +1,121 @@ +use dioxus::core_macro::render; +use dioxus::prelude::rsx; +use dioxus_core::{AttributeBox, Element, HasAttributesBox, Scope}; +use dioxus_html::{ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; + +#[test] +fn props_spread() { + pub struct FooProps<'a> { + pub open: Option<&'a str>, + attributes: Vec>, + } + + // ----- + impl<'a> FooProps<'a> { + #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] + #[allow(dead_code)] + pub fn builder() -> FooPropsBuilder<'a, ((), ), > { + FooPropsBuilder { fields: ((), ), attributes: Vec::new(), _phantom: core::default::Default::default() } + } + } + #[must_use] + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, non_snake_case)] + pub struct FooPropsBuilder<'a, TypedBuilderFields, > { + fields: TypedBuilderFields, + attributes: Vec>, + _phantom: ( core::marker::PhantomData<&'a ()> ), + } + //impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } + impl<'a> dioxus::prelude::Properties for FooProps<'a> { + type Builder = FooPropsBuilder<'a, ((), ), >; + const IS_STATIC: bool = false; + fn builder() -> Self::Builder { FooProps::builder() } + unsafe fn memoize(&self, other: &Self) -> bool { false } + } + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, non_snake_case)] + pub trait FooPropsBuilder_Optional { fn into_value T>(self, default: F) -> T; } + impl FooPropsBuilder_Optional for () { fn into_value T>(self, default: F) -> T { default() } } + impl FooPropsBuilder_Optional for (T, ) { fn into_value T>(self, _: F) -> T { self.0 } } + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl<'a> FooPropsBuilder<'a, ((), )> { + pub fn open(self, open: &'a str) -> FooPropsBuilder<'a, ((Option<&'a str>, + // pub attributes: Vec>, + ), )> { + let open = (Some(open), ); + let (_, ) = self.fields; + FooPropsBuilder { fields: (open, ), attributes: self.attributes, _phantom: self._phantom } + } + } + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, non_snake_case)] + pub enum FooPropsBuilder_Error_Repeated_field_open {} + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl<'a> FooPropsBuilder<'a, ((Option<&'a str>, + // pub attributes: Vec>, + ), )> { + #[deprecated(note = "Repeated field open")] + pub fn open(self, _: FooPropsBuilder_Error_Repeated_field_open) -> FooPropsBuilder<'a, ((Option<&'a str>, + // pub attributes: Vec>, + ), )> { self } + } + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open, ), > { + pub fn build(self) -> FooProps<'a> { + let (open, ) = self.fields; + let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); + FooProps { open, attributes: self.attributes } + } + } + // ----- + + impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A, )>> for FooPropsBuilder<'a, (A, )> { + fn push_attribute(self, attr: AttributeBox<'a>) -> FooPropsBuilder<'a, (A, )> { + let mut attrs = Vec::from(self.attributes); + attrs.push(attr); + FooPropsBuilder { fields: self.fields, attributes: attrs, _phantom: self._phantom } + } + } + impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} + impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} + + use dioxus::prelude::*; + use dioxus_html::AudioExtension; + + #[allow(non_snake_case)] + pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { + let muted = false; + let attributes = cx.props.attributes; + render! { + // rsx! { + // audio { + // muted: muted, + // } + // } + ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { + static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; + let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; + for attr in attributes { + attrs.push(__cx.attr(attr.name, attr.value.into_value(__cx.bump()), attr.namespace, attr.volatile)); + }; + ::dioxus::core::VNode { + parent: None, + key: None, + template: std::cell::Cell::new(TEMPLATE), + root_ids: Default::default(), + dynamic_nodes: __cx.bump().alloc([]), + dynamic_attrs: __cx.bump().alloc(attrs), + } + }) + } + } + + rsx! { + Foo { + autoplay: true, + controls: true, + } + }; +} \ No newline at end of file diff --git a/packages/html-internal-macro/Cargo.toml b/packages/html-internal-macro/Cargo.toml new file mode 100644 index 000000000..ce8ad54b1 --- /dev/null +++ b/packages/html-internal-macro/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "dioxus-html-internal-macro" +version = { workspace = true } +edition = "2021" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "liveview"] +license = "MIT OR Apache-2.0" +description = "HTML function macros for Dioxus" + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "1.0.66" +syn = { version = "2", features = ["full"] } +quote = "^1.0.26" +convert_case = "^0.6.0" + +[lib] +proc-macro = true + +[[test]] +name = "tests" +path = "tests/progress.rs" + +[dev-dependencies] +trybuild = { version = "1.0.82", features = ["diff"] } \ No newline at end of file diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs new file mode 100644 index 000000000..5794fd073 --- /dev/null +++ b/packages/html-internal-macro/src/lib.rs @@ -0,0 +1,83 @@ +use proc_macro::TokenStream; + +use convert_case::{Case, Casing}; +use quote::{quote, TokenStreamExt, ToTokens}; +use syn::{braced, Ident, parse_macro_input, Token}; +use syn::__private::TokenStream2; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; + +#[proc_macro] +pub fn impl_extension_attributes(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ImplExtensionAttributes); + input.to_token_stream().into() +} + +struct ImplExtensionAttributes { + is_element: bool, + name: Ident, + attrs: Punctuated, +} + +impl Parse for ImplExtensionAttributes { + fn parse(input: ParseStream) -> syn::Result { + let content; + + let element: Ident = input.parse()?; + let name = input.parse()?; + braced!(content in input); + let attrs = content.parse_terminated(Ident::parse, Token![,])?; + + Ok(ImplExtensionAttributes { + is_element: element.to_string() == "ELEMENT", + name, + attrs, + }) + } +} + +impl ToTokens for ImplExtensionAttributes { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let name = &self.name; + let camel_name = name.to_string().to_case(Case::UpperCamel); + let impl_name = Ident::new(format!("{}Impl", &camel_name).as_str(), name.span()); + let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span()); + let marker_name = Ident::new(format!("Extended{}Marker", &camel_name).as_str(), name.span()); + + if !self.is_element { + tokens.append_all(quote! { + trait #impl_name {} + impl #name for #impl_name {} + }); + } + + let defs = self.attrs.iter().map(|ident| { + quote! { + fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self; + } + }); + let impls = self.attrs.iter().map(|ident| { + let d = if self.is_element { + quote! { #name::#ident } + } else { + quote! { #impl_name::#ident } + }; + quote! { + fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self { + let d = #d; + self.push_attribute(AttributeBox::new(d.0, value, d.1, d.2)) + } + } + }); + tokens.append_all(quote! { + pub trait #marker_name {} + + pub trait #extension_name<'a> { + #(#defs)* + } + impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a, T> + #marker_name { + #(#impls)* + } + }); + } +} diff --git a/packages/html-internal-macro/tests/01-simple.rs b/packages/html-internal-macro/tests/01-simple.rs new file mode 100644 index 000000000..e71fdf554 --- /dev/null +++ b/packages/html-internal-macro/tests/01-simple.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/packages/html-internal-macro/tests/progress.rs b/packages/html-internal-macro/tests/progress.rs new file mode 100644 index 000000000..563805397 --- /dev/null +++ b/packages/html-internal-macro/tests/progress.rs @@ -0,0 +1,5 @@ +#[test] +fn tests() { + let t = trybuild::TestCases::new(); + t.pass("tests/01-simple.rs"); +} diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 86ccb029d..cdf072a7c 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -12,6 +12,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { workspace = true } dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true } +dioxus-html-internal-macro = { workspace = true } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { workspace = true, optional = true } diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 7e5bed124..e7e682bf5 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1,9 +1,15 @@ #![allow(non_upper_case_globals)] + +use dioxus_core::AttributeBox; +use dioxus_core::HasAttributesBox; +use dioxus_core::prelude::IntoAttributeValue; +use dioxus_html_internal_macro::impl_extension_attributes; +#[cfg(feature = "hot-reload-context")] +use dioxus_rsx::HotReloadingContext; + #[cfg(feature = "hot-reload-context")] use crate::{map_global_attributes, map_svg_attributes}; use crate::{GlobalAttributes, SvgAttributes}; -#[cfg(feature = "hot-reload-context")] -use dioxus_rsx::HotReloadingContext; pub type AttributeDiscription = (&'static str, Option<&'static str>, bool); @@ -106,6 +112,8 @@ macro_rules! impl_element { } impl GlobalAttributes for $name {} + + impl_extension_attributes![ELEMENT $name { $($fil,)* }]; }; ( diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 3792397ad..2c7718686 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -1,5 +1,10 @@ #![allow(non_upper_case_globals)] +use dioxus_core::HasAttributesBox; +use dioxus_core::AttributeBox; +use dioxus_core::prelude::IntoAttributeValue; +use dioxus_html_internal_macro::impl_extension_attributes; + use crate::AttributeDiscription; #[cfg(feature = "hot-reload-context")] @@ -62,6 +67,8 @@ macro_rules! trait_methods { )* None } + + impl_extension_attributes![GLOBAL $trait { $($name,)* }]; }; // Rename the incoming ident and apply a custom namespace diff --git a/packages/rsx-rosetta/src/lib.rs b/packages/rsx-rosetta/src/lib.rs index 2ceb5f038..97678cf3a 100644 --- a/packages/rsx-rosetta/src/lib.rs +++ b/packages/rsx-rosetta/src/lib.rs @@ -73,6 +73,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { attributes, key: None, brace: Default::default(), + extra_attributes: None, })) } diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 8f45d91ff..dd33c825f 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -21,6 +21,7 @@ pub struct Element { pub attributes: Vec, pub children: Vec, pub brace: syn::token::Brace, + pub extra_attributes: Option, } impl Parse for Element { @@ -35,6 +36,7 @@ impl Parse for Element { let mut children: Vec = vec![]; let mut key = None; let mut _el_ref = None; + let mut extra_attributes = None; // parse fields with commas // break when we don't get this pattern anymore @@ -42,6 +44,11 @@ impl Parse for Element { // "def": 456, // abc: 123, loop { + if content.peek(Token![...]) { + content.parse::()?; + extra_attributes = Some(content.parse::()?); + } + // Parse the raw literal fields if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) { let name = content.parse::()?; @@ -160,6 +167,7 @@ impl Parse for Element { attributes, children, brace, + extra_attributes }) } } From dc446b5e5b46bb9a0687d4a1ff5d574abf809e14 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Sep 2023 16:38:27 -0500 Subject: [PATCH 02/26] fix extending an element --- packages/core-macro/src/props/mod.rs | 2 +- packages/core/src/fragment.rs | 2 +- packages/core/src/lib.rs | 10 +- packages/core/src/nodes.rs | 35 ++---- packages/core/src/properties.rs | 11 +- packages/dioxus/tests/props_spread.rs | 142 +++++++++++++++++------- packages/html-internal-macro/src/lib.rs | 15 ++- packages/html/src/elements.rs | 3 +- packages/html/src/global_attributes.rs | 3 +- packages/rsx/src/component.rs | 4 +- 10 files changed, 138 insertions(+), 89 deletions(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 6a0d24a95..e24b8327a 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -661,7 +661,7 @@ Finally, call `.build()` to create the instance of `{name}`. { type Builder = #builder_name #generics_with_empty; const IS_STATIC: bool = #is_static; - fn builder() -> Self::Builder { + fn builder(_cx: &::dioxus_core::prelude::ScopeState) -> Self::Builder { #name::builder() } unsafe fn memoize(&self, other: &Self) -> bool { diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index a9e919bfa..fa8c7e4bd 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -94,7 +94,7 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> { impl<'a> Properties for FragmentProps<'a> { type Builder = FragmentBuilder<'a, false>; const IS_STATIC: bool = false; - fn builder() -> Self::Builder { + fn builder(_cx: &ScopeState) -> Self::Builder { FragmentBuilder(None) } unsafe fn memoize(&self, _other: &Self) -> bool { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index b9e514a7c..230ae4c34 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -73,11 +73,11 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeBox, AttributeValue, BorrowedAttributeValue, - CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, IntoDynNode, - LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, - TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, - VirtualDom, + fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, + CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, + IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, + ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, + VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0e3a9708b..f3d0ea750 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -833,33 +833,12 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { } } -pub struct AttributeBox<'a> { - /// The name of the attribute. - pub name: &'a str, - - /// The value of the attribute - pub value: Box + 'static>, - - /// The namespace of the attribute. - /// - /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups. - pub namespace: Option<&'static str>, - - /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated - pub volatile: bool, -} - -impl<'a> AttributeBox<'a> { - pub fn new(name: &'a str, value: impl IntoAttributeValue<'a> + 'static, namespace: Option<&'static str>, volatile: bool) -> Self { - Self { - name, - value: Box::new(value), - namespace, - volatile, - } - } -} - pub trait HasAttributesBox<'a, T> { - fn push_attribute(self, attr: AttributeBox<'a>) -> T; + fn push_attribute( + self, + name: &str, + ns: Option<&str>, + attr: impl IntoAttributeValue<'a>, + volatile: bool, + ) -> T; } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index f754f8b68..d3f9f6989 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -41,7 +41,7 @@ pub trait Properties: Sized { const IS_STATIC: bool; /// Create a builder for this component. - fn builder() -> Self::Builder; + fn builder(cx: &ScopeState) -> Self::Builder; /// Memoization can only happen if the props are valid for the 'static lifetime /// @@ -54,7 +54,7 @@ pub trait Properties: Sized { impl Properties for () { type Builder = EmptyBuilder; const IS_STATIC: bool = true; - fn builder() -> Self::Builder { + fn builder(_cx: &ScopeState) -> Self::Builder { EmptyBuilder {} } unsafe fn memoize(&self, _other: &Self) -> bool { @@ -70,8 +70,11 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder { - T::builder() +pub fn fc_to_builder<'a, T: Properties + 'a>( + cx: &ScopeState, + _: fn(Scope<'a, T>) -> Element<'a>, +) -> T::Builder { + T::builder(cx) } #[cfg(not(miri))] diff --git a/packages/dioxus/tests/props_spread.rs b/packages/dioxus/tests/props_spread.rs index a183e11da..8839e86e8 100644 --- a/packages/dioxus/tests/props_spread.rs +++ b/packages/dioxus/tests/props_spread.rs @@ -1,51 +1,83 @@ use dioxus::core_macro::render; use dioxus::prelude::rsx; -use dioxus_core::{AttributeBox, Element, HasAttributesBox, Scope}; +use dioxus_core::{Attribute, Element, HasAttributesBox, Scope}; use dioxus_html::{ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; #[test] fn props_spread() { pub struct FooProps<'a> { pub open: Option<&'a str>, - attributes: Vec>, + attributes: Vec>, } // ----- impl<'a> FooProps<'a> { #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] #[allow(dead_code)] - pub fn builder() -> FooPropsBuilder<'a, ((), ), > { - FooPropsBuilder { fields: ((), ), attributes: Vec::new(), _phantom: core::default::Default::default() } + pub fn builder(cx: &ScopeState) -> FooPropsBuilder<'a, ((),)> { + FooPropsBuilder { + bump: cx.bump(), + fields: ((),), + attributes: Vec::new(), + _phantom: core::default::Default::default(), + } } } #[must_use] #[doc(hidden)] #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub struct FooPropsBuilder<'a, TypedBuilderFields, > { + pub struct FooPropsBuilder<'a, TypedBuilderFields> { + bump: &'a ::dioxus::core::exports::bumpalo::Bump, fields: TypedBuilderFields, - attributes: Vec>, - _phantom: ( core::marker::PhantomData<&'a ()> ), + attributes: Vec>, + _phantom: (core::marker::PhantomData<&'a ()>), } //impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } impl<'a> dioxus::prelude::Properties for FooProps<'a> { - type Builder = FooPropsBuilder<'a, ((), ), >; + type Builder = FooPropsBuilder<'a, ((),)>; const IS_STATIC: bool = false; - fn builder() -> Self::Builder { FooProps::builder() } - unsafe fn memoize(&self, other: &Self) -> bool { false } + fn builder(cx: &ScopeState) -> Self::Builder { + FooProps::builder(cx) + } + unsafe fn memoize(&self, other: &Self) -> bool { + false + } } #[doc(hidden)] #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub trait FooPropsBuilder_Optional { fn into_value T>(self, default: F) -> T; } - impl FooPropsBuilder_Optional for () { fn into_value T>(self, default: F) -> T { default() } } - impl FooPropsBuilder_Optional for (T, ) { fn into_value T>(self, _: F) -> T { self.0 } } + pub trait FooPropsBuilder_Optional { + fn into_value T>(self, default: F) -> T; + } + impl FooPropsBuilder_Optional for () { + fn into_value T>(self, default: F) -> T { + default() + } + } + impl FooPropsBuilder_Optional for (T,) { + fn into_value T>(self, _: F) -> T { + self.0 + } + } #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a> FooPropsBuilder<'a, ((), )> { - pub fn open(self, open: &'a str) -> FooPropsBuilder<'a, ((Option<&'a str>, - // pub attributes: Vec>, - ), )> { - let open = (Some(open), ); - let (_, ) = self.fields; - FooPropsBuilder { fields: (open, ), attributes: self.attributes, _phantom: self._phantom } + impl<'a> FooPropsBuilder<'a, ((),)> { + pub fn open( + self, + open: &'a str, + ) -> FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > { + let open = (Some(open),); + let (_,) = self.fields; + FooPropsBuilder { + bump: self.bump, + fields: (open,), + attributes: self.attributes, + _phantom: self._phantom, + } } } #[doc(hidden)] @@ -53,33 +85,67 @@ fn props_spread() { pub enum FooPropsBuilder_Error_Repeated_field_open {} #[doc(hidden)] #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a> FooPropsBuilder<'a, ((Option<&'a str>, - // pub attributes: Vec>, - ), )> { + impl<'a> + FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > + { #[deprecated(note = "Repeated field open")] - pub fn open(self, _: FooPropsBuilder_Error_Repeated_field_open) -> FooPropsBuilder<'a, ((Option<&'a str>, - // pub attributes: Vec>, - ), )> { self } + pub fn open( + self, + _: FooPropsBuilder_Error_Repeated_field_open, + ) -> FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > { + self + } } #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open, ), > { + impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open,)> { pub fn build(self) -> FooProps<'a> { - let (open, ) = self.fields; + let (open,) = self.fields; let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); - FooProps { open, attributes: self.attributes } + FooProps { + open, + attributes: self.attributes, + } } } // ----- - impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A, )>> for FooPropsBuilder<'a, (A, )> { - fn push_attribute(self, attr: AttributeBox<'a>) -> FooPropsBuilder<'a, (A, )> { + impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A,)>> for FooPropsBuilder<'a, (A,)> { + fn push_attribute( + self, + name: &str, + ns: Option<&str>, + attr: impl IntoAttributeValue<'a>, + volatile: bool, + ) -> Self { let mut attrs = Vec::from(self.attributes); - attrs.push(attr); - FooPropsBuilder { fields: self.fields, attributes: attrs, _phantom: self._phantom } + attrs.push(Attribute::new( + name, + attr.into_value(self.bump), + ns, + volatile, + )); + FooPropsBuilder { + bump: self.bump, + fields: self.fields, + attributes: attrs, + _phantom: self._phantom, + } } } - impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} - impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} + impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} + impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} use dioxus::prelude::*; use dioxus_html::AudioExtension; @@ -87,7 +153,7 @@ fn props_spread() { #[allow(non_snake_case)] pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { let muted = false; - let attributes = cx.props.attributes; + let attributes = &cx.props.attributes; render! { // rsx! { // audio { @@ -99,7 +165,7 @@ fn props_spread() { let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; for attr in attributes { attrs.push(__cx.attr(attr.name, attr.value.into_value(__cx.bump()), attr.namespace, attr.volatile)); - }; + } ::dioxus::core::VNode { parent: None, key: None, @@ -118,4 +184,4 @@ fn props_spread() { controls: true, } }; -} \ No newline at end of file +} diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs index 5794fd073..1a1d22d08 100644 --- a/packages/html-internal-macro/src/lib.rs +++ b/packages/html-internal-macro/src/lib.rs @@ -1,11 +1,11 @@ use proc_macro::TokenStream; use convert_case::{Case, Casing}; -use quote::{quote, TokenStreamExt, ToTokens}; -use syn::{braced, Ident, parse_macro_input, Token}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::__private::TokenStream2; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; +use syn::{braced, parse_macro_input, Ident, Token}; #[proc_macro] pub fn impl_extension_attributes(input: TokenStream) -> TokenStream { @@ -42,11 +42,14 @@ impl ToTokens for ImplExtensionAttributes { let camel_name = name.to_string().to_case(Case::UpperCamel); let impl_name = Ident::new(format!("{}Impl", &camel_name).as_str(), name.span()); let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span()); - let marker_name = Ident::new(format!("Extended{}Marker", &camel_name).as_str(), name.span()); + let marker_name = Ident::new( + format!("Extended{}Marker", &camel_name).as_str(), + name.span(), + ); if !self.is_element { tokens.append_all(quote! { - trait #impl_name {} + struct #impl_name; impl #name for #impl_name {} }); } @@ -60,12 +63,12 @@ impl ToTokens for ImplExtensionAttributes { let d = if self.is_element { quote! { #name::#ident } } else { - quote! { #impl_name::#ident } + quote! { <#impl_name as #name>::#ident } }; quote! { fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self { let d = #d; - self.push_attribute(AttributeBox::new(d.0, value, d.1, d.2)) + self.push_attribute(d.0, d.1, value, d.2) } } }); diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index e7e682bf5..e1aaf84f4 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1,8 +1,7 @@ #![allow(non_upper_case_globals)] -use dioxus_core::AttributeBox; -use dioxus_core::HasAttributesBox; use dioxus_core::prelude::IntoAttributeValue; +use dioxus_core::HasAttributesBox; use dioxus_html_internal_macro::impl_extension_attributes; #[cfg(feature = "hot-reload-context")] use dioxus_rsx::HotReloadingContext; diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 2c7718686..97811fe1f 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -1,8 +1,7 @@ #![allow(non_upper_case_globals)] -use dioxus_core::HasAttributesBox; -use dioxus_core::AttributeBox; use dioxus_core::prelude::IntoAttributeValue; +use dioxus_core::HasAttributesBox; use dioxus_html_internal_macro::impl_extension_attributes; use crate::AttributeDiscription; diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 6487e5ab7..bccef2638 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -151,8 +151,8 @@ impl ToTokens for Component { } None => { let mut toks = match prop_gen_args { - Some(gen_args) => quote! { fc_to_builder(#name #gen_args) }, - None => quote! { fc_to_builder(#name) }, + Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) }, + None => quote! { fc_to_builder(__cx, #name) }, }; for field in &self.fields { match field.name.to_string().as_str() { From fc8c25280a5dc25acf5d9883ffab8ab6f785da0c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 20 Sep 2023 16:02:04 -0500 Subject: [PATCH 03/26] allow many attributes to be attached to one element --- packages/core/src/create.rs | 18 +++- packages/core/src/diff.rs | 84 ++++++++++++++-- packages/core/src/lib.rs | 16 ++-- packages/core/src/nodes.rs | 76 +++++++++++++-- packages/core/src/scopes.rs | 29 +++--- packages/core/src/virtual_dom.rs | 95 +++++++++++++++---- packages/core/tests/fuzzing.rs | 7 +- packages/dioxus/tests/props_spread.rs | 2 +- .../html-internal-macro/tests/01-simple.rs | 2 +- packages/html/src/events.rs | 4 +- packages/rsx/src/element.rs | 10 +- packages/ssr/src/renderer.rs | 42 ++++---- 12 files changed, 289 insertions(+), 96 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 7fbc8a9c8..226a446f9 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,5 +1,7 @@ use crate::any_props::AnyProps; -use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText}; +use crate::innerlude::{ + AttributeType, BorrowedAttributeValue, MountedAttribute, VComponent, VPlaceholder, VText, +}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; @@ -283,7 +285,7 @@ impl<'b> VirtualDom { let id = self.assign_static_node_as_dynamic(path, root, node, attr_id); loop { - self.write_attribute(&node.dynamic_attrs[attr_id], id); + self.write_attribute_type(&node.dynamic_attrs[attr_id], id); // Only push the dynamic attributes forward if they match the current path (same element) match attrs.next_if(|(_, p)| *p == path) { @@ -294,10 +296,20 @@ impl<'b> VirtualDom { } } - fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) { + fn write_attribute_type(&mut self, attribute: &'b MountedAttribute<'b>, id: ElementId) { // Make sure we set the attribute's associated id attribute.mounted_element.set(id); + match &attribute.ty { + AttributeType::Single(attribute) => self.write_attribute(attribute, id), + AttributeType::Many(attribute) => { + for attribute in *attribute { + self.write_attribute(attribute, id); + } + } + } + } + pub(crate) fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) { // Safety: we promise not to re-alias this text later on after committing it to the mutation let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) }; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 270c81730..12fb31b1b 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,7 +1,9 @@ use crate::{ any_props::AnyProps, arena::ElementId, - innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText}, + innerlude::{ + AttributeType, BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText, + }, mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, @@ -103,16 +105,51 @@ impl<'b> VirtualDom { .zip(right_template.dynamic_attrs.iter()) .for_each(|(left_attr, right_attr)| { // Move over the ID from the old to the new - right_attr - .mounted_element - .set(left_attr.mounted_element.get()); + let mounted_id = left_attr.mounted_element.get(); + right_attr.mounted_element.set(mounted_id); // We want to make sure anything that gets pulled is valid self.update_template(left_attr.mounted_element.get(), right_template); - // If the attributes are different (or volatile), we need to update them - if left_attr.value != right_attr.value || left_attr.volatile { - self.update_attribute(right_attr, left_attr); + match (&left_attr.ty, &right_attr.ty) { + (AttributeType::Single(left), AttributeType::Single(right)) => { + self.diff_attribute(left, right, mounted_id) + } + (AttributeType::Many(left), AttributeType::Many(right)) => { + let mut left_iter = left.iter().peekable(); + let mut right_iter = right.iter().peekable(); + + loop { + match (left_iter.peek(), right_iter.peek()) { + (Some(left), Some(right)) => { + // check which name is greater + match left.name.cmp(right.name) { + std::cmp::Ordering::Less => self.remove_attribute( + left.name, + left.namespace, + mounted_id, + ), + std::cmp::Ordering::Greater => { + self.write_attribute(right, mounted_id) + } + std::cmp::Ordering::Equal => { + self.diff_attribute(left, right, mounted_id) + } + } + } + (Some(_), None) => { + let left = left_iter.next().unwrap(); + self.remove_attribute(left.name, left.namespace, mounted_id) + } + (None, Some(_)) => { + let right = right_iter.next().unwrap(); + self.write_attribute(right, mounted_id) + } + (None, None) => break, + } + } + } + _ => unreachable!("The macro should never generate this case"), } }); @@ -138,6 +175,18 @@ impl<'b> VirtualDom { } } + fn diff_attribute( + &mut self, + left_attr: &'b Attribute<'b>, + right_attr: &'b Attribute<'b>, + id: ElementId, + ) { + // If the attributes are different (or volatile), we need to update them + if left_attr.value != right_attr.value || left_attr.volatile { + self.update_attribute(right_attr, left_attr, id); + } + } + fn diff_dynamic_node( &mut self, left_node: &'b DynamicNode<'b>, @@ -155,12 +204,29 @@ impl<'b> VirtualDom { }; } - fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) { + fn remove_attribute(&mut self, name: &'b str, ns: Option<&'static str>, id: ElementId) { + let name = unsafe { std::mem::transmute(name) }; + let value: BorrowedAttributeValue<'b> = BorrowedAttributeValue::None; + let value = unsafe { std::mem::transmute(value) }; + self.mutations.push(Mutation::SetAttribute { + id, + ns, + name, + value, + }); + } + + fn update_attribute( + &mut self, + right_attr: &'b Attribute<'b>, + left_attr: &'b Attribute<'b>, + id: ElementId, + ) { let name = unsafe { std::mem::transmute(left_attr.name) }; let value: BorrowedAttributeValue<'b> = (&right_attr.value).into(); let value = unsafe { std::mem::transmute(value) }; self.mutations.push(Mutation::SetAttribute { - id: left_attr.mounted_element.get(), + id, ns: right_attr.namespace, name, value, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 230ae4c34..45fc43451 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -73,11 +73,11 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, - CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, HasAttributesBox, - IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, - ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, - VPlaceholder, VText, VirtualDom, + fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeType, AttributeValue, + BorrowedAttributeValue, CapturedError, Component, DynamicNode, Element, ElementId, Event, + Fragment, HasAttributesBox, IntoDynNode, LazyNodes, MountedAttribute, Mutation, Mutations, + Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, + TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types @@ -88,9 +88,9 @@ pub mod prelude { consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, provide_context, provide_context_to_scope, provide_root_context, push_future, remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue, - Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes, - Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, - TemplateNode, Throw, VNode, VirtualDom, + AttributeType, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, + LazyNodes, MountedAttribute, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, + Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, }; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index f3d0ea750..e34fb7d42 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -60,7 +60,7 @@ pub struct VNode<'a> { pub dynamic_nodes: &'a [DynamicNode<'a>], /// The dynamic parts of the template - pub dynamic_attrs: &'a [Attribute<'a>], + pub dynamic_attrs: &'a [MountedAttribute<'a>], } impl<'a> VNode<'a> { @@ -414,6 +414,44 @@ pub enum TemplateAttribute<'a> { }, } +#[derive(Debug)] +pub struct MountedAttribute<'a> { + pub(crate) ty: AttributeType<'a>, + + /// The element in the DOM that this attribute belongs to + pub(crate) mounted_element: Cell, +} + +impl<'a> From> for MountedAttribute<'a> { + fn from(attr: Attribute<'a>) -> Self { + Self { + ty: AttributeType::Single(attr), + mounted_element: Default::default(), + } + } +} + +impl<'a> From<&'a [Attribute<'a>]> for MountedAttribute<'a> { + fn from(attr: &'a [Attribute<'a>]) -> Self { + Self { + ty: AttributeType::Many(attr), + mounted_element: Default::default(), + } + } +} + +impl<'a> MountedAttribute<'a> { + /// Get the type of this attribute + pub fn attribute_type(&self) -> &AttributeType<'a> { + &self.ty + } + + /// Get the element that this attribute is mounted to + pub fn mounted_element(&self) -> ElementId { + self.mounted_element.get() + } +} + /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` #[derive(Debug)] pub struct Attribute<'a> { @@ -430,9 +468,6 @@ pub struct Attribute<'a> { /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated pub volatile: bool, - - /// The element in the DOM that this attribute belongs to - pub(crate) mounted_element: Cell, } impl<'a> Attribute<'a> { @@ -448,13 +483,38 @@ impl<'a> Attribute<'a> { value, namespace, volatile, - mounted_element: Cell::new(ElementId::default()), + } + } +} + +#[derive(Debug)] +pub enum AttributeType<'a> { + Single(Attribute<'a>), + /// Many different attributes sorted by name + Many(&'a [Attribute<'a>]), +} + +impl<'a> AttributeType<'a> { + /// Call the given function on each attribute + pub fn for_each<'b, F>(&'b self, mut f: F) + where + F: FnMut(&'b Attribute<'a>), + { + match self { + Self::Single(attr) => f(attr), + Self::Many(attrs) => attrs.iter().for_each(f), } } - /// Get the element that this attribute is mounted to - pub fn mounted_element(&self) -> ElementId { - self.mounted_element.get() + /// Try to call the given function on each attribute + pub fn try_for_each<'b, F, E>(&'b self, mut f: F) -> Result<(), E> + where + F: FnMut(&'b Attribute<'a>) -> Result<(), E>, + { + match self { + Self::Single(attr) => f(attr), + Self::Many(attrs) => attrs.iter().try_for_each(f), + } } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 5ada89069..5d279f86a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -8,7 +8,8 @@ use crate::{ nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, scope_context::ScopeContext, - AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId, + AnyValue, Attribute, AttributeType, AttributeValue, Element, Event, MountedAttribute, + Properties, TaskId, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -335,14 +336,10 @@ impl<'src> ScopeState { let mut listeners = self.attributes_to_drop.borrow_mut(); for attr in element.dynamic_attrs { - match attr.value { - AttributeValue::Any(_) | AttributeValue::Listener(_) => { - let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; - listeners.push(unbounded); - } - - _ => (), - } + attr.ty.for_each(|attr| { + let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; + listeners.push(unbounded); + }) } let mut props = self.borrowed_props.borrow_mut(); @@ -393,13 +390,15 @@ impl<'src> ScopeState { value: impl IntoAttributeValue<'src>, namespace: Option<&'static str>, volatile: bool, - ) -> Attribute<'src> { - Attribute { - name, - namespace, - volatile, + ) -> MountedAttribute<'src> { + MountedAttribute { + ty: AttributeType::Single(Attribute { + name, + namespace, + volatile, + value: value.into_value(self.bump()), + }), mounted_element: Default::default(), - value: value.into_value(self.bump()), } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a1def71d2..bee13b3a2 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -5,13 +5,13 @@ use crate::{ any_props::VProps, arena::{ElementId, ElementRef}, - innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, + innerlude::{AttributeType, DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, - AttributeValue, Element, Event, Scope, + Attribute, AttributeValue, Element, Event, Scope, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -371,12 +371,30 @@ impl VirtualDom { for (idx, attr) in template.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - if attr.name.trim_start_matches("on") == name - && target_path.is_decendant(&this_path) - { - listeners.push(&attr.value); + fn add_listener<'a>( + attribute: &'a Attribute<'a>, + event_name: &str, + listeners: &mut Vec<&'a AttributeValue<'a>>, + ) { + if attribute.name.trim_start_matches("on") == event_name { + listeners.push(&attribute.value); + } + listeners.push(&attribute.value); + } + + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + if target_path.is_decendant(&this_path) { + match &attr.ty { + AttributeType::Single(attribute) => { + add_listener(attribute, name, &mut listeners); + } + AttributeType::Many(attributes) => { + for attribute in *attributes { + add_listener(attribute, name, &mut listeners); + } + } + } // Break if this is the exact target element. // This means we won't call two listeners with the same name on the same element. This should be // documented, or be rejected from the rsx! macro outright @@ -422,20 +440,57 @@ impl VirtualDom { for (idx, attr) in template.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - // Only call the listener if this is the exact target element. - if attr.name.trim_start_matches("on") == name && target_path == this_path { - if let AttributeValue::Listener(listener) = &attr.value { - let origin = el_ref.scope; - self.runtime.scope_stack.borrow_mut().push(origin); - self.runtime.rendering.set(false); - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); - } - self.runtime.scope_stack.borrow_mut().pop(); - self.runtime.rendering.set(true); + fn call_listener( + attribute: &Attribute, + event_name: &str, + uievent: &Event, + runtime: &Runtime, + origin: ScopeId, + ) -> bool { + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + // Only call the listener if this is the exact target element. + if attribute.name.trim_start_matches("on") == event_name { + if let AttributeValue::Listener(listener) = &attribute.value { + runtime.scope_stack.borrow_mut().push(origin); + runtime.rendering.set(false); + if let Some(cb) = listener.borrow_mut().as_deref_mut() { + cb(uievent.clone()); + } + runtime.scope_stack.borrow_mut().pop(); + runtime.rendering.set(true); - break; + return true; + } + } + false + } + + if target_path == this_path { + match &attr.ty { + AttributeType::Single(attribute) => { + if call_listener( + attribute, + name, + &uievent, + &self.runtime, + el_ref.scope, + ) { + return; + } + } + AttributeType::Many(attributes) => { + for attribute in *attributes { + if call_listener( + attribute, + name, + &uievent, + &self.runtime, + el_ref.scope, + ) { + return; + } + } + } } } } diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 40bb5dcbf..0de10ec56 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -1,7 +1,7 @@ #![cfg(not(miri))] use dioxus::prelude::Props; -use dioxus_core::*; +use dioxus_core::{MountedAttribute, *}; use std::{cell::Cell, collections::HashSet}; fn random_ns() -> Option<&'static str> { @@ -205,7 +205,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { } } -fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { +fn create_random_dynamic_attr(cx: &ScopeState) -> MountedAttribute { let value = match rand::random::() % 7 { 0 => AttributeValue::Text(Box::leak( format!("{}", rand::random::()).into_boxed_str(), @@ -217,7 +217,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { 5 => AttributeValue::None, 6 => { let value = cx.listener(|e: Event| println!("{:?}", e)); - return Attribute::new("ondata", value, None, false); + return Attribute::new("ondata", value, None, false).into(); } _ => unreachable!(), }; @@ -227,6 +227,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { random_ns(), rand::random(), ) + .into() } static mut TEMPLATE_COUNT: usize = 0; diff --git a/packages/dioxus/tests/props_spread.rs b/packages/dioxus/tests/props_spread.rs index 8839e86e8..01423ea80 100644 --- a/packages/dioxus/tests/props_spread.rs +++ b/packages/dioxus/tests/props_spread.rs @@ -164,7 +164,7 @@ fn props_spread() { static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; for attr in attributes { - attrs.push(__cx.attr(attr.name, attr.value.into_value(__cx.bump()), attr.namespace, attr.volatile)); + attrs.push(attr); } ::dioxus::core::VNode { parent: None, diff --git a/packages/html-internal-macro/tests/01-simple.rs b/packages/html-internal-macro/tests/01-simple.rs index e71fdf554..f328e4d9d 100644 --- a/packages/html-internal-macro/tests/01-simple.rs +++ b/packages/html-internal-macro/tests/01-simple.rs @@ -1 +1 @@ -fn main() {} \ No newline at end of file +fn main() {} diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index c59149493..dc4e2b815 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -9,7 +9,7 @@ macro_rules! impl_event { $( $( #[$attr] )* #[inline] - pub fn $name<'a, E: crate::EventReturn, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::Attribute<'a> { + pub fn $name<'a, E: crate::EventReturn, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::MountedAttribute<'a> { ::dioxus_core::Attribute::new( stringify!($name), _cx.listener(move |e: ::dioxus_core::Event<$data>| { @@ -17,7 +17,7 @@ macro_rules! impl_event { }), None, false, - ) + ).into() } )* }; diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index dd33c825f..f64ab9809 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -167,7 +167,7 @@ impl Parse for Element { attributes, children, brace, - extra_attributes + extra_attributes, }) } } @@ -397,11 +397,3 @@ impl ToTokens for ElementAttrNamed { tokens.append_all(attribute); } } - -// ::dioxus::core::Attribute { -// name: stringify!(#name), -// namespace: None, -// volatile: false, -// mounted_node: Default::default(), -// value: ::dioxus::core::AttributeValue::Text(#value), -// } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index f23e654fb..74066ad44 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -79,23 +79,31 @@ impl Renderer { match segment { Segment::Attr(idx) => { let attr = &template.dynamic_attrs[*idx]; - if attr.name == "dangerous_inner_html" { - inner_html = Some(attr); - } else if attr.namespace == Some("style") { - accumulated_dynamic_styles.push(attr); - } else { - match attr.value { - AttributeValue::Text(value) => { - write!(buf, " {}=\"{}\"", attr.name, value)? - } - AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, - AttributeValue::Int(value) => write!(buf, " {}={}", attr.name, value)?, - AttributeValue::Float(value) => { - write!(buf, " {}={}", attr.name, value)? - } - _ => {} - }; - } + attr.attribute_type().try_for_each(|attr| { + if attr.name == "dangerous_inner_html" { + inner_html = Some(attr); + } else if attr.namespace == Some("style") { + accumulated_dynamic_styles.push(attr); + } else { + match attr.value { + AttributeValue::Text(value) => { + write!(buf, " {}=\"{}\"", attr.name, value)? + } + AttributeValue::Bool(value) => { + write!(buf, " {}={}", attr.name, value)? + } + AttributeValue::Int(value) => { + write!(buf, " {}={}", attr.name, value)? + } + AttributeValue::Float(value) => { + write!(buf, " {}={}", attr.name, value)? + } + _ => {} + }; + } + + Ok(()) + })?; } Segment::Node(idx) => match &template.dynamic_nodes[*idx] { DynamicNode::Component(node) => { From fa494349fee7c25d1a284d9c08f674a5f04cf286 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 20 Sep 2023 16:15:11 -0500 Subject: [PATCH 04/26] add a lifetime to the properties trait and make the manual spread example work --- examples/spread.rs | 209 ++++++++++++++++++++++++++ packages/core-macro/src/props/mod.rs | 4 +- packages/core/src/fragment.rs | 2 +- packages/core/src/nodes.rs | 4 +- packages/core/src/properties.rs | 10 +- packages/core/src/scopes.rs | 2 +- packages/dioxus/tests/props_spread.rs | 187 ----------------------- 7 files changed, 220 insertions(+), 198 deletions(-) create mode 100644 examples/spread.rs delete mode 100644 packages/dioxus/tests/props_spread.rs diff --git a/examples/spread.rs b/examples/spread.rs new file mode 100644 index 000000000..eb09e5dd9 --- /dev/null +++ b/examples/spread.rs @@ -0,0 +1,209 @@ +//! Example: SSR +//! +//! This example shows how we can render the Dioxus Virtualdom using SSR. + +use dioxus::core::{Attribute, HasAttributesBox}; +use dioxus::html::{AudioExtension, ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; +use dioxus::prelude::*; + +fn main() { + // We can render VirtualDoms + let mut vdom = VirtualDom::new(app); + let _ = vdom.rebuild(); + println!("{}", dioxus_ssr::render(&vdom)); + + // Or we can render rsx! calls themselves + println!( + "{}", + dioxus_ssr::render_lazy(rsx! { + div { + h1 { "Hello, world!" } + } + }) + ); + + // We can configure the SSR rendering to add ids for rehydration + println!("{}", dioxus_ssr::pre_render(&vdom)); + + // We can render to a buf directly too + let mut file = String::new(); + let mut renderer = dioxus_ssr::Renderer::default(); + renderer.render_to(&mut file, &vdom).unwrap(); + println!("{file}"); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx!(Foo { + autoplay: true, + controls: true, + })) +} + +pub struct FooProps<'a> { + pub open: Option<&'a str>, + attributes: Vec>, +} + +// ----- +impl<'a> FooProps<'a> { + #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] + #[allow(dead_code)] + pub fn builder(cx: &'a ScopeState) -> FooPropsBuilder<'a, ((),)> { + FooPropsBuilder { + bump: cx.bump(), + fields: ((),), + attributes: Vec::new(), + _phantom: core::default::Default::default(), + } + } +} +#[must_use] +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, non_snake_case)] +pub struct FooPropsBuilder<'a, TypedBuilderFields> { + bump: &'a ::dioxus::core::exports::bumpalo::Bump, + fields: TypedBuilderFields, + attributes: Vec>, + _phantom: (core::marker::PhantomData<&'a ()>), +} +//impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } +impl<'a> dioxus::prelude::Properties<'a> for FooProps<'a> { + type Builder = FooPropsBuilder<'a, ((),)>; + const IS_STATIC: bool = false; + fn builder(cx: &'a ScopeState) -> Self::Builder { + FooProps::builder(cx) + } + unsafe fn memoize(&self, other: &Self) -> bool { + false + } +} +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, non_snake_case)] +pub trait FooPropsBuilder_Optional { + fn into_value T>(self, default: F) -> T; +} +impl FooPropsBuilder_Optional for () { + fn into_value T>(self, default: F) -> T { + default() + } +} +impl FooPropsBuilder_Optional for (T,) { + fn into_value T>(self, _: F) -> T { + self.0 + } +} +#[allow(dead_code, non_camel_case_types, missing_docs)] +impl<'a> FooPropsBuilder<'a, ((),)> { + pub fn open( + self, + open: &'a str, + ) -> FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > { + let open = (Some(open),); + let (_,) = self.fields; + FooPropsBuilder { + bump: self.bump, + fields: (open,), + attributes: self.attributes, + _phantom: self._phantom, + } + } +} +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, non_snake_case)] +pub enum FooPropsBuilder_Error_Repeated_field_open {} +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, missing_docs)] +impl<'a> + FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > +{ + #[deprecated(note = "Repeated field open")] + pub fn open( + self, + _: FooPropsBuilder_Error_Repeated_field_open, + ) -> FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > { + self + } +} +#[allow(dead_code, non_camel_case_types, missing_docs)] +impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open,)> { + pub fn build(self) -> FooProps<'a> { + let (open,) = self.fields; + let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); + FooProps { + open, + attributes: self.attributes, + } + } +} +// ----- + +impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A,)>> for FooPropsBuilder<'a, (A,)> { + fn push_attribute( + self, + name: &'a str, + ns: Option<&'static str>, + attr: impl IntoAttributeValue<'a>, + volatile: bool, + ) -> Self { + let mut attrs = self.attributes; + // We insert attributes so that the list is binary-searchable + if let Err(index) = attrs.binary_search_by(|probe| probe.name.cmp(name)) { + attrs.insert( + index, + Attribute::new(name, attr.into_value(self.bump), ns, volatile), + ); + } + FooPropsBuilder { + bump: self.bump, + fields: self.fields, + attributes: attrs, + _phantom: self._phantom, + } + } +} +impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} +impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} + +#[allow(non_snake_case)] +pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { + let muted = false; + let attributes = &cx.props.attributes; + render! { + // rsx! { + // audio { + // muted: muted, + // } + // } + ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { + static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "123" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; + let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; + attrs.push((&**attributes).into()); + ::dioxus::core::VNode { + parent: None, + key: None, + template: std::cell::Cell::new(TEMPLATE), + root_ids: Default::default(), + dynamic_nodes: __cx.bump().alloc([]), + dynamic_attrs: __cx.bump().alloc(attrs), + } + }) + } +} diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index e24b8327a..c7824db85 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -656,12 +656,12 @@ Finally, call `.build()` to create the instance of `{name}`. } } - impl #impl_generics dioxus::prelude::Properties for #name #ty_generics + impl #impl_generics ::dioxus::prelude::Properties<'_> for #name #ty_generics #b_generics_where_extras_predicates { type Builder = #builder_name #generics_with_empty; const IS_STATIC: bool = #is_static; - fn builder(_cx: &::dioxus_core::prelude::ScopeState) -> Self::Builder { + fn builder(_cx: &::dioxus::prelude::ScopeState) -> Self::Builder { #name::builder() } unsafe fn memoize(&self, other: &Self) -> bool { diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index fa8c7e4bd..0c282a930 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -91,7 +91,7 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> { /// }) /// } /// ``` -impl<'a> Properties for FragmentProps<'a> { +impl<'a> Properties<'_> for FragmentProps<'a> { type Builder = FragmentBuilder<'a, false>; const IS_STATIC: bool = false; fn builder(_cx: &ScopeState) -> Self::Builder { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e34fb7d42..2d3f7fcb0 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -896,8 +896,8 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { pub trait HasAttributesBox<'a, T> { fn push_attribute( self, - name: &str, - ns: Option<&str>, + name: &'a str, + ns: Option<&'static str>, attr: impl IntoAttributeValue<'a>, volatile: bool, ) -> T; diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index d3f9f6989..d05fbf932 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -32,7 +32,7 @@ use crate::innerlude::*; /// data: &'a str /// } /// ``` -pub trait Properties: Sized { +pub trait Properties<'a>: Sized { /// The type of the builder for this component. /// Used to create "in-progress" versions of the props. type Builder; @@ -41,7 +41,7 @@ pub trait Properties: Sized { const IS_STATIC: bool; /// Create a builder for this component. - fn builder(cx: &ScopeState) -> Self::Builder; + fn builder(cx: &'a ScopeState) -> Self::Builder; /// Memoization can only happen if the props are valid for the 'static lifetime /// @@ -51,7 +51,7 @@ pub trait Properties: Sized { unsafe fn memoize(&self, other: &Self) -> bool; } -impl Properties for () { +impl Properties<'_> for () { type Builder = EmptyBuilder; const IS_STATIC: bool = true; fn builder(_cx: &ScopeState) -> Self::Builder { @@ -70,8 +70,8 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>( - cx: &ScopeState, +pub fn fc_to_builder<'a, T: Properties<'a> + 'a>( + cx: &'a ScopeState, _: fn(Scope<'a, T>) -> Element<'a>, ) -> T::Builder { T::builder(cx) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 5d279f86a..62d640037 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -423,7 +423,7 @@ impl<'src> ScopeState { fn_name: &'static str, ) -> DynamicNode<'src> where - P: Properties + 'child, + P: Properties<'child> + 'child, 'src: 'child, { let vcomp = VProps::new(component, P::memoize, props); diff --git a/packages/dioxus/tests/props_spread.rs b/packages/dioxus/tests/props_spread.rs deleted file mode 100644 index 01423ea80..000000000 --- a/packages/dioxus/tests/props_spread.rs +++ /dev/null @@ -1,187 +0,0 @@ -use dioxus::core_macro::render; -use dioxus::prelude::rsx; -use dioxus_core::{Attribute, Element, HasAttributesBox, Scope}; -use dioxus_html::{ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; - -#[test] -fn props_spread() { - pub struct FooProps<'a> { - pub open: Option<&'a str>, - attributes: Vec>, - } - - // ----- - impl<'a> FooProps<'a> { - #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] - #[allow(dead_code)] - pub fn builder(cx: &ScopeState) -> FooPropsBuilder<'a, ((),)> { - FooPropsBuilder { - bump: cx.bump(), - fields: ((),), - attributes: Vec::new(), - _phantom: core::default::Default::default(), - } - } - } - #[must_use] - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub struct FooPropsBuilder<'a, TypedBuilderFields> { - bump: &'a ::dioxus::core::exports::bumpalo::Bump, - fields: TypedBuilderFields, - attributes: Vec>, - _phantom: (core::marker::PhantomData<&'a ()>), - } - //impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } - impl<'a> dioxus::prelude::Properties for FooProps<'a> { - type Builder = FooPropsBuilder<'a, ((),)>; - const IS_STATIC: bool = false; - fn builder(cx: &ScopeState) -> Self::Builder { - FooProps::builder(cx) - } - unsafe fn memoize(&self, other: &Self) -> bool { - false - } - } - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub trait FooPropsBuilder_Optional { - fn into_value T>(self, default: F) -> T; - } - impl FooPropsBuilder_Optional for () { - fn into_value T>(self, default: F) -> T { - default() - } - } - impl FooPropsBuilder_Optional for (T,) { - fn into_value T>(self, _: F) -> T { - self.0 - } - } - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a> FooPropsBuilder<'a, ((),)> { - pub fn open( - self, - open: &'a str, - ) -> FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > { - let open = (Some(open),); - let (_,) = self.fields; - FooPropsBuilder { - bump: self.bump, - fields: (open,), - attributes: self.attributes, - _phantom: self._phantom, - } - } - } - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub enum FooPropsBuilder_Error_Repeated_field_open {} - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a> - FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > - { - #[deprecated(note = "Repeated field open")] - pub fn open( - self, - _: FooPropsBuilder_Error_Repeated_field_open, - ) -> FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > { - self - } - } - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open,)> { - pub fn build(self) -> FooProps<'a> { - let (open,) = self.fields; - let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); - FooProps { - open, - attributes: self.attributes, - } - } - } - // ----- - - impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A,)>> for FooPropsBuilder<'a, (A,)> { - fn push_attribute( - self, - name: &str, - ns: Option<&str>, - attr: impl IntoAttributeValue<'a>, - volatile: bool, - ) -> Self { - let mut attrs = Vec::from(self.attributes); - attrs.push(Attribute::new( - name, - attr.into_value(self.bump), - ns, - volatile, - )); - FooPropsBuilder { - bump: self.bump, - fields: self.fields, - attributes: attrs, - _phantom: self._phantom, - } - } - } - impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} - impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} - - use dioxus::prelude::*; - use dioxus_html::AudioExtension; - - #[allow(non_snake_case)] - pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { - let muted = false; - let attributes = &cx.props.attributes; - render! { - // rsx! { - // audio { - // muted: muted, - // } - // } - ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { - static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; - let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; - for attr in attributes { - attrs.push(attr); - } - ::dioxus::core::VNode { - parent: None, - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Default::default(), - dynamic_nodes: __cx.bump().alloc([]), - dynamic_attrs: __cx.bump().alloc(attrs), - } - }) - } - } - - rsx! { - Foo { - autoplay: true, - controls: true, - } - }; -} From 97035a44820e15c9d4ec3a2dac834387ddd00071 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 20 Sep 2023 16:36:43 -0500 Subject: [PATCH 05/26] simplify spread example --- examples/spread.rs | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index eb09e5dd9..a59fdf8f9 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -3,6 +3,7 @@ //! This example shows how we can render the Dioxus Virtualdom using SSR. use dioxus::core::{Attribute, HasAttributesBox}; +use dioxus::html::GlobalAttributesExtension; use dioxus::html::{AudioExtension, ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; use dioxus::prelude::*; @@ -11,31 +12,14 @@ fn main() { let mut vdom = VirtualDom::new(app); let _ = vdom.rebuild(); println!("{}", dioxus_ssr::render(&vdom)); - - // Or we can render rsx! calls themselves - println!( - "{}", - dioxus_ssr::render_lazy(rsx! { - div { - h1 { "Hello, world!" } - } - }) - ); - - // We can configure the SSR rendering to add ids for rehydration - println!("{}", dioxus_ssr::pre_render(&vdom)); - - // We can render to a buf directly too - let mut file = String::new(); - let mut renderer = dioxus_ssr::Renderer::default(); - renderer.render_to(&mut file, &vdom).unwrap(); - println!("{file}"); } fn app(cx: Scope) -> Element { cx.render(rsx!(Foo { autoplay: true, controls: true, + src: "https://www.w3schools.com/tags/horse.ogg", + width: "100", })) } @@ -64,7 +48,7 @@ pub struct FooPropsBuilder<'a, TypedBuilderFields> { bump: &'a ::dioxus::core::exports::bumpalo::Bump, fields: TypedBuilderFields, attributes: Vec>, - _phantom: (core::marker::PhantomData<&'a ()>), + _phantom: core::marker::PhantomData<&'a ()>, } //impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } impl<'a> dioxus::prelude::Properties<'a> for FooProps<'a> { @@ -73,7 +57,7 @@ impl<'a> dioxus::prelude::Properties<'a> for FooProps<'a> { fn builder(cx: &'a ScopeState) -> Self::Builder { FooProps::builder(cx) } - unsafe fn memoize(&self, other: &Self) -> bool { + unsafe fn memoize(&self, _other: &Self) -> bool { false } } @@ -184,23 +168,21 @@ impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} #[allow(non_snake_case)] pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { - let muted = false; let attributes = &cx.props.attributes; render! { // rsx! { // audio { - // muted: muted, + // ...attributes, // } // } ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "123" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; - let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; - attrs.push((&**attributes).into()); + let attrs = vec![(&**attributes).into()]; ::dioxus::core::VNode { parent: None, key: None, template: std::cell::Cell::new(TEMPLATE), - root_ids: Default::default(), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump()).into(), dynamic_nodes: __cx.bump().alloc([]), dynamic_attrs: __cx.bump().alloc(attrs), } From 0e0819d2846a845d56fbbbf48a786dbae63a3be6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 22 Sep 2023 09:19:54 -0500 Subject: [PATCH 06/26] further simplify spread example --- examples/spread.rs | 223 +++++++++++---------------------------------- 1 file changed, 54 insertions(+), 169 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index a59fdf8f9..76b324f28 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -1,191 +1,76 @@ -//! Example: SSR +//! Example: README.md showcase //! -//! This example shows how we can render the Dioxus Virtualdom using SSR. +//! The example from the README.md. -use dioxus::core::{Attribute, HasAttributesBox}; -use dioxus::html::GlobalAttributesExtension; -use dioxus::html::{AudioExtension, ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; -use dioxus::prelude::*; +use dioxus::{ + core::{exports::bumpalo::Bump, Attribute, HasAttributesBox}, + html::{ExtendedGlobalAttributesMarker, GlobalAttributesExtension}, + prelude::*, +}; fn main() { - // We can render VirtualDoms - let mut vdom = VirtualDom::new(app); - let _ = vdom.rebuild(); - println!("{}", dioxus_ssr::render(&vdom)); + dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { - cx.render(rsx!(Foo { - autoplay: true, - controls: true, - src: "https://www.w3schools.com/tags/horse.ogg", - width: "100", - })) + cx.render(::dioxus::core::LazyNodes::new( + move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { + static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { + name: "src/main.rs:15:15:289", + roots: &[::dioxus::core::TemplateNode::Dynamic { id: 0usize }], + node_paths: &[&[0u8]], + attr_paths: &[], + }; + ::dioxus::core::VNode { + parent: None, + key: None, + template: std::cell::Cell::new(TEMPLATE), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( + 1usize, + __cx.bump(), + ) + .into(), + dynamic_nodes: __cx.bump().alloc([__cx.component( + Component, + Props { + bump: __cx.bump(), + attributes: Vec::new(), + } + .width("hello"), + "Component", + )]), + dynamic_attrs: __cx.bump().alloc([]), + } + }, + )) } -pub struct FooProps<'a> { - pub open: Option<&'a str>, +#[derive(Props)] +struct Props<'a> { + bump: &'a Bump, attributes: Vec>, } -// ----- -impl<'a> FooProps<'a> { - #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] - #[allow(dead_code)] - pub fn builder(cx: &'a ScopeState) -> FooPropsBuilder<'a, ((),)> { - FooPropsBuilder { - bump: cx.bump(), - fields: ((),), - attributes: Vec::new(), - _phantom: core::default::Default::default(), - } - } -} -#[must_use] -#[doc(hidden)] -#[allow(dead_code, non_camel_case_types, non_snake_case)] -pub struct FooPropsBuilder<'a, TypedBuilderFields> { - bump: &'a ::dioxus::core::exports::bumpalo::Bump, - fields: TypedBuilderFields, - attributes: Vec>, - _phantom: core::marker::PhantomData<&'a ()>, -} -//impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } -impl<'a> dioxus::prelude::Properties<'a> for FooProps<'a> { - type Builder = FooPropsBuilder<'a, ((),)>; - const IS_STATIC: bool = false; - fn builder(cx: &'a ScopeState) -> Self::Builder { - FooProps::builder(cx) - } - unsafe fn memoize(&self, _other: &Self) -> bool { - false - } -} -#[doc(hidden)] -#[allow(dead_code, non_camel_case_types, non_snake_case)] -pub trait FooPropsBuilder_Optional { - fn into_value T>(self, default: F) -> T; -} -impl FooPropsBuilder_Optional for () { - fn into_value T>(self, default: F) -> T { - default() - } -} -impl FooPropsBuilder_Optional for (T,) { - fn into_value T>(self, _: F) -> T { - self.0 - } -} -#[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a> FooPropsBuilder<'a, ((),)> { - pub fn open( - self, - open: &'a str, - ) -> FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > { - let open = (Some(open),); - let (_,) = self.fields; - FooPropsBuilder { - bump: self.bump, - fields: (open,), - attributes: self.attributes, - _phantom: self._phantom, - } - } -} -#[doc(hidden)] -#[allow(dead_code, non_camel_case_types, non_snake_case)] -pub enum FooPropsBuilder_Error_Repeated_field_open {} -#[doc(hidden)] -#[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a> - FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > -{ - #[deprecated(note = "Repeated field open")] - pub fn open( - self, - _: FooPropsBuilder_Error_Repeated_field_open, - ) -> FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > { - self - } -} -#[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open,)> { - pub fn build(self) -> FooProps<'a> { - let (open,) = self.fields; - let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); - FooProps { - open, - attributes: self.attributes, - } - } -} -// ----- - -impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A,)>> for FooPropsBuilder<'a, (A,)> { +impl<'a> HasAttributesBox<'a, Props<'a>> for Props<'a> { fn push_attribute( - self, + mut self, name: &'a str, ns: Option<&'static str>, attr: impl IntoAttributeValue<'a>, volatile: bool, ) -> Self { - let mut attrs = self.attributes; - // We insert attributes so that the list is binary-searchable - if let Err(index) = attrs.binary_search_by(|probe| probe.name.cmp(name)) { - attrs.insert( - index, - Attribute::new(name, attr.into_value(self.bump), ns, volatile), - ); - } - FooPropsBuilder { - bump: self.bump, - fields: self.fields, - attributes: attrs, - _phantom: self._phantom, - } + self.attributes.push(Attribute { + name, + namespace: ns, + value: attr.into_value(self.bump), + volatile, + }); + self } } -impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} -impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} -#[allow(non_snake_case)] -pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { - let attributes = &cx.props.attributes; - render! { - // rsx! { - // audio { - // ...attributes, - // } - // } - ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { - static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "123" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; - let attrs = vec![(&**attributes).into()]; - ::dioxus::core::VNode { - parent: None, - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump()).into(), - dynamic_nodes: __cx.bump().alloc([]), - dynamic_attrs: __cx.bump().alloc(attrs), - } - }) - } +impl ExtendedGlobalAttributesMarker for Props<'_> {} + +fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { + todo!() } From 5ed116b060c317e4ed657e2f592fb927b514a54c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 22 Sep 2023 09:24:00 -0500 Subject: [PATCH 07/26] fix bounds on generated element extensions --- examples/spread.rs | 30 ++++++++++++++++++++++--- packages/html-internal-macro/src/lib.rs | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index 76b324f28..0458ef08d 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -9,7 +9,11 @@ use dioxus::{ }; fn main() { - dioxus_desktop::launch(app); + let mut dom = VirtualDom::new(app); + dom.rebuild(); + let html = dioxus_ssr::render(&dom); + + println!("{}", html); } fn app(cx: Scope) -> Element { @@ -36,7 +40,8 @@ fn app(cx: Scope) -> Element { bump: __cx.bump(), attributes: Vec::new(), } - .width("hello"), + .width(10) + .height("100px"), "Component", )]), dynamic_attrs: __cx.bump().alloc([]), @@ -72,5 +77,24 @@ impl<'a> HasAttributesBox<'a, Props<'a>> for Props<'a> { impl ExtendedGlobalAttributesMarker for Props<'_> {} fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { - todo!() + let attributes = &cx.props.attributes; + render! { + // rsx! { + // audio { + // ...attributes, + // } + // } + ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { + static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "123" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; + let attrs = vec![(&**attributes).into()]; + ::dioxus::core::VNode { + parent: None, + key: None, + template: std::cell::Cell::new(TEMPLATE), + root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump()).into(), + dynamic_nodes: __cx.bump().alloc([]), + dynamic_attrs: __cx.bump().alloc(attrs), + } + }) + } } diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs index 1a1d22d08..4b7d23ad5 100644 --- a/packages/html-internal-macro/src/lib.rs +++ b/packages/html-internal-macro/src/lib.rs @@ -56,7 +56,7 @@ impl ToTokens for ImplExtensionAttributes { let defs = self.attrs.iter().map(|ident| { quote! { - fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self; + fn #ident(self, value: impl IntoAttributeValue<'a>) -> Self; } }); let impls = self.attrs.iter().map(|ident| { @@ -66,7 +66,7 @@ impl ToTokens for ImplExtensionAttributes { quote! { <#impl_name as #name>::#ident } }; quote! { - fn #ident(self, value: impl IntoAttributeValue<'a> + 'static) -> Self { + fn #ident(self, value: impl IntoAttributeValue<'a>) -> Self { let d = #d; self.push_attribute(d.0, d.1, value, d.2) } From 31cd15d61fefdaff1765a973cc8abe303ec53741 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 22 Sep 2023 09:31:13 -0500 Subject: [PATCH 08/26] fix clippy --- examples/spread.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index 0458ef08d..bab1c426f 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -1,7 +1,3 @@ -//! Example: README.md showcase -//! -//! The example from the README.md. - use dioxus::{ core::{exports::bumpalo::Bump, Attribute, HasAttributesBox}, html::{ExtendedGlobalAttributesMarker, GlobalAttributesExtension}, @@ -10,7 +6,7 @@ use dioxus::{ fn main() { let mut dom = VirtualDom::new(app); - dom.rebuild(); + let _ = dom.rebuild(); let html = dioxus_ssr::render(&dom); println!("{}", html); @@ -79,22 +75,8 @@ impl ExtendedGlobalAttributesMarker for Props<'_> {} fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { let attributes = &cx.props.attributes; render! { - // rsx! { - // audio { - // ...attributes, - // } - // } - ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { - static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "123" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; - let attrs = vec![(&**attributes).into()]; - ::dioxus::core::VNode { - parent: None, - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump()).into(), - dynamic_nodes: __cx.bump().alloc([]), - dynamic_attrs: __cx.bump().alloc(attrs), - } - }) + audio { + ..attributes, + } } } From 5b65c4cfb4e4b19df113f799437371a7f1327ad5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 22 Sep 2023 10:04:34 -0500 Subject: [PATCH 09/26] implement spreading props in the rsx macro --- examples/spread.rs | 2 +- packages/autofmt/src/element.rs | 21 ++++-- packages/autofmt/src/writer.rs | 30 +++++---- packages/rsx-rosetta/src/lib.rs | 16 ++--- packages/rsx/src/attribute.rs | 33 +++++++++- packages/rsx/src/element.rs | 96 ++++++++++++++++++--------- packages/rsx/src/lib.rs | 112 ++++++++++++++++++-------------- 7 files changed, 208 insertions(+), 102 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index bab1c426f..75c026aff 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -73,7 +73,7 @@ impl<'a> HasAttributesBox<'a, Props<'a>> for Props<'a> { impl ExtendedGlobalAttributesMarker for Props<'_> {} fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { - let attributes = &cx.props.attributes; + let attributes = &*cx.props.attributes; render! { audio { ..attributes, diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 4f47331c1..ba4f51451 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -49,7 +49,6 @@ impl Writer<'_> { attributes, children, brace, - extra_attributes, .. } = el; @@ -167,7 +166,7 @@ impl Writer<'_> { fn write_attributes( &mut self, - attributes: &[ElementAttrNamed], + attributes: &[AttributeType], key: &Option, sameline: bool, ) -> Result { @@ -189,7 +188,7 @@ impl Writer<'_> { while let Some(attr) = attr_iter.next() { self.out.indent += 1; if !sameline { - self.write_comments(attr.attr.start())?; + self.write_comments(attr.start())?; } self.out.indent -= 1; @@ -290,7 +289,14 @@ impl Writer<'_> { Ok(()) } - fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result { + fn write_attribute(&mut self, attr: &AttributeType) -> Result { + match attr { + AttributeType::Named(attr) => self.write_named_attribute(attr), + AttributeType::Spread(attr) => self.write_spread_attribute(attr), + } + } + + fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result { self.write_attribute_name(&attr.attr.name)?; write!(self.out, ": ")?; self.write_attribute_value(&attr.attr.value)?; @@ -298,6 +304,13 @@ impl Writer<'_> { Ok(()) } + fn write_spread_attribute(&mut self, attr: &Expr) -> Result { + write!(self.out, "..")?; + write!(self.out, "{}", prettyplease::unparse_expr(attr))?; + + Ok(()) + } + // make sure the comments are actually relevant to this element. // test by making sure this element is the primary element on this line pub fn current_span_is_primary(&self, location: Span) -> bool { diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 788007319..165267714 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -1,4 +1,4 @@ -use dioxus_rsx::{BodyNode, ElementAttrNamed, ElementAttrValue, ForLoop}; +use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop}; use proc_macro2::{LineColumn, Span}; use quote::ToTokens; use std::{ @@ -165,12 +165,12 @@ impl<'a> Writer<'a> { } } - pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize { + pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize { let mut total = 0; for attr in attributes { - if self.current_span_is_primary(attr.attr.start()) { - 'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() { + if self.current_span_is_primary(attr.start()) { + 'line: for line in self.src[..attr.start().start().line - 1].iter().rev() { match (line.trim().starts_with("//"), line.is_empty()) { (true, _) => return 100000, (_, true) => continue 'line, @@ -179,16 +179,24 @@ impl<'a> Writer<'a> { } } - total += match &attr.attr.name { - dioxus_rsx::ElementAttrName::BuiltIn(name) => { - let name = name.to_string(); - name.len() + match attr { + AttributeType::Named(attr) => { + let name_len = match &attr.attr.name { + dioxus_rsx::ElementAttrName::BuiltIn(name) => { + let name = name.to_string(); + name.len() + } + dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2, + }; + total += name_len; + total += self.attr_value_len(&attr.attr.value); + } + AttributeType::Spread(expr) => { + let expr_len = self.retrieve_formatted_expr(expr).len(); + total += expr_len + 3; } - dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2, }; - total += self.attr_value_len(&attr.attr.value); - total += 6; } diff --git a/packages/rsx-rosetta/src/lib.rs b/packages/rsx-rosetta/src/lib.rs index 22c1c52a9..2497e4411 100644 --- a/packages/rsx-rosetta/src/lib.rs +++ b/packages/rsx-rosetta/src/lib.rs @@ -1,6 +1,7 @@ use convert_case::{Case, Casing}; use dioxus_rsx::{ - BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput, + AttributeType, BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, + ElementName, IfmtInput, }; pub use html_parser::{Dom, Node}; use proc_macro2::{Ident, Span}; @@ -34,7 +35,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { Ident::new(new_name.as_str(), Span::call_site()) }; - ElementAttrNamed { + AttributeType::Named(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text( @@ -42,13 +43,13 @@ pub fn rsx_node_from_html(node: &Node) -> Option { )), name: dioxus_rsx::ElementAttrName::BuiltIn(ident), }, - } + }) }) .collect(); let class = el.classes.join(" "); if !class.is_empty() { - attributes.push(ElementAttrNamed { + attributes.push(AttributeType::Named(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new( @@ -57,11 +58,11 @@ pub fn rsx_node_from_html(node: &Node) -> Option { )), value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)), }, - }); + })); } if let Some(id) = &el.id { - attributes.push(ElementAttrNamed { + attributes.push(AttributeType::Named(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new( @@ -70,7 +71,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { )), value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)), }, - }); + })); } let children = el.children.iter().filter_map(rsx_node_from_html).collect(); @@ -82,7 +83,6 @@ pub fn rsx_node_from_html(node: &Node) -> Option { merged_attributes: Default::default(), key: None, brace: Default::default(), - extra_attributes: None, })) } diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index 38cb7c853..6f5d0a096 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -4,7 +4,38 @@ use super::*; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{parse_quote, Expr, ExprIf, Ident, LitStr}; +use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr}; + +#[derive(PartialEq, Eq, Clone, Debug, Hash)] +pub enum AttributeType { + Named(ElementAttrNamed), + Spread(Expr), +} + +impl AttributeType { + pub fn start(&self) -> Span { + match self { + AttributeType::Named(n) => n.attr.start(), + AttributeType::Spread(e) => e.span(), + } + } + + pub(crate) fn try_combine(&self, other: &Self) -> Option { + match (self, other) { + (Self::Named(a), Self::Named(b)) => a.try_combine(b).map(Self::Named), + _ => None, + } + } +} + +impl ToTokens for AttributeType { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match self { + AttributeType::Named(n) => tokens.append_all(quote! { #n }), + AttributeType::Spread(e) => tokens.append_all(quote! { #e.into() }), + } + } +} #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub struct ElementAttrNamed { diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 66ee422c2..e62fc6d6e 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -8,7 +8,7 @@ use syn::{ parse::{Parse, ParseBuffer, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Ident, LitStr, Result, Token, + Expr, Ident, LitStr, Result, Token, }; // ======================================= @@ -18,11 +18,10 @@ use syn::{ pub struct Element { pub name: ElementName, pub key: Option, - pub attributes: Vec, - pub merged_attributes: Vec, + pub attributes: Vec, + pub merged_attributes: Vec, pub children: Vec, pub brace: syn::token::Brace, - pub extra_attributes: Option, } impl Parse for Element { @@ -33,7 +32,7 @@ impl Parse for Element { let content: ParseBuffer; let brace = syn::braced!(content in stream); - let mut attributes: Vec = vec![]; + let mut attributes: Vec = vec![]; let mut children: Vec = vec![]; let mut key = None; @@ -43,9 +42,20 @@ impl Parse for Element { // "def": 456, // abc: 123, loop { - if content.peek(Token![...]) { - content.parse::()?; - extra_attributes = Some(content.parse::()?); + if content.peek(Token![..]) { + content.parse::()?; + let expr = content.parse::()?; + let span = expr.span(); + attributes.push(attribute::AttributeType::Spread(expr)); + + if content.is_empty() { + break; + } + + if content.parse::().is_err() { + missing_trailing_comma!(span); + } + continue; } // Parse the raw literal fields @@ -56,13 +66,13 @@ impl Parse for Element { content.parse::()?; let value = content.parse::()?; - attributes.push(ElementAttrNamed { + attributes.push(attribute::AttributeType::Named(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { name: ElementAttrName::Custom(name), value, }, - }); + })); if content.is_empty() { break; @@ -85,13 +95,13 @@ impl Parse for Element { let span = content.span(); if name_str.starts_with("on") { - attributes.push(ElementAttrNamed { + attributes.push(attribute::AttributeType::Named(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { name: ElementAttrName::BuiltIn(name), value: ElementAttrValue::EventTokens(content.parse()?), }, - }); + })); } else { match name_str.as_str() { "key" => { @@ -99,13 +109,13 @@ impl Parse for Element { } _ => { let value = content.parse::()?; - attributes.push(ElementAttrNamed { + attributes.push(attribute::AttributeType::Named(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { name: ElementAttrName::BuiltIn(name), value, }, - }); + })); } } } @@ -114,7 +124,6 @@ impl Parse for Element { break; } - // todo: add a message saying you need to include commas between fields if content.parse::().is_err() { missing_trailing_comma!(span); } @@ -126,12 +135,26 @@ impl Parse for Element { // Deduplicate any attributes that can be combined // For example, if there are two `class` attributes, combine them into one - let mut merged_attributes: Vec = Vec::new(); + let mut merged_attributes: Vec = Vec::new(); for attr in &attributes { - if let Some(old_attr_index) = merged_attributes - .iter() - .position(|a| a.attr.name == attr.attr.name) - { + if let Some(old_attr_index) = merged_attributes.iter().position(|a| { + matches!((a, attr), ( + AttributeType::Named(ElementAttrNamed { + attr: ElementAttr { + name: ElementAttrName::BuiltIn(old_name), + .. + }, + .. + }), + AttributeType::Named(ElementAttrNamed { + attr: ElementAttr { + name: ElementAttrName::BuiltIn(new_name), + .. + }, + .. + }), + ) if old_name == new_name) + }) { let old_attr = &mut merged_attributes[old_attr_index]; if let Some(combined) = old_attr.try_combine(attr) { *old_attr = combined; @@ -165,7 +188,6 @@ impl Parse for Element { merged_attributes, children, brace, - extra_attributes, }) } } @@ -180,15 +202,31 @@ impl ToTokens for Element { None => quote! { None }, }; - let listeners = self - .merged_attributes - .iter() - .filter(|f| matches!(f.attr.value, ElementAttrValue::EventTokens { .. })); + let listeners = self.merged_attributes.iter().filter(|f| { + matches!( + f, + AttributeType::Named(ElementAttrNamed { + attr: ElementAttr { + value: ElementAttrValue::EventTokens { .. }, + .. + }, + .. + }) + ) + }); - let attr = self - .merged_attributes - .iter() - .filter(|f| !matches!(f.attr.value, ElementAttrValue::EventTokens { .. })); + let attr = self.merged_attributes.iter().filter(|f| { + !matches!( + f, + AttributeType::Named(ElementAttrNamed { + attr: ElementAttr { + value: ElementAttrValue::EventTokens { .. }, + .. + }, + .. + }) + ) + }); tokens.append_all(quote! { __cx.element( diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 356379d09..24bb8b533 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -261,7 +261,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { #[cfg(feature = "hot_reload")] #[derive(Default, Debug)] struct DynamicMapping { - attribute_to_idx: std::collections::HashMap>, + attribute_to_idx: std::collections::HashMap>, last_attribute_idx: usize, node_to_idx: std::collections::HashMap>, last_element_idx: usize, @@ -277,7 +277,7 @@ impl DynamicMapping { new } - fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option { + fn get_attribute_idx(&mut self, attr: &AttributeType) -> Option { self.attribute_to_idx .get_mut(attr) .and_then(|idxs| idxs.pop()) @@ -287,7 +287,7 @@ impl DynamicMapping { self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop()) } - fn insert_attribute(&mut self, attr: ElementAttr) -> usize { + fn insert_attribute(&mut self, attr: AttributeType) -> usize { let idx = self.last_attribute_idx; self.last_attribute_idx += 1; @@ -309,10 +309,17 @@ impl DynamicMapping { match node { BodyNode::Element(el) => { for attr in el.merged_attributes { - match &attr.attr.value { - ElementAttrValue::AttrLiteral(input) if input.is_static() => {} + match &attr { + AttributeType::Named(ElementAttrNamed { + attr: + ElementAttr { + value: ElementAttrValue::AttrLiteral(input), + .. + }, + .. + }) if input.is_static() => {} _ => { - self.insert_attribute(attr.attr); + self.insert_attribute(attr); } } } @@ -340,7 +347,7 @@ impl DynamicMapping { #[derive(Default, Debug)] pub struct DynamicContext<'a> { dynamic_nodes: Vec<&'a BodyNode>, - dynamic_attributes: Vec<&'a ElementAttrNamed>, + dynamic_attributes: Vec<&'a AttributeType>, current_path: Vec, node_paths: Vec>, @@ -360,10 +367,16 @@ impl<'a> DynamicContext<'a> { let mut static_attrs = Vec::new(); for attr in &el.merged_attributes { - match &attr.attr.value { - ElementAttrValue::AttrLiteral(value) if value.is_static() => { + match &attr { + AttributeType::Named(ElementAttrNamed { + attr: + ElementAttr { + value: ElementAttrValue::AttrLiteral(value), + name, + }, + .. + }) if value.is_static() => { let value = value.source.as_ref().unwrap(); - let name = &attr.attr.name; let attribute_name_rust = name.to_string(); let (name, namespace) = Ctx::map_attribute(&element_name_rust, &attribute_name_rust) @@ -377,7 +390,7 @@ impl<'a> DynamicContext<'a> { _ => { let idx = match mapping { - Some(mapping) => mapping.get_attribute_idx(&attr.attr)?, + Some(mapping) => mapping.get_attribute_idx(&attr)?, None => self.dynamic_attributes.len(), }; self.dynamic_attributes.push(attr); @@ -447,47 +460,50 @@ impl<'a> DynamicContext<'a> { ElementName::Ident(i) => quote! { dioxus_elements::#i::#name }, ElementName::Custom(_) => quote! { None }, }; - let static_attrs = el - .merged_attributes - .iter() - .map(|attr| match &attr.attr.value { - ElementAttrValue::AttrLiteral(value) if value.is_static() => { - let value = value.to_static().unwrap(); - let ns = { - match &attr.attr.name { - ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)), - ElementAttrName::Custom(_) => quote!(None), - } - }; - let name = &attr.attr.name; - let name = match (el_name, name) { - (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => { - quote! { #el_name::#name.0 } - } - _ => { - let as_string = name.to_string(); - quote! { #as_string } - } - }; - quote! { - ::dioxus::core::TemplateAttribute::Static { - name: #name, - namespace: #ns, - value: #value, + let static_attrs = el.merged_attributes.iter().map(|attr| match attr { + AttributeType::Named(ElementAttrNamed { + attr: + ElementAttr { + value: ElementAttrValue::AttrLiteral(value), + name, + }, + .. + }) if value.is_static() => { + let value = value.to_static().unwrap(); + let ns = { + match name { + ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)), + ElementAttrName::Custom(_) => quote!(None), + } + }; + let name = match (el_name, name) { + (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => { + quote! { #el_name::#name.0 } + } + _ => { + let as_string = name.to_string(); + quote! { #as_string } + } + }; + quote! { + ::dioxus::core::TemplateAttribute::Static { + name: #name, + namespace: #ns, + value: #value, - // todo: we don't diff these so we never apply the volatile flag - // volatile: dioxus_elements::#el_name::#name.2, - } + // todo: we don't diff these so we never apply the volatile flag + // volatile: dioxus_elements::#el_name::#name.2, } } + } - _ => { - let ct = self.dynamic_attributes.len(); - self.dynamic_attributes.push(attr); - self.attr_paths.push(self.current_path.clone()); - quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } } - } - }); + _ => { + let ct = self.dynamic_attributes.len(); + self.dynamic_attributes.push(attr); + self.attr_paths.push(self.current_path.clone()); + quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } } + } + }); let attrs = quote! { #(#static_attrs),*}; From 56b1a0aefc5e35a33e5ae0c3d74df311c1d34be4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 25 Sep 2023 15:42:37 -0500 Subject: [PATCH 10/26] add bump to props --- packages/core-macro/src/props/mod.rs | 103 +++++++++++++++++++++------ packages/core-macro/tests/rsx.rs | 2 + 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 67e64b0f0..f5ad99ce8 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -540,15 +540,35 @@ mod struct_info { // Therefore, we will generate code that shortcircuits the "comparison" in memoization let are_there_generics = !self.generics.params.is_empty(); - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let generics = self.generics.clone(); + let (_, ty_generics, where_clause) = generics.split_for_impl(); + let impl_generics = self.modify_generics(|g| { + // Add a bump lifetime to the generics + g.params.insert( + 0, + syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + ))), + ); + }); + let (impl_generics, b_initial_generics, _) = impl_generics.split_for_impl(); let all_fields_param = syn::GenericParam::Type( syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(), ); let b_generics = self.modify_generics(|g| { + // Add a bump lifetime to the generics + g.params.insert( + 0, + syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + ))), + ); g.params.insert(0, all_fields_param.clone()); }); let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type())); - let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { + let generics_with_empty = modify_types_generics_hack(&b_initial_generics, |args| { args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into())); }); let phantom_generics = self.generics.params.iter().map(|param| match param { @@ -634,8 +654,9 @@ Finally, call `.build()` to create the instance of `{name}`. impl #impl_generics #name #ty_generics #where_clause { #[doc = #builder_method_doc] #[allow(dead_code)] - #vis fn builder() -> #builder_name #generics_with_empty { + #vis fn builder(_cx: &'__bump ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty { #builder_name { + bump: _cx.bump(), fields: #empties_tuple, _phantom: ::core::default::Default::default(), } @@ -646,6 +667,7 @@ Finally, call `.build()` to create the instance of `{name}`. #builder_type_doc #[allow(dead_code, non_camel_case_types, non_snake_case)] #vis struct #builder_name #b_generics { + bump: &'__bump ::dioxus::core::exports::bumpalo::Bump, fields: #all_fields_param, _phantom: (#( #phantom_generics ),*), } @@ -653,19 +675,20 @@ Finally, call `.build()` to create the instance of `{name}`. impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where { fn clone(&self) -> Self { Self { + bump: self.bump, fields: self.fields.clone(), _phantom: ::core::default::Default::default(), } } } - impl #impl_generics ::dioxus::prelude::Properties<'_> for #name #ty_generics + impl #impl_generics ::dioxus::prelude::Properties<'__bump> for #name #ty_generics #b_generics_where_extras_predicates { type Builder = #builder_name #generics_with_empty; const IS_STATIC: bool = #is_static; - fn builder(_cx: &::dioxus::prelude::ScopeState) -> Self::Builder { - #name::builder() + fn builder(_cx: &'__bump ::dioxus::prelude::ScopeState) -> Self::Builder { + #name::builder(_cx) } unsafe fn memoize(&self, other: &Self) -> bool { #can_memoize @@ -720,11 +743,13 @@ Finally, call `.build()` to create the instance of `{name}`. ty: field_type, .. } = field; - let mut ty_generics: Vec = self - .generics - .params - .iter() - .map(|generic_param| match generic_param { + // Add the bump lifetime to the generics + let mut ty_generics: Vec = vec![syn::GenericArgument::Lifetime( + syn::Lifetime::new("'__bump", proc_macro2::Span::call_site()), + )]; + + ty_generics.extend(self.generics.params.iter().map( + |generic_param| match generic_param { syn::GenericParam::Type(type_param) => { let ident = type_param.ident.clone(); syn::parse(quote!(#ident).into()).unwrap() @@ -736,11 +761,19 @@ Finally, call `.build()` to create the instance of `{name}`. let ident = const_param.ident.clone(); syn::parse(quote!(#ident).into()).unwrap() } - }) - .collect(); + }, + )); let mut target_generics_tuple = empty_type_tuple(); let mut ty_generics_tuple = empty_type_tuple(); let generics = self.modify_generics(|g| { + // Add a bump lifetime to the generics + g.params.insert( + 0, + syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + ))), + ); let index_after_lifetime_in_generics = g .params .iter() @@ -826,6 +859,7 @@ Finally, call `.build()` to create the instance of `{name}`. let #field_name = (#arg_expr,); let ( #(#descructuring,)* ) = self.fields; #builder_name { + bump: self.bump, fields: ( #(#reconstructing,)* ), _phantom: self._phantom, } @@ -858,11 +892,14 @@ Finally, call `.build()` to create the instance of `{name}`. name: ref field_name, .. } = field; - let mut builder_generics: Vec = self - .generics - .params - .iter() - .map(|generic_param| match generic_param { + // Add a bump lifetime to the generics + let mut builder_generics: Vec = + vec![syn::GenericArgument::Lifetime(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + ))]; + builder_generics.extend(self.generics.params.iter().map(|generic_param| { + match generic_param { syn::GenericParam::Type(type_param) => { let ident = &type_param.ident; syn::parse(quote!(#ident).into()).unwrap() @@ -874,10 +911,18 @@ Finally, call `.build()` to create the instance of `{name}`. let ident = &const_param.ident; syn::parse(quote!(#ident).into()).unwrap() } - }) - .collect(); + } + })); let mut builder_generics_tuple = empty_type_tuple(); let generics = self.modify_generics(|g| { + // Add a bump lifetime to the generics + g.params.insert( + 0, + syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + ))), + ); let index_after_lifetime_in_generics = g .params .iter() @@ -962,6 +1007,15 @@ Finally, call `.build()` to create the instance of `{name}`. } = *self; let generics = self.modify_generics(|g| { + // Add a bump lifetime to the generics + g.params.insert( + 0, + syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + ))), + ); + let index_after_lifetime_in_generics = g .params .iter() @@ -1000,6 +1054,15 @@ Finally, call `.build()` to create the instance of `{name}`. let (_, ty_generics, where_clause) = self.generics.split_for_impl(); let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| { + // Add a bump lifetime to the generics + args.insert( + 0, + syn::GenericArgument::Lifetime(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + )), + ); + args.insert( 0, syn::GenericArgument::Type( diff --git a/packages/core-macro/tests/rsx.rs b/packages/core-macro/tests/rsx.rs index f4037f928..ff01681fb 100644 --- a/packages/core-macro/tests/rsx.rs +++ b/packages/core-macro/tests/rsx.rs @@ -1,3 +1,5 @@ +use dioxus::prelude::*; + #[test] fn rsx() { let t = trybuild::TestCases::new(); From 7b51bb806018453a5a3931dd8ae92582e63e2342 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 26 Sep 2023 19:23:00 -0500 Subject: [PATCH 11/26] WIP: add extends to the props macro --- examples/spread.rs | 69 +---- packages/core-macro/Cargo.toml | 1 + packages/core-macro/src/props/mod.rs | 338 +++++++++++++++++------- packages/core/src/lib.rs | 7 +- packages/core/src/nodes.rs | 4 +- packages/html-internal-macro/src/lib.rs | 2 +- 6 files changed, 270 insertions(+), 151 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index 75c026aff..f3be4d2b7 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -1,3 +1,4 @@ +use crate::dioxus_elements::ExtendedDivMarker; use dioxus::{ core::{exports::bumpalo::Bump, Attribute, HasAttributesBox}, html::{ExtendedGlobalAttributesMarker, GlobalAttributesExtension}, @@ -13,65 +14,15 @@ fn main() { } fn app(cx: Scope) -> Element { - cx.render(::dioxus::core::LazyNodes::new( - move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { - static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { - name: "src/main.rs:15:15:289", - roots: &[::dioxus::core::TemplateNode::Dynamic { id: 0usize }], - node_paths: &[&[0u8]], - attr_paths: &[], - }; - ::dioxus::core::VNode { - parent: None, - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( - 1usize, - __cx.bump(), - ) - .into(), - dynamic_nodes: __cx.bump().alloc([__cx.component( - Component, - Props { - bump: __cx.bump(), - attributes: Vec::new(), - } - .width(10) - .height("100px"), - "Component", - )]), - dynamic_attrs: __cx.bump().alloc([]), - } - }, - )) -} - -#[derive(Props)] -struct Props<'a> { - bump: &'a Bump, - attributes: Vec>, -} - -impl<'a> HasAttributesBox<'a, Props<'a>> for Props<'a> { - fn push_attribute( - mut self, - name: &'a str, - ns: Option<&'static str>, - attr: impl IntoAttributeValue<'a>, - volatile: bool, - ) -> Self { - self.attributes.push(Attribute { - name, - namespace: ns, - value: attr.into_value(self.bump), - volatile, - }); - self + render! { + Component { + width: "10px", + height: "10px", + left: 1, + } } } -impl ExtendedGlobalAttributesMarker for Props<'_> {} - fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { let attributes = &*cx.props.attributes; render! { @@ -80,3 +31,9 @@ fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { } } } + +#[derive(Props)] +struct Props<'a> { + #[props(extends = GlobalAttributes)] + attributes: Vec>, +} diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index d928d5fec..c30f85289 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -19,6 +19,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] } dioxus-rsx = { workspace = true } dioxus-core = { workspace = true } constcat = "0.3.0" +convert_case = "^0.6.0" # testing [dev-dependencies] diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index f5ad99ce8..6285643c5 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -24,6 +24,10 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result { .included_fields() .map(|f| struct_info.field_impl(f)) .collect::, _>>()?; + let extends = struct_info + .extend_fields() + .map(|f| struct_info.extends_impl(f)) + .collect::, _>>()?; let fields = quote!(#(#fields)*).into_iter(); let required_fields = struct_info .included_fields() @@ -36,6 +40,7 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result { #builder_creation #conversion_helper #( #fields )* + #( #extends )* #( #required_fields )* #build_method } @@ -167,8 +172,8 @@ mod field_info { use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; - use syn::Expr; use syn::{parse::Error, punctuated::Punctuated}; + use syn::{Expr, Path}; use super::util::{ expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, @@ -199,6 +204,13 @@ mod field_info { ); } + // extended field is automatically empty + if !builder_attr.extends.is_empty() { + builder_attr.default = Some( + syn::parse(quote!(::core::default::Default::default()).into()).unwrap(), + ); + } + // auto detect optional let strip_option_auto = builder_attr.strip_option || !builder_attr.ignore_option @@ -257,6 +269,7 @@ mod field_info { pub auto_into: bool, pub strip_option: bool, pub ignore_option: bool, + pub extends: Vec, } impl FieldBuilderAttr { @@ -309,6 +322,17 @@ mod field_info { let name = expr_to_single_string(&assign.left) .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?; match name.as_str() { + "extends" => { + if let syn::Expr::Path(path) = *assign.right { + self.extends.push(path.path); + Ok(()) + } else { + Err(Error::new_spanned( + assign.right, + "Expected simple identifier", + )) + } + } "default" => { self.default = Some(*assign.right); Ok(()) @@ -363,6 +387,11 @@ mod field_info { Ok(()) } + "extend" => { + self.extends.push(path.path); + Ok(()) + } + _ => { macro_rules! handle_fields { ( $( $flag:expr, $field:ident, $already:expr; )* ) => { @@ -466,11 +495,14 @@ fn type_from_inside_option(ty: &syn::Type, check_option_name: bool) -> Option<&s } mod struct_info { + use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::quote; use syn::parse::Error; use syn::punctuated::Punctuated; - use syn::Expr; + use syn::spanned::Spanned; + use syn::visit::Visit; + use syn::{Expr, Ident}; use super::field_info::{FieldBuilderAttr, FieldInfo}; use super::util::{ @@ -493,7 +525,46 @@ mod struct_info { impl<'a> StructInfo<'a> { pub fn included_fields(&self) -> impl Iterator> { - self.fields.iter().filter(|f| !f.builder_attr.skip) + self.fields + .iter() + .filter(|f| !f.builder_attr.skip && f.builder_attr.extends.is_empty()) + } + + pub fn extend_fields(&self) -> impl Iterator> { + self.fields + .iter() + .filter(|f| !f.builder_attr.extends.is_empty()) + } + + fn extend_lifetime(&self) -> syn::Result> { + let first_extend = self.extend_fields().next(); + + match first_extend { + Some(f) => { + struct VisitFirstLifetime(Option); + + impl Visit<'_> for VisitFirstLifetime { + fn visit_lifetime(&mut self, lifetime: &'_ syn::Lifetime) { + if self.0.is_none() { + self.0 = Some(lifetime.clone()); + } + } + } + + let name = f.name; + let mut visitor = VisitFirstLifetime(None); + + visitor.visit_type(&f.ty); + + visitor.0.ok_or_else(|| { + syn::Error::new_spanned( + name, + "Unable to find lifetime for extended field. Please specify it manually", + ) + }).map(Some) + } + None => Ok(None), + } } pub fn new( @@ -542,29 +613,12 @@ mod struct_info { let generics = self.generics.clone(); let (_, ty_generics, where_clause) = generics.split_for_impl(); - let impl_generics = self.modify_generics(|g| { - // Add a bump lifetime to the generics - g.params.insert( - 0, - syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - ))), - ); - }); + let impl_generics = self.generics.clone(); let (impl_generics, b_initial_generics, _) = impl_generics.split_for_impl(); let all_fields_param = syn::GenericParam::Type( syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(), ); let b_generics = self.modify_generics(|g| { - // Add a bump lifetime to the generics - g.params.insert( - 0, - syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - ))), - ); g.params.insert(0, all_fields_param.clone()); }); let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type())); @@ -629,8 +683,7 @@ Finally, call `.build()` to create the instance of `{name}`. quote!(#[doc(hidden)]) }; - let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) = - b_generics.split_for_impl(); + let (_, _, b_generics_where_extras_predicates) = b_generics.split_for_impl(); let mut b_generics_where: syn::WhereClause = syn::parse2(quote! { where TypedBuilderFields: Clone })?; @@ -650,12 +703,26 @@ Finally, call `.build()` to create the instance of `{name}`. false => quote! { true }, }; + let extend_fields = self.extend_fields().map(|f| { + let name = f.name; + let ty = f.ty; + quote!(#name: #ty) + }); + let extend_fields_value = self.extend_fields().map(|f| { + let name = f.name; + quote!(#name: Vec::new()) + }); + let extend_lifetime = self + .extend_lifetime()? + .unwrap_or(syn::Lifetime::new("'_", proc_macro2::Span::call_site())); + Ok(quote! { impl #impl_generics #name #ty_generics #where_clause { #[doc = #builder_method_doc] #[allow(dead_code)] - #vis fn builder(_cx: &'__bump ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty { + #vis fn builder(_cx: & #extend_lifetime ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty { #builder_name { + #(#extend_fields_value,)* bump: _cx.bump(), fields: #empties_tuple, _phantom: ::core::default::Default::default(), @@ -667,27 +734,18 @@ Finally, call `.build()` to create the instance of `{name}`. #builder_type_doc #[allow(dead_code, non_camel_case_types, non_snake_case)] #vis struct #builder_name #b_generics { - bump: &'__bump ::dioxus::core::exports::bumpalo::Bump, + #(#extend_fields,)* + bump: & #extend_lifetime ::dioxus::core::exports::bumpalo::Bump, fields: #all_fields_param, _phantom: (#( #phantom_generics ),*), } - impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where { - fn clone(&self) -> Self { - Self { - bump: self.bump, - fields: self.fields.clone(), - _phantom: ::core::default::Default::default(), - } - } - } - - impl #impl_generics ::dioxus::prelude::Properties<'__bump> for #name #ty_generics + impl #impl_generics ::dioxus::prelude::Properties<#extend_lifetime> for #name #ty_generics #b_generics_where_extras_predicates { type Builder = #builder_name #generics_with_empty; const IS_STATIC: bool = #is_static; - fn builder(_cx: &'__bump ::dioxus::prelude::ScopeState) -> Self::Builder { + fn builder(_cx: &#extend_lifetime ::dioxus::prelude::ScopeState) -> Self::Builder { #name::builder(_cx) } unsafe fn memoize(&self, other: &Self) -> bool { @@ -723,6 +781,137 @@ Finally, call `.build()` to create the instance of `{name}`. }) } + pub fn extends_impl(&self, field: &FieldInfo) -> Result { + let StructInfo { + ref builder_name, .. + } = *self; + + let field_name = field.name; + + let descructuring = self.included_fields().map(|f| { + if f.ordinal == field.ordinal { + quote!(_) + } else { + let name = f.name; + quote!(#name) + } + }); + let reconstructing = self.included_fields().map(|f| f.name); + + // Add the bump lifetime to the generics + let mut ty_generics: Vec = self + .generics + .params + .iter() + .map(|generic_param| match generic_param { + syn::GenericParam::Type(type_param) => { + let ident = type_param.ident.clone(); + syn::parse(quote!(#ident).into()).unwrap() + } + syn::GenericParam::Lifetime(lifetime_def) => { + syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()) + } + syn::GenericParam::Const(const_param) => { + let ident = const_param.ident.clone(); + syn::parse(quote!(#ident).into()).unwrap() + } + }) + .collect(); + let mut target_generics_tuple = empty_type_tuple(); + let mut ty_generics_tuple = empty_type_tuple(); + let generics = self.modify_generics(|g| { + let index_after_lifetime_in_generics = g + .params + .iter() + .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_))) + .count(); + for f in self.included_fields() { + if f.ordinal == field.ordinal { + ty_generics_tuple.elems.push_value(empty_type()); + target_generics_tuple + .elems + .push_value(f.tuplized_type_ty_param()); + } else { + g.params + .insert(index_after_lifetime_in_generics, f.generic_ty_param()); + let generic_argument: syn::Type = f.type_ident(); + ty_generics_tuple.elems.push_value(generic_argument.clone()); + target_generics_tuple.elems.push_value(generic_argument); + } + ty_generics_tuple.elems.push_punct(Default::default()); + target_generics_tuple.elems.push_punct(Default::default()); + } + }); + let mut target_generics = ty_generics.clone(); + let index_after_lifetime_in_generics = target_generics + .iter() + .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_))) + .count(); + target_generics.insert( + index_after_lifetime_in_generics, + syn::GenericArgument::Type(target_generics_tuple.into()), + ); + ty_generics.insert( + index_after_lifetime_in_generics, + syn::GenericArgument::Type(ty_generics_tuple.into()), + ); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let forward_extended_fields = self.extend_fields().map(|f| { + let name = f.name; + quote!(#name: self.#name) + }); + + let extends_impl = field.builder_attr.extends.iter().map(|path| { + let name_str = path_to_single_string(path).unwrap(); + let camel_name = name_str.to_case(Case::UpperCamel); + let marker_name = Ident::new( + format!("Extended{}Marker", &camel_name).as_str(), + path.span(), + ); + quote! { + impl #impl_generics #marker_name for #builder_name < #( #ty_generics ),* > #where_clause {} + } + }); + let extend_lifetime = self.extend_lifetime()?.ok_or(Error::new_spanned( + field_name, + "Unable to find lifetime for extended field. Please specify it manually", + ))?; + + Ok(quote! { + impl #impl_generics ::dioxus::prelude::HasAttributesBox<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause { + fn push_attribute( + mut self, + name: &#extend_lifetime str, + ns: Option<&'static str>, + attr: impl ::dioxus::prelude::IntoAttributeValue<#extend_lifetime>, + volatile: bool + ) -> Self { + let ( #(#descructuring,)* ) = self.fields; + self.#field_name.push( + ::dioxus::core::Attribute::new( + name, + { + use ::dioxus::prelude::IntoAttributeValue; + attr.into_value(self.bump) + }, + ns, + volatile, + ) + ); + #builder_name { + #(#forward_extended_fields,)* + bump: self.bump, + fields: ( #(#reconstructing,)* ), + _phantom: self._phantom, + } + } + } + + #(#extends_impl)* + }) + } + pub fn field_impl(&self, field: &FieldInfo) -> Result { let StructInfo { ref builder_name, .. @@ -744,12 +933,11 @@ Finally, call `.build()` to create the instance of `{name}`. .. } = field; // Add the bump lifetime to the generics - let mut ty_generics: Vec = vec![syn::GenericArgument::Lifetime( - syn::Lifetime::new("'__bump", proc_macro2::Span::call_site()), - )]; - - ty_generics.extend(self.generics.params.iter().map( - |generic_param| match generic_param { + let mut ty_generics: Vec = self + .generics + .params + .iter() + .map(|generic_param| match generic_param { syn::GenericParam::Type(type_param) => { let ident = type_param.ident.clone(); syn::parse(quote!(#ident).into()).unwrap() @@ -761,19 +949,11 @@ Finally, call `.build()` to create the instance of `{name}`. let ident = const_param.ident.clone(); syn::parse(quote!(#ident).into()).unwrap() } - }, - )); + }) + .collect(); let mut target_generics_tuple = empty_type_tuple(); let mut ty_generics_tuple = empty_type_tuple(); let generics = self.modify_generics(|g| { - // Add a bump lifetime to the generics - g.params.insert( - 0, - syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - ))), - ); let index_after_lifetime_in_generics = g .params .iter() @@ -851,6 +1031,11 @@ Finally, call `.build()` to create the instance of `{name}`. ); let repeated_fields_error_message = format!("Repeated field {field_name}"); + let forward_extended_fields = self.extend_fields().map(|f| { + let name = f.name; + quote!(#name: self.#name) + }); + Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause { @@ -859,6 +1044,7 @@ Finally, call `.build()` to create the instance of `{name}`. let #field_name = (#arg_expr,); let ( #(#descructuring,)* ) = self.fields; #builder_name { + #(#forward_extended_fields,)* bump: self.bump, fields: ( #(#reconstructing,)* ), _phantom: self._phantom, @@ -893,13 +1079,11 @@ Finally, call `.build()` to create the instance of `{name}`. .. } = field; // Add a bump lifetime to the generics - let mut builder_generics: Vec = - vec![syn::GenericArgument::Lifetime(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - ))]; - builder_generics.extend(self.generics.params.iter().map(|generic_param| { - match generic_param { + let mut builder_generics: Vec = self + .generics + .params + .iter() + .map(|generic_param| match generic_param { syn::GenericParam::Type(type_param) => { let ident = &type_param.ident; syn::parse(quote!(#ident).into()).unwrap() @@ -911,18 +1095,10 @@ Finally, call `.build()` to create the instance of `{name}`. let ident = &const_param.ident; syn::parse(quote!(#ident).into()).unwrap() } - } - })); + }) + .collect(); let mut builder_generics_tuple = empty_type_tuple(); let generics = self.modify_generics(|g| { - // Add a bump lifetime to the generics - g.params.insert( - 0, - syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - ))), - ); let index_after_lifetime_in_generics = g .params .iter() @@ -1007,15 +1183,6 @@ Finally, call `.build()` to create the instance of `{name}`. } = *self; let generics = self.modify_generics(|g| { - // Add a bump lifetime to the generics - g.params.insert( - 0, - syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - ))), - ); - let index_after_lifetime_in_generics = g .params .iter() @@ -1054,15 +1221,6 @@ Finally, call `.build()` to create the instance of `{name}`. let (_, ty_generics, where_clause) = self.generics.split_for_impl(); let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| { - // Add a bump lifetime to the generics - args.insert( - 0, - syn::GenericArgument::Lifetime(syn::Lifetime::new( - "'__bump", - proc_macro2::Span::call_site(), - )), - ); - args.insert( 0, syn::GenericArgument::Type( @@ -1088,7 +1246,9 @@ Finally, call `.build()` to create the instance of `{name}`. // reordering based on that, but for now this much simpler thing is a reasonable approach. let assignments = self.fields.iter().map(|field| { let name = &field.name; - if let Some(ref default) = field.builder_attr.default { + if !field.builder_attr.extends.is_empty() { + quote!(let #name = self.#name;) + } else if let Some(ref default) = field.builder_attr.default { if field.builder_attr.skip { quote!(let #name = #default;) } else { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index dbebd33e0..5723e9418 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -89,9 +89,10 @@ pub mod prelude { consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, provide_context, provide_context_to_scope, provide_root_context, push_future, remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue, - AttributeType, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, - LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, - Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, + AttributeType, Component, Element, Event, EventHandler, Fragment, HasAttributesBox, + IntoAttributeValue, LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, + ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, + VNode, VirtualDom, }; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index a31481195..d0d10062a 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -899,12 +899,12 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { } } -pub trait HasAttributesBox<'a, T> { +pub trait HasAttributesBox<'a> { fn push_attribute( self, name: &'a str, ns: Option<&'static str>, attr: impl IntoAttributeValue<'a>, volatile: bool, - ) -> T; + ) -> Self; } diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs index 4b7d23ad5..99c3db0d1 100644 --- a/packages/html-internal-macro/src/lib.rs +++ b/packages/html-internal-macro/src/lib.rs @@ -78,7 +78,7 @@ impl ToTokens for ImplExtensionAttributes { pub trait #extension_name<'a> { #(#defs)* } - impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a, T> + #marker_name { + impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a> + #marker_name { #(#impls)* } }); From b60671e0feaa8cd61efe6965194bb3b11a822b7c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 27 Sep 2023 10:02:49 -0500 Subject: [PATCH 12/26] fix non extending attributes --- examples/spread.rs | 7 +-- packages/core-macro/src/props/mod.rs | 65 +++++++++++++++++-------- packages/core/src/lib.rs | 8 +-- packages/html-internal-macro/src/lib.rs | 22 +++------ packages/html/src/elements.rs | 9 +++- packages/html/src/lib.rs | 7 +++ 6 files changed, 70 insertions(+), 48 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index f3be4d2b7..70d677201 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -1,9 +1,4 @@ -use crate::dioxus_elements::ExtendedDivMarker; -use dioxus::{ - core::{exports::bumpalo::Bump, Attribute, HasAttributesBox}, - html::{ExtendedGlobalAttributesMarker, GlobalAttributesExtension}, - prelude::*, -}; +use dioxus::prelude::*; fn main() { let mut dom = VirtualDom::new(app); diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 6285643c5..6b5ad30f3 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -502,7 +502,7 @@ mod struct_info { use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::visit::Visit; - use syn::{Expr, Ident}; + use syn::{parse_quote, Expr, Ident}; use super::field_info::{FieldBuilderAttr, FieldInfo}; use super::util::{ @@ -611,10 +611,17 @@ mod struct_info { // Therefore, we will generate code that shortcircuits the "comparison" in memoization let are_there_generics = !self.generics.params.is_empty(); + let extend_lifetime = self.extend_lifetime()?; + let generics = self.generics.clone(); let (_, ty_generics, where_clause) = generics.split_for_impl(); - let impl_generics = self.generics.clone(); - let (impl_generics, b_initial_generics, _) = impl_generics.split_for_impl(); + let impl_generics = self.modify_generics(|g| { + if extend_lifetime.is_none() { + g.params.insert(0, parse_quote!('__bump)); + } + }); + let (impl_generics, _, _) = impl_generics.split_for_impl(); + let (_, b_initial_generics, _) = self.generics.split_for_impl(); let all_fields_param = syn::GenericParam::Type( syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(), ); @@ -712,9 +719,21 @@ Finally, call `.build()` to create the instance of `{name}`. let name = f.name; quote!(#name: Vec::new()) }); - let extend_lifetime = self - .extend_lifetime()? - .unwrap_or(syn::Lifetime::new("'_", proc_macro2::Span::call_site())); + let has_extend_fields = self.extend_fields().next().is_some(); + let take_bump = if has_extend_fields { + quote!(bump: _cx.bump(),) + } else { + quote!() + }; + let bump_field = if has_extend_fields { + quote!(bump: & #extend_lifetime ::dioxus::core::exports::bumpalo::Bump,) + } else { + quote!() + }; + let extend_lifetime = extend_lifetime.unwrap_or(syn::Lifetime::new( + "'__bump", + proc_macro2::Span::call_site(), + )); Ok(quote! { impl #impl_generics #name #ty_generics #where_clause { @@ -723,7 +742,7 @@ Finally, call `.build()` to create the instance of `{name}`. #vis fn builder(_cx: & #extend_lifetime ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty { #builder_name { #(#extend_fields_value,)* - bump: _cx.bump(), + #take_bump fields: #empties_tuple, _phantom: ::core::default::Default::default(), } @@ -735,7 +754,7 @@ Finally, call `.build()` to create the instance of `{name}`. #[allow(dead_code, non_camel_case_types, non_snake_case)] #vis struct #builder_name #b_generics { #(#extend_fields,)* - bump: & #extend_lifetime ::dioxus::core::exports::bumpalo::Bump, + #bump_field fields: #all_fields_param, _phantom: (#( #phantom_generics ),*), } @@ -862,22 +881,23 @@ Finally, call `.build()` to create the instance of `{name}`. quote!(#name: self.#name) }); - let extends_impl = field.builder_attr.extends.iter().map(|path| { - let name_str = path_to_single_string(path).unwrap(); - let camel_name = name_str.to_case(Case::UpperCamel); - let marker_name = Ident::new( - format!("Extended{}Marker", &camel_name).as_str(), - path.span(), - ); - quote! { - impl #impl_generics #marker_name for #builder_name < #( #ty_generics ),* > #where_clause {} - } - }); let extend_lifetime = self.extend_lifetime()?.ok_or(Error::new_spanned( field_name, "Unable to find lifetime for extended field. Please specify it manually", ))?; + let extends_impl = field.builder_attr.extends.iter().map(|path| { + let name_str = path_to_single_string(path).unwrap(); + let camel_name = name_str.to_case(Case::UpperCamel); + let marker_name = Ident::new( + format!("{}Extension", &camel_name).as_str(), + path.span(), + ); + quote! { + impl #impl_generics dioxus_elements::extensions::#marker_name < #extend_lifetime > for #builder_name < #( #ty_generics ),* > #where_clause {} + } + }); + Ok(quote! { impl #impl_generics ::dioxus::prelude::HasAttributesBox<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause { fn push_attribute( @@ -1035,6 +1055,11 @@ Finally, call `.build()` to create the instance of `{name}`. let name = f.name; quote!(#name: self.#name) }); + let forward_bump = if self.extend_fields().next().is_some() { + quote!(bump: self.bump,) + } else { + quote!() + }; Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] @@ -1045,7 +1070,7 @@ Finally, call `.build()` to create the instance of `{name}`. let ( #(#descructuring,)* ) = self.fields; #builder_name { #(#forward_extended_fields,)* - bump: self.bump, + #forward_bump fields: ( #(#reconstructing,)* ), _phantom: self._phantom, } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 5723e9418..5fb5d7ba3 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -89,10 +89,10 @@ pub mod prelude { consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, provide_context, provide_context_to_scope, provide_root_context, push_future, remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue, - AttributeType, Component, Element, Event, EventHandler, Fragment, HasAttributesBox, - IntoAttributeValue, LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, - VNode, VirtualDom, + Attribute, AttributeType, Component, Element, Event, EventHandler, Fragment, + HasAttributesBox, IntoAttributeValue, LazyNodes, MountedAttribute, Properties, Runtime, + RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, + TemplateNode, Throw, VNode, VirtualDom, }; } diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs index 99c3db0d1..3de23f6d6 100644 --- a/packages/html-internal-macro/src/lib.rs +++ b/packages/html-internal-macro/src/lib.rs @@ -39,13 +39,13 @@ impl Parse for ImplExtensionAttributes { impl ToTokens for ImplExtensionAttributes { fn to_tokens(&self, tokens: &mut TokenStream2) { let name = &self.name; - let camel_name = name.to_string().to_case(Case::UpperCamel); + let name_string = name.to_string(); + let camel_name = name_string + .strip_prefix("r#") + .unwrap_or(&name_string) + .to_case(Case::UpperCamel); let impl_name = Ident::new(format!("{}Impl", &camel_name).as_str(), name.span()); let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span()); - let marker_name = Ident::new( - format!("Extended{}Marker", &camel_name).as_str(), - name.span(), - ); if !self.is_element { tokens.append_all(quote! { @@ -54,11 +54,6 @@ impl ToTokens for ImplExtensionAttributes { }); } - let defs = self.attrs.iter().map(|ident| { - quote! { - fn #ident(self, value: impl IntoAttributeValue<'a>) -> Self; - } - }); let impls = self.attrs.iter().map(|ident| { let d = if self.is_element { quote! { #name::#ident } @@ -73,12 +68,7 @@ impl ToTokens for ImplExtensionAttributes { } }); tokens.append_all(quote! { - pub trait #marker_name {} - - pub trait #extension_name<'a> { - #(#defs)* - } - impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a> + #marker_name { + pub trait #extension_name<'a>: HasAttributesBox<'a> + Sized { #(#impls)* } }); diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index ab9b6ea32..a004f65a2 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -111,8 +111,6 @@ macro_rules! impl_element { } impl GlobalAttributes for $name {} - - impl_extension_attributes![ELEMENT $name { $($fil,)* }]; }; ( @@ -289,6 +287,13 @@ macro_rules! builder_constructors { } ); )* + + pub(crate) mod extensions { + use super::*; + $( + impl_extension_attributes![ELEMENT $name { $($fil,)* }]; + )* + } }; } diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 2fc1baeac..b3fccb4c9 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -39,7 +39,14 @@ pub use render_template::*; mod eval; +pub mod extensions { + pub use crate::elements::extensions::*; + pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension}; +} + pub mod prelude { + pub use crate::elements::extensions::*; pub use crate::eval::*; pub use crate::events::*; + pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension}; } From 76a050314a359c80545e1fd9dd80d44b2e712159 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 27 Sep 2023 10:05:02 -0500 Subject: [PATCH 13/26] make spread example more interesting --- examples/spread.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/spread.rs b/examples/spread.rs index 70d677201..698713d51 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -12,6 +12,8 @@ fn app(cx: Scope) -> Element { render! { Component { width: "10px", + extra_data: "hello{1}", + extra_data2: "hello{2}", height: "10px", left: 1, } @@ -23,6 +25,7 @@ fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { render! { audio { ..attributes, + "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" } } } @@ -31,4 +34,6 @@ fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { struct Props<'a> { #[props(extends = GlobalAttributes)] attributes: Vec>, + extra_data: &'a str, + extra_data2: &'a str, } From ee25c03e74a9809d19d6cb7a3f7e90f3d5fc031c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 27 Sep 2023 10:13:45 -0500 Subject: [PATCH 14/26] fix some clippy lints --- examples/README.md | 1 - examples/spread.rs | 3 +-- packages/core-macro/src/props/mod.rs | 4 +++- packages/core/src/lib.rs | 10 +++++----- packages/core/src/nodes.rs | 13 ++++++++++++- packages/html-internal-macro/src/lib.rs | 2 +- packages/html/src/elements.rs | 2 +- packages/html/src/global_attributes.rs | 2 +- packages/rsx/src/attribute.rs | 2 +- 9 files changed, 25 insertions(+), 14 deletions(-) diff --git a/examples/README.md b/examples/README.md index 93530ba30..a20ffe60e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -139,7 +139,6 @@ Missing Features Missing examples - Shared state - Root-less element groups -- Spread props - Custom elements - Component Children: Pass children into child components - Render To string: Render a mounted virtualdom to a string diff --git a/examples/spread.rs b/examples/spread.rs index 698713d51..dc6b7b01d 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -21,10 +21,9 @@ fn app(cx: Scope) -> Element { } fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { - let attributes = &*cx.props.attributes; render! { audio { - ..attributes, + ..cx.props.attributes, "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" } } diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 6b5ad30f3..a0bbdd4e9 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -894,12 +894,14 @@ Finally, call `.build()` to create the instance of `{name}`. path.span(), ); quote! { + #[allow(dead_code, non_camel_case_types, missing_docs)] impl #impl_generics dioxus_elements::extensions::#marker_name < #extend_lifetime > for #builder_name < #( #ty_generics ),* > #where_clause {} } }); Ok(quote! { - impl #impl_generics ::dioxus::prelude::HasAttributesBox<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause { + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl #impl_generics ::dioxus::prelude::HasAttributes<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause { fn push_attribute( mut self, name: &#extend_lifetime str, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 5fb5d7ba3..e2f8e96c1 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -76,7 +76,7 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeType, AttributeValue, BorrowedAttributeValue, CapturedError, Component, DynamicNode, Element, ElementId, Event, - Fragment, HasAttributesBox, IntoDynNode, LazyNodes, MountedAttribute, Mutation, Mutations, + Fragment, HasAttributes, IntoDynNode, LazyNodes, MountedAttribute, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, }; @@ -89,10 +89,10 @@ pub mod prelude { consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, provide_context, provide_context_to_scope, provide_root_context, push_future, remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue, - Attribute, AttributeType, Component, Element, Event, EventHandler, Fragment, - HasAttributesBox, IntoAttributeValue, LazyNodes, MountedAttribute, Properties, Runtime, - RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, - TemplateNode, Throw, VNode, VirtualDom, + Attribute, AttributeType, Component, Element, Event, EventHandler, Fragment, HasAttributes, + IntoAttributeValue, LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, + ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, + VNode, VirtualDom, }; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d0d10062a..2665db5a3 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -414,6 +414,7 @@ pub enum TemplateAttribute<'a> { }, } +/// An attribute with information about its position in the DOM and the element it was mounted to #[derive(Debug)] pub struct MountedAttribute<'a> { pub(crate) ty: AttributeType<'a>, @@ -440,6 +441,12 @@ impl<'a> From<&'a [Attribute<'a>]> for MountedAttribute<'a> { } } +impl<'a> From<&'a Vec>> for MountedAttribute<'a> { + fn from(attr: &'a Vec>) -> Self { + attr.as_slice().into() + } +} + impl<'a> MountedAttribute<'a> { /// Get the type of this attribute pub fn attribute_type(&self) -> &AttributeType<'a> { @@ -487,8 +494,10 @@ impl<'a> Attribute<'a> { } } +/// The type of an attribute #[derive(Debug)] pub enum AttributeType<'a> { + /// A single attribute Single(Attribute<'a>), /// Many different attributes sorted by name Many(&'a [Attribute<'a>]), @@ -899,7 +908,9 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { } } -pub trait HasAttributesBox<'a> { +/// A trait for anything that has a dynamic list of attributes +pub trait HasAttributes<'a> { + /// Push an attribute onto the list of attributes fn push_attribute( self, name: &'a str, diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs index 3de23f6d6..049b36469 100644 --- a/packages/html-internal-macro/src/lib.rs +++ b/packages/html-internal-macro/src/lib.rs @@ -68,7 +68,7 @@ impl ToTokens for ImplExtensionAttributes { } }); tokens.append_all(quote! { - pub trait #extension_name<'a>: HasAttributesBox<'a> + Sized { + pub trait #extension_name<'a>: HasAttributes<'a> + Sized { #(#impls)* } }); diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index a004f65a2..fe7447790 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1,7 +1,7 @@ #![allow(non_upper_case_globals)] use dioxus_core::prelude::IntoAttributeValue; -use dioxus_core::HasAttributesBox; +use dioxus_core::HasAttributes; use dioxus_html_internal_macro::impl_extension_attributes; #[cfg(feature = "hot-reload-context")] use dioxus_rsx::HotReloadingContext; diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 97811fe1f..41a57ab84 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -1,7 +1,7 @@ #![allow(non_upper_case_globals)] use dioxus_core::prelude::IntoAttributeValue; -use dioxus_core::HasAttributesBox; +use dioxus_core::HasAttributes; use dioxus_html_internal_macro::impl_extension_attributes; use crate::AttributeDiscription; diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index 6f5d0a096..76acc43bf 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -32,7 +32,7 @@ impl ToTokens for AttributeType { fn to_tokens(&self, tokens: &mut TokenStream2) { match self { AttributeType::Named(n) => tokens.append_all(quote! { #n }), - AttributeType::Spread(e) => tokens.append_all(quote! { #e.into() }), + AttributeType::Spread(e) => tokens.append_all(quote! { (&#e).into() }), } } } From 3e55bb8885f041a4c9a901983dc4590d569d415a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 27 Sep 2023 10:21:33 -0500 Subject: [PATCH 15/26] fix some clippy lints --- packages/core-macro/src/props/mod.rs | 2 +- packages/html-internal-macro/src/lib.rs | 2 +- packages/rsx/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index a0bbdd4e9..3289d891b 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -554,7 +554,7 @@ mod struct_info { let name = f.name; let mut visitor = VisitFirstLifetime(None); - visitor.visit_type(&f.ty); + visitor.visit_type(f.ty); visitor.0.ok_or_else(|| { syn::Error::new_spanned( diff --git a/packages/html-internal-macro/src/lib.rs b/packages/html-internal-macro/src/lib.rs index 049b36469..a404a9b16 100644 --- a/packages/html-internal-macro/src/lib.rs +++ b/packages/html-internal-macro/src/lib.rs @@ -29,7 +29,7 @@ impl Parse for ImplExtensionAttributes { let attrs = content.parse_terminated(Ident::parse, Token![,])?; Ok(ImplExtensionAttributes { - is_element: element.to_string() == "ELEMENT", + is_element: element == "ELEMENT", name, attrs, }) diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 24bb8b533..193abe85d 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -390,7 +390,7 @@ impl<'a> DynamicContext<'a> { _ => { let idx = match mapping { - Some(mapping) => mapping.get_attribute_idx(&attr)?, + Some(mapping) => mapping.get_attribute_idx(attr)?, None => self.dynamic_attributes.len(), }; self.dynamic_attributes.push(attr); From c13735709466ec0ec14c4bc8fee505aace4b5f7b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 27 Sep 2023 10:22:49 -0500 Subject: [PATCH 16/26] fix hydration --- packages/core/src/scope_context.rs | 2 +- packages/web/src/rehydrate.rs | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 79ed05d75..59bed3b1f 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -128,7 +128,7 @@ impl ScopeContext { parent.name ); if let Some(shared) = parent.shared_contexts.borrow().iter().find_map(|any| { - tracing::trace!("found context {:?}", any.type_id()); + tracing::trace!("found context {:?}", (**any).type_id()); any.downcast_ref::() }) { return Some(shared.clone()); diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 4415ceb37..4ee437963 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -128,18 +128,20 @@ impl WebsysDom { for attr in *attrs { if let dioxus_core::TemplateAttribute::Dynamic { id } = attr { let attribute = &vnode.dynamic_attrs[*id]; - let value = &attribute.value; - let id = attribute.mounted_element(); - mounted_id = Some(id); - let name = attribute.name; - if let AttributeValue::Listener(_) = value { - let event_name = &name[2..]; - self.interpreter.new_event_listener( - event_name, - id.0 as u32, - event_bubbles(event_name) as u8, - ); - } + &attribute.attribute_type().for_each(|attribute| { + let value = &attribute.value; + let id = attribute.mounted_element(); + mounted_id = Some(id); + let name = attribute.name; + if let AttributeValue::Listener(_) = value { + let event_name = &name[2..]; + self.interpreter.new_event_listener( + event_name, + id.0 as u32, + event_bubbles(event_name) as u8, + ); + } + }); } } if let Some(id) = mounted_id { From eb15014b9d3678ec2bfd15a73a8e04b93facffca Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 Oct 2023 16:42:42 -0500 Subject: [PATCH 17/26] fix hydration mounted element --- packages/web/src/rehydrate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 4ee437963..1553b26dc 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -128,9 +128,9 @@ impl WebsysDom { for attr in *attrs { if let dioxus_core::TemplateAttribute::Dynamic { id } = attr { let attribute = &vnode.dynamic_attrs[*id]; - &attribute.attribute_type().for_each(|attribute| { + let id = attribute.mounted_element(); + attribute.attribute_type().for_each(|attribute| { let value = &attribute.value; - let id = attribute.mounted_element(); mounted_id = Some(id); let name = attribute.name; if let AttributeValue::Listener(_) = value { From d5b7a6d15e348fa101cd02844601682ae51a6798 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 Oct 2023 08:46:24 -0500 Subject: [PATCH 18/26] fix fuzzing test --- examples/spread.rs | 1 + packages/native-core/tests/fuzzing.rs | 28 +++++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index dc6b7b01d..b13124a5f 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -20,6 +20,7 @@ fn app(cx: Scope) -> Element { } } +#[component] fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { render! { audio { diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 4f0a34b70..63376f86e 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -204,6 +204,20 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { } } +fn create_random_dynamic_mounted_attr(cx: &ScopeState) -> MountedAttribute { + match rand::random::() % 2 { + 0 => MountedAttribute::from( + &*cx.bump().alloc( + (0..(rand::random::() % 3) as usize) + .map(|_| create_random_dynamic_attr(cx)) + .collect::>(), + ), + ), + 1 => MountedAttribute::from(create_random_dynamic_attr(cx)), + _ => unreachable!(), + } +} + fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { let value = match rand::random::() % 6 { 0 => AttributeValue::Text(Box::leak( @@ -214,7 +228,6 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { 3 => AttributeValue::Bool(rand::random()), 4 => cx.any_value(rand::random::()), 5 => AttributeValue::None, - // Listener(RefCell>>), _ => unreachable!(), }; Attribute::new( @@ -273,11 +286,14 @@ fn create_random_element(cx: Scope) -> Element { .collect(); cx.bump().alloc(dynamic_nodes) }, - dynamic_attrs: cx.bump().alloc( - (0..template.attr_paths.len()) - .map(|_| create_random_dynamic_attr(cx)) - .collect::>(), - ), + dynamic_attrs: cx + .bump() + .alloc( + (0..template.attr_paths.len()) + .map(|_| create_random_dynamic_mounted_attr(cx)) + .collect::>(), + ) + .as_slice(), }; Some(node) } From ed95ac932dc260b6a5e3feb0b14a860ba2a67562 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 Oct 2023 08:58:28 -0500 Subject: [PATCH 19/26] fix double triggering events --- packages/core/src/virtual_dom.rs | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 7bd5ae0b9..f94b35678 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -375,30 +375,14 @@ impl VirtualDom { for (idx, attr) in template.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; - fn add_listener<'a>( - attribute: &'a Attribute<'a>, - event_name: &str, - listeners: &mut Vec<&'a AttributeValue<'a>>, - ) { - if attribute.name.trim_start_matches("on") == event_name { - listeners.push(&attribute.value); - } - - listeners.push(&attribute.value); - } - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one if target_path.is_decendant(&this_path) { - match &attr.ty { - AttributeType::Single(attribute) => { - add_listener(attribute, name, &mut listeners); + attr.ty.for_each(|attribute| { + if attribute.name.trim_start_matches("on") == name { + listeners.push(&attribute.value); } - AttributeType::Many(attributes) => { - for attribute in *attributes { - add_listener(attribute, name, &mut listeners); - } - } - } + }); + // Break if this is the exact target element. // This means we won't call two listeners with the same name on the same element. This should be // documented, or be rejected from the rsx! macro outright From 7f0fa07757c62930f60577fa762035e6bcdd4cd0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 Oct 2023 09:11:21 -0500 Subject: [PATCH 20/26] fix hot reloading --- packages/core-macro/tests/rsx.rs | 2 -- packages/fullstack/src/hooks/server_cached.rs | 1 + packages/rsx/src/attribute.rs | 16 +++++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core-macro/tests/rsx.rs b/packages/core-macro/tests/rsx.rs index ff01681fb..f4037f928 100644 --- a/packages/core-macro/tests/rsx.rs +++ b/packages/core-macro/tests/rsx.rs @@ -1,5 +1,3 @@ -use dioxus::prelude::*; - #[test] fn rsx() { let t = trybuild::TestCases::new(); diff --git a/packages/fullstack/src/hooks/server_cached.rs b/packages/fullstack/src/hooks/server_cached.rs index 519f70dae..fdccd070a 100644 --- a/packages/fullstack/src/hooks/server_cached.rs +++ b/packages/fullstack/src/hooks/server_cached.rs @@ -15,6 +15,7 @@ use serde::{de::DeserializeOwned, Serialize}; /// let state1 = use_state(cx, || from_server(|| { /// 1234 /// })); +/// todo!() /// } /// ``` pub fn server_cached(server_fn: impl Fn() -> O) -> O { diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index 76acc43bf..4c0db5a33 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -37,12 +37,26 @@ impl ToTokens for AttributeType { } } -#[derive(PartialEq, Eq, Clone, Debug, Hash)] +#[derive(Clone, Debug)] pub struct ElementAttrNamed { pub el_name: ElementName, pub attr: ElementAttr, } +impl Hash for ElementAttrNamed { + fn hash(&self, state: &mut H) { + self.attr.name.hash(state); + } +} + +impl PartialEq for ElementAttrNamed { + fn eq(&self, other: &Self) -> bool { + self.attr == other.attr + } +} + +impl Eq for ElementAttrNamed {} + impl ElementAttrNamed { pub(crate) fn try_combine(&self, other: &Self) -> Option { if self.el_name == other.el_name && self.attr.name == other.attr.name { From 46fec1ae482e38cfee26e925ea22b88544dcf18a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Nov 2023 13:46:57 -0500 Subject: [PATCH 21/26] fix clippy --- packages/core-macro/src/props/mod.rs | 4 +--- packages/core/src/diff.rs | 2 +- packages/rsx/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 2ef213762..59a5c0cd6 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -936,9 +936,7 @@ Finally, call `.build()` to create the instance of `{name}`. pub fn field_impl(&self, field: &FieldInfo) -> Result { let FieldInfo { - name: field_name, - ty: field_type, - .. + name: field_name, .. } = field; if *field_name == "key" { return Err(Error::new_spanned(field_name, "Naming a prop `key` is not allowed because the name can conflict with the built in key attribute. See https://dioxuslabs.com/learn/0.4/reference/dynamic_rendering#rendering-lists for more information about keys")); diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 64d16b1ce..5e7bf7573 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -626,7 +626,7 @@ impl<'b> VirtualDom { // If none of the old keys are reused by the new children, then we remove all the remaining old children and // create the new children afresh. if shared_keys.is_empty() { - if old.get(0).is_some() { + if !old.is_empty() { self.remove_nodes(&old[1..]); self.replace(&old[0], new); } else { diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index d4170ffe9..7a705ba6b 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -195,7 +195,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { fn to_tokens(&self, out_tokens: &mut TokenStream2) { let mut context = DynamicContext::default(); - let key = match self.roots.get(0) { + let key = match self.roots.first() { Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(), Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(), _ => None, From b3c7ebfdbab8bd4f88f4db61ecf706492c531a43 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 27 Nov 2023 16:12:48 -0600 Subject: [PATCH 22/26] fix merged changes --- packages/core/src/scopes.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 6123efddd..f54e3d5cf 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -351,20 +351,22 @@ impl<'src> ScopeState { let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); for attr in element.dynamic_attrs { - match attr.value { - // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped - AttributeValue::Listener(_) => { - let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; - listeners.push(unbounded); - } - // We need to drop any values manually to make sure that their drop implementation is called before the next render - AttributeValue::Any(_) => { - let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; - self.previous_frame().add_attribute_to_drop(unbounded); - } + attr.ty.for_each(|attr| { + match attr.value { + // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped + AttributeValue::Listener(_) => { + let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; + listeners.push(unbounded); + } + // We need to drop any values manually to make sure that their drop implementation is called before the next render + AttributeValue::Any(_) => { + let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; + self.previous_frame().add_attribute_to_drop(unbounded); + } - _ => (), - } + _ => (), + } + }) } let mut props = self.borrowed_props.borrow_mut(); From 5b6f9d6b80ef06c83a55a5399d3ddcd7e29479df Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 6 Jan 2024 08:58:49 -0600 Subject: [PATCH 23/26] fix merge --- examples/spread.rs | 7 ++----- packages/core/src/diff.rs | 18 +++++++++++------- packages/core/src/error_boundary.rs | 4 ++-- packages/core/src/lib.rs | 11 +++++------ packages/core/src/nodes.rs | 2 +- packages/core/src/virtual_dom.rs | 13 +++++++------ 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/examples/spread.rs b/examples/spread.rs index b13124a5f..45bdc90af 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element { extra_data: "hello{1}", extra_data2: "hello{2}", height: "10px", - left: 1, + left: 1 } } } @@ -23,10 +23,7 @@ fn app(cx: Scope) -> Element { #[component] fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { render! { - audio { - ..cx.props.attributes, - "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" - } + audio { ..cx.props.attributes, "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" } } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 756922e4d..a116aee4d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -126,10 +126,11 @@ impl<'b> VirtualDom { .dynamic_attrs .iter() .zip(right_template.dynamic_attrs.iter()) - .for_each(|(left_attr, right_attr)| { + .enumerate() + .for_each(|(idx, (left_attr, right_attr))| { // Move over the ID from the old to the new - let mounted_element = left_attr.mounted_element.get(); - right_attr.mounted_element.set(mounted_element); + let mounted_id = left_attr.mounted_element.get(); + right_attr.mounted_element.set(mounted_id); match (&left_attr.ty, &right_attr.ty) { (AttributeType::Single(left), AttributeType::Single(right)) => { @@ -149,9 +150,12 @@ impl<'b> VirtualDom { left.namespace, mounted_id, ), - std::cmp::Ordering::Greater => { - self.write_attribute(right, mounted_id) - } + std::cmp::Ordering::Greater => self.write_attribute( + right_template, + right, + idx, + mounted_id, + ), std::cmp::Ordering::Equal => { self.diff_attribute(left, right, mounted_id) } @@ -163,7 +167,7 @@ impl<'b> VirtualDom { } (None, Some(_)) => { let right = right_iter.next().unwrap(); - self.write_attribute(right, mounted_id) + self.write_attribute(right_template, right, idx, mounted_id) } (None, None) => break, } diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index d42f59395..717e1baa7 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -334,10 +334,10 @@ where } } } -impl<'a> Properties for ErrorBoundaryProps<'a> { +impl<'a> Properties<'a> for ErrorBoundaryProps<'a> { type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>; const IS_STATIC: bool = false; - fn builder() -> Self::Builder { + fn builder(_: &'a ScopeState) -> Self::Builder { ErrorBoundaryProps::builder() } unsafe fn memoize(&self, _: &Self) -> bool { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 1c41c42de..c478ec657 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -90,12 +90,11 @@ pub mod prelude { pub use crate::innerlude::{ consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, provide_context, provide_context_to_scope, provide_root_context, push_future, - remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, - use_error_boundary, AnyValue, AnyValue, Attribute, AttributeType, Component, Element, - ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, - IntoDynNode, LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, - VNode, VirtualDom, + remove_future, schedule_update_any, spawn, spawn_forever, suspend, use_error_boundary, + AnyValue, Attribute, AttributeType, Component, Element, ErrorBoundary, Event, EventHandler, + Fragment, HasAttributes, IntoAttributeValue, IntoDynNode, LazyNodes, MountedAttribute, + Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, + TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, }; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 91ace558f..6396504a4 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -92,7 +92,7 @@ impl<'a> VNode<'a> { template: Template<'static>, root_ids: bumpalo::collections::Vec<'a, ElementId>, dynamic_nodes: &'a [DynamicNode<'a>], - dynamic_attrs: &'a [Attribute<'a>], + dynamic_attrs: &'a [MountedAttribute<'a>], ) -> Self { Self { key, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 5a9e29683..28ccbc76a 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -5,13 +5,13 @@ use crate::{ any_props::VProps, arena::{ElementId, ElementRef}, - innerlude::{AttributeType, DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, + innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, - Attribute, AttributeValue, Element, Event, Scope, VNode, + AttributeValue, Element, Event, Scope, VNode, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -371,7 +371,6 @@ impl VirtualDom { .get(parent_path.template.0) .cloned() .map(|el| (*parent_path, el)); - let mut listeners = vec![]; // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required let uievent = Event { @@ -383,6 +382,8 @@ impl VirtualDom { if bubbles { // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent. while let Some((path, el_ref)) = parent_node { + let mut listeners = vec![]; + // safety: we maintain references of all vnodes in the element slab let template = unsafe { el_ref.unwrap().as_ref() }; let node_template = template.template.get(); @@ -396,7 +397,7 @@ impl VirtualDom { attr.ty.for_each(|attribute| { if attribute.name.trim_start_matches("on") == name { if let AttributeValue::Listener(listener) = &attribute.value { - listeners.push(&listener); + listeners.push(listener); } } }); @@ -412,7 +413,7 @@ impl VirtualDom { // Now that we've accumulated all the parent attributes for the target element, call them in reverse order // We check the bubble state between each call to see if the event has been stopped from bubbling - for listener in listeners.drain(..).rev() { + for listener in listeners.into_iter().rev() { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); @@ -451,7 +452,7 @@ impl VirtualDom { let mut should_stop = false; attr.ty.for_each(|attribute| { if attribute.name.trim_start_matches("on") == name { - if let AttributeValue::Listener(listener) = &attr.value { + if let AttributeValue::Listener(listener) = &attribute.value { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); From 5d842bad2bf0edc1d71d7a70dd17b25943c8ecc0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 6 Jan 2024 08:59:57 -0600 Subject: [PATCH 24/26] add the visit feature to syn in core-macro --- packages/core-macro/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index b560c770c..ae3dc9cdd 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -15,7 +15,7 @@ proc-macro = true [dependencies] proc-macro2 = { version = "1.0" } quote = "1.0" -syn = { version = "2.0", features = ["full", "extra-traits"] } +syn = { version = "2.0", features = ["full", "extra-traits", "visit"] } dioxus-rsx = { workspace = true } dioxus-core = { workspace = true } constcat = "0.3.0" From 502c80b0add6e0a39e00d75c1afb71adf316c548 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 6 Jan 2024 09:00:44 -0600 Subject: [PATCH 25/26] remove an unused import --- packages/core/tests/fuzzing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 99e7acd6e..af5e9d05c 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -2,7 +2,7 @@ use dioxus::prelude::Props; use dioxus_core::{MountedAttribute, *}; -use std::{cell::Cell, cfg, collections::HashSet}; +use std::{cfg, collections::HashSet}; fn random_ns() -> Option<&'static str> { let namespace = rand::random::() % 2; From f3ad445882ac43fbefc1895194c1242a29d7d482 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 6 Jan 2024 09:08:51 -0600 Subject: [PATCH 26/26] fix hydration --- packages/html/src/events/mouse.rs | 3 ++- packages/web/src/rehydrate.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/html/src/events/mouse.rs b/packages/html/src/events/mouse.rs index 0c659ed63..879c8e1ea 100644 --- a/packages/html/src/events/mouse.rs +++ b/packages/html/src/events/mouse.rs @@ -107,7 +107,7 @@ impl_event! { pub fn ondoubleclick<'a, E: crate::EventReturn, T>( _cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event) -> E + 'a, -) -> ::dioxus_core::Attribute<'a> { +) -> ::dioxus_core::MountedAttribute<'a> { ::dioxus_core::Attribute::new( "ondblclick", _cx.listener(move |e: ::dioxus_core::Event| { @@ -116,6 +116,7 @@ pub fn ondoubleclick<'a, E: crate::EventReturn, T>( None, false, ) + .into() } impl MouseData { diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 397eeaf4b..e50da9e2b 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -1,4 +1,5 @@ use crate::dom::WebsysDom; +use dioxus_core::AttributeValue; use dioxus_core::{DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom}; #[derive(Debug)]