dioxus_core/
nodes.rs

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