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