فهرست منبع

fix custom attribute value support

Evan Almloff 2 سال پیش
والد
کامیت
8a1c96a68c

+ 2 - 2
packages/autofmt/src/buffer.rs

@@ -144,8 +144,8 @@ impl Buffer {
         let mut total = 0;
 
         for attr in attributes {
-            if self.current_span_is_primary(attr.attr.flart()) {
-                'line: for line in self.src[..attr.attr.flart().start().line - 1].iter().rev() {
+            if self.current_span_is_primary(attr.attr.start()) {
+                'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
                     match (line.trim().starts_with("//"), line.is_empty()) {
                         (true, _) => return 100000,
                         (_, true) => continue 'line,

+ 1 - 1
packages/autofmt/src/element.rs

@@ -157,7 +157,7 @@ impl Buffer {
         while let Some(attr) = attr_iter.next() {
             self.indent += 1;
             if !sameline {
-                self.write_comments(attr.attr.flart())?;
+                self.write_comments(attr.attr.start())?;
             }
             self.indent -= 1;
 

+ 13 - 22
packages/core/src/create.rs

@@ -105,12 +105,22 @@ impl<'b> VirtualDom {
                             attribute.mounted_element.set(id);
 
                             // Safety: we promise not to re-alias this text later on after committing it to the mutation
-                            let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
+                            let unbounded_name: &str =
+                                unsafe { std::mem::transmute(attribute.name) };
 
                             match &attribute.value {
-                                AttributeValue::Text(value) => {
+                                AttributeValue::Listener(_) => {
+                                    self.mutations.push(NewEventListener {
+                                        // all listeners start with "on"
+                                        name: &unbounded_name[2..],
+                                        scope: cur_scope,
+                                        id,
+                                    })
+                                }
+                                _ => {
                                     // Safety: we promise not to re-alias this text later on after committing it to the mutation
-                                    let unbounded_value = unsafe { std::mem::transmute(*value) };
+                                    let unbounded_value =
+                                        unsafe { std::mem::transmute(attribute.value.clone()) };
 
                                     self.mutations.push(SetAttribute {
                                         name: unbounded_name,
@@ -119,25 +129,6 @@ impl<'b> VirtualDom {
                                         id,
                                     })
                                 }
-                                AttributeValue::Bool(value) => {
-                                    self.mutations.push(SetBoolAttribute {
-                                        name: unbounded_name,
-                                        value: *value,
-                                        id,
-                                    })
-                                }
-                                AttributeValue::Listener(_) => {
-                                    self.mutations.push(NewEventListener {
-                                        // all listeners start with "on"
-                                        name: &unbounded_name[2..],
-                                        scope: cur_scope,
-                                        id,
-                                    })
-                                }
-                                AttributeValue::Float(_) => todo!(),
-                                AttributeValue::Int(_) => todo!(),
-                                AttributeValue::Any(_) => todo!(),
-                                AttributeValue::None => todo!(),
                             }
 
                             // Only push the dynamic attributes forward if they match the current path (same element)

+ 8 - 14
packages/core/src/diff.rs

@@ -77,20 +77,14 @@ impl<'b> VirtualDom {
 
             if left_attr.value != right_attr.value || left_attr.volatile {
                 // todo: add more types of attribute values
-                match right_attr.value {
-                    AttributeValue::Text(text) => {
-                        let name = unsafe { std::mem::transmute(left_attr.name) };
-                        let value = unsafe { std::mem::transmute(text) };
-                        self.mutations.push(Mutation::SetAttribute {
-                            id: left_attr.mounted_element.get(),
-                            ns: right_attr.namespace,
-                            name,
-                            value,
-                        });
-                    }
-                    // todo: more types of attribute values
-                    _ => todo!("other attribute types"),
-                }
+                let name = unsafe { std::mem::transmute(left_attr.name) };
+                let value = unsafe { std::mem::transmute(right_attr.value.clone()) };
+                self.mutations.push(Mutation::SetAttribute {
+                    id: left_attr.mounted_element.get(),
+                    ns: right_attr.namespace,
+                    name,
+                    value,
+                });
             }
         }
 

+ 7 - 7
packages/core/src/lib.rs

@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
-    fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
-    Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
-    ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
-    TemplateNode, VComponent, VNode, VText, VirtualDom,
+    fc_to_builder, AnyValueBox, Attribute, AttributeValue, Component, DynamicNode, Element,
+    ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation, Mutations,
+    Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId,
+    Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
 };
 
 /// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
     pub use crate::innerlude::{
-        fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope,
-        ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode,
-        VirtualDom,
+        fc_to_builder, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
+        Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
+        TemplateNode, VNode, VirtualDom,
     };
 }
 

+ 5 - 16
packages/core/src/mutations.rs

@@ -1,6 +1,6 @@
 use fxhash::FxHashSet;
 
-use crate::{arena::ElementId, ScopeId, Template};
+use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
 
 /// A container for all the relevant steps to modify the Real DOM
 ///
@@ -48,7 +48,7 @@ impl<'a> Mutations<'a> {
 
     /// Push a new mutation into the dom_edits list
     pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
-        self.edits.push(mutation)
+        unsafe { self.edits.push(std::mem::transmute(mutation)) }
     }
 }
 
@@ -61,7 +61,7 @@ impl<'a> Mutations<'a> {
     derive(serde::Serialize, serde::Deserialize),
     serde(tag = "type")
 )]
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq)]
 pub enum Mutation<'a> {
     /// Add these m children to the target element
     AppendChildren {
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
     SetAttribute {
         /// The name of the attribute to set.
         name: &'a str,
+
         /// The value of the attribute.
-        value: &'a str,
+        value: AttributeValue<'a>,
 
         /// The ID of the node to set the attribute of.
         id: ElementId,
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
         ns: Option<&'a str>,
     },
 
-    /// Set the value of a node's attribute.
-    SetBoolAttribute {
-        /// The name of the attribute to set.
-        name: &'a str,
-
-        /// The value of the attribute.
-        value: bool,
-
-        /// The ID of the node to set the attribute of.
-        id: ElementId,
-    },
-
     /// Set the textcontent of a node.
     SetText {
         /// The textcontent of the node

+ 71 - 6
packages/core/src/nodes.rs

@@ -8,6 +8,7 @@ use std::{
     cell::{Cell, RefCell},
     fmt::Arguments,
     future::Future,
+    rc::Rc,
 };
 
 pub type TemplateId = &'static str;
@@ -88,7 +89,7 @@ impl<'a> VNode<'a> {
     pub(crate) fn clear_listeners(&self) {
         for attr in self.dynamic_attrs {
             if let AttributeValue::Listener(l) = &attr.value {
-                l.borrow_mut().take();
+                l.0.borrow_mut().take();
             }
         }
     }
@@ -317,6 +318,9 @@ pub struct Attribute<'a> {
 ///
 /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
 /// variant.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(untagged))]
+#[derive(Clone)]
 pub enum AttributeValue<'a> {
     /// Text attribute
     Text(&'a str),
@@ -331,16 +335,67 @@ pub enum AttributeValue<'a> {
     Bool(bool),
 
     /// A listener, like "onclick"
-    Listener(RefCell<Option<ListenerCb<'a>>>),
+    Listener(ListenerCb<'a>),
 
     /// An arbitrary value that implements PartialEq and is static
-    Any(BumpBox<'a, dyn AnyValue>),
+    Any(AnyValueBox),
 
     /// A "none" value, resulting in the removal of an attribute from the dom
     None,
 }
 
-type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
+pub type ListenerCbInner<'a> = RefCell<Option<BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>>>;
+pub struct ListenerCb<'a>(pub ListenerCbInner<'a>);
+
+impl Clone for ListenerCb<'_> {
+    fn clone(&self) -> Self {
+        panic!("ListenerCb cannot be cloned")
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl<'a> serde::Serialize for ListenerCb<'a> {
+    fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        panic!("ListenerCb cannot be serialized")
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl<'de, 'a> serde::Deserialize<'de> for ListenerCb<'a> {
+    fn deserialize<D>(_: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        panic!("ListenerCb cannot be deserialized")
+    }
+}
+
+/// A boxed value that implements PartialEq and Any
+#[derive(Clone)]
+pub struct AnyValueBox(pub Rc<dyn AnyValue>);
+
+#[cfg(feature = "serialize")]
+impl serde::Serialize for AnyValueBox {
+    fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        panic!("AnyValueBox cannot be serialized")
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl<'de> serde::Deserialize<'de> for AnyValueBox {
+    fn deserialize<D>(_: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        panic!("AnyValueBox cannot be deserialized")
+    }
+}
 
 impl<'a> std::fmt::Debug for AttributeValue<'a> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -364,8 +419,8 @@ impl<'a> PartialEq for AttributeValue<'a> {
             (Self::Int(l0), Self::Int(r0)) => l0 == r0,
             (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
             (Self::Listener(_), Self::Listener(_)) => true,
-            (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
-            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+            (Self::Any(l0), Self::Any(r0)) => l0.0.any_cmp(r0.0.as_ref()),
+            _ => false,
         }
     }
 }
@@ -559,21 +614,25 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
         AttributeValue::Text(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for f64 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Float(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for i64 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Int(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for bool {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Bool(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
     fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
         use bumpalo::core_alloc::fmt::Write;
@@ -582,3 +641,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
         AttributeValue::Text(str_buf.into_bump_str())
     }
 }
+
+impl<'a> IntoAttributeValue<'a> for AnyValueBox {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Any(self)
+    }
+}

+ 2 - 2
packages/core/src/scopes.rs

@@ -4,7 +4,7 @@ use crate::{
     arena::ElementId,
     bump_frame::BumpFrame,
     innerlude::{DynamicNode, EventHandler, VComponent, VText},
-    innerlude::{Scheduler, SchedulerMsg},
+    innerlude::{ListenerCb, Scheduler, SchedulerMsg},
     lazynodes::LazyNodes,
     nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
     Attribute, AttributeValue, Element, Event, Properties, TaskId,
@@ -483,7 +483,7 @@ impl<'src> ScopeState {
             }))
         };
 
-        AttributeValue::Listener(RefCell::new(Some(boxed)))
+        AttributeValue::Listener(ListenerCb(RefCell::new(Some(boxed))))
     }
 
     /// Store a value between renders. The foundational hook for all other hooks.

+ 5 - 5
packages/core/src/virtual_dom.rs

@@ -386,7 +386,7 @@ impl VirtualDom {
             // We check the bubble state between each call to see if the event has been stopped from bubbling
             for listener in listeners.drain(..).rev() {
                 if let AttributeValue::Listener(listener) = listener {
-                    if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                    if let Some(cb) = listener.0.borrow_mut().as_deref_mut() {
                         cb(uievent.clone());
                     }
 
@@ -493,7 +493,7 @@ impl VirtualDom {
             RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
         }
 
-        self.finalize()
+        unsafe { std::mem::transmute(self.finalize()) }
     }
 
     /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
@@ -591,7 +591,7 @@ impl VirtualDom {
 
             // If there's no pending suspense, then we have no reason to wait for anything
             if self.scheduler.leaves.borrow().is_empty() {
-                return self.finalize();
+                return unsafe { std::mem::transmute(self.finalize()) };
             }
 
             // Poll the suspense leaves in the meantime
@@ -605,13 +605,13 @@ impl VirtualDom {
             if let Either::Left((_, _)) = select(&mut deadline, pinned).await {
                 // release the borrowed
                 drop(work);
-                return self.finalize();
+                return unsafe { std::mem::transmute(self.finalize()) };
             }
         }
     }
 
     /// Swap the current mutations with a new
-    fn finalize(&mut self) -> Mutations {
+    fn finalize(&mut self) -> Mutations<'static> {
         // todo: make this a routine
         let mut out = Mutations::default();
         std::mem::swap(&mut self.mutations, &mut out);

+ 17 - 4
packages/core/tests/attr_cleanup.rs

@@ -2,6 +2,7 @@
 //!
 //! This tests to ensure we clean it up
 
+use bumpalo::Bump;
 use dioxus::core::{ElementId, Mutation::*};
 use dioxus::prelude::*;
 
@@ -22,6 +23,8 @@ fn attrs_cycle() {
         }
     });
 
+    let bump = Bump::new();
+
     assert_eq!(
         dom.rebuild().santize().edits,
         [
@@ -36,8 +39,13 @@ fn attrs_cycle() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             AssignId { path: &[0,], id: ElementId(3,) },
-            SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None },
-            SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None },
+            SetAttribute {
+                name: "class",
+                value: "1".into_value(&bump),
+                id: ElementId(3,),
+                ns: None
+            },
+            SetAttribute { name: "id", value: "1".into_value(&bump), id: ElementId(3,), ns: None },
             ReplaceWith { id: ElementId(1,), m: 1 },
         ]
     );
@@ -57,8 +65,13 @@ fn attrs_cycle() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
             AssignId { path: &[0], id: ElementId(1) },
-            SetAttribute { name: "class", value: "3", id: ElementId(1), ns: None },
-            SetAttribute { name: "id", value: "3", id: ElementId(1), ns: None },
+            SetAttribute {
+                name: "class",
+                value: "3".into_value(&bump),
+                id: ElementId(1),
+                ns: None
+            },
+            SetAttribute { name: "id", value: "3".into_value(&bump), id: ElementId(1), ns: None },
             ReplaceWith { id: ElementId(3), m: 1 }
         ]
     );

+ 9 - 2
packages/core/tests/boolattrs.rs

@@ -1,15 +1,22 @@
+use bumpalo::Bump;
 use dioxus::core::{ElementId, Mutation::*};
 use dioxus::prelude::*;
 
 #[test]
 fn bool_test() {
     let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false })));
+    let bump = Bump::new();
     assert_eq!(
         app.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
+            SetAttribute {
+                name: "hidden",
+                value: false.into_value(&bump),
+                id: ElementId(1,),
+                ns: None
+            },
             AppendChildren { m: 1, id: ElementId(0) },
         ]
-    )
+    );
 }

