From 9877b0f0b12270b1041b7be06bf6604aa9be7738 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 17 Apr 2023 12:41:54 -0500 Subject: [PATCH] finish shadow dom docs --- packages/native-core/examples/custom_attr.rs | 2 +- packages/native-core/examples/font_size.rs | 2 +- packages/native-core/examples/simple.rs | 2 +- .../native-core/examples/simple_dioxus.rs | 2 +- packages/native-core/src/custom_element.rs | 81 ++++++++++--------- packages/native-core/src/real_dom.rs | 62 ++++++++++++-- packages/native-core/src/tree.rs | 31 ++++--- .../tests/called_minimally_on_build.rs | 2 +- packages/native-core/tests/custom_element.rs | 2 +- packages/rink/src/focus.rs | 4 +- packages/rink/src/hooks.rs | 2 +- 11 files changed, 123 insertions(+), 69 deletions(-) diff --git a/packages/native-core/examples/custom_attr.rs b/packages/native-core/examples/custom_attr.rs index ee78a69f4..e7bbb36ea 100644 --- a/packages/native-core/examples/custom_attr.rs +++ b/packages/native-core/examples/custom_attr.rs @@ -215,7 +215,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(true, |node| { + rdom.traverse_depth_first_advanced(true, |node| { let indent = " ".repeat(node.height() as usize); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/examples/font_size.rs b/packages/native-core/examples/font_size.rs index 4ff6b876d..40261baa2 100644 --- a/packages/native-core/examples/font_size.rs +++ b/packages/native-core/examples/font_size.rs @@ -160,7 +160,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(true, |node| { + rdom.traverse_depth_first_advanced(true, |node| { let indent = " ".repeat(node.height() as usize); let font_size = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/examples/simple.rs b/packages/native-core/examples/simple.rs index b44f05e63..276d06d08 100644 --- a/packages/native-core/examples/simple.rs +++ b/packages/native-core/examples/simple.rs @@ -206,7 +206,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(true, |node| { + rdom.traverse_depth_first_advanced(true, |node| { let indent = " ".repeat(node.height() as usize); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/examples/simple_dioxus.rs b/packages/native-core/examples/simple_dioxus.rs index c5aebd5d3..af0e4ca00 100644 --- a/packages/native-core/examples/simple_dioxus.rs +++ b/packages/native-core/examples/simple_dioxus.rs @@ -234,7 +234,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(true, |node| { + rdom.traverse_depth_first_advanced(true, |node| { let indent = " ".repeat(node.height() as usize); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/src/custom_element.rs b/packages/native-core/src/custom_element.rs index 675e928f1..3675089f6 100644 --- a/packages/native-core/src/custom_element.rs +++ b/packages/native-core/src/custom_element.rs @@ -1,5 +1,10 @@ -//! A custom element is a controlled element that renders to a shadow DOM. -//! Each custom element is registered with a element name +//! 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}; @@ -48,22 +53,22 @@ impl CustomElementRegistry { }; if let Some((tag, ns)) = element_tag { if let Some(builder) = self.builders.get(&(tag.as_str(), ns.as_deref())) { - let boxed_widget = { (builder.create)(node.reborrow()) }; + let boxed_custom_element = { (builder.create)(node.reborrow()) }; - let shadow_roots = boxed_widget.roots(); + 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_widget.slot(), + boxed_custom_element.slot(), ); - let boxed_widget = CustomElementManager { - inner: Arc::new(RwLock::new(boxed_widget)), + let boxed_custom_element = CustomElementManager { + inner: Arc::new(RwLock::new(boxed_custom_element)), }; - node.insert(boxed_widget); + node.insert(boxed_custom_element); } } } @@ -73,40 +78,49 @@ struct CustomElementBuilder { create: fn(NodeMut) -> Box>, } -/// A controlled element that renders to a shadow DOM +/// 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: Send + Sync + 'static { - /// The tag the widget is registered under. + /// The tag of the element const NAME: &'static str; - /// The namespace the widget is registered under. + /// The namespace of the element const NAMESPACE: Option<&'static str> = None; - /// Create a new widget *without mounting* it. - fn create(node: NodeMut) -> Self; + /// 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) -> Self; - /// The root node of the widget. This must be static once the element is created. + /// The root node of the custom element. These roots must be not change once the element is created. fn roots(&self) -> Vec; - /// The slot to render children of the element into. This must be static once the element is created. + /// The slot to render children of the element into. The slot must be not change once the element is created. fn slot(&self) -> Option { None } - /// Called when the attributes of the widget are changed. + /// 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, attributes: &AttributeMask); } -/// A factory for creating widgets +/// A factory for creating custom elements +/// +/// Register with [`RealDom::register_custom_element_with_factory`] pub trait CustomElementFactory, V: FromAnyValue + Send + Sync = ()>: Send + Sync + 'static { - /// The tag the widget is registered under. + /// The tag of the element const NAME: &'static str; - /// The namespace the widget is registered under. + /// The namespace of the element const NAMESPACE: Option<&'static str> = None; - /// Create a new widget. + /// 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) -> W; } @@ -120,16 +134,19 @@ impl, V: FromAnyValue + Send + Sync> CustomElementFactory: Send + Sync + 'static { - /// Called when the attributes of the widget are changed. + /// 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, attributes: &AttributeMask); - /// The root node of the widget. + /// The root node of the custom element. These roots must be not change once the element is created. fn roots(&self) -> Vec; - /// The slot to render children of the element into. - fn slot(&self) -> Option; + /// The slot to render children of the element into. The slot must be not change once the element is created. + fn slot(&self) -> Option { + None + } } impl, V: FromAnyValue + Send + Sync> CustomElementUpdater for W { @@ -146,23 +163,13 @@ impl, V: FromAnyValue + Send + Sync> CustomElementUpdater } } -/// A concrete structure for managing a any widget. +/// A dynamic trait object wrapper for [`CustomElementUpdater`] #[derive(Component, Clone)] -pub struct CustomElementManager { +pub(crate) struct CustomElementManager { inner: Arc>>>, } impl CustomElementManager { - /// The root node of the widget's shadow DOM. - pub fn roots(&self) -> Vec { - self.inner.read().unwrap().roots() - } - - /// The slot to render children of the element into. - pub fn slot(&self) -> Option { - self.inner.read().unwrap().slot() - } - /// Update the custom element based on attributes changed. pub fn on_attributes_changed(&self, light_root: NodeMut, attributes: &AttributeMask) { self.inner diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 61e9644a3..a0eff4b2c 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -384,20 +384,35 @@ impl RealDom { } /// Traverses the dom in a depth first manner, calling the provided function on each node. - pub fn traverse_depth_first(&self, enter_shadow_doms: bool, mut f: impl FnMut(NodeRef)) { + /// If `enter_shadow_dom` is true, then the traversal will enter shadow doms in the tree. + pub fn traverse_depth_first_advanced( + &self, + enter_shadow_dom: bool, + mut f: impl FnMut(NodeRef), + ) { let mut stack = vec![self.root_id()]; let tree = self.tree_ref(); while let Some(id) = stack.pop() { if let Some(node) = self.get(id) { f(node); - let children = tree.children_ids_advanced(id, enter_shadow_doms); + let children = tree.children_ids_advanced(id, enter_shadow_dom); stack.extend(children.iter().copied().rev()); } } } + /// Traverses the dom in a depth first manner, calling the provided function on each node. + pub fn traverse_depth_first(&self, f: impl FnMut(NodeRef)) { + self.traverse_depth_first_advanced(true, f) + } + /// Traverses the dom in a breadth first manner, calling the provided function on each node. - pub fn traverse_breadth_first(&self, enter_shadow_doms: bool, mut f: impl FnMut(NodeRef)) { + /// If `enter_shadow_dom` is true, then the traversal will enter shadow doms in the tree. + pub fn traverse_breadth_first_advanced( + &self, + enter_shadow_doms: bool, + mut f: impl FnMut(NodeRef), + ) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); let tree = self.tree_ref(); @@ -412,8 +427,14 @@ impl RealDom { } } + /// Traverses the dom in a breadth first manner, calling the provided function on each node. + pub fn traverse_breadth_first(&self, f: impl FnMut(NodeRef)) { + self.traverse_breadth_first_advanced(true, f); + } + /// Traverses the dom in a depth first manner mutably, calling the provided function on each node. - pub fn traverse_depth_first_mut( + /// If `enter_shadow_dom` is true, then the traversal will enter shadow doms in the tree. + pub fn traverse_depth_first_mut_advanced( &mut self, enter_shadow_doms: bool, mut f: impl FnMut(NodeMut), @@ -432,8 +453,14 @@ impl RealDom { } } + /// Traverses the dom in a depth first manner mutably, calling the provided function on each node. + pub fn traverse_depth_first_mut(&mut self, f: impl FnMut(NodeMut)) { + self.traverse_depth_first_mut_advanced(true, f) + } + /// Traverses the dom in a breadth first manner mutably, calling the provided function on each node. - pub fn traverse_breadth_first_mut( + /// If `enter_shadow_dom` is true, then the traversal will enter shadow doms in the tree. + pub fn traverse_breadth_first_mut_advanced( &mut self, enter_shadow_doms: bool, mut f: impl FnMut(NodeMut), @@ -453,6 +480,11 @@ impl RealDom { } } + /// Traverses the dom in a breadth first manner mutably, calling the provided function on each node. + pub fn traverse_breadth_first_mut(&mut self, f: impl FnMut(NodeMut)) { + self.traverse_breadth_first_mut_advanced(true, f); + } + /// Adds a [`NodeWatcher`] to the dom. Node watchers are called whenever a node is created or removed. pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static + Send + Sync) { self.node_watchers.write().unwrap().push(Box::new(watcher)); @@ -481,11 +513,11 @@ impl RealDom { /// Registers a new custom element. pub fn register_custom_element>(&mut self) { - self.register_custom_element_factory::() + self.register_custom_element_with_factory::() } /// Registers a new custom element with a custom factory. - pub fn register_custom_element_factory(&mut self) + pub fn register_custom_element_with_factory(&mut self) where F: CustomElementFactory, U: CustomElementUpdater, @@ -563,6 +595,14 @@ pub trait NodeImmutable: Sized { .then(|| ViewEntry::new(view, self.id())) } + /// Get the ids of the children of the current node, if enter_shadow_dom is true and the current node is a shadow slot, the ids of the nodes under the node the shadow slot is attached to will be returned + #[inline] + fn children_ids_advanced(&self, id: NodeId, enter_shadow_dom: bool) -> Vec { + self.real_dom() + .tree_ref() + .children_ids_advanced(id, enter_shadow_dom) + } + /// Get the ids of the children of the current node #[inline] fn child_ids(&self) -> Vec { @@ -581,6 +621,14 @@ pub trait NodeImmutable: Sized { .collect() } + /// Get the id of the parent of the current node, if enter_shadow_dom is true and the current node is a shadow root, the node the shadow root is attached to will be returned + #[inline] + fn parent_id_advanced(&self, id: NodeId, enter_shadow_dom: bool) -> Option { + self.real_dom() + .tree_ref() + .parent_id_advanced(id, enter_shadow_dom) + } + /// Get the id of the parent of the current node #[inline] fn parent_id(&self) -> Option { diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 1f26dc6a7..3f3777607 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -4,10 +4,10 @@ use crate::NodeId; use shipyard::{Component, EntitiesViewMut, Get, View, ViewMut}; use std::fmt::Debug; -/// A shadow_tree of a tree. +/// A shadow tree reference inside of a tree. This tree is isolated from the main tree. #[derive(PartialEq, Eq, Clone, Debug, Component)] pub struct ShadowTree { - /// The root of the shadow_tree + /// The root of the shadow tree pub shadow_roots: Vec, /// The node that children of the super tree should be inserted under. pub slot: Option, @@ -22,7 +22,7 @@ pub struct Node { /// If this node is a slot in a shadow_tree, this is node whose child_subtree is that shadow_tree. slot_for_light_tree: Option, /// If this node is a root of a shadow_tree, this is the node whose child_subtree is that shadow_tree. - light_tree_root: Option, + root_for_light_tree: Option, height: u16, } @@ -37,8 +37,8 @@ pub trait TreeRef { #[inline] fn parent_id_advanced(&self, id: NodeId, enter_shadow_dom: bool) -> Option { // If this node is the root of a shadow_tree, return the node the shadow_tree is attached - let light_tree_root = self.light_tree_root(id); - match (light_tree_root, enter_shadow_dom) { + let root_for_light_tree = self.root_for_light_tree(id); + match (root_for_light_tree, enter_shadow_dom) { (Some(id), true) => Some(id), _ => { let parent_id = self.parent_id(id); @@ -74,11 +74,10 @@ pub trait TreeRef { fn children_ids(&self, id: NodeId) -> Vec; /// The shadow tree tree under the node. fn shadow_tree(&self, id: NodeId) -> Option<&ShadowTree>; - // TODO: rethink naming /// The node that contains the shadow tree this node is a slot for fn slot_for_light_tree(&self, id: NodeId) -> Option; /// The node that contains the shadow tree this node is a root of - fn light_tree_root(&self, id: NodeId) -> Option; + fn root_for_light_tree(&self, id: NodeId) -> Option; /// The height of the node. fn height(&self, id: NodeId) -> Option; /// Returns true if the node exists. @@ -132,8 +131,8 @@ impl<'a> TreeRef for TreeRefView<'a> { self.get(id).ok()?.slot_for_light_tree } - fn light_tree_root(&self, id: NodeId) -> Option { - self.get(id).ok()?.light_tree_root + fn root_for_light_tree(&self, id: NodeId) -> Option { + self.get(id).ok()?.root_for_light_tree } } @@ -151,14 +150,14 @@ impl<'a> TreeMut for TreeMutView<'a> { // If this node is a slot in a shadow_tree, remove it from the shadow_tree. if let Some(light_tree) = light_tree { - let light_tree_root = (&mut tree.1).get(light_tree).unwrap(); + let root_for_light_tree = (&mut tree.1).get(light_tree).unwrap(); - if let Some(shadow_tree) = &mut light_tree_root.child_subtree { + if let Some(shadow_tree) = &mut root_for_light_tree.child_subtree { shadow_tree.slot = None; } debug_assert!( - light_tree_root.children.is_empty(), + root_for_light_tree.children.is_empty(), "ShadowTree root should have no children when slot is removed." ); } @@ -186,7 +185,7 @@ impl<'a> TreeMut for TreeMutView<'a> { height: 0, child_subtree: None, slot_for_light_tree: None, - light_tree_root: None, + root_for_light_tree: None, }, ); } @@ -283,7 +282,7 @@ impl<'a> TreeMut for TreeMutView<'a> { // Now that we have created the shadow_tree, we need to update the height of the shadow_tree roots for root in shadow_roots { - (&mut self.1).get(root).unwrap().light_tree_root = Some(id); + (&mut self.1).get(root).unwrap().root_for_light_tree = Some(id); set_height(self, root, light_root_height + 1); } } @@ -403,9 +402,9 @@ impl<'a> TreeRef for TreeMutView<'a> { node_data.get(id).ok()?.slot_for_light_tree } - fn light_tree_root(&self, id: NodeId) -> Option { + fn root_for_light_tree(&self, id: NodeId) -> Option { let node_data = &self.1; - node_data.get(id).ok()?.light_tree_root + node_data.get(id).ok()?.root_for_light_tree } } diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index f4e8cf6e1..3fa789a6c 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -146,7 +146,7 @@ macro_rules! test_state{ dioxus_state.apply_mutations(&mut dom, mutations); dom.update_state(SendAnyMap::new()); - dom.traverse_depth_first(false, |n| { + dom.traverse_depth_first_advanced(false, |n| { $( assert_eq!(n.get::<$state>().unwrap().0, 1); )* diff --git a/packages/native-core/tests/custom_element.rs b/packages/native-core/tests/custom_element.rs index 7bddeacc9..935ac94d0 100644 --- a/packages/native-core/tests/custom_element.rs +++ b/packages/native-core/tests/custom_element.rs @@ -301,7 +301,7 @@ fn custom_elements_work() { rdom.update_state(ctx); // render... - rdom.traverse_depth_first(true, |node| { + rdom.traverse_depth_first_advanced(true, |node| { let node_type = &*node.node_type(); let height = node.height() as usize; let indent = " ".repeat(height); diff --git a/packages/rink/src/focus.rs b/packages/rink/src/focus.rs index 4ede95485..e26f79b3d 100644 --- a/packages/rink/src/focus.rs +++ b/packages/rink/src/focus.rs @@ -183,7 +183,7 @@ impl FocusState { if forward { // find the closest focusable element after the current level - rdom.traverse_depth_first(true, |n| { + rdom.traverse_depth_first(|n| { let node_level = n.get::().unwrap().level; if node_level != *focus_level && node_level.focusable() @@ -200,7 +200,7 @@ impl FocusState { }); } else { // find the closest focusable element before the current level - rdom.traverse_depth_first(true, |n| { + rdom.traverse_depth_first(|n| { let node_level = n.get::().unwrap().level; if node_level != *focus_level && node_level.focusable() diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index b54cc2eaf..55a9274aa 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -550,7 +550,7 @@ impl InnerInputState { // update focus if was_released { let mut focus_id = None; - dom.traverse_depth_first(true, |node| { + dom.traverse_depth_first(|node| { let node_layout = layout .layout(node.get::().unwrap().node.unwrap()) .unwrap();