partially restore spreading attributes

This commit is contained in:
Evan Almloff committed 2024-01-14 15:21:19 -06:00
1 parent 2470a46511
commit 36e9eb1160
19 files changed
+252 -194

No files matched your search

+8 -8
View File
@@ -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,
} }
+21 -62
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -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,
+7 -7
View File
@@ -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,
}; };
} }
+44 -7
View File
@@ -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,
+23 -19
View File
@@ -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;
}
} }
} }
} }
+4 -4
View File
@@ -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" }
} }
} }
}); });
+3 -6
View File
@@ -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!(
+1 -1
View File
@@ -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}" }
} }
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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 }
})} })}
} }
+1 -1
View File
@@ -25,7 +25,7 @@ async fn it_works() {
}); });
}); });
render!(()) render!({ () })
} }
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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)*
} }
}); });
+4 -3
View File
@@ -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,)* }];
// )* )*
} }
}; };
} }
+2 -1
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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);