+ 1 - 1
packages/core/tests/borrowedstate.rs

@@ -18,7 +18,7 @@ fn test_borrowed_state() {
             ReplacePlaceholder { path: &[0,], m: 1 },
             AppendChildren { m: 1, id: ElementId(0) },
         ]
-    )
+    );
 }
 
 fn Parent(cx: Scope) -> Element {

+ 3 - 1
packages/core/tests/bubble_error.rs

@@ -20,7 +20,9 @@ fn app(cx: Scope) -> Element {
 fn bubbles_error() {
     let mut dom = VirtualDom::new(app);
 
-    let _edits = dom.rebuild().santize();
+    {
+        let _edits = dom.rebuild().santize();
+    }
 
     dom.mark_dirty(ScopeId(0));
 

+ 10 - 8
packages/core/tests/cycle.rs

@@ -12,14 +12,16 @@ fn cycling_elements() {
         })
     });
 
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AppendChildren { m: 1, id: ElementId(0) },
-        ]
-    );
+    {
+        let edits = dom.rebuild().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                AppendChildren { m: 1, id: ElementId(0) },
+            ]
+        );
+    }
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(

+ 15 - 13
packages/core/tests/diff_component.rs

@@ -59,19 +59,21 @@ fn component_swap() {
     }
 
     let mut dom = VirtualDom::new(app);
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
-            ReplacePlaceholder { path: &[1], m: 3 },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
-            AppendChildren { m: 2, id: ElementId(0) }
-        ]
-    );
+    {
+        let edits = dom.rebuild().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+                ReplacePlaceholder { path: &[1], m: 3 },
+                LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+                AppendChildren { m: 2, id: ElementId(0) }
+            ]
+        );
+    }
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(

+ 20 - 18
packages/core/tests/diff_keyed_list.rs

@@ -20,22 +20,24 @@ fn keyed_diffing_out_of_order() {
         cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    assert_eq!(
-        dom.rebuild().santize().edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
-            AppendChildren { m: 10, id: ElementId(0) },
-        ]
-    );
+    {
+        assert_eq!(
+            dom.rebuild().santize().edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
+                AppendChildren { m: 10, id: ElementId(0) },
+            ]
+        );
+    }
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
@@ -44,7 +46,7 @@ fn keyed_diffing_out_of_order() {
             PushRoot { id: ElementId(7,) },
             InsertBefore { id: ElementId(5,), m: 1 },
         ]
