yew/dom_bundle/
bnode.rs

1//! This module contains the bundle version of an abstract node [BNode]
2
3use std::fmt;
4
5use web_sys::{Element, Node};
6
7use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot};
8use crate::dom_bundle::{Reconcilable, ReconcileTarget};
9use crate::html::AnyScope;
10use crate::virtual_dom::{Key, VNode};
11
12/// The bundle implementation to [VNode].
13pub(super) enum BNode {
14    /// A bind between `VTag` and `Element`.
15    Tag(Box<BTag>),
16    /// A bind between `VText` and `TextNode`.
17    Text(BText),
18    /// A bind between `VComp` and `Element`.
19    Comp(BComp),
20    /// A holder for a list of other nodes.
21    List(BList),
22    /// A portal to another part of the document
23    Portal(BPortal),
24    /// A holder for any `Node` (necessary for replacing node).
25    Ref(Node),
26    /// A suspendible document fragment.
27    Suspense(Box<BSuspense>),
28    /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue).
29    Raw(BRaw),
30}
31
32impl BNode {
33    /// Get the key of the underlying node
34    pub fn key(&self) -> Option<&Key> {
35        match self {
36            Self::Comp(bsusp) => bsusp.key(),
37            Self::List(blist) => blist.key(),
38            Self::Ref(_) => None,
39            Self::Tag(btag) => btag.key(),
40            Self::Text(_) => None,
41            Self::Portal(bportal) => bportal.key(),
42            Self::Suspense(bsusp) => bsusp.key(),
43            Self::Raw(_) => None,
44        }
45    }
46}
47
48impl ReconcileTarget for BNode {
49    /// Remove VNode from parent.
50    fn detach(self, root: &BSubtree, parent: &Element, parent_to_detach: bool) {
51        match self {
52            Self::Tag(vtag) => vtag.detach(root, parent, parent_to_detach),
53            Self::Text(btext) => btext.detach(root, parent, parent_to_detach),
54            Self::Comp(bsusp) => bsusp.detach(root, parent, parent_to_detach),
55            Self::List(blist) => blist.detach(root, parent, parent_to_detach),
56            Self::Ref(ref node) => {
57                // Always remove user-defined nodes to clear possible parent references of them
58                if parent.remove_child(node).is_err() {
59                    tracing::warn!("Node not found to remove VRef");
60                }
61            }
62            Self::Portal(bportal) => bportal.detach(root, parent, parent_to_detach),
63            Self::Suspense(bsusp) => bsusp.detach(root, parent, parent_to_detach),
64            Self::Raw(raw) => raw.detach(root, parent, parent_to_detach),
65        }
66    }
67
68    fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
69        match self {
70            Self::Tag(ref vtag) => vtag.shift(next_parent, slot),
71            Self::Text(ref btext) => btext.shift(next_parent, slot),
72            Self::Comp(ref bsusp) => bsusp.shift(next_parent, slot),
73            Self::List(ref vlist) => vlist.shift(next_parent, slot),
74            Self::Ref(ref node) => {
75                slot.insert(next_parent, node);
76
77                DomSlot::at(node.clone())
78            }
79            Self::Portal(ref vportal) => vportal.shift(next_parent, slot),
80            Self::Suspense(ref vsuspense) => vsuspense.shift(next_parent, slot),
81            Self::Raw(ref braw) => braw.shift(next_parent, slot),
82        }
83    }
84}
85
86impl Reconcilable for VNode {
87    type Bundle = BNode;
88
89    fn attach(
90        self,
91        root: &BSubtree,
92        parent_scope: &AnyScope,
93        parent: &Element,
94        slot: DomSlot,
95    ) -> (DomSlot, Self::Bundle) {
96        match self {
97            VNode::VTag(vtag) => {
98                let (node_ref, tag) = vtag.attach(root, parent_scope, parent, slot);
99                (node_ref, tag.into())
100            }
101            VNode::VText(vtext) => {
102                let (node_ref, text) = vtext.attach(root, parent_scope, parent, slot);
103                (node_ref, text.into())
104            }
105            VNode::VComp(vcomp) => {
106                let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, slot);
107                (node_ref, comp.into())
108            }
109            VNode::VList(vlist) => {
110                let (node_ref, list) = vlist.attach(root, parent_scope, parent, slot);
111                (node_ref, list.into())
112            }
113            VNode::VRef(node) => {
114                slot.insert(parent, &node);
115                (DomSlot::at(node.clone()), BNode::Ref(node))
116            }
117            VNode::VPortal(vportal) => {
118                let (node_ref, portal) = vportal.attach(root, parent_scope, parent, slot);
119                (node_ref, portal.into())
120            }
121            VNode::VSuspense(vsuspsense) => {
122                let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, slot);
123                (node_ref, suspsense.into())
124            }
125            VNode::VRaw(vraw) => {
126                let (node_ref, raw) = vraw.attach(root, parent_scope, parent, slot);
127                (node_ref, raw.into())
128            }
129        }
130    }
131
132    fn reconcile_node(
133        self,
134        root: &BSubtree,
135        parent_scope: &AnyScope,
136        parent: &Element,
137        slot: DomSlot,
138        bundle: &mut BNode,
139    ) -> DomSlot {
140        self.reconcile(root, parent_scope, parent, slot, bundle)
141    }
142
143    fn reconcile(
144        self,
145        root: &BSubtree,
146        parent_scope: &AnyScope,
147        parent: &Element,
148        slot: DomSlot,
149        bundle: &mut BNode,
150    ) -> DomSlot {
151        match self {
152            VNode::VTag(vtag) => vtag.reconcile_node(root, parent_scope, parent, slot, bundle),
153            VNode::VText(vtext) => vtext.reconcile_node(root, parent_scope, parent, slot, bundle),
154            VNode::VComp(vcomp) => vcomp.reconcile_node(root, parent_scope, parent, slot, bundle),
155            VNode::VList(vlist) => vlist.reconcile_node(root, parent_scope, parent, slot, bundle),
156            VNode::VRef(node) => match bundle {
157                BNode::Ref(ref n) if &node == n => DomSlot::at(node),
158                _ => VNode::VRef(node).replace(root, parent_scope, parent, slot, bundle),
159            },
160            VNode::VPortal(vportal) => {
161                vportal.reconcile_node(root, parent_scope, parent, slot, bundle)
162            }
163            VNode::VSuspense(vsuspsense) => {
164                vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle)
165            }
166            VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle),
167        }
168    }
169}
170
171impl From<BText> for BNode {
172    #[inline]
173    fn from(btext: BText) -> Self {
174        Self::Text(btext)
175    }
176}
177
178impl From<BList> for BNode {
179    #[inline]
180    fn from(blist: BList) -> Self {
181        Self::List(blist)
182    }
183}
184
185impl From<BTag> for BNode {
186    #[inline]
187    fn from(btag: BTag) -> Self {
188        Self::Tag(Box::new(btag))
189    }
190}
191
192impl From<BComp> for BNode {
193    #[inline]
194    fn from(bcomp: BComp) -> Self {
195        Self::Comp(bcomp)
196    }
197}
198
199impl From<BPortal> for BNode {
200    #[inline]
201    fn from(bportal: BPortal) -> Self {
202        Self::Portal(bportal)
203    }
204}
205
206impl From<BSuspense> for BNode {
207    #[inline]
208    fn from(bsusp: BSuspense) -> Self {
209        Self::Suspense(Box::new(bsusp))
210    }
211}
212
213impl From<BRaw> for BNode {
214    #[inline]
215    fn from(braw: BRaw) -> Self {
216        Self::Raw(braw)
217    }
218}
219
220impl fmt::Debug for BNode {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        match *self {
223            Self::Tag(ref vtag) => vtag.fmt(f),
224            Self::Text(ref btext) => btext.fmt(f),
225            Self::Comp(ref bsusp) => bsusp.fmt(f),
226            Self::List(ref vlist) => vlist.fmt(f),
227            Self::Ref(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)),
228            Self::Portal(ref vportal) => vportal.fmt(f),
229            Self::Suspense(ref bsusp) => bsusp.fmt(f),
230            Self::Raw(ref braw) => braw.fmt(f),
231        }
232    }
233}
234
235#[cfg(feature = "hydration")]
236mod feat_hydration {
237    use super::*;
238    use crate::dom_bundle::{Fragment, Hydratable};
239
240    impl Hydratable for VNode {
241        fn hydrate(
242            self,
243            root: &BSubtree,
244            parent_scope: &AnyScope,
245            parent: &Element,
246            fragment: &mut Fragment,
247        ) -> Self::Bundle {
248            match self {
249                VNode::VTag(vtag) => vtag.hydrate(root, parent_scope, parent, fragment).into(),
250                VNode::VText(vtext) => vtext.hydrate(root, parent_scope, parent, fragment).into(),
251                VNode::VComp(vcomp) => vcomp.hydrate(root, parent_scope, parent, fragment).into(),
252                VNode::VList(vlist) => vlist.hydrate(root, parent_scope, parent, fragment).into(),
253                // You cannot hydrate a VRef.
254                VNode::VRef(_) => {
255                    panic!(
256                        "VRef is not hydratable. Try moving it to a component mounted after an \
257                         effect."
258                    )
259                }
260                // You cannot hydrate a VPortal.
261                VNode::VPortal(_) => {
262                    panic!(
263                        "VPortal is not hydratable. Try creating your portal by delaying it with \
264                         use_effect."
265                    )
266                }
267                VNode::VSuspense(vsuspense) => vsuspense
268                    .hydrate(root, parent_scope, parent, fragment)
269                    .into(),
270                VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(),
271            }
272        }
273    }
274}
275
276#[cfg(target_arch = "wasm32")]
277#[cfg(test)]
278mod layout_tests {
279    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
280
281    use super::*;
282    use crate::tests::layout_tests::{diff_layouts, TestLayout};
283
284    wasm_bindgen_test_configure!(run_in_browser);
285
286    #[test]
287    fn diff() {
288        let document = gloo::utils::document();
289        let vref_node_1 = VNode::VRef(document.create_element("i").unwrap().into());
290        let vref_node_2 = VNode::VRef(document.create_element("b").unwrap().into());
291
292        let layout1 = TestLayout {
293            name: "1",
294            node: vref_node_1,
295            expected: "<i></i>",
296        };
297
298        let layout2 = TestLayout {
299            name: "2",
300            node: vref_node_2,
301            expected: "<b></b>",
302        };
303
304        diff_layouts(vec![layout1, layout2]);
305    }
306}