Skip to main content

dioxus_core/
nodes.rs

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