-    )
+    );
 }
 
 /// Should result in moves only
@@ -70,7 +72,7 @@ fn keyed_diffing_out_of_order_adds() {
             PushRoot { id: ElementId(4,) },
             InsertBefore { id: ElementId(1,), m: 2 },
         ]
-    )
+    );
 }
 
 /// Should result in moves only

+ 72 - 62
packages/core/tests/diff_unkeyed_list.rs

@@ -314,66 +314,76 @@ fn remove_many() {
         })
     });
 
-    let edits = dom.rebuild().santize();
-    assert!(edits.templates.is_empty());
-    assert_eq!(
-        edits.edits,
-        [
-            CreatePlaceholder { id: ElementId(1,) },
-            AppendChildren { id: ElementId(0), m: 1 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
-            ReplaceWith { id: ElementId(1,), m: 1 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
-            HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
-            HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
-            HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
-            InsertAfter { id: ElementId(2,), m: 4 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            Remove { id: ElementId(9,) },
-            Remove { id: ElementId(7,) },
-            Remove { id: ElementId(5,) },
-            Remove { id: ElementId(1,) },
-            CreatePlaceholder { id: ElementId(3,) },
-            ReplaceWith { id: ElementId(2,), m: 1 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) },
-            ReplaceWith { id: ElementId(3,), m: 1 },
-        ]
-    )
+    {
+        let edits = dom.rebuild().santize();
+        assert!(edits.templates.is_empty());
+        assert_eq!(
+            edits.edits,
+            [
+                CreatePlaceholder { id: ElementId(1,) },
+                AppendChildren { id: ElementId(0), m: 1 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+                HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
+                ReplaceWith { id: ElementId(1,), m: 1 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+                HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+                HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+                HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
+                InsertAfter { id: ElementId(2,), m: 4 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                Remove { id: ElementId(9,) },
+                Remove { id: ElementId(7,) },
+                Remove { id: ElementId(5,) },
+                Remove { id: ElementId(1,) },
+                CreatePlaceholder { id: ElementId(3,) },
+                ReplaceWith { id: ElementId(2,), m: 1 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+                HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) },
+                ReplaceWith { id: ElementId(3,), m: 1 },
+            ]
+        )
+    }
 }

+ 12 - 6
packages/core/tests/kitchen_sink.rs

@@ -1,3 +1,4 @@
+use bumpalo::Bump;
 use dioxus::core::{ElementId, Mutation};
 use dioxus::prelude::*;
 
@@ -26,17 +27,22 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
 #[test]
 fn dual_stream() {
     let mut dom = VirtualDom::new(basic_syntax_is_a_template);
+    let bump = Bump::new();
     let edits = dom.rebuild().santize();
 
     use Mutation::*;
-    assert_eq!(
-        edits.edits,
+    assert_eq!(edits.edits, {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
+            SetAttribute {
+                name: "class",
+                value: "123".into_value(&bump),
+                id: ElementId(1),
+                ns: None,
+            },
             NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) },
             HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
-            AppendChildren { id: ElementId(0), m: 1 }
-        ],
-    );
+            AppendChildren { id: ElementId(0), m: 1 },
+        ]
+    });
 }

