1use 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
13pub(super) enum BNode {
15 Tag(Box<BTag>),
17 Text(BText),
19 Comp(BComp),
21 List(BList),
23 Portal(BPortal),
25 Ref(Node),
27 Suspense(Box<BSuspense>),
29 Raw(BRaw),
31}
32
33impl BNode {
34 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 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 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 VNode::VRef(_) => {
293 panic!(
294 "VRef is not hydratable. Try moving it to a component mounted after an \
295 effect."
296 )
297 }
298 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}