dioxus_core/
nodes.rs

1use crate::{
2    any_props::BoxedAnyProps,
3    arena::ElementId,
4    events::ListenerCallback,
5    innerlude::{ElementRef, MountId, ScopeState, VProps},
6    properties::ComponentFunction,
7    Element, Event, Properties, RenderError, ScopeId, VirtualDom,
8};
9use dioxus_core_types::DioxusFormattable;
10use std::ops::Deref;
11use std::rc::Rc;
12use std::vec;
13use std::{
14    any::{Any, TypeId},
15    cell::Cell,
16    fmt::{Arguments, Debug},
17};
18
19/// The information about the
20#[derive(Debug)]
21pub(crate) struct VNodeMount {
22    /// The parent of this node
23    pub parent: Option<ElementRef>,
24
25    /// A back link to the original node
26    pub node: VNode,
27
28    /// The IDs for the roots of this template - to be used when moving the template around and removing it from
29    /// the actual Dom
30    pub root_ids: Box<[ElementId]>,
31
32    /// The element in the DOM that each attribute is mounted to
33    pub(crate) mounted_attributes: Box<[ElementId]>,
34
35    /// For components: This is the ScopeId the component is mounted to
36    /// For other dynamic nodes: This is element in the DOM that each dynamic node is mounted to
37    pub(crate) mounted_dynamic_nodes: Box<[usize]>,
38}
39
40/// A reference to a template along with any context needed to hydrate it
41///
42/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
43/// static parts of the template.
44#[derive(Debug)]
45pub struct VNodeInner {
46    /// The key given to the root of this template.
47    ///
48    /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
49    pub key: Option<String>,
50
51    /// The static nodes and static descriptor of the template
52    pub template: Template,
53
54    /// The dynamic nodes in the template
55    pub dynamic_nodes: Box<[DynamicNode]>,
56
57    /// The dynamic attribute slots in the template
58    ///
59    /// This is a list of positions in the template where dynamic attributes can be inserted.
60    ///
61    /// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
62    ///
63    /// For example:
64    /// ```rust
65    /// # use dioxus::prelude::*;
66    /// let class = "my-class";
67    /// let attrs = vec![];
68    /// let color = "red";
69    ///
70    /// rsx! {
71    ///     div {
72    ///         class: "{class}",
73    ///         ..attrs,
74    ///         p {
75    ///             color: "{color}",
76    ///         }
77    ///     }
78    /// };
79    /// ```
80    ///
81    /// Would be represented as:
82    /// ```text
83    /// [
84    ///     [class, every attribute in attrs sorted by name], // Slot 0 in the template
85    ///     [color], // Slot 1 in the template
86    /// ]
87    /// ```
88    pub dynamic_attrs: Box<[Box<[Attribute]>]>,
89}
90
91/// A reference to a template along with any context needed to hydrate it
92///
93/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
94/// static parts of the template.
95#[derive(Debug, Clone)]
96pub struct VNode {
97    vnode: Rc<VNodeInner>,
98
99    /// The mount information for this template
100    pub(crate) mount: Cell<MountId>,
101}
102
103impl AsRef<VNode> for Element {
104    fn as_ref(&self) -> &VNode {
105        match self {
106            Element::Ok(node) => node,
107            Element::Err(RenderError::Aborted(err)) => &err.render,
108            Element::Err(RenderError::Suspended(fut)) => &fut.placeholder,
109        }
110    }
111}
112
113impl From<&Element> for VNode {
114    fn from(val: &Element) -> Self {
115        AsRef::as_ref(val).clone()
116    }
117}
118
119impl From<Element> for VNode {
120    fn from(val: Element) -> Self {
121        match val {
122            Element::Ok(node) => node,
123            Element::Err(RenderError::Aborted(err)) => err.render,
124            Element::Err(RenderError::Suspended(fut)) => fut.placeholder,
125        }
126    }
127}
128
129/// A tiny helper trait to get the vnode for a Element
130pub(crate) trait AsVNode {
131    /// Get the vnode for this element
132    fn as_vnode(&self) -> &VNode;
133
134    /// Create a deep clone of this VNode
135    fn deep_clone(&self) -> Self;
136}
137
138impl AsVNode for Element {
139    fn as_vnode(&self) -> &VNode {
140        AsRef::as_ref(self)
141    }
142
143    fn deep_clone(&self) -> Self {
144        match self {
145            Ok(node) => Ok(node.deep_clone()),
146            Err(RenderError::Aborted(err)) => Err(RenderError::Aborted(err.deep_clone())),
147            Err(RenderError::Suspended(fut)) => Err(RenderError::Suspended(fut.deep_clone())),
148        }
149    }
150}
151
152impl Default for VNode {
153    fn default() -> Self {
154        Self::placeholder()
155    }
156}
157
158impl PartialEq for VNode {
159    fn eq(&self, other: &Self) -> bool {
160        Rc::ptr_eq(&self.vnode, &other.vnode)
161    }
162}
163
164impl Deref for VNode {
165    type Target = VNodeInner;
166
167    fn deref(&self) -> &Self::Target {
168        &self.vnode
169    }
170}
171
172impl VNode {
173    /// Create a template with no nodes that will be skipped over during diffing
174    pub fn empty() -> Element {
175        Ok(Self::default())
176    }
177
178    /// Create a template with a single placeholder node
179    pub fn placeholder() -> Self {
180        use std::cell::OnceCell;
181        // We can reuse all placeholders across the same thread to save memory
182        thread_local! {
183            static PLACEHOLDER_VNODE: OnceCell<Rc<VNodeInner>> = const { OnceCell::new() };
184        }
185        let vnode = PLACEHOLDER_VNODE.with(|cell| {
186            cell.get_or_init(move || {
187                Rc::new(VNodeInner {
188                    key: None,
189                    dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
190                    dynamic_attrs: Box::new([]),
191                    template: Template {
192                        roots: &[TemplateNode::Dynamic { id: 0 }],
193                        node_paths: &[&[0]],
194                        attr_paths: &[],
195                    },
196                })
197            })
198            .clone()
199        });
200        Self {
201            vnode,
202            mount: Default::default(),
203        }
204    }
205
206    /// Create a new VNode
207    pub fn new(
208        key: Option<String>,
209        template: Template,
210        dynamic_nodes: Box<[DynamicNode]>,
211        dynamic_attrs: Box<[Box<[Attribute]>]>,
212    ) -> Self {
213        Self {
214            vnode: Rc::new(VNodeInner {
215                key,
216                template,
217                dynamic_nodes,
218                dynamic_attrs,
219            }),
220            mount: Default::default(),
221        }
222    }
223
224    /// Load a dynamic root at the given index
225    ///
226    /// Returns [`None`] if the root is actually a static node (Element/Text)
227    pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> {
228        self.template.roots[idx]
229            .dynamic_id()
230            .map(|id| &self.dynamic_nodes[id])
231    }
232
233    /// Get the mounted id for a dynamic node index
234    pub fn mounted_dynamic_node(
235        &self,
236        dynamic_node_idx: usize,
237        dom: &VirtualDom,
238    ) -> Option<ElementId> {
239        let mount = self.mount.get().as_usize()?;
240
241        match &self.dynamic_nodes[dynamic_node_idx] {
242            DynamicNode::Text(_) | DynamicNode::Placeholder(_) => {
243                let mounts = dom.runtime.mounts.borrow();
244                mounts
245                    .get(mount)?
246                    .mounted_dynamic_nodes
247                    .get(dynamic_node_idx)
248                    .map(|id| ElementId(*id))
249            }
250            _ => None,
251        }
252    }
253
254    /// Get the mounted id for a root node index
255    pub fn mounted_root(&self, root_idx: usize, dom: &VirtualDom) -> Option<ElementId> {
256        let mount = self.mount.get().as_usize()?;
257
258        let mounts = dom.runtime.mounts.borrow();
259        mounts.get(mount)?.root_ids.get(root_idx).copied()
260    }
261
262    /// Get the mounted id for a dynamic attribute index
263    pub fn mounted_dynamic_attribute(
264        &self,
265        dynamic_attribute_idx: usize,
266        dom: &VirtualDom,
267    ) -> Option<ElementId> {
268        let mount = self.mount.get().as_usize()?;
269
270        let mounts = dom.runtime.mounts.borrow();
271        mounts
272            .get(mount)?
273            .mounted_attributes
274            .get(dynamic_attribute_idx)
275            .copied()
276    }
277
278    /// Create a deep clone of this VNode
279    pub(crate) fn deep_clone(&self) -> Self {
280        Self {
281            vnode: Rc::new(VNodeInner {
282                key: self.vnode.key.clone(),
283                template: self.vnode.template,
284                dynamic_nodes: self
285                    .vnode
286                    .dynamic_nodes
287                    .iter()
288                    .map(|node| match node {
289                        DynamicNode::Fragment(nodes) => DynamicNode::Fragment(
290                            nodes.iter().map(|node| node.deep_clone()).collect(),
291                        ),
292                        other => other.clone(),
293                    })
294                    .collect(),
295                dynamic_attrs: self
296                    .vnode
297                    .dynamic_attrs
298                    .iter()
299                    .map(|attr| {
300                        attr.iter()
301                            .map(|attribute| attribute.deep_clone())
302                            .collect()
303                    })
304                    .collect(),
305            }),
306            mount: Default::default(),
307        }
308    }
309}
310
311type StaticStr = &'static str;
312type StaticPathArray = &'static [&'static [u8]];
313type StaticTemplateArray = &'static [TemplateNode];
314type StaticTemplateAttributeArray = &'static [TemplateAttribute];
315
316/// A static layout of a UI tree that describes a set of dynamic and static nodes.
317///
318/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
319/// dynamic node. This struct can be created at compile time. It promises that its pointer is unique, allow Dioxus to use
320/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
321#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
322#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
323pub struct Template {
324    /// The list of template nodes that make up the template
325    ///
326    /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
327    #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
328    pub roots: StaticTemplateArray,
329
330    /// The paths of each node relative to the root of the template.
331    ///
332    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
333    /// topmost element, not the `roots` field.
334    #[cfg_attr(
335        feature = "serialize",
336        serde(deserialize_with = "deserialize_bytes_leaky")
337    )]
338    pub node_paths: StaticPathArray,
339
340    /// The paths of each dynamic attribute relative to the root of the template
341    ///
342    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
343    /// topmost element, not the `roots` field.
344    #[cfg_attr(
345        feature = "serialize",
346        serde(deserialize_with = "deserialize_bytes_leaky", bound = "")
347    )]
348    pub attr_paths: StaticPathArray,
349}
350
351// Are identical static items merged in the current build. Rust doesn't have a cfg(merge_statics) attribute
352// so we have to check this manually
353#[allow(unpredictable_function_pointer_comparisons)] // This attribute should be removed once MSRV is 1.85 or greater and the below change is made
354fn static_items_merged() -> bool {
355    fn a() {}
356    fn b() {}
357    a as fn() == b as fn()
358    // std::ptr::fn_addr_eq(a as fn(), b as fn()) <<<<---- This should replace the a as fn() === b as fn() once the MSRV is 1.85 or greater
359}
360
361impl std::hash::Hash for Template {
362    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
363        // If identical static items are merged, we can compare templates by pointer
364        if static_items_merged() {
365            std::ptr::hash(self.roots as *const _, state);
366            std::ptr::hash(self.node_paths as *const _, state);
367            std::ptr::hash(self.attr_paths as *const _, state);
368        }
369        // Otherwise, we hash by value
370        else {
371            self.roots.hash(state);
372            self.node_paths.hash(state);
373            self.attr_paths.hash(state);
374        }
375    }
376}
377
378impl PartialEq for Template {
379    fn eq(&self, other: &Self) -> bool {
380        // If identical static items are merged, we can compare templates by pointer
381        if static_items_merged() {
382            std::ptr::eq(self.roots as *const _, other.roots as *const _)
383                && std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
384                && std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
385        }
386        // Otherwise, we compare by value
387        else {
388            self.roots == other.roots
389                && self.node_paths == other.node_paths
390                && self.attr_paths == other.attr_paths
391        }
392    }
393}
394
395#[cfg(feature = "serialize")]
396pub(crate) fn deserialize_string_leaky<'a, 'de, D>(
397    deserializer: D,
398) -> Result<&'static str, D::Error>
399where
400    D: serde::Deserializer<'de>,
401{
402    use serde::Deserialize;
403
404    let deserialized = String::deserialize(deserializer)?;
405    Ok(&*Box::leak(deserialized.into_boxed_str()))
406}
407
408#[cfg(feature = "serialize")]
409fn deserialize_bytes_leaky<'a, 'de, D>(
410    deserializer: D,
411) -> Result<&'static [&'static [u8]], D::Error>
412where
413    D: serde::Deserializer<'de>,
414{
415    use serde::Deserialize;
416
417    let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
418    let deserialized = deserialized
419        .into_iter()
420        .map(|v| &*Box::leak(v.into_boxed_slice()))
421        .collect::<Vec<_>>();
422    Ok(&*Box::leak(deserialized.into_boxed_slice()))
423}
424
425#[cfg(feature = "serialize")]
426pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'static [T], D::Error>
427where
428    T: serde::Deserialize<'de>,
429    D: serde::Deserializer<'de>,
430{
431    use serde::Deserialize;
432
433    let deserialized = Box::<[T]>::deserialize(deserializer)?;
434    Ok(&*Box::leak(deserialized))
435}
436
437#[cfg(feature = "serialize")]
438pub(crate) fn deserialize_option_leaky<'a, 'de, D>(
439    deserializer: D,
440) -> Result<Option<&'static str>, D::Error>
441where
442    D: serde::Deserializer<'de>,
443{
444    use serde::Deserialize;
445
446    let deserialized = Option::<String>::deserialize(deserializer)?;
447    Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str())))
448}
449
450impl Template {
451    /// Is this template worth caching at all, since it's completely runtime?
452    ///
453    /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.
454    pub fn is_completely_dynamic(&self) -> bool {
455        use TemplateNode::*;
456        self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
457    }
458}
459
460/// A statically known node in a layout.
461///
462/// This can be created at compile time, saving the VirtualDom time when diffing the tree
463#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
464#[cfg_attr(
465    feature = "serialize",
466    derive(serde::Serialize, serde::Deserialize),
467    serde(tag = "type")
468)]
469pub enum TemplateNode {
470    /// An statically known element in the dom.
471    ///
472    /// In HTML this would be something like `<div id="123"> </div>`
473    Element {
474        /// The name of the element
475        ///
476        /// IE for a div, it would be the string "div"
477        #[cfg_attr(
478            feature = "serialize",
479            serde(deserialize_with = "deserialize_string_leaky")
480        )]
481        tag: StaticStr,
482
483        /// The namespace of the element
484        ///
485        /// In HTML, this would be a valid URI that defines a namespace for all elements below it
486        /// SVG is an example of this namespace
487        #[cfg_attr(
488            feature = "serialize",
489            serde(deserialize_with = "deserialize_option_leaky")
490        )]
491        namespace: Option<StaticStr>,
492
493        /// A list of possibly dynamic attributes for this element
494        ///
495        /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
496        #[cfg_attr(
497            feature = "serialize",
498            serde(deserialize_with = "deserialize_leaky", bound = "")
499        )]
500        attrs: StaticTemplateAttributeArray,
501
502        /// A list of template nodes that define another set of template nodes
503        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
504        children: StaticTemplateArray,
505    },
506
507    /// This template node is just a piece of static text
508    Text {
509        /// The actual text
510        #[cfg_attr(
511            feature = "serialize",
512            serde(deserialize_with = "deserialize_string_leaky", bound = "")
513        )]
514        text: StaticStr,
515    },
516
517    /// This template node is unknown, and needs to be created at runtime.
518    Dynamic {
519        /// The index of the dynamic node in the VNode's dynamic_nodes list
520        id: usize,
521    },
522}
523
524impl TemplateNode {
525    /// Try to load the dynamic node at the given index
526    pub fn dynamic_id(&self) -> Option<usize> {
527        use TemplateNode::*;
528        match self {
529            Dynamic { id } => Some(*id),
530            _ => None,
531        }
532    }
533}
534
535/// A node created at runtime
536///
537/// This node's index in the DynamicNode list on VNode should match its respective `Dynamic` index
538#[derive(Debug, Clone)]
539pub enum DynamicNode {
540    /// A component node
541    ///
542    /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
543    /// assigned scope are dynamic.
544    ///
545    /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
546    /// the render function at runtime
547    Component(VComponent),
548
549    /// A text node
550    Text(VText),
551
552    /// A placeholder
553    ///
554    /// Used by suspense when a node isn't ready and by fragments that don't render anything
555    ///
556    /// In code, this is just an ElementId whose initial value is set to 0 upon creation
557    Placeholder(VPlaceholder),
558
559    /// A list of VNodes.
560    ///
561    /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
562    /// or iterators.
563    Fragment(Vec<VNode>),
564}
565
566impl DynamicNode {
567    /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`]
568    pub fn make_node<'c, I>(into: impl IntoDynNode<I> + 'c) -> DynamicNode {
569        into.into_dyn_node()
570    }
571}
572
573impl Default for DynamicNode {
574    fn default() -> Self {
575        Self::Placeholder(Default::default())
576    }
577}
578
579/// An instance of a child component
580pub struct VComponent {
581    /// The name of this component
582    pub name: &'static str,
583
584    /// The raw pointer to the render function
585    pub(crate) render_fn: usize,
586
587    /// The props for this component
588    pub(crate) props: BoxedAnyProps,
589}
590
591impl Clone for VComponent {
592    fn clone(&self) -> Self {
593        Self {
594            name: self.name,
595            props: self.props.duplicate(),
596            render_fn: self.render_fn,
597        }
598    }
599}
600
601impl VComponent {
602    /// Create a new [`VComponent`] variant
603    pub fn new<P, M: 'static>(
604        component: impl ComponentFunction<P, M>,
605        props: P,
606        fn_name: &'static str,
607    ) -> Self
608    where
609        P: Properties + 'static,
610    {
611        let render_fn = component.fn_ptr();
612        let props = Box::new(VProps::new(
613            component,
614            <P as Properties>::memoize,
615            props,
616            fn_name,
617        ));
618
619        VComponent {
620            render_fn,
621            name: fn_name,
622            props,
623        }
624    }
625
626    /// Get the [`ScopeId`] this node is mounted to if it's mounted
627    ///
628    /// This is useful for rendering nodes outside of the VirtualDom, such as in SSR
629    ///
630    /// Returns [`None`] if the node is not mounted
631    pub fn mounted_scope_id(
632        &self,
633        dynamic_node_index: usize,
634        vnode: &VNode,
635        dom: &VirtualDom,
636    ) -> Option<ScopeId> {
637        let mount = vnode.mount.get().as_usize()?;
638
639        let mounts = dom.runtime.mounts.borrow();
640        let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
641
642        Some(ScopeId(scope_id))
643    }
644
645    /// Get the scope this node is mounted to if it's mounted
646    ///
647    /// This is useful for rendering nodes outside of the VirtualDom, such as in SSR
648    ///
649    /// Returns [`None`] if the node is not mounted
650    pub fn mounted_scope<'a>(
651        &self,
652        dynamic_node_index: usize,
653        vnode: &VNode,
654        dom: &'a VirtualDom,
655    ) -> Option<&'a ScopeState> {
656        let mount = vnode.mount.get().as_usize()?;
657
658        let mounts = dom.runtime.mounts.borrow();
659        let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
660
661        dom.scopes.get(scope_id)
662    }
663}
664
665impl std::fmt::Debug for VComponent {
666    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667        f.debug_struct("VComponent")
668            .field("name", &self.name)
669            .finish()
670    }
671}
672
673/// A text node
674#[derive(Clone, Debug)]
675pub struct VText {
676    /// The actual text itself
677    pub value: String,
678}
679
680impl VText {
681    /// Create a new VText
682    pub fn new(value: impl ToString) -> Self {
683        Self {
684            value: value.to_string(),
685        }
686    }
687}
688
689impl From<Arguments<'_>> for VText {
690    fn from(args: Arguments) -> Self {
691        Self::new(args.to_string())
692    }
693}
694
695/// A placeholder node, used by suspense and fragments
696#[derive(Clone, Debug, Default)]
697#[non_exhaustive]
698pub struct VPlaceholder {}
699
700/// An attribute of the TemplateNode, created at compile time
701#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
702#[cfg_attr(
703    feature = "serialize",
704    derive(serde::Serialize, serde::Deserialize),
705    serde(tag = "type")
706)]
707pub enum TemplateAttribute {
708    /// This attribute is entirely known at compile time, enabling
709    Static {
710        /// The name of this attribute.
711        ///
712        /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
713        #[cfg_attr(
714            feature = "serialize",
715            serde(deserialize_with = "deserialize_string_leaky", bound = "")
716        )]
717        name: StaticStr,
718
719        /// The value of this attribute, known at compile time
720        ///
721        /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
722        #[cfg_attr(
723            feature = "serialize",
724            serde(deserialize_with = "deserialize_string_leaky", bound = "")
725        )]
726        value: StaticStr,
727
728        /// The namespace of this attribute. Does not exist in the HTML spec
729        #[cfg_attr(
730            feature = "serialize",
731            serde(deserialize_with = "deserialize_option_leaky", bound = "")
732        )]
733        namespace: Option<StaticStr>,
734    },
735
736    /// The attribute in this position is actually determined dynamically at runtime
737    ///
738    /// This is the index into the dynamic_attributes field on the container VNode
739    Dynamic {
740        /// The index
741        id: usize,
742    },
743}
744
745/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
746#[derive(Debug, Clone, PartialEq)]
747pub struct Attribute {
748    /// The name of the attribute.
749    pub name: &'static str,
750
751    /// The value of the attribute
752    pub value: AttributeValue,
753
754    /// The namespace of the attribute.
755    ///
756    /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
757    pub namespace: Option<&'static str>,
758
759    /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
760    pub volatile: bool,
761}
762
763impl Attribute {
764    /// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
765    ///
766    /// "Volatile" refers to whether or not Dioxus should always override the value. This helps prevent the UI in
767    /// some renderers stay in sync with the VirtualDom's understanding of the world
768    pub fn new<T>(
769        name: &'static str,
770        value: impl IntoAttributeValue<T>,
771        namespace: Option<&'static str>,
772        volatile: bool,
773    ) -> Attribute {
774        Attribute {
775            name,
776            namespace,
777            volatile,
778            value: value.into_value(),
779        }
780    }
781
782    /// Create a new deep clone of this attribute
783    pub(crate) fn deep_clone(&self) -> Self {
784        Attribute {
785            name: self.name,
786            namespace: self.namespace,
787            volatile: self.volatile,
788            value: self.value.clone(),
789        }
790    }
791}
792
793/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
794///
795/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
796/// variant.
797#[derive(Clone)]
798pub enum AttributeValue {
799    /// Text attribute
800    Text(String),
801
802    /// A float
803    Float(f64),
804
805    /// Signed integer
806    Int(i64),
807
808    /// Boolean
809    Bool(bool),
810
811    /// A listener, like "onclick"
812    Listener(ListenerCallback),
813
814    /// An arbitrary value that implements PartialEq and is static
815    Any(Rc<dyn AnyValue>),
816
817    /// A "none" value, resulting in the removal of an attribute from the dom
818    None,
819}
820
821impl AttributeValue {
822    /// Create a new [`AttributeValue`] with the listener variant from a callback
823    ///
824    /// The callback must be confined to the lifetime of the ScopeState
825    pub fn listener<T: 'static>(callback: impl FnMut(Event<T>) + 'static) -> AttributeValue {
826        AttributeValue::Listener(ListenerCallback::new(callback).erase())
827    }
828
829    /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
830    pub fn any_value<T: AnyValue>(value: T) -> AttributeValue {
831        AttributeValue::Any(Rc::new(value))
832    }
833}
834
835impl std::fmt::Debug for AttributeValue {
836    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
837        match self {
838            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
839            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
840            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
841            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
842            Self::Listener(_) => f.debug_tuple("Listener").finish(),
843            Self::Any(_) => f.debug_tuple("Any").finish(),
844            Self::None => write!(f, "None"),
845        }
846    }
847}
848
849impl PartialEq for AttributeValue {
850    fn eq(&self, other: &Self) -> bool {
851        match (self, other) {
852            (Self::Text(l0), Self::Text(r0)) => l0 == r0,
853            (Self::Float(l0), Self::Float(r0)) => l0 == r0,
854            (Self::Int(l0), Self::Int(r0)) => l0 == r0,
855            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
856            (Self::Listener(l0), Self::Listener(r0)) => l0 == r0,
857            (Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()),
858            (Self::None, Self::None) => true,
859            _ => false,
860        }
861    }
862}
863
864#[doc(hidden)]
865pub trait AnyValue: 'static {
866    fn any_cmp(&self, other: &dyn AnyValue) -> bool;
867    fn as_any(&self) -> &dyn Any;
868    fn type_id(&self) -> TypeId {
869        self.as_any().type_id()
870    }
871}
872
873impl<T: Any + PartialEq + 'static> AnyValue for T {
874    fn any_cmp(&self, other: &dyn AnyValue) -> bool {
875        if let Some(other) = other.as_any().downcast_ref() {
876            self == other
877        } else {
878            false
879        }
880    }
881
882    fn as_any(&self) -> &dyn Any {
883        self
884    }
885}
886
887/// A trait that allows various items to be converted into a dynamic node for the rsx macro
888pub trait IntoDynNode<A = ()> {
889    /// Consume this item and produce a DynamicNode
890    fn into_dyn_node(self) -> DynamicNode;
891}
892
893impl IntoDynNode for () {
894    fn into_dyn_node(self) -> DynamicNode {
895        DynamicNode::default()
896    }
897}
898impl IntoDynNode for VNode {
899    fn into_dyn_node(self) -> DynamicNode {
900        DynamicNode::Fragment(vec![self])
901    }
902}
903impl IntoDynNode for DynamicNode {
904    fn into_dyn_node(self) -> DynamicNode {
905        self
906    }
907}
908impl<T: IntoDynNode> IntoDynNode for Option<T> {
909    fn into_dyn_node(self) -> DynamicNode {
910        match self {
911            Some(val) => val.into_dyn_node(),
912            None => DynamicNode::default(),
913        }
914    }
915}
916impl IntoDynNode for &Element {
917    fn into_dyn_node(self) -> DynamicNode {
918        match self.as_ref() {
919            Ok(val) => val.into_dyn_node(),
920            _ => DynamicNode::default(),
921        }
922    }
923}
924impl IntoDynNode for Element {
925    fn into_dyn_node(self) -> DynamicNode {
926        match self {
927            Ok(val) => val.into_dyn_node(),
928            _ => DynamicNode::default(),
929        }
930    }
931}
932impl IntoDynNode for &Option<VNode> {
933    fn into_dyn_node(self) -> DynamicNode {
934        match self.as_ref() {
935            Some(val) => val.clone().into_dyn_node(),
936            _ => DynamicNode::default(),
937        }
938    }
939}
940impl IntoDynNode for &str {
941    fn into_dyn_node(self) -> DynamicNode {
942        DynamicNode::Text(VText {
943            value: self.to_string(),
944        })
945    }
946}
947impl IntoDynNode for String {
948    fn into_dyn_node(self) -> DynamicNode {
949        DynamicNode::Text(VText { value: self })
950    }
951}
952impl IntoDynNode for Arguments<'_> {
953    fn into_dyn_node(self) -> DynamicNode {
954        DynamicNode::Text(VText {
955            value: self.to_string(),
956        })
957    }
958}
959impl IntoDynNode for &VNode {
960    fn into_dyn_node(self) -> DynamicNode {
961        DynamicNode::Fragment(vec![self.clone()])
962    }
963}
964
965pub trait IntoVNode {
966    fn into_vnode(self) -> VNode;
967}
968impl IntoVNode for VNode {
969    fn into_vnode(self) -> VNode {
970        self
971    }
972}
973impl IntoVNode for &VNode {
974    fn into_vnode(self) -> VNode {
975        self.clone()
976    }
977}
978impl IntoVNode for Element {
979    fn into_vnode(self) -> VNode {
980        match self {
981            Ok(val) => val.into_vnode(),
982            _ => VNode::empty().unwrap(),
983        }
984    }
985}
986impl IntoVNode for &Element {
987    fn into_vnode(self) -> VNode {
988        match self {
989            Ok(val) => val.into_vnode(),
990            _ => VNode::empty().unwrap(),
991        }
992    }
993}
994impl IntoVNode for Option<VNode> {
995    fn into_vnode(self) -> VNode {
996        match self {
997            Some(val) => val.into_vnode(),
998            _ => VNode::empty().unwrap(),
999        }
1000    }
1001}
1002impl IntoVNode for &Option<VNode> {
1003    fn into_vnode(self) -> VNode {
1004        match self.as_ref() {
1005            Some(val) => val.clone().into_vnode(),
1006            _ => VNode::empty().unwrap(),
1007        }
1008    }
1009}
1010impl IntoVNode for Option<Element> {
1011    fn into_vnode(self) -> VNode {
1012        match self {
1013            Some(val) => val.into_vnode(),
1014            _ => VNode::empty().unwrap(),
1015        }
1016    }
1017}
1018impl IntoVNode for &Option<Element> {
1019    fn into_vnode(self) -> VNode {
1020        match self.as_ref() {
1021            Some(val) => val.clone().into_vnode(),
1022            _ => VNode::empty().unwrap(),
1023        }
1024    }
1025}
1026
1027// Note that we're using the E as a generic but this is never crafted anyways.
1028pub struct FromNodeIterator;
1029impl<T, I> IntoDynNode<FromNodeIterator> for T
1030where
1031    T: Iterator<Item = I>,
1032    I: IntoVNode,
1033{
1034    fn into_dyn_node(self) -> DynamicNode {
1035        let children: Vec<_> = self.into_iter().map(|node| node.into_vnode()).collect();
1036
1037        if children.is_empty() {
1038            DynamicNode::default()
1039        } else {
1040            DynamicNode::Fragment(children)
1041        }
1042    }
1043}
1044
1045/// A value that can be converted into an attribute value
1046pub trait IntoAttributeValue<T = ()> {
1047    /// Convert into an attribute value
1048    fn into_value(self) -> AttributeValue;
1049}
1050
1051impl IntoAttributeValue for AttributeValue {
1052    fn into_value(self) -> AttributeValue {
1053        self
1054    }
1055}
1056
1057impl IntoAttributeValue for &str {
1058    fn into_value(self) -> AttributeValue {
1059        AttributeValue::Text(self.to_string())
1060    }
1061}
1062
1063impl IntoAttributeValue for String {
1064    fn into_value(self) -> AttributeValue {
1065        AttributeValue::Text(self)
1066    }
1067}
1068
1069impl IntoAttributeValue for f32 {
1070    fn into_value(self) -> AttributeValue {
1071        AttributeValue::Float(self as _)
1072    }
1073}
1074impl IntoAttributeValue for f64 {
1075    fn into_value(self) -> AttributeValue {
1076        AttributeValue::Float(self)
1077    }
1078}
1079
1080impl IntoAttributeValue for i8 {
1081    fn into_value(self) -> AttributeValue {
1082        AttributeValue::Int(self as _)
1083    }
1084}
1085impl IntoAttributeValue for i16 {
1086    fn into_value(self) -> AttributeValue {
1087        AttributeValue::Int(self as _)
1088    }
1089}
1090impl IntoAttributeValue for i32 {
1091    fn into_value(self) -> AttributeValue {
1092        AttributeValue::Int(self as _)
1093    }
1094}
1095impl IntoAttributeValue for i64 {
1096    fn into_value(self) -> AttributeValue {
1097        AttributeValue::Int(self)
1098    }
1099}
1100impl IntoAttributeValue for isize {
1101    fn into_value(self) -> AttributeValue {
1102        AttributeValue::Int(self as _)
1103    }
1104}
1105impl IntoAttributeValue for i128 {
1106    fn into_value(self) -> AttributeValue {
1107        AttributeValue::Int(self as _)
1108    }
1109}
1110
1111impl IntoAttributeValue for u8 {
1112    fn into_value(self) -> AttributeValue {
1113        AttributeValue::Int(self as _)
1114    }
1115}
1116impl IntoAttributeValue for u16 {
1117    fn into_value(self) -> AttributeValue {
1118        AttributeValue::Int(self as _)
1119    }
1120}
1121impl IntoAttributeValue for u32 {
1122    fn into_value(self) -> AttributeValue {
1123        AttributeValue::Int(self as _)
1124    }
1125}
1126impl IntoAttributeValue for u64 {
1127    fn into_value(self) -> AttributeValue {
1128        AttributeValue::Int(self as _)
1129    }
1130}
1131impl IntoAttributeValue for usize {
1132    fn into_value(self) -> AttributeValue {
1133        AttributeValue::Int(self as _)
1134    }
1135}
1136impl IntoAttributeValue for u128 {
1137    fn into_value(self) -> AttributeValue {
1138        AttributeValue::Int(self as _)
1139    }
1140}
1141
1142impl IntoAttributeValue for bool {
1143    fn into_value(self) -> AttributeValue {
1144        AttributeValue::Bool(self)
1145    }
1146}
1147
1148impl IntoAttributeValue for Arguments<'_> {
1149    fn into_value(self) -> AttributeValue {
1150        AttributeValue::Text(self.to_string())
1151    }
1152}
1153
1154impl IntoAttributeValue for Rc<dyn AnyValue> {
1155    fn into_value(self) -> AttributeValue {
1156        AttributeValue::Any(self)
1157    }
1158}
1159
1160impl<T> IntoAttributeValue for ListenerCallback<T> {
1161    fn into_value(self) -> AttributeValue {
1162        AttributeValue::Listener(self.erase())
1163    }
1164}
1165
1166impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
1167    fn into_value(self) -> AttributeValue {
1168        match self {
1169            Some(val) => val.into_value(),
1170            None => AttributeValue::None,
1171        }
1172    }
1173}
1174
1175pub struct AnyFmtMarker;
1176impl<T> IntoAttributeValue<AnyFmtMarker> for T
1177where
1178    T: DioxusFormattable,
1179{
1180    fn into_value(self) -> AttributeValue {
1181        AttributeValue::Text(self.format().to_string())
1182    }
1183}
1184
1185/// A trait for anything that has a dynamic list of attributes
1186pub trait HasAttributes {
1187    /// Push an attribute onto the list of attributes
1188    fn push_attribute<T>(
1189        self,
1190        name: &'static str,
1191        ns: Option<&'static str>,
1192        attr: impl IntoAttributeValue<T>,
1193        volatile: bool,
1194    ) -> Self;
1195}