+ 28 - 26
packages/core/tests/suspense.rs

@@ -9,32 +9,34 @@ use std::time::Duration;
 async fn it_works() {
     let mut dom = VirtualDom::new(app);
 
-    let mutations = dom.rebuild().santize();
-
-    // We should at least get the top-level template in before pausing for the children
-    // note: we dont test template edits anymore
-    // assert_eq!(
-    //     mutations.templates,
-    //     [
-    //         CreateElement { name: "div" },
-    //         CreateStaticText { value: "Waiting for child..." },
-    //         CreateStaticPlaceholder,
-    //         AppendChildren { m: 2 },
-    //         SaveTemplate { name: "template", m: 1 }
-    //     ]
-    // );
-
-    // And we should load it in and assign the placeholder properly
-    assert_eq!(
-        mutations.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
-            // can we even?
-            AssignId { path: &[1], id: ElementId(3) },
-            AppendChildren { m: 1, id: ElementId(0) },
-        ]
-    );
+    {
+        let mutations = dom.rebuild().santize();
+
+        // We should at least get the top-level template in before pausing for the children
+        // note: we dont test template edits anymore
+        // assert_eq!(
+        //     mutations.templates,
+        //     [
+        //         CreateElement { name: "div" },
+        //         CreateStaticText { value: "Waiting for child..." },
+        //         CreateStaticPlaceholder,
+        //         AppendChildren { m: 2 },
+        //         SaveTemplate { name: "template", m: 1 }
+        //     ]
+        // );
+
+        // And we should load it in and assign the placeholder properly
+        assert_eq!(
+            mutations.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
+                // can we even?
+                AssignId { path: &[1], id: ElementId(3) },
+                AppendChildren { m: 1, id: ElementId(0) },
+            ]
+        );
+    }
 
     // wait just a moment, not enough time for the boundary to resolve
 

