dioxus_core/
nodes.rs

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