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 name is unique, allow Dioxus to use
345/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
346///
347/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
348/// ways, with the suggested approach being the unique code location (file, line, col, etc).
349#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
350#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
351pub struct Template {
352    /// The list of template nodes that make up the template
353    ///
354    /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
355    #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
356    pub roots: StaticTemplateArray,
357
358    /// The paths of each node relative to the root of the template.
359    ///
360    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
361    /// topmost element, not the `roots` field.
362    #[cfg_attr(
363        feature = "serialize",
364        serde(deserialize_with = "deserialize_bytes_leaky")
365    )]
366    pub node_paths: StaticPathArray,
367
368    /// The paths of each dynamic attribute relative to the root of the template
369    ///
370    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
371    /// topmost element, not the `roots` field.
372    #[cfg_attr(
373        feature = "serialize",
374        serde(deserialize_with = "deserialize_bytes_leaky", bound = "")
375    )]
376    pub attr_paths: StaticPathArray,
377}
378
379// Are identical static items merged in the current build. Rust doesn't have a cfg(merge_statics) attribute
380// so we have to check this manually
381#[allow(unpredictable_function_pointer_comparisons)] // This attribute should be removed once MSRV is 1.85 or greater and the below change is made
382fn static_items_merged() -> bool {
383    fn a() {}
384    fn b() {}
385    a as fn() == b as fn()
386    // 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
387}
388
389impl std::hash::Hash for Template {
390    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
391        // If identical static items are merged, we can compare templates by pointer
392        if static_items_merged() {
393            std::ptr::hash(self.roots as *const _, state);
394            std::ptr::hash(self.node_paths as *const _, state);
395            std::ptr::hash(self.attr_paths as *const _, state);
396        }
397        // Otherwise, we hash by value
398        else {
399            self.roots.hash(state);
400            self.node_paths.hash(state);
401            self.attr_paths.hash(state);
402        }
403    }
404}
405
406impl PartialEq for Template {
407    fn eq(&self, other: &Self) -> bool {
408        // If identical static items are merged, we can compare templates by pointer
409        if static_items_merged() {
410            std::ptr::eq(self.roots as *const _, other.roots as *const _)
411                && std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
412                && std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
413        }
414        // Otherwise, we compare by value
415        else {
416            self.roots == other.roots
417                && self.node_paths == other.node_paths
418                && self.attr_paths == other.attr_paths
419        }
420    }
421}
422
423#[cfg(feature = "serialize")]
424pub(crate) fn deserialize_string_leaky<'a, 'de, D>(
425    deserializer: D,
426) -> Result<&'static str, D::Error>
427where
428    D: serde::Deserializer<'de>,
429{
430    use serde::Deserialize;
431
432    let deserialized = String::deserialize(deserializer)?;
433    Ok(&*Box::leak(deserialized.into_boxed_str()))
434}
435
436#[cfg(feature = "serialize")]
437fn deserialize_bytes_leaky<'a, 'de, D>(
438    deserializer: D,
439) -> Result<&'static [&'static [u8]], D::Error>
440where
441    D: serde::Deserializer<'de>,
442{
443    use serde::Deserialize;
444
445    let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
446    let deserialized = deserialized
447        .into_iter()
448        .map(|v| &*Box::leak(v.into_boxed_slice()))
449        .collect::<Vec<_>>();
450    Ok(&*Box::leak(deserialized.into_boxed_slice()))
451}
452
453#[cfg(feature = "serialize")]
454pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'static [T], D::Error>
455where
456    T: serde::Deserialize<'de>,
457    D: serde::Deserializer<'de>,
458{
459    use serde::Deserialize;
460
461    let deserialized = Box::<[T]>::deserialize(deserializer)?;
462    Ok(&*Box::leak(deserialized))
463}
464
465#[cfg(feature = "serialize")]
466pub(crate) fn deserialize_option_leaky<'a, 'de, D>(
467    deserializer: D,
468) -> Result<Option<&'static str>, D::Error>
469where
470    D: serde::Deserializer<'de>,
471{
472    use serde::Deserialize;
473
474    let deserialized = Option::<String>::deserialize(deserializer)?;
475    Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str())))
476}
477
478impl Template {
479    /// Is this template worth caching at all, since it's completely runtime?
480    ///
481    /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.
482    pub fn is_completely_dynamic(&self) -> bool {
483        use TemplateNode::*;
484        self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
485    }
486}
487
488/// A statically known node in a layout.
489///
490/// This can be created at compile time, saving the VirtualDom time when diffing the tree
491#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
492#[cfg_attr(
493    feature = "serialize",
494    derive(serde::Serialize, serde::Deserialize),
495    serde(tag = "type")
496)]
497pub enum TemplateNode {
498    /// An statically known element in the dom.
499    ///
500    /// In HTML this would be something like `<div id="123"> </div>`
501    Element {
502        /// The name of the element
503        ///
504        /// IE for a div, it would be the string "div"
505        #[cfg_attr(
506            feature = "serialize",
507            serde(deserialize_with = "deserialize_string_leaky")
508        )]
509        tag: StaticStr,
510
511        /// The namespace of the element
512        ///
513        /// In HTML, this would be a valid URI that defines a namespace for all elements below it
514        /// SVG is an example of this namespace
515        #[cfg_attr(
516            feature = "serialize",
517            serde(deserialize_with = "deserialize_option_leaky")
518        )]
519        namespace: Option<StaticStr>,
520
521        /// A list of possibly dynamic attributes for this element
522        ///
523        /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
524        #[cfg_attr(
525            feature = "serialize",
526            serde(deserialize_with = "deserialize_leaky", bound = "")
527        )]
528        attrs: StaticTemplateAttributeArray,
529
530        /// A list of template nodes that define another set of template nodes
531        #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
532        children: StaticTemplateArray,
533    },
534
535    /// This template node is just a piece of static text
536    Text {
537        /// The actual text
538        #[cfg_attr(
539            feature = "serialize",
540            serde(deserialize_with = "deserialize_string_leaky", bound = "")
541        )]
542        text: StaticStr,
543    },
544
545    /// This template node is unknown, and needs to be created at runtime.
546    Dynamic {
547        /// The index of the dynamic node in the VNode's dynamic_nodes list
548        id: usize,
549    },
550}
551
552impl TemplateNode {
553    /// Try to load the dynamic node at the given index
554    pub fn dynamic_id(&self) -> Option<usize> {
555        use TemplateNode::*;
556        match self {
557            Dynamic { id } => Some(*id),
558            _ => None,
559        }
560    }
561}
562
563/// A node created at runtime
564///
565/// This node's index in the DynamicNode list on VNode should match its respective `Dynamic` index
566#[derive(Debug, Clone)]
567pub enum DynamicNode {
568    /// A component node
569    ///
570    /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
571    /// assigned scope are dynamic.
572    ///
573    /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
574    /// the render function at runtime
575    Component(VComponent),
576
577    /// A text node
578    Text(VText),
579
580    /// A placeholder
581    ///
582    /// Used by suspense when a node isn't ready and by fragments that don't render anything
583    ///
584    /// In code, this is just an ElementId whose initial value is set to 0 upon creation
585    Placeholder(VPlaceholder),
586
587    /// A list of VNodes.
588    ///
589    /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
590    /// or iterators.
591    Fragment(Vec<VNode>),
592}
593
594impl DynamicNode {
595    /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`]
596    pub fn make_node<'c, I>(into: impl IntoDynNode<I> + 'c) -> DynamicNode {
597        into.into_dyn_node()
598    }
599}
600
601impl Default for DynamicNode {
602    fn default() -> Self {
603        Self::Placeholder(Default::default())
604    }
605}
606
607/// An instance of a child component
608pub struct VComponent {
609    /// The name of this component
610    pub name: &'static str,
611
612    /// The raw pointer to the render function
613    pub(crate) render_fn: usize,
614
615    /// The props for this component
616    pub(crate) props: BoxedAnyProps,
617}
618
619impl Clone for VComponent {
620    fn clone(&self) -> Self {
621        Self {
622            name: self.name,
623            props: self.props.duplicate(),
624            render_fn: self.render_fn,
625        }
626    }
627}
628
629impl VComponent {
630    /// Create a new [`VComponent`] variant
631    pub fn new<P, M: 'static>(
632        component: impl ComponentFunction<P, M>,
633        props: P,
634        fn_name: &'static str,
635    ) -> Self
636    where
637        P: Properties + 'static,
638    {
639        let render_fn = component.fn_ptr();
640        let props = Box::new(VProps::new(
641            component,
642            <P as Properties>::memoize,
643            props,
644            fn_name,
645        ));
646
647        VComponent {
648            render_fn,
649            name: fn_name,
650            props,
651        }
652    }
653
654    /// Get the [`ScopeId`] this node is mounted to if it's mounted
655    ///
656    /// This is useful for rendering nodes outside of the VirtualDom, such as in SSR
657    ///
658    /// Returns [`None`] if the node is not mounted
659    pub fn mounted_scope_id(
660        &self,
661        dynamic_node_index: usize,
662        vnode: &VNode,
663        dom: &VirtualDom,
664    ) -> Option<ScopeId> {
665        let mount = vnode.mount.get().as_usize()?;
666
667        let mounts = dom.runtime.mounts.borrow();
668        let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
669
670        Some(ScopeId(scope_id))
671    }
672
673    /// Get the scope this node is mounted to if it's mounted
674    ///
675    /// This is useful for rendering nodes outside of the VirtualDom, such as in SSR
676    ///
677    /// Returns [`None`] if the node is not mounted
678    pub fn mounted_scope<'a>(
679        &self,
680        dynamic_node_index: usize,
681        vnode: &VNode,
682        dom: &'a VirtualDom,
683    ) -> Option<&'a ScopeState> {
684        let mount = vnode.mount.get().as_usize()?;
685
686        let mounts = dom.runtime.mounts.borrow();
687        let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
688
689        dom.scopes.get(scope_id)
690    }
691}
692
693impl std::fmt::Debug for VComponent {
694    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695        f.debug_struct("VComponent")
696            .field("name", &self.name)
697            .finish()
698    }
699}
700
701/// A text node
702#[derive(Clone, Debug)]
703pub struct VText {
704    /// The actual text itself
705    pub value: String,
706}
707
708impl VText {
709    /// Create a new VText
710    pub fn new(value: impl ToString) -> Self {
711        Self {
712            value: value.to_string(),
713        }
714    }
715}
716
717impl From<Arguments<'_>> for VText {
718    fn from(args: Arguments) -> Self {
719        Self::new(args.to_string())
720    }
721}
722
723/// A placeholder node, used by suspense and fragments
724#[derive(Clone, Debug, Default)]
725#[non_exhaustive]
726pub struct VPlaceholder {}
727
728/// An attribute of the TemplateNode, created at compile time
729#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
730#[cfg_attr(
731    feature = "serialize",
732    derive(serde::Serialize, serde::Deserialize),
733    serde(tag = "type")
734)]
735pub enum TemplateAttribute {
736    /// This attribute is entirely known at compile time, enabling
737    Static {
738        /// The name of this attribute.
739        ///
740        /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
741        #[cfg_attr(
742            feature = "serialize",
743            serde(deserialize_with = "deserialize_string_leaky", bound = "")
744        )]
745        name: StaticStr,
746
747        /// The value of this attribute, known at compile time
748        ///
749        /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
750        #[cfg_attr(
751            feature = "serialize",
752            serde(deserialize_with = "deserialize_string_leaky", bound = "")
753        )]
754        value: StaticStr,
755
756        /// The namespace of this attribute. Does not exist in the HTML spec
757        #[cfg_attr(
758            feature = "serialize",
759            serde(deserialize_with = "deserialize_option_leaky", bound = "")
760        )]
761        namespace: Option<StaticStr>,
762    },
763
764    /// The attribute in this position is actually determined dynamically at runtime
765    ///
766    /// This is the index into the dynamic_attributes field on the container VNode
767    Dynamic {
768        /// The index
769        id: usize,
770    },
771}
772
773/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
774#[derive(Debug, Clone, PartialEq)]
775pub struct Attribute {
776    /// The name of the attribute.
777    pub name: &'static str,
778
779    /// The value of the attribute
780    pub value: AttributeValue,
781
782    /// The namespace of the attribute.
783    ///
784    /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
785    pub namespace: Option<&'static str>,
786
787    /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
788    pub volatile: bool,
789}
790
791impl Attribute {
792    /// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
793    ///
794    /// "Volatile" refers to whether or not Dioxus should always override the value. This helps prevent the UI in
795    /// some renderers stay in sync with the VirtualDom's understanding of the world
796    pub fn new<T>(
797        name: &'static str,
798        value: impl IntoAttributeValue<T>,
799        namespace: Option<&'static str>,
800        volatile: bool,
801    ) -> Attribute {
802        Attribute {
803            name,
804            namespace,
805            volatile,
806            value: value.into_value(),
807        }
808    }
809
810    /// Create a new deep clone of this attribute
811    pub(crate) fn deep_clone(&self) -> Self {
812        Attribute {
813            name: self.name,
814            namespace: self.namespace,
815            volatile: self.volatile,
816            value: match &self.value {
817                AttributeValue::Listener(listener) => {
818                    AttributeValue::Listener(listener.leak_reference().unwrap())
819                }
820                value => value.clone(),
821            },
822        }
823    }
824}
825
826/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
827///
828/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
829/// variant.
830#[derive(Clone)]
831pub enum AttributeValue {
832    /// Text attribute
833    Text(String),
834
835    /// A float
836    Float(f64),
837
838    /// Signed integer
839    Int(i64),
840
841    /// Boolean
842    Bool(bool),
843
844    /// A listener, like "onclick"
845    Listener(ListenerCb),
846
847    /// An arbitrary value that implements PartialEq and is static
848    Any(Rc<dyn AnyValue>),
849
850    /// A "none" value, resulting in the removal of an attribute from the dom
851    None,
852}
853
854impl AttributeValue {
855    /// Create a new [`AttributeValue`] with the listener variant from a callback
856    ///
857    /// The callback must be confined to the lifetime of the ScopeState
858    pub fn listener<T: 'static>(mut callback: impl FnMut(Event<T>) + 'static) -> AttributeValue {
859        // TODO: maybe don't use the copy-variant of EventHandler here?
860        // Maybe, create an Owned variant so we are less likely to run into leaks
861        AttributeValue::Listener(EventHandler::leak(move |event: Event<dyn Any>| {
862            let data = event.data.downcast::<T>().unwrap();
863            callback(Event {
864                metadata: event.metadata.clone(),
865                data,
866            });
867        }))
868    }
869
870    /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
871    pub fn any_value<T: AnyValue>(value: T) -> AttributeValue {
872        AttributeValue::Any(Rc::new(value))
873    }
874}
875
876pub type ListenerCb = EventHandler<Event<dyn Any>>;
877
878impl std::fmt::Debug for AttributeValue {
879    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
880        match self {
881            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
882            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
883            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
884            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
885            Self::Listener(listener) => f.debug_tuple("Listener").field(listener).finish(),
886            Self::Any(_) => f.debug_tuple("Any").finish(),
887            Self::None => write!(f, "None"),
888        }
889    }
890}
891
892impl PartialEq for AttributeValue {
893    fn eq(&self, other: &Self) -> bool {
894        match (self, other) {
895            (Self::Text(l0), Self::Text(r0)) => l0 == r0,
896            (Self::Float(l0), Self::Float(r0)) => l0 == r0,
897            (Self::Int(l0), Self::Int(r0)) => l0 == r0,
898            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
899            (Self::Listener(l0), Self::Listener(r0)) => l0 == r0,
900            (Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()),
901            (Self::None, Self::None) => true,
902            _ => false,
903        }
904    }
905}
906
907#[doc(hidden)]
908pub trait AnyValue: 'static {
909    fn any_cmp(&self, other: &dyn AnyValue) -> bool;
910    fn as_any(&self) -> &dyn Any;
911    fn type_id(&self) -> TypeId {
912        self.as_any().type_id()
913    }
914}
915
916impl<T: Any + PartialEq + 'static> AnyValue for T {
917    fn any_cmp(&self, other: &dyn AnyValue) -> bool {
918        if let Some(other) = other.as_any().downcast_ref() {
919            self == other
920        } else {
921            false
922        }
923    }
924
925    fn as_any(&self) -> &dyn Any {
926        self
927    }
928}
929
930/// A trait that allows various items to be converted into a dynamic node for the rsx macro
931pub trait IntoDynNode<A = ()> {
932    /// Consume this item and produce a DynamicNode
933    fn into_dyn_node(self) -> DynamicNode;
934}
935
936impl IntoDynNode for () {
937    fn into_dyn_node(self) -> DynamicNode {
938        DynamicNode::default()
939    }
940}
941impl IntoDynNode for VNode {
942    fn into_dyn_node(self) -> DynamicNode {
943        DynamicNode::Fragment(vec![self])
944    }
945}
946impl IntoDynNode for DynamicNode {
947    fn into_dyn_node(self) -> DynamicNode {
948        self
949    }
950}
951impl<T: IntoDynNode> IntoDynNode for Option<T> {
952    fn into_dyn_node(self) -> DynamicNode {
953        match self {
954            Some(val) => val.into_dyn_node(),
955            None => DynamicNode::default(),
956        }
957    }
958}
959impl IntoDynNode for &Element {
960    fn into_dyn_node(self) -> DynamicNode {
961        match self.as_ref() {
962            Ok(val) => val.into_dyn_node(),
963            _ => DynamicNode::default(),
964        }
965    }
966}
967impl IntoDynNode for Element {
968    fn into_dyn_node(self) -> DynamicNode {
969        match self {
970            Ok(val) => val.into_dyn_node(),
971            _ => DynamicNode::default(),
972        }
973    }
974}
975impl IntoDynNode for &Option<VNode> {
976    fn into_dyn_node(self) -> DynamicNode {
977        match self.as_ref() {
978            Some(val) => val.clone().into_dyn_node(),
979            _ => DynamicNode::default(),
980        }
981    }
982}
983impl IntoDynNode for &str {
984    fn into_dyn_node(self) -> DynamicNode {
985        DynamicNode::Text(VText {
986            value: self.to_string(),
987        })
988    }
989}
990impl IntoDynNode for String {
991    fn into_dyn_node(self) -> DynamicNode {
992        DynamicNode::Text(VText { value: self })
993    }
994}
995impl IntoDynNode for Arguments<'_> {
996    fn into_dyn_node(self) -> DynamicNode {
997        DynamicNode::Text(VText {
998            value: self.to_string(),
999        })
1000    }
1001}
1002impl IntoDynNode for &VNode {
1003    fn into_dyn_node(self) -> DynamicNode {
1004        DynamicNode::Fragment(vec![self.clone()])
1005    }
1006}
1007
1008pub trait IntoVNode {
1009    fn into_vnode(self) -> VNode;
1010}
1011impl IntoVNode for VNode {
1012    fn into_vnode(self) -> VNode {
1013        self
1014    }
1015}
1016impl IntoVNode for &VNode {
1017    fn into_vnode(self) -> VNode {
1018        self.clone()
1019    }
1020}
1021impl IntoVNode for Element {
1022    fn into_vnode(self) -> VNode {
1023        match self {
1024            Ok(val) => val.into_vnode(),
1025            _ => VNode::empty().unwrap(),
1026        }
1027    }
1028}
1029impl IntoVNode for &Element {
1030    fn into_vnode(self) -> VNode {
1031        match self {
1032            Ok(val) => val.into_vnode(),
1033            _ => VNode::empty().unwrap(),
1034        }
1035    }
1036}
1037impl IntoVNode for Option<VNode> {
1038    fn into_vnode(self) -> VNode {
1039        match self {
1040            Some(val) => val.into_vnode(),
1041            _ => VNode::empty().unwrap(),
1042        }
1043    }
1044}
1045impl IntoVNode for &Option<VNode> {
1046    fn into_vnode(self) -> VNode {
1047        match self.as_ref() {
1048            Some(val) => val.clone().into_vnode(),
1049            _ => VNode::empty().unwrap(),
1050        }
1051    }
1052}
1053impl IntoVNode for Option<Element> {
1054    fn into_vnode(self) -> VNode {
1055        match self {
1056            Some(val) => val.into_vnode(),
1057            _ => VNode::empty().unwrap(),
1058        }
1059    }
1060}
1061impl IntoVNode for &Option<Element> {
1062    fn into_vnode(self) -> VNode {
1063        match self.as_ref() {
1064            Some(val) => val.clone().into_vnode(),
1065            _ => VNode::empty().unwrap(),
1066        }
1067    }
1068}
1069
1070// Note that we're using the E as a generic but this is never crafted anyways.
1071pub struct FromNodeIterator;
1072impl<T, I> IntoDynNode<FromNodeIterator> for T
1073where
1074    T: Iterator<Item = I>,
1075    I: IntoVNode,
1076{
1077    fn into_dyn_node(self) -> DynamicNode {
1078        let children: Vec<_> = self.into_iter().map(|node| node.into_vnode()).collect();
1079
1080        if children.is_empty() {
1081            DynamicNode::default()
1082        } else {
1083            DynamicNode::Fragment(children)
1084        }
1085    }
1086}
1087
1088/// A value that can be converted into an attribute value
1089pub trait IntoAttributeValue<T = ()> {
1090    /// Convert into an attribute value
1091    fn into_value(self) -> AttributeValue;
1092}
1093
1094impl IntoAttributeValue for AttributeValue {
1095    fn into_value(self) -> AttributeValue {
1096        self
1097    }
1098}
1099
1100impl IntoAttributeValue for &str {
1101    fn into_value(self) -> AttributeValue {
1102        AttributeValue::Text(self.to_string())
1103    }
1104}
1105
1106impl IntoAttributeValue for String {
1107    fn into_value(self) -> AttributeValue {
1108        AttributeValue::Text(self)
1109    }
1110}
1111
1112impl IntoAttributeValue for f32 {
1113    fn into_value(self) -> AttributeValue {
1114        AttributeValue::Float(self as _)
1115    }
1116}
1117impl IntoAttributeValue for f64 {
1118    fn into_value(self) -> AttributeValue {
1119        AttributeValue::Float(self)
1120    }
1121}
1122
1123impl IntoAttributeValue for i8 {
1124    fn into_value(self) -> AttributeValue {
1125        AttributeValue::Int(self as _)
1126    }
1127}
1128impl IntoAttributeValue for i16 {
1129    fn into_value(self) -> AttributeValue {
1130        AttributeValue::Int(self as _)
1131    }
1132}
1133impl IntoAttributeValue for i32 {
1134    fn into_value(self) -> AttributeValue {
1135        AttributeValue::Int(self as _)
1136    }
1137}
1138impl IntoAttributeValue for i64 {
1139    fn into_value(self) -> AttributeValue {
1140        AttributeValue::Int(self)
1141    }
1142}
1143impl IntoAttributeValue for isize {
1144    fn into_value(self) -> AttributeValue {
1145        AttributeValue::Int(self as _)
1146    }
1147}
1148impl IntoAttributeValue for i128 {
1149    fn into_value(self) -> AttributeValue {
1150        AttributeValue::Int(self as _)
1151    }
1152}
1153
1154impl IntoAttributeValue for u8 {
1155    fn into_value(self) -> AttributeValue {
1156        AttributeValue::Int(self as _)
1157    }
1158}
1159impl IntoAttributeValue for u16 {
1160    fn into_value(self) -> AttributeValue {
1161        AttributeValue::Int(self as _)
1162    }
1163}
1164impl IntoAttributeValue for u32 {
1165    fn into_value(self) -> AttributeValue {
1166        AttributeValue::Int(self as _)
1167    }
1168}
1169impl IntoAttributeValue for u64 {
1170    fn into_value(self) -> AttributeValue {
1171        AttributeValue::Int(self as _)
1172    }
1173}
1174impl IntoAttributeValue for usize {
1175    fn into_value(self) -> AttributeValue {
1176        AttributeValue::Int(self as _)
1177    }
1178}
1179impl IntoAttributeValue for u128 {
1180    fn into_value(self) -> AttributeValue {
1181        AttributeValue::Int(self as _)
1182    }
1183}
1184
1185impl IntoAttributeValue for bool {
1186    fn into_value(self) -> AttributeValue {
1187        AttributeValue::Bool(self)
1188    }
1189}
1190
1191impl IntoAttributeValue for Arguments<'_> {
1192    fn into_value(self) -> AttributeValue {
1193        AttributeValue::Text(self.to_string())
1194    }
1195}
1196
1197impl IntoAttributeValue for Rc<dyn AnyValue> {
1198    fn into_value(self) -> AttributeValue {
1199        AttributeValue::Any(self)
1200    }
1201}
1202
1203impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
1204    fn into_value(self) -> AttributeValue {
1205        match self {
1206            Some(val) => val.into_value(),
1207            None => AttributeValue::None,
1208        }
1209    }
1210}
1211
1212pub struct AnyFmtMarker;
1213impl<T> IntoAttributeValue<AnyFmtMarker> for T
1214where
1215    T: DioxusFormattable,
1216{
1217    fn into_value(self) -> AttributeValue {
1218        AttributeValue::Text(self.format().to_string())
1219    }
1220}
1221
1222/// A trait for anything that has a dynamic list of attributes
1223pub trait HasAttributes {
1224    /// Push an attribute onto the list of attributes
1225    fn push_attribute<T>(
1226        self,
1227        name: &'static str,
1228        ns: Option<&'static str>,
1229        attr: impl IntoAttributeValue<T>,
1230        volatile: bool,
1231    ) -> Self;
1232}