+ 1 - 1
packages/dioxus/benches/jsframework.rs

@@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) {
 
     c.bench_function("create rows", |b| {
         let mut dom = VirtualDom::new(app);
-        dom.rebuild();
+        let _ = dom.rebuild();
 
         b.iter(|| {
             let g = dom.rebuild();

+ 1 - 2
packages/native-core-macro/Cargo.toml

@@ -18,10 +18,9 @@ proc-macro = true
 syn = { version = "1.0.11", features = ["extra-traits"] }
 quote = "1.0"
 dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
-
-[dev-dependencies]
 dioxus = { path = "../dioxus" }
 
+[dev-dependencies]
 smallvec = "1.6"
 rustc-hash = "1.1.0"
 anymap = "0.12.1"

+ 1 - 2
packages/native-core-macro/src/lib.rs

@@ -306,8 +306,7 @@ impl Member {
                     + field.ty.to_token_stream().to_string().as_str())
                 .as_str(),
                 Span::call_site(),
-            )
-            .into(),
+            ),
             ident: field.ident.as_ref()?.clone(),
         })
     }

+ 35 - 6
packages/native-core/src/node.rs

@@ -1,6 +1,7 @@
 use crate::{state::State, tree::NodeId};
-use dioxus_core::ElementId;
+use dioxus_core::{AnyValueBox, AttributeValue, ElementId};
 use rustc_hash::{FxHashMap, FxHashSet};
