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);
}
}