This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

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