+use std::fmt::Debug;
 
 /// The node is stored client side and stores only basic data about the node.
 #[derive(Debug, Clone)]
@@ -72,15 +73,43 @@ pub struct OwnedAttributeView<'a> {
     pub value: &'a OwnedAttributeValue,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone)]
 pub enum OwnedAttributeValue {
     Text(String),
-    Float(f32),
-    Int(i32),
+    Float(f64),
+    Int(i64),
     Bool(bool),
+    Any(AnyValueBox),
     None,
 }
 
+impl Debug for OwnedAttributeValue {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
+            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
+            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
+            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
+            Self::Any(_) => f.debug_tuple("Any").finish(),
+            Self::None => write!(f, "None"),
+        }
+    }
+}
+
+impl From<AttributeValue<'_>> for OwnedAttributeValue {
+    fn from(value: AttributeValue<'_>) -> Self {
+        match value {
+            AttributeValue::Text(text) => Self::Text(text.to_string()),
+            AttributeValue::Float(float) => Self::Float(float),
+            AttributeValue::Int(int) => Self::Int(int),
+            AttributeValue::Bool(bool) => Self::Bool(bool),
+            AttributeValue::Any(any) => Self::Any(any),
+            AttributeValue::None => Self::None,
+            _ => Self::None,
+        }
+    }
+}
+
 impl OwnedAttributeValue {
     pub fn as_text(&self) -> Option<&str> {
         match self {
@@ -89,14 +118,14 @@ impl OwnedAttributeValue {
         }
     }
 
-    pub fn as_float(&self) -> Option<f32> {
+    pub fn as_float(&self) -> Option<f64> {
         match self {
             OwnedAttributeValue::Float(float) => Some(*float),
             _ => None,
         }
     }
 
-    pub fn as_int(&self) -> Option<i32> {
+    pub fn as_int(&self) -> Option<i64> {
         match self {
             OwnedAttributeValue::Int(int) => Some(*int),
             _ => None,

+ 11 - 43
packages/native-core/src/real_dom.rs

@@ -102,11 +102,7 @@ impl<S: State> RealDom<S> {
         self.tree.add_child(node_id, child_id);
     }
 
-    fn create_template_node(
-        &mut self,
-        node: &TemplateNode,
-        mutations_vec: &mut FxHashMap<RealNodeId, NodeMask>,
-    ) -> RealNodeId {
+    fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId {
         match node {
             TemplateNode::Element {
                 tag,
@@ -139,27 +135,18 @@ impl<S: State> RealDom<S> {
                 });
                 let node_id = self.create_node(node);
                 for child in *children {
-                    let child_id = self.create_template_node(child, mutations_vec);
+                    let child_id = self.create_template_node(child);
                     self.add_child(node_id, child_id);
                 }
                 node_id
             }
-            TemplateNode::Text { text } => {
-                let node_id = self.create_node(Node::new(NodeType::Text {
-                    text: text.to_string(),
-                }));
-                node_id
-            }
-            TemplateNode::Dynamic { .. } => {
-                let node_id = self.create_node(Node::new(NodeType::Placeholder));
-                node_id
-            }
-            TemplateNode::DynamicText { .. } => {
-                let node_id = self.create_node(Node::new(NodeType::Text {
-                    text: String::new(),
-                }));
-                node_id
-            }
+            TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text {
+                text: text.to_string(),
+            })),
+            TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)),
+            TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text {
+                text: String::new(),
+            })),
         }
     }
 
