dioxus_core/
nodes.rs

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