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::virtual_dom::{Key, VNode};
11
12pub(super) enum BNode {
14 Tag(Box<BTag>),
16 Text(BText),
18 Comp(BComp),
20 List(BList),
22 Portal(BPortal),
24 Ref(Node),
26 Suspense(Box<BSuspense>),
28 Raw(BRaw),
30}
31
32impl BNode {
33 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 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 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 VNode::VRef(_) => {
255 panic!(
256 "VRef is not hydratable. Try moving it to a component mounted after an \
257 effect."
258 )
259 }
260 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}