dioxus_native_core/
custom_element.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! A custom element is a controlled element that renders to a shadow DOM. This allows you to create elements that act like widgets without relying on a specific framework.
//!
//! Each custom element is registered with a element name and namespace with [`RealDom::register_custom_element`] or [`RealDom::register_custom_element_with_factory`]. Once registered, they will be created automatically when the element is added to the DOM.

// Used in doc links
#[allow(unused)]
use crate::real_dom::RealDom;

use std::sync::{Arc, RwLock};

use rustc_hash::FxHashMap;
use shipyard::Component;

use crate::{
    node::{FromAnyValue, NodeType},
    node_ref::AttributeMask,
    prelude::{NodeImmutable, NodeMut},
    tree::TreeMut,
    NodeId,
};

pub(crate) struct CustomElementRegistry<V: FromAnyValue + Send + Sync> {
    builders: FxHashMap<(&'static str, Option<&'static str>), CustomElementBuilder<V>>,
}

impl<V: FromAnyValue + Send + Sync> Default for CustomElementRegistry<V> {
    fn default() -> Self {
        Self {
            builders: FxHashMap::default(),
        }
    }
}

impl<V: FromAnyValue + Send + Sync> CustomElementRegistry<V> {
    pub fn register<F, U>(&mut self)
    where
        F: CustomElementFactory<U, V>,
        U: CustomElementUpdater<V>,
    {
        self.builders.insert(
            (F::NAME, F::NAMESPACE),
            CustomElementBuilder {
                create: |node| Box::new(F::create(node)),
            },
        );
    }

    pub fn add_shadow_dom(&self, mut node: NodeMut<V>) {
        let element_tag = if let NodeType::Element(el) = &*node.node_type() {
            Some((el.tag.clone(), el.namespace.clone()))
        } else {
            None
        };
        if let Some((tag, ns)) = element_tag {
            if let Some(builder) = self.builders.get(&(tag.as_str(), ns.as_deref())) {
                let boxed_custom_element = { (builder.create)(node.reborrow()) };

                let shadow_roots = boxed_custom_element.roots();

                let light_id = node.id();
                node.real_dom_mut().tree_mut().create_subtree(
                    light_id,
                    shadow_roots,
                    boxed_custom_element.slot(),
                );

                let boxed_custom_element = CustomElementManager {
                    inner: Arc::new(RwLock::new(boxed_custom_element)),
                };

                node.insert(boxed_custom_element);
            }
        }
    }
}

struct CustomElementBuilder<V: FromAnyValue + Send + Sync> {
    create: fn(NodeMut<V>) -> Box<dyn CustomElementUpdater<V>>,
}

/// A controlled element that renders to a shadow DOM.
///
/// Register with [`RealDom::register_custom_element`]
///
/// This is a simplified custom element trait for elements that can create themselves. For more granular control, implement [`CustomElementFactory`] and [`CustomElementUpdater`] instead.
pub trait CustomElement<V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static {
    /// The tag of the element
    const NAME: &'static str;

    /// The namespace of the element
    const NAMESPACE: Option<&'static str> = None;

    /// Create a new element *without mounting* it.
    /// The node passed in is the light DOM node. The element should not modify the light DOM node, but it can get the [`NodeMut::real_dom_mut`] from the node to create new nodes.
    fn create(light_root: NodeMut<V>) -> Self;

    /// The root node of the custom element. These roots must be not change once the element is created.
    fn roots(&self) -> Vec<NodeId>;

    /// The slot to render children of the element into. The slot must be not change once the element is created.
    fn slot(&self) -> Option<NodeId> {
        None
    }

    /// Update the custom element's shadow tree with the new attributes.
    /// Called when the attributes of the custom element are changed.
    fn attributes_changed(&mut self, light_node: NodeMut<V>, attributes: &AttributeMask);
}

/// A factory for creating custom elements
///
/// Register with [`RealDom::register_custom_element_with_factory`]
pub trait CustomElementFactory<W: CustomElementUpdater<V>, V: FromAnyValue + Send + Sync = ()>:
    Send + Sync + 'static
{
    /// The tag of the element
    const NAME: &'static str;

    /// The namespace of the element
    const NAMESPACE: Option<&'static str> = None;

    /// Create a new element *without mounting* it.
    /// The node passed in is the light DOM node. The element should not modify the light DOM node, but it can get the [`NodeMut::real_dom_mut`] from the node to create new nodes.
    fn create(dom: NodeMut<V>) -> W;
}

impl<W: CustomElement<V>, V: FromAnyValue + Send + Sync> CustomElementFactory<W, V> for W {
    const NAME: &'static str = W::NAME;

    const NAMESPACE: Option<&'static str> = W::NAMESPACE;

    fn create(node: NodeMut<V>) -> Self {
        Self::create(node)
    }
}

/// A trait for updating custom elements
pub trait CustomElementUpdater<V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static {
    /// Update the custom element's shadow tree with the new attributes.
    /// Called when the attributes of the custom element are changed.
    fn attributes_changed(&mut self, light_root: NodeMut<V>, attributes: &AttributeMask);

    /// The root node of the custom element. These roots must be not change once the element is created.
    fn roots(&self) -> Vec<NodeId>;

    /// The slot to render children of the element into. The slot must be not change once the element is created.
    fn slot(&self) -> Option<NodeId> {
        None
    }
}

impl<W: CustomElement<V>, V: FromAnyValue + Send + Sync> CustomElementUpdater<V> for W {
    fn attributes_changed(&mut self, light_root: NodeMut<V>, attributes: &AttributeMask) {
        self.attributes_changed(light_root, attributes);
    }

    fn roots(&self) -> Vec<NodeId> {
        self.roots()
    }

    fn slot(&self) -> Option<NodeId> {
        self.slot()
    }
}

/// A dynamic trait object wrapper for [`CustomElementUpdater`]
#[derive(Component, Clone)]
pub(crate) struct CustomElementManager<V: FromAnyValue = ()> {
    inner: Arc<RwLock<Box<dyn CustomElementUpdater<V>>>>,
}

impl<V: FromAnyValue + Send + Sync> CustomElementManager<V> {
    /// Update the custom element based on attributes changed.
    pub fn on_attributes_changed(&self, light_root: NodeMut<V>, attributes: &AttributeMask) {
        self.inner
            .write()
            .unwrap()
            .attributes_changed(light_root, attributes);
    }
}