custom_element.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. //! 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.
  2. //!
  3. //! 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.
  4. // Used in doc links
  5. #[allow(unused)]
  6. use crate::real_dom::RealDom;
  7. use std::sync::{Arc, RwLock};
  8. use rustc_hash::FxHashMap;
  9. use shipyard::Component;
  10. use crate::{
  11. node::{FromAnyValue, NodeType},
  12. node_ref::AttributeMask,
  13. prelude::{NodeImmutable, NodeMut},
  14. tree::TreeMut,
  15. NodeId,
  16. };
  17. pub(crate) struct CustomElementRegistry<V: FromAnyValue + Send + Sync> {
  18. builders: FxHashMap<(&'static str, Option<&'static str>), CustomElementBuilder<V>>,
  19. }
  20. impl<V: FromAnyValue + Send + Sync> Default for CustomElementRegistry<V> {
  21. fn default() -> Self {
  22. Self {
  23. builders: FxHashMap::default(),
  24. }
  25. }
  26. }
  27. impl<V: FromAnyValue + Send + Sync> CustomElementRegistry<V> {
  28. pub fn register<F, U>(&mut self)
  29. where
  30. F: CustomElementFactory<U, V>,
  31. U: CustomElementUpdater<V>,
  32. {
  33. self.builders.insert(
  34. (F::NAME, F::NAMESPACE),
  35. CustomElementBuilder {
  36. create: |node| Box::new(F::create(node)),
  37. },
  38. );
  39. }
  40. pub fn add_shadow_dom(&self, mut node: NodeMut<V>) {
  41. let element_tag = if let NodeType::Element(el) = &*node.node_type() {
  42. Some((el.tag.clone(), el.namespace.clone()))
  43. } else {
  44. None
  45. };
  46. if let Some((tag, ns)) = element_tag {
  47. if let Some(builder) = self.builders.get(&(tag.as_str(), ns.as_deref())) {
  48. let boxed_custom_element = { (builder.create)(node.reborrow()) };
  49. let shadow_roots = boxed_custom_element.roots();
  50. let light_id = node.id();
  51. node.real_dom_mut().tree_mut().create_subtree(
  52. light_id,
  53. shadow_roots,
  54. boxed_custom_element.slot(),
  55. );
  56. let boxed_custom_element = CustomElementManager {
  57. inner: Arc::new(RwLock::new(boxed_custom_element)),
  58. };
  59. node.insert(boxed_custom_element);
  60. }
  61. }
  62. }
  63. }
  64. struct CustomElementBuilder<V: FromAnyValue + Send + Sync> {
  65. create: fn(NodeMut<V>) -> Box<dyn CustomElementUpdater<V>>,
  66. }
  67. /// A controlled element that renders to a shadow DOM.
  68. ///
  69. /// Register with [`RealDom::register_custom_element`]
  70. ///
  71. /// This is a simplified custom element trait for elements that can create themselves. For more granular control, implement [`CustomElementFactory`] and [`CustomElementUpdater`] instead.
  72. pub trait CustomElement<V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static {
  73. /// The tag of the element
  74. const NAME: &'static str;
  75. /// The namespace of the element
  76. const NAMESPACE: Option<&'static str> = None;
  77. /// Create a new element *without mounting* it.
  78. /// 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.
  79. fn create(light_root: NodeMut<V>) -> Self;
  80. /// The root node of the custom element. These roots must be not change once the element is created.
  81. fn roots(&self) -> Vec<NodeId>;
  82. /// The slot to render children of the element into. The slot must be not change once the element is created.
  83. fn slot(&self) -> Option<NodeId> {
  84. None
  85. }
  86. /// Update the custom element's shadow tree with the new attributes.
  87. /// Called when the attributes of the custom element are changed.
  88. fn attributes_changed(&mut self, light_node: NodeMut<V>, attributes: &AttributeMask);
  89. }
  90. /// A factory for creating custom elements
  91. ///
  92. /// Register with [`RealDom::register_custom_element_with_factory`]
  93. pub trait CustomElementFactory<W: CustomElementUpdater<V>, V: FromAnyValue + Send + Sync = ()>:
  94. Send + Sync + 'static
  95. {
  96. /// The tag of the element
  97. const NAME: &'static str;
  98. /// The namespace of the element
  99. const NAMESPACE: Option<&'static str> = None;
  100. /// Create a new element *without mounting* it.
  101. /// 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.
  102. fn create(dom: NodeMut<V>) -> W;
  103. }
  104. impl<W: CustomElement<V>, V: FromAnyValue + Send + Sync> CustomElementFactory<W, V> for W {
  105. const NAME: &'static str = W::NAME;
  106. const NAMESPACE: Option<&'static str> = W::NAMESPACE;
  107. fn create(node: NodeMut<V>) -> Self {
  108. Self::create(node)
  109. }
  110. }
  111. /// A trait for updating custom elements
  112. pub trait CustomElementUpdater<V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static {
  113. /// Update the custom element's shadow tree with the new attributes.
  114. /// Called when the attributes of the custom element are changed.
  115. fn attributes_changed(&mut self, light_root: NodeMut<V>, attributes: &AttributeMask);
  116. /// The root node of the custom element. These roots must be not change once the element is created.
  117. fn roots(&self) -> Vec<NodeId>;
  118. /// The slot to render children of the element into. The slot must be not change once the element is created.
  119. fn slot(&self) -> Option<NodeId> {
  120. None
  121. }
  122. }
  123. impl<W: CustomElement<V>, V: FromAnyValue + Send + Sync> CustomElementUpdater<V> for W {
  124. fn attributes_changed(&mut self, light_root: NodeMut<V>, attributes: &AttributeMask) {
  125. self.attributes_changed(light_root, attributes);
  126. }
  127. fn roots(&self) -> Vec<NodeId> {
  128. self.roots()
  129. }
  130. fn slot(&self) -> Option<NodeId> {
  131. self.slot()
  132. }
  133. }
  134. /// A dynamic trait object wrapper for [`CustomElementUpdater`]
  135. #[derive(Component, Clone)]
  136. pub(crate) struct CustomElementManager<V: FromAnyValue = ()> {
  137. inner: Arc<RwLock<Box<dyn CustomElementUpdater<V>>>>,
  138. }
  139. impl<V: FromAnyValue + Send + Sync> CustomElementManager<V> {
  140. /// Update the custom element based on attributes changed.
  141. pub fn on_attributes_changed(&self, light_root: NodeMut<V>, attributes: &AttributeMask) {
  142. self.inner
  143. .write()
  144. .unwrap()
  145. .attributes_changed(light_root, attributes);
  146. }
  147. }