From dc446b5e5b46bb9a0687d4a1ff5d574abf809e14 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Sep 2023 16:38:27 -0500 Subject: [PATCH] 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() {