@@ -172,7 +159,7 @@ impl<S: State> RealDom<S> {
         for template in mutations.templates {
             let mut template_root_ids = Vec::new();
             for root in template.roots {
-                let id = self.create_template_node(root, &mut nodes_updated);
+                let id = self.create_template_node(root);
                 template_root_ids.push(id);
             }
             self.templates
@@ -283,26 +270,7 @@ impl<S: State> RealDom<S> {
                                 namespace: ns.map(|s| s.to_string()),
                                 volatile: false,
                             },
-                            crate::node::OwnedAttributeValue::Text(value.to_string()),
-                        );
-                        mark_dirty(
-                            node_id,
-                            NodeMask::new_with_attrs(AttributeMask::single(name)),
-                            &mut nodes_updated,
-                        );
-                    }
-                }
-                SetBoolAttribute { name, value, id } => {
-                    let node_id = self.element_to_node_id(id);
-                    let node = self.tree.get_mut(node_id).unwrap();
-                    if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type {
-                        attributes.insert(
-                            OwnedAttributeDiscription {
-                                name: name.to_string(),
-                                namespace: None,
-                                volatile: false,
-                            },
-                            crate::node::OwnedAttributeValue::Bool(value),
+                            OwnedAttributeValue::from(value),
                         );
                         mark_dirty(
                             node_id,

+ 3 - 3
packages/rsx/src/element.rs

@@ -211,7 +211,7 @@ pub enum ElementAttr {
 }
 
 impl ElementAttr {
-    pub fn flart(&self) -> Span {
+    pub fn start(&self) -> Span {
         match self {
             ElementAttr::AttrText { name, .. } => name.span(),
             ElementAttr::AttrExpression { name, .. } => name.span(),
@@ -265,7 +265,7 @@ impl ToTokens for ElementAttrNamed {
             ElementAttr::CustomAttrText { name, value } => {
                 quote! {
                     __cx.attr(
-                        dioxus_elements::#el_name::#name.0,
+                        #name,
                         #value,
                         None,
                         false
@@ -275,7 +275,7 @@ impl ToTokens for ElementAttrNamed {
             ElementAttr::CustomAttrExpression { name, value } => {
                 quote! {
                     __cx.attr(
-                        dioxus_elements::#el_name::#name.0,
+                        #name,
                         #value,
                         None,
                         false

+ 1 - 1
packages/tui/src/focus.rs

@@ -79,7 +79,7 @@ impl NodeDepState for Focus {
                 if let Some(index) = a
                     .value
                     .as_int()
-                    .or_else(|| a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
+                    .or_else(|| a.value.as_text().and_then(|v| v.parse::<i64>().ok()))
                 {
                     match index.cmp(&0) {
                         Ordering::Less => FocusLevel::Unfocusable,

+ 15 - 4
packages/web/src/dom.rs

@@ -75,10 +75,21 @@ impl WebsysDom {
                     value,
                     id,
                     ns,
-                } => i.SetAttribute(id.0 as u32, name, value.into(), ns),
-                SetBoolAttribute { name, value, id } => {
-                    i.SetBoolAttribute(id.0 as u32, name, value)
-                }
+                } => match value {
+                    dioxus_core::AttributeValue::Text(txt) => {
+                        i.SetAttribute(id.0 as u32, name, txt.into(), ns)
+                    }
+                    dioxus_core::AttributeValue::Float(f) => {
+                        i.SetAttribute(id.0 as u32, name, f.into(), ns)
+                    }
+                    dioxus_core::AttributeValue::Int(n) => {
+                        i.SetAttribute(id.0 as u32, name, n.into(), ns)
+                    }
+                    dioxus_core::AttributeValue::Bool(b) => {
+                        i.SetBoolAttribute(id.0 as u32, name, b)
+                    }
+                    _ => unreachable!(),
+                },
                 SetText { value, id } => i.SetText(id.0 as u32, value.into()),
                 NewEventListener { name, id, .. } => {
                     self.interpreter.NewEventListener(

+ 5 - 3
packages/web/src/lib.rs

@@ -187,10 +187,12 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
 
     // if should_hydrate {
     // } else {
-    let edits = dom.rebuild();
+    {
+        let edits = dom.rebuild();
 
-    websys_dom.load_templates(&edits.templates);
-    websys_dom.apply_edits(edits.edits);
+        websys_dom.load_templates(&edits.templates);
+        websys_dom.apply_edits(edits.edits);
+    }
 
     // the mutations come back with nothing - we need to actually mount them
     websys_dom.mount();