partially restore spreading attributes
This commit is contained in:
1 parent
2470a46511
commit
36e9eb1160
19 files changed
+252
-194
No files matched your search
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::{core::NoOpMutations, prelude::*};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
let _ = dom.rebuild();
|
let _ = dom.rebuild(&mut NoOpMutations);
|
||||||
let html = dioxus_ssr::render(&dom);
|
let html = dioxus_ssr::render(&dom);
|
||||||
|
|
||||||
println!("{}", html);
|
println!("{}", html);
|
||||||
@@ -23,14 +23,14 @@ fn app() -> Element {
|
|||||||
#[component]
|
#[component]
|
||||||
fn Component(props: Props) -> Element {
|
fn Component(props: Props) -> Element {
|
||||||
render! {
|
render! {
|
||||||
audio { ..cx.props.attributes, "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" }
|
audio { ..props.attributes, "1: {props.extra_data}\n2: {props.extra_data2}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props, PartialEq, Clone)]
|
||||||
struct Props<'a> {
|
struct Props {
|
||||||
#[props(extends = GlobalAttributes)]
|
#[props(extends = GlobalAttributes)]
|
||||||
attributes: Vec<Attribute<'a>>,
|
attributes: Vec<Attribute>,
|
||||||
extra_data: &'a str,
|
extra_data: String,
|
||||||
extra_data2: &'a str,
|
extra_data2: String,
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
|||||||
#builder_creation
|
#builder_creation
|
||||||
#conversion_helper
|
#conversion_helper
|
||||||
#( #fields )*
|
#( #fields )*
|
||||||
// #( #extends )*
|
#( #extends )*
|
||||||
#( #required_fields )*
|
#( #required_fields )*
|
||||||
#build_method
|
#build_method
|
||||||
}
|
}
|
||||||
@@ -497,8 +497,7 @@ mod struct_info {
|
|||||||
use syn::parse::Error;
|
use syn::parse::Error;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
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::field_info::{FieldBuilderAttr, FieldInfo};
|
||||||
use super::util::{
|
use super::util::{
|
||||||
@@ -532,37 +531,6 @@ mod struct_info {
|
|||||||
.filter(|f| !f.builder_attr.extends.is_empty())
|
.filter(|f| !f.builder_attr.extends.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_lifetime(&self) -> syn::Result<Option<syn::Lifetime>> {
|
|
||||||
let first_extend = self.extend_fields().next();
|
|
||||||
|
|
||||||
match first_extend {
|
|
||||||
Some(f) => {
|
|
||||||
struct VisitFirstLifetime(Option<syn::Lifetime>);
|
|
||||||
|
|
||||||
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(
|
pub fn new(
|
||||||
ast: &'a syn::DeriveInput,
|
ast: &'a syn::DeriveInput,
|
||||||
fields: impl Iterator<Item = &'a syn::Field>,
|
fields: impl Iterator<Item = &'a syn::Field>,
|
||||||
@@ -607,16 +575,8 @@ mod struct_info {
|
|||||||
// Therefore, we will generate code that shortcircuits the "comparison" in memoization
|
// Therefore, we will generate code that shortcircuits the "comparison" in memoization
|
||||||
let are_there_generics = !self.generics.params.is_empty();
|
let are_there_generics = !self.generics.params.is_empty();
|
||||||
|
|
||||||
let extend_lifetime = self.extend_lifetime()?;
|
|
||||||
|
|
||||||
let generics = self.generics.clone();
|
let generics = self.generics.clone();
|
||||||
let (_, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = 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 (_, b_initial_generics, _) = self.generics.split_for_impl();
|
||||||
let all_fields_param = syn::GenericParam::Type(
|
let all_fields_param = syn::GenericParam::Type(
|
||||||
syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
|
syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
|
||||||
@@ -699,12 +659,23 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
false => quote! { self == other },
|
false => quote! { self == other },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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())
|
||||||
|
});
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl #impl_generics #name #ty_generics #where_clause {
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
#[doc = #builder_method_doc]
|
#[doc = #builder_method_doc]
|
||||||
#[allow(dead_code, clippy::type_complexity)]
|
#[allow(dead_code, clippy::type_complexity)]
|
||||||
#vis fn builder() -> #builder_name #generics_with_empty {
|
#vis fn builder() -> #builder_name #generics_with_empty {
|
||||||
#builder_name {
|
#builder_name {
|
||||||
|
#(#extend_fields_value,)*
|
||||||
fields: #empties_tuple,
|
fields: #empties_tuple,
|
||||||
_phantom: ::core::default::Default::default(),
|
_phantom: ::core::default::Default::default(),
|
||||||
}
|
}
|
||||||
@@ -715,11 +686,12 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
#builder_type_doc
|
#builder_type_doc
|
||||||
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
#vis struct #builder_name #b_generics {
|
#vis struct #builder_name #b_generics {
|
||||||
|
#(#extend_fields,)*
|
||||||
fields: #all_fields_param,
|
fields: #all_fields_param,
|
||||||
_phantom: (#( #phantom_generics ),*),
|
_phantom: (#( #phantom_generics ),*),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #impl_generics ::dioxus::prelude::Properties<#extend_lifetime> for #name #ty_generics
|
impl #impl_generics ::dioxus::prelude::Properties for #name #ty_generics
|
||||||
#b_generics_where_extras_predicates
|
#b_generics_where_extras_predicates
|
||||||
{
|
{
|
||||||
type Builder = #builder_name #generics_with_empty;
|
type Builder = #builder_name #generics_with_empty;
|
||||||
@@ -776,7 +748,6 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
});
|
});
|
||||||
let reconstructing = self.included_fields().map(|f| f.name);
|
let reconstructing = self.included_fields().map(|f| f.name);
|
||||||
|
|
||||||
// Add the bump lifetime to the generics
|
|
||||||
let mut ty_generics: Vec<syn::GenericArgument> = self
|
let mut ty_generics: Vec<syn::GenericArgument> = self
|
||||||
.generics
|
.generics
|
||||||
.params
|
.params
|
||||||
@@ -840,11 +811,6 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
quote!(#name: self.#name)
|
quote!(#name: self.#name)
|
||||||
});
|
});
|
||||||
|
|
||||||
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 extends_impl = field.builder_attr.extends.iter().map(|path| {
|
||||||
let name_str = path_to_single_string(path).unwrap();
|
let name_str = path_to_single_string(path).unwrap();
|
||||||
let camel_name = name_str.to_case(Case::UpperCamel);
|
let camel_name = name_str.to_case(Case::UpperCamel);
|
||||||
@@ -854,18 +820,18 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
);
|
);
|
||||||
quote! {
|
quote! {
|
||||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
#[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 {}
|
impl #impl_generics dioxus_elements::extensions::#marker_name for #builder_name < #( #ty_generics ),* > #where_clause {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
impl #impl_generics ::dioxus::prelude::HasAttributes<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause {
|
impl #impl_generics ::dioxus::prelude::HasAttributes for #builder_name < #( #ty_generics ),* > #where_clause {
|
||||||
fn push_attribute(
|
fn push_attribute(
|
||||||
mut self,
|
mut self,
|
||||||
name: &#extend_lifetime str,
|
name: &'static str,
|
||||||
ns: Option<&'static str>,
|
ns: Option<&'static str>,
|
||||||
attr: impl ::dioxus::prelude::IntoAttributeValue<#extend_lifetime>,
|
attr: impl ::dioxus::prelude::IntoAttributeValue,
|
||||||
volatile: bool
|
volatile: bool
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ( #(#descructuring,)* ) = self.fields;
|
let ( #(#descructuring,)* ) = self.fields;
|
||||||
@@ -874,7 +840,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
name,
|
name,
|
||||||
{
|
{
|
||||||
use ::dioxus::prelude::IntoAttributeValue;
|
use ::dioxus::prelude::IntoAttributeValue;
|
||||||
attr.into_value(self.bump)
|
attr.into_value()
|
||||||
},
|
},
|
||||||
ns,
|
ns,
|
||||||
volatile,
|
volatile,
|
||||||
@@ -882,7 +848,6 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
);
|
);
|
||||||
#builder_name {
|
#builder_name {
|
||||||
#(#forward_extended_fields,)*
|
#(#forward_extended_fields,)*
|
||||||
bump: self.bump,
|
|
||||||
fields: ( #(#reconstructing,)* ),
|
fields: ( #(#reconstructing,)* ),
|
||||||
_phantom: self._phantom,
|
_phantom: self._phantom,
|
||||||
}
|
}
|
||||||
@@ -1007,11 +972,6 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
let name = f.name;
|
let name = f.name;
|
||||||
quote!(#name: self.#name)
|
quote!(#name: self.#name)
|
||||||
});
|
});
|
||||||
let forward_bump = if self.extend_fields().next().is_some() {
|
|
||||||
quote!(bump: self.bump,)
|
|
||||||
} else {
|
|
||||||
quote!()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
@@ -1023,7 +983,6 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
let ( #(#descructuring,)* ) = self.fields;
|
let ( #(#descructuring,)* ) = self.fields;
|
||||||
#builder_name {
|
#builder_name {
|
||||||
#(#forward_extended_fields,)*
|
#(#forward_extended_fields,)*
|
||||||
#forward_bump
|
|
||||||
fields: ( #(#reconstructing,)* ),
|
fields: ( #(#reconstructing,)* ),
|
||||||
_phantom: self._phantom,
|
_phantom: self._phantom,
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-54
@@ -7,7 +7,6 @@ use crate::{
|
|||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
|
innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
|
||||||
nodes::DynamicNode,
|
nodes::DynamicNode,
|
||||||
nodes::RenderReturn,
|
|
||||||
scopes::ScopeId,
|
scopes::ScopeId,
|
||||||
TemplateNode,
|
TemplateNode,
|
||||||
TemplateNode::*,
|
TemplateNode::*,
|
||||||
@@ -61,16 +60,7 @@ impl VNode {
|
|||||||
|
|
||||||
// If the templates are the same, we can diff the attributes and children
|
// If the templates are the same, we can diff the attributes and children
|
||||||
// Start with the attributes
|
// Start with the attributes
|
||||||
self.dynamic_attrs
|
self.diff_attributes(new, dom, to);
|
||||||
.iter()
|
|
||||||
.zip(new.dynamic_attrs.iter())
|
|
||||||
.enumerate()
|
|
||||||
.for_each(|(idx, (old_attr, new_attr))| {
|
|
||||||
// If the attributes are different (or volatile), we need to update them
|
|
||||||
if old_attr.value != new_attr.value || new_attr.volatile {
|
|
||||||
self.update_attribute(mount, idx, new_attr, to);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now diff the dynamic nodes
|
// Now diff the dynamic nodes
|
||||||
self.dynamic_nodes
|
self.dynamic_nodes
|
||||||
@@ -317,17 +307,108 @@ impl VNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn update_attribute(
|
pub(super) fn diff_attributes(
|
||||||
&self,
|
&self,
|
||||||
mount: &VNodeMount,
|
new: &VNode,
|
||||||
idx: usize,
|
dom: &mut VirtualDom,
|
||||||
new_attr: &Attribute,
|
|
||||||
to: &mut impl WriteMutations,
|
to: &mut impl WriteMutations,
|
||||||
) {
|
) {
|
||||||
let name = &new_attr.name;
|
let mount_id = self.mount.get();
|
||||||
let value = &new_attr.value;
|
for (idx, (old_attrs, new_attrs)) in self
|
||||||
let id = mount.mounted_attributes[idx];
|
.dynamic_attrs
|
||||||
to.set_attribute(name, new_attr.namespace, value, id);
|
.iter()
|
||||||
|
.zip(new.dynamic_attrs.iter())
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let mut old_attributes_iter = old_attrs.iter().peekable();
|
||||||
|
let mut new_attributes_iter = new_attrs.iter().peekable();
|
||||||
|
let attribute_id = dom.mounts[mount_id.0].mounted_attributes[idx];
|
||||||
|
let path = self.template.get().attr_paths[idx];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match (old_attributes_iter.peek(), new_attributes_iter.peek()) {
|
||||||
|
(Some(old_attribute), Some(new_attribute)) => {
|
||||||
|
// check which name is greater
|
||||||
|
match old_attribute.name.cmp(new_attribute.name) {
|
||||||
|
// The two attributes are the same, so diff them
|
||||||
|
std::cmp::Ordering::Equal => {
|
||||||
|
let old = old_attributes_iter.next().unwrap();
|
||||||
|
let new = new_attributes_iter.next().unwrap();
|
||||||
|
if old.value != new.value {
|
||||||
|
self.write_attribute(
|
||||||
|
path,
|
||||||
|
new,
|
||||||
|
attribute_id,
|
||||||
|
mount_id,
|
||||||
|
dom,
|
||||||
|
to,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In a sorted list, if the old attribute name is first, then the new attribute is missing
|
||||||
|
std::cmp::Ordering::Less => {
|
||||||
|
let old = old_attributes_iter.next().unwrap();
|
||||||
|
self.remove_attribute(old, attribute_id, to)
|
||||||
|
}
|
||||||
|
// In a sorted list, if the new attribute name is first, then the old attribute is missing
|
||||||
|
std::cmp::Ordering::Greater => {
|
||||||
|
let new = new_attributes_iter.next().unwrap();
|
||||||
|
self.write_attribute(path, new, attribute_id, mount_id, dom, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(_), None) => {
|
||||||
|
let left = old_attributes_iter.next().unwrap();
|
||||||
|
self.remove_attribute(left, attribute_id, to)
|
||||||
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
let right = new_attributes_iter.next().unwrap();
|
||||||
|
self.write_attribute(path, right, attribute_id, mount_id, dom, to)
|
||||||
|
}
|
||||||
|
(None, None) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_attribute(&self, attribute: &Attribute, id: ElementId, to: &mut impl WriteMutations) {
|
||||||
|
match &attribute.value {
|
||||||
|
AttributeValue::Listener(_) => {
|
||||||
|
to.remove_event_listener(&attribute.name[2..], id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
to.set_attribute(
|
||||||
|
attribute.name,
|
||||||
|
attribute.namespace,
|
||||||
|
&AttributeValue::None,
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_attribute(
|
||||||
|
&self,
|
||||||
|
path: &'static [u8],
|
||||||
|
attribute: &Attribute,
|
||||||
|
id: ElementId,
|
||||||
|
mount: MountId,
|
||||||
|
dom: &mut VirtualDom,
|
||||||
|
to: &mut impl WriteMutations,
|
||||||
|
) {
|
||||||
|
match &attribute.value {
|
||||||
|
AttributeValue::Listener(_) => {
|
||||||
|
let element_ref = ElementRef {
|
||||||
|
path: ElementPath { path },
|
||||||
|
mount,
|
||||||
|
};
|
||||||
|
dom.elements[id.0] = Some(element_ref);
|
||||||
|
to.create_event_listener(&attribute.name[2..], id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
to.set_attribute(attribute.name, attribute.namespace, &attribute.value, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lightly diff the two templates, checking only their roots.
|
/// Lightly diff the two templates, checking only their roots.
|
||||||
@@ -650,7 +731,9 @@ impl VNode {
|
|||||||
let id = self.assign_static_node_as_dynamic(path, root, dom, to);
|
let id = self.assign_static_node_as_dynamic(path, root, dom, to);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
self.write_attribute(mount, attr_id, id, dom, to);
|
for attr in &*self.dynamic_attrs[attr_id] {
|
||||||
|
self.write_attribute(path, attr, id, mount, dom, to);
|
||||||
|
}
|
||||||
|
|
||||||
// Only push the dynamic attributes forward if they match the current path (same element)
|
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||||
match attrs.next_if(|(_, p)| *p == path) {
|
match attrs.next_if(|(_, p)| *p == path) {
|
||||||
@@ -661,40 +744,6 @@ impl VNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_attribute(
|
|
||||||
&self,
|
|
||||||
mount: MountId,
|
|
||||||
idx: usize,
|
|
||||||
id: ElementId,
|
|
||||||
dom: &mut VirtualDom,
|
|
||||||
to: &mut impl WriteMutations,
|
|
||||||
) {
|
|
||||||
// Make sure we set the attribute's associated id
|
|
||||||
dom.mounts[mount.0].mounted_attributes[idx] = id;
|
|
||||||
|
|
||||||
let attribute = &self.dynamic_attrs[idx];
|
|
||||||
|
|
||||||
match &attribute.value {
|
|
||||||
AttributeValue::Listener(_) => {
|
|
||||||
// If this is a listener, we need to create an element reference for it so that when we receive an event, we can find the element
|
|
||||||
let path = &self.template.get().attr_paths[idx];
|
|
||||||
|
|
||||||
// The mount information should always be in the VDOM at this point
|
|
||||||
debug_assert!(dom.mounts.get(mount.0).is_some());
|
|
||||||
|
|
||||||
let element_ref = ElementRef {
|
|
||||||
path: ElementPath { path },
|
|
||||||
mount,
|
|
||||||
};
|
|
||||||
dom.elements[id.0] = Some(element_ref);
|
|
||||||
to.create_event_listener(&attribute.name[2..], id);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
to.set_attribute(attribute.name, attribute.namespace, &attribute.value, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_template_root(
|
fn load_template_root(
|
||||||
&self,
|
&self,
|
||||||
mount: MountId,
|
mount: MountId,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
backtrace::Backtrace,
|
backtrace::Backtrace,
|
||||||
cell::{Cell, RefCell},
|
cell::RefCell,
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
fc_to_builder, generation, once, schedule_update, schedule_update_any, vdom_is_rendering,
|
fc_to_builder, generation, once, schedule_update, schedule_update_any, vdom_is_rendering,
|
||||||
AnyValue, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element, ElementId,
|
AnyValue, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element, ElementId,
|
||||||
Event, Fragment, IntoDynNode, Mutation, Mutations, NoOpMutations, Properties, RenderReturn,
|
Event, Fragment, HasAttributes, IntoDynNode, Mutation, Mutations, NoOpMutations, Properties,
|
||||||
ScopeId, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner,
|
RenderReturn, ScopeId, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,
|
||||||
VPlaceholder, VText, VirtualDom, WriteMutations,
|
VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The purpose of this module is to alleviate imports of many common types
|
/// The purpose of this module is to alleviate imports of many common types
|
||||||
@@ -87,9 +87,9 @@ pub mod prelude {
|
|||||||
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
|
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
|
||||||
has_context, needs_update, once, parent_scope, provide_context, provide_root_context,
|
has_context, needs_update, once, parent_scope, provide_context, provide_root_context,
|
||||||
push_future, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever,
|
push_future, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever,
|
||||||
suspend, use_error_boundary, use_hook, AnyValue, Component, Element, ErrorBoundary, Event,
|
suspend, use_error_boundary, use_hook, AnyValue, Attribute, Component, Element,
|
||||||
EventHandler, Fragment, IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard,
|
ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
|
||||||
ScopeId, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner,
|
IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, Task, Template, TemplateAttribute,
|
||||||
VirtualDom,
|
TemplateNode, Throw, VNode, VNodeInner, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use crate::{arena::ElementId, Element, Event};
|
use crate::{arena::ElementId, Element, Event};
|
||||||
use crate::{Properties, VirtualDom};
|
use crate::{Properties, VirtualDom};
|
||||||
|
use core::panic;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
@@ -94,11 +95,34 @@ pub struct VNodeInner {
|
|||||||
/// The static nodes and static descriptor of the template
|
/// The static nodes and static descriptor of the template
|
||||||
pub template: Cell<Template>,
|
pub template: Cell<Template>,
|
||||||
|
|
||||||
/// The dynamic parts of the template
|
/// The dynamic nodes in the template
|
||||||
pub dynamic_nodes: Box<[DynamicNode]>,
|
pub dynamic_nodes: Box<[DynamicNode]>,
|
||||||
|
|
||||||
/// The dynamic parts of the template
|
/// The dynamic attribute slots in the template
|
||||||
pub dynamic_attrs: Box<[Attribute]>,
|
///
|
||||||
|
/// This is a list of positions in the template where dynamic attributes can be inserted.
|
||||||
|
///
|
||||||
|
/// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```rust
|
||||||
|
/// div {
|
||||||
|
/// class: "{class}",
|
||||||
|
/// ..attrs,
|
||||||
|
/// p {
|
||||||
|
/// color: "{color}",
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Would be represented as:
|
||||||
|
/// ```rust
|
||||||
|
/// [
|
||||||
|
/// [class, every attribute in attrs sorted by name], // Slot 0 in the template
|
||||||
|
/// [color], // Slot 1 in the template
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
pub dynamic_attrs: Box<[Box<[Attribute]>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reference to a template along with any context needed to hydrate it
|
/// A reference to a template along with any context needed to hydrate it
|
||||||
@@ -186,7 +210,7 @@ impl VNode {
|
|||||||
key: Option<String>,
|
key: Option<String>,
|
||||||
template: Template,
|
template: Template,
|
||||||
dynamic_nodes: Box<[DynamicNode]>,
|
dynamic_nodes: Box<[DynamicNode]>,
|
||||||
dynamic_attrs: Box<[Attribute]>,
|
dynamic_attrs: Box<[Box<[Attribute]>]>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vnode: Rc::new(VNodeInner {
|
vnode: Rc::new(VNodeInner {
|
||||||
@@ -572,7 +596,7 @@ pub enum TemplateAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Attribute {
|
pub struct Attribute {
|
||||||
/// The name of the attribute.
|
/// The name of the attribute.
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
@@ -687,6 +711,19 @@ impl PartialEq for AttributeValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Clone for AttributeValue {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Text(arg0) => Self::Text(arg0.clone()),
|
||||||
|
Self::Float(arg0) => Self::Float(*arg0),
|
||||||
|
Self::Int(arg0) => Self::Int(*arg0),
|
||||||
|
Self::Bool(arg0) => Self::Bool(*arg0),
|
||||||
|
Self::Listener(_) | Self::Any(_) => panic!("Cannot clone listener or any value"),
|
||||||
|
Self::None => Self::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait AnyValue: 'static {
|
pub trait AnyValue: 'static {
|
||||||
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
||||||
@@ -880,11 +917,11 @@ impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for anything that has a dynamic list of attributes
|
/// A trait for anything that has a dynamic list of attributes
|
||||||
pub trait HasAttributes<'a> {
|
pub trait HasAttributes {
|
||||||
/// Push an attribute onto the list of attributes
|
/// Push an attribute onto the list of attributes
|
||||||
fn push_attribute(
|
fn push_attribute(
|
||||||
self,
|
self,
|
||||||
name: &'a str,
|
name: &'static str,
|
||||||
ns: Option<&'static str>,
|
ns: Option<&'static str>,
|
||||||
attr: impl IntoAttributeValue,
|
attr: impl IntoAttributeValue,
|
||||||
volatile: bool,
|
volatile: bool,
|
||||||
|
|||||||
@@ -391,20 +391,22 @@ impl VirtualDom {
|
|||||||
let node_template = el_ref.template.get();
|
let node_template = el_ref.template.get();
|
||||||
let target_path = path.path;
|
let target_path = path.path;
|
||||||
|
|
||||||
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
for (idx, attrs) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||||
let this_path = node_template.attr_paths[idx];
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
for attr in attrs.iter() {
|
||||||
if attr.name.trim_start_matches("on") == name
|
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||||
&& target_path.is_decendant(&this_path)
|
if attr.name.trim_start_matches("on") == name
|
||||||
{
|
&& target_path.is_decendant(&this_path)
|
||||||
listeners.push(&attr.value);
|
{
|
||||||
|
listeners.push(&attr.value);
|
||||||
|
|
||||||
// Break if this is the exact target element.
|
// 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
|
// 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
|
// documented, or be rejected from the rsx! macro outright
|
||||||
if target_path == this_path {
|
if target_path == this_path {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,15 +438,17 @@ impl VirtualDom {
|
|||||||
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||||
let this_path = node_template.attr_paths[idx];
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
for attr in attr.iter() {
|
||||||
// Only call the listener if this is the exact target element.
|
// 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 == this_path {
|
// Only call the listener if this is the exact target element.
|
||||||
if let AttributeValue::Listener(listener) = &attr.value {
|
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
||||||
self.runtime.rendering.set(false);
|
if let AttributeValue::Listener(listener) = &attr.value {
|
||||||
listener.call(uievent.clone());
|
self.runtime.rendering.set(false);
|
||||||
self.runtime.rendering.set(true);
|
listener.call(uievent.clone());
|
||||||
|
self.runtime.rendering.set(true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ fn create() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_list() {
|
fn create_list() {
|
||||||
let mut dom = VirtualDom::new(|| render! {(0..3).map(|f| render!( div { "hello" } ))});
|
let mut dom = VirtualDom::new(|| render! {{(0..3).map(|f| render!( div { "hello" } ))}});
|
||||||
|
|
||||||
let _edits = dom.rebuild_to_vec().santize();
|
let _edits = dom.rebuild_to_vec().santize();
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ fn create_components() {
|
|||||||
fn Child(cx: ChildProps) -> Element {
|
fn Child(cx: ChildProps) -> Element {
|
||||||
render! {
|
render! {
|
||||||
h1 {}
|
h1 {}
|
||||||
div { &cx.children }
|
div { {cx.children} }
|
||||||
p {}
|
p {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,10 +152,10 @@ fn anchors() {
|
|||||||
let mut dom = VirtualDom::new(|| {
|
let mut dom = VirtualDom::new(|| {
|
||||||
render! {
|
render! {
|
||||||
if true {
|
if true {
|
||||||
render!( div { "hello" } )
|
div { "hello" }
|
||||||
}
|
}
|
||||||
if false {
|
if false {
|
||||||
render!( div { "goodbye" } )
|
div { "goodbye" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use dioxus_core::ElementId;
|
|||||||
#[test]
|
#[test]
|
||||||
fn empty_fragment_creates_nothing() {
|
fn empty_fragment_creates_nothing() {
|
||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
render!(())
|
render!({ () })
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vdom = VirtualDom::new(app);
|
let mut vdom = VirtualDom::new(app);
|
||||||
@@ -55,7 +55,7 @@ fn fragments_nested() {
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
))
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -77,10 +77,7 @@ fn fragments_across_components() {
|
|||||||
|
|
||||||
fn demo_child() -> Element {
|
fn demo_child() -> Element {
|
||||||
let world = "world";
|
let world = "world";
|
||||||
render! {
|
render! { "hellO!", {world} }
|
||||||
"hellO!"
|
|
||||||
{world}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ fn list_shrink_multiroot() {
|
|||||||
let mut dom = VirtualDom::new(|| {
|
let mut dom = VirtualDom::new(|| {
|
||||||
render! {
|
render! {
|
||||||
div {
|
div {
|
||||||
for i in 0..cx.generation() {
|
for i in 0..generation() {
|
||||||
div { "{i}" }
|
div { "{i}" }
|
||||||
div { "{i}" }
|
div { "{i}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![cfg(not(miri))]
|
#![cfg(not(miri))]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_core::{prelude::EventHandler, AttributeValue, DynamicNode, VComponent, *};
|
use dioxus_core::{prelude::EventHandler, AttributeValue, DynamicNode, VComponent, VNode, *};
|
||||||
use std::{cfg, collections::HashSet, default::Default};
|
use std::{cfg, collections::HashSet, default::Default};
|
||||||
|
|
||||||
fn random_ns() -> Option<&'static str> {
|
fn random_ns() -> Option<&'static str> {
|
||||||
@@ -250,7 +250,7 @@ fn create_random_element(cx: DepthProps) -> Element {
|
|||||||
)
|
)
|
||||||
.into_boxed_str(),
|
.into_boxed_str(),
|
||||||
));
|
));
|
||||||
let node = cx.vnode(
|
let node = VNode::new(
|
||||||
None,
|
None,
|
||||||
template,
|
template,
|
||||||
dynamic_node_types
|
dynamic_node_types
|
||||||
@@ -263,7 +263,7 @@ fn create_random_element(cx: DepthProps) -> Element {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
(0..template.attr_paths.len())
|
(0..template.attr_paths.len())
|
||||||
.map(|_| create_random_dynamic_attr())
|
.map(|_| Box::new([create_random_dynamic_attr()]) as Box<[Attribute]>)
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
Some(node)
|
Some(node)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ fn app() -> Element {
|
|||||||
}
|
}
|
||||||
button { onclick: move |_| idx -= 1, "-" }
|
button { onclick: move |_| idx -= 1, "-" }
|
||||||
ul {
|
ul {
|
||||||
{(0..**idx).map(|i| rsx! {
|
{(0..*idx()).map(|i| rsx! {
|
||||||
ChildExample { i: i, onhover: onhover }
|
ChildExample { i: i, onhover: onhover }
|
||||||
})}
|
})}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async fn it_works() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
render!(())
|
render!({ () })
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ use tao::event::{Event, StartCause, WindowEvent};
|
|||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn launch(root: Component) {
|
pub fn launch(root: fn() -> Element) {
|
||||||
launch_with_props(root, (), Config::default())
|
launch_with_props(|root| root(), root, Config::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Launch the WebView and run the event loop, with configuration.
|
/// Launch the WebView and run the event loop, with configuration.
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ impl ToTokens for ImplExtensionAttributes {
|
|||||||
quote! { <#impl_name as #name>::#ident }
|
quote! { <#impl_name as #name>::#ident }
|
||||||
};
|
};
|
||||||
quote! {
|
quote! {
|
||||||
fn #ident(self, value: impl IntoAttributeValue<'a>) -> Self {
|
fn #ident(self, value: impl IntoAttributeValue) -> Self {
|
||||||
let d = #d;
|
let d = #d;
|
||||||
self.push_attribute(d.0, d.1, value, d.2)
|
self.push_attribute(d.0, d.1, value, d.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
pub trait #extension_name<'a>: HasAttributes<'a> + Sized {
|
pub trait #extension_name: HasAttributes + Sized {
|
||||||
#(#impls)*
|
#(#impls)*
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
use dioxus_core::prelude::IntoAttributeValue;
|
use dioxus_core::prelude::IntoAttributeValue;
|
||||||
|
use dioxus_core::HasAttributes;
|
||||||
use dioxus_html_internal_macro::impl_extension_attributes;
|
use dioxus_html_internal_macro::impl_extension_attributes;
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
use dioxus_rsx::HotReloadingContext;
|
use dioxus_rsx::HotReloadingContext;
|
||||||
@@ -385,9 +386,9 @@ macro_rules! builder_constructors {
|
|||||||
|
|
||||||
pub(crate) mod extensions {
|
pub(crate) mod extensions {
|
||||||
use super::*;
|
use super::*;
|
||||||
// $(
|
$(
|
||||||
// impl_extension_attributes![ELEMENT $name { $($fil,)* }];
|
impl_extension_attributes![ELEMENT $name { $($fil,)* }];
|
||||||
// )*
|
)*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
use dioxus_core::prelude::IntoAttributeValue;
|
use dioxus_core::prelude::IntoAttributeValue;
|
||||||
|
use dioxus_core::HasAttributes;
|
||||||
use dioxus_html_internal_macro::impl_extension_attributes;
|
use dioxus_html_internal_macro::impl_extension_attributes;
|
||||||
|
|
||||||
use crate::AttributeDiscription;
|
use crate::AttributeDiscription;
|
||||||
@@ -110,7 +111,7 @@ macro_rules! trait_methods {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl_extension_attributes![GLOBAL $trait { $($name,)* }];
|
impl_extension_attributes![GLOBAL $trait { $($name,)* }];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rename the incoming ident and apply a custom namespace
|
// Rename the incoming ident and apply a custom namespace
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ pub mod eval;
|
|||||||
|
|
||||||
pub mod extensions {
|
pub mod extensions {
|
||||||
pub use crate::elements::extensions::*;
|
pub use crate::elements::extensions::*;
|
||||||
// pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
@@ -58,7 +58,7 @@ pub mod prelude {
|
|||||||
#[cfg(feature = "eval")]
|
#[cfg(feature = "eval")]
|
||||||
pub use crate::eval::*;
|
pub use crate::eval::*;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
// pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||||
pub use crate::point_interaction::*;
|
pub use crate::point_interaction::*;
|
||||||
pub use keyboard_types::{self, Code, Key, Location, Modifiers};
|
pub use keyboard_types::{self, Code, Key, Location, Modifiers};
|
||||||
}
|
}
|
||||||
+20
-10
@@ -256,8 +256,8 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||||||
::dioxus::core::VNode::new(
|
::dioxus::core::VNode::new(
|
||||||
#key_tokens,
|
#key_tokens,
|
||||||
TEMPLATE,
|
TEMPLATE,
|
||||||
Box::new([ #( #node_printer ),* ]),
|
Box::new([ #( #node_printer),* ]),
|
||||||
Box::new([ #( #dyn_attr_printer ),* ]),
|
Box::new([ #( Box::new([ #( #dyn_attr_printer),* ]) ),* ]),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -352,7 +352,7 @@ impl DynamicMapping {
|
|||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct DynamicContext<'a> {
|
pub struct DynamicContext<'a> {
|
||||||
dynamic_nodes: Vec<&'a BodyNode>,
|
dynamic_nodes: Vec<&'a BodyNode>,
|
||||||
dynamic_attributes: Vec<&'a AttributeType>,
|
dynamic_attributes: Vec<Vec<&'a AttributeType>>,
|
||||||
current_path: Vec<u8>,
|
current_path: Vec<u8>,
|
||||||
|
|
||||||
node_paths: Vec<Vec<u8>>,
|
node_paths: Vec<Vec<u8>>,
|
||||||
@@ -398,7 +398,7 @@ impl<'a> DynamicContext<'a> {
|
|||||||
Some(mapping) => mapping.get_attribute_idx(attr)?,
|
Some(mapping) => mapping.get_attribute_idx(attr)?,
|
||||||
None => self.dynamic_attributes.len(),
|
None => self.dynamic_attributes.len(),
|
||||||
};
|
};
|
||||||
self.dynamic_attributes.push(attr);
|
self.dynamic_attributes.push(vec![attr]);
|
||||||
|
|
||||||
if self.attr_paths.len() <= idx {
|
if self.attr_paths.len() <= idx {
|
||||||
self.attr_paths.resize_with(idx + 1, Vec::new);
|
self.attr_paths.resize_with(idx + 1, Vec::new);
|
||||||
@@ -498,19 +498,29 @@ impl<'a> DynamicContext<'a> {
|
|||||||
|
|
||||||
// todo: we don't diff these so we never apply the volatile flag
|
// todo: we don't diff these so we never apply the volatile flag
|
||||||
// volatile: dioxus_elements::#el_name::#name.2,
|
// volatile: dioxus_elements::#el_name::#name.2,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let ct = self.dynamic_attributes.len();
|
// If this attribute is dynamic, but it already exists in the template, we can reuse the index
|
||||||
self.dynamic_attributes.push(attr);
|
if let Some(attribute_index) = self
|
||||||
self.attr_paths.push(self.current_path.clone());
|
.attr_paths
|
||||||
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
|
.iter()
|
||||||
|
.position(|path| path == &self.current_path)
|
||||||
|
{
|
||||||
|
self.dynamic_attributes[attribute_index].push(attr);
|
||||||
|
quote! {}
|
||||||
|
} else {
|
||||||
|
let ct = self.dynamic_attributes.len();
|
||||||
|
self.dynamic_attributes.push(vec![attr]);
|
||||||
|
self.attr_paths.push(self.current_path.clone());
|
||||||
|
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct }, }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let attrs = quote! { #(#static_attrs),*};
|
let attrs = quote! { #(#static_attrs)* };
|
||||||
|
|
||||||
let children = el.children.iter().enumerate().map(|(idx, root)| {
|
let children = el.children.iter().enumerate().map(|(idx, root)| {
|
||||||
self.current_path.push(idx as u8);
|
self.current_path.push(idx as u8);
|
||||||
|
|||||||
Reference in new issue
Block a user