1use wasm_bindgen::JsCast;
2use web_sys::{Element, Node};
3
4use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
5use crate::html::AnyScope;
6use crate::virtual_dom::vtag::{MATHML_NAMESPACE, SVG_NAMESPACE};
7use crate::virtual_dom::VRaw;
8use crate::AttrValue;
9
10#[derive(Debug)]
11pub struct BRaw {
12 reference: Option<Node>,
13 children_count: usize,
14 html: AttrValue,
15}
16
17impl BRaw {
18 fn create_elements(html: &str, parent_namespace: Option<&str>) -> Vec<Node> {
19 let div = gloo::utils::document()
20 .create_element_ns(parent_namespace, "div")
21 .unwrap();
22 div.set_inner_html(html);
23 let children = div.child_nodes();
24 let children = js_sys::Array::from(&children);
25 let children = children.to_vec();
26 children
27 .into_iter()
28 .map(|it| it.unchecked_into())
29 .collect::<Vec<_>>()
30 }
31
32 fn detach_bundle(&self, parent: &Element) {
33 let mut next_node = self.reference.clone();
34 for _ in 0..self.children_count {
35 if let Some(node) = next_node {
36 next_node = node.next_sibling();
37 parent.remove_child(&node).unwrap();
38 }
39 }
40 }
41
42 fn position(&self, next_slot: DomSlot) -> DomSlot {
43 self.reference
44 .as_ref()
45 .map(|n| DomSlot::at(n.clone()))
46 .unwrap_or(next_slot)
47 }
48}
49
50impl ReconcileTarget for BRaw {
51 fn detach(self, _root: &BSubtree, parent: &Element, _parent_to_detach: bool) {
52 self.detach_bundle(parent);
53 }
54
55 fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
56 let mut next_node = self.reference.clone();
57 for _ in 0..self.children_count {
58 if let Some(node) = next_node {
59 next_node = node.next_sibling();
60 slot.insert(next_parent, &node);
61 }
62 }
63 self.position(slot)
64 }
65}
66
67impl Reconcilable for VRaw {
68 type Bundle = BRaw;
69
70 fn attach(
71 self,
72 _root: &BSubtree,
73 _parent_scope: &AnyScope,
74 parent: &Element,
75 slot: DomSlot,
76 ) -> (DomSlot, Self::Bundle) {
77 let namespace = if parent.namespace_uri().is_some_and(|ns| ns == SVG_NAMESPACE) {
78 Some(SVG_NAMESPACE)
79 } else if parent
80 .namespace_uri()
81 .is_some_and(|ns| ns == MATHML_NAMESPACE)
82 {
83 Some(MATHML_NAMESPACE)
84 } else {
85 None
86 };
87
88 let elements = BRaw::create_elements(&self.html, namespace);
89 let count = elements.len();
90 let mut iter = elements.into_iter();
91 let reference = iter.next();
92 if let Some(ref first) = reference {
93 slot.insert(parent, first);
94 for ref child in iter {
95 slot.insert(parent, child);
96 }
97 }
98 let this = BRaw {
99 reference,
100 children_count: count,
101 html: self.html,
102 };
103 (this.position(slot), this)
104 }
105
106 fn reconcile_node(
107 self,
108 root: &BSubtree,
109 parent_scope: &AnyScope,
110 parent: &Element,
111 slot: DomSlot,
112 bundle: &mut BNode,
113 ) -> DomSlot {
114 match bundle {
115 BNode::Raw(raw) if raw.html == self.html => raw.position(slot),
116 BNode::Raw(raw) => self.reconcile(root, parent_scope, parent, slot, raw),
117 _ => self.replace(root, parent_scope, parent, slot, bundle),
118 }
119 }
120
121 fn reconcile(
122 self,
123 root: &BSubtree,
124 parent_scope: &AnyScope,
125 parent: &Element,
126 slot: DomSlot,
127 bundle: &mut Self::Bundle,
128 ) -> DomSlot {
129 if self.html != bundle.html {
130 bundle.detach_bundle(parent);
133 let (node_ref, braw) = self.attach(root, parent_scope, parent, slot);
134 *bundle = braw;
135 node_ref
136 } else {
137 bundle.position(slot)
138 }
139 }
140}
141
142#[cfg(feature = "hydration")]
143mod feat_hydration {
144 use super::*;
145 use crate::dom_bundle::{Fragment, Hydratable};
146 use crate::virtual_dom::Collectable;
147
148 impl Hydratable for VRaw {
149 fn hydrate(
150 self,
151 _root: &BSubtree,
152 _parent_scope: &AnyScope,
153 parent: &Element,
154 fragment: &mut Fragment,
155 ) -> Self::Bundle {
156 let collectable = Collectable::Raw;
157 let fallback_fragment = Fragment::collect_between(fragment, &collectable, parent);
158
159 let Self { html } = self;
160
161 BRaw {
162 children_count: fallback_fragment.len(),
163 reference: fallback_fragment.iter().next().cloned(),
164 html,
165 }
166 }
167 }
168}
169
170#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
171#[cfg(test)]
172mod tests {
173 use gloo::utils::document;
174 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
175
176 use super::*;
177 use crate::dom_bundle::utils::{
178 setup_parent, setup_parent_and_sibling, setup_parent_svg, SIBLING_CONTENT,
179 };
180 use crate::virtual_dom::VNode;
181
182 wasm_bindgen_test_configure!(run_in_browser);
183
184 #[test]
185 fn braw_works_one_node() {
186 let (root, scope, parent) = setup_parent();
187
188 const HTML: &str = "<span>text</span>";
189 let elem = VNode::from_html_unchecked(HTML.into());
190 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
191 assert_braw(&mut elem);
192 assert_eq!(parent.inner_html(), HTML);
193 }
194
195 #[test]
196 fn braw_works_svg() {
197 let (root, scope, parent) = setup_parent_svg();
198
199 const HTML: &str = r#"<circle cx="50" cy="50" r="40"></circle>"#;
200 let elem = VNode::from_html_unchecked(HTML.into());
201 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
202 assert_braw(&mut elem);
203 assert_eq!(parent.inner_html(), HTML);
204 assert_eq!(
205 parent
206 .first_child()
207 .unwrap()
208 .unchecked_into::<Element>()
209 .namespace_uri(),
210 Some(SVG_NAMESPACE.to_owned())
211 );
212 }
213
214 #[test]
215 fn braw_works_no_node() {
216 let (root, scope, parent) = setup_parent();
217
218 const HTML: &str = "";
219 let elem = VNode::from_html_unchecked(HTML.into());
220 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
221 assert_braw(&mut elem);
222 assert_eq!(parent.inner_html(), HTML)
223 }
224
225 #[test]
226 fn braw_works_one_node_nested() {
227 let (root, scope, parent) = setup_parent();
228
229 const HTML: &str =
230 r#"<p>one <a href="https://yew.rs">link</a> more paragraph</p><div>here</div>"#;
231 let elem = VNode::from_html_unchecked(HTML.into());
232 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
233 assert_braw(&mut elem);
234 assert_eq!(parent.inner_html(), HTML)
235 }
236 #[test]
237 fn braw_works_multi_top_nodes() {
238 let (root, scope, parent) = setup_parent();
239
240 const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
241 let elem = VNode::from_html_unchecked(HTML.into());
242 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
243 assert_braw(&mut elem);
244 assert_eq!(parent.inner_html(), HTML)
245 }
246
247 #[test]
248 fn braw_detach_works_multi_node() {
249 let (root, scope, parent) = setup_parent();
250
251 const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
252 let elem = VNode::from_html_unchecked(HTML.into());
253 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
254 assert_braw(&mut elem);
255 assert_eq!(parent.inner_html(), HTML);
256 elem.detach(&root, &parent, false);
257 assert_eq!(parent.inner_html(), "");
258 }
259
260 #[test]
261 fn braw_detach_works_single_node() {
262 let (root, scope, parent) = setup_parent();
263
264 const HTML: &str = r#"<p>paragraph</p>"#;
265 let elem = VNode::from_html_unchecked(HTML.into());
266 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
267 assert_braw(&mut elem);
268 assert_eq!(parent.inner_html(), HTML);
269 elem.detach(&root, &parent, false);
270 assert_eq!(parent.inner_html(), "");
271 }
272
273 #[test]
274 fn braw_detach_works_empty() {
275 let (root, scope, parent) = setup_parent();
276
277 const HTML: &str = "";
278 let elem = VNode::from_html_unchecked(HTML.into());
279 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
280 assert_braw(&mut elem);
281 assert_eq!(parent.inner_html(), HTML);
282 elem.detach(&root, &parent, false);
283 assert_eq!(parent.inner_html(), "");
284 }
285
286 #[test]
287 fn braw_works_one_node_sibling_attached() {
288 let (root, scope, parent, sibling) = setup_parent_and_sibling();
289
290 const HTML: &str = "<span>text</span>";
291 let elem = VNode::from_html_unchecked(HTML.into());
292 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
293 assert_braw(&mut elem);
294 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
295 }
296
297 #[test]
298 fn braw_works_no_node_sibling_attached() {
299 let (root, scope, parent, sibling) = setup_parent_and_sibling();
300
301 const HTML: &str = "";
302 let elem = VNode::from_html_unchecked(HTML.into());
303 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
304 assert_braw(&mut elem);
305 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
306 }
307
308 #[test]
309 fn braw_works_one_node_nested_sibling_attached() {
310 let (root, scope, parent, sibling) = setup_parent_and_sibling();
311
312 const HTML: &str =
313 r#"<p>one <a href="https://yew.rs">link</a> more paragraph</p><div>here</div>"#;
314 let elem = VNode::from_html_unchecked(HTML.into());
315 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
316 assert_braw(&mut elem);
317 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
318 }
319 #[test]
320 fn braw_works_multi_top_nodes_sibling_attached() {
321 let (root, scope, parent, sibling) = setup_parent_and_sibling();
322
323 const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
324 let elem = VNode::from_html_unchecked(HTML.into());
325 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
326 assert_braw(&mut elem);
327 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
328 }
329
330 #[test]
331 fn braw_detach_works_multi_node_sibling_attached() {
332 let (root, scope, parent, sibling) = setup_parent_and_sibling();
333
334 const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
335 let elem = VNode::from_html_unchecked(HTML.into());
336 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
337 assert_braw(&mut elem);
338 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
339 elem.detach(&root, &parent, false);
340 assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT))
341 }
342
343 #[test]
344 fn braw_detach_works_single_node_sibling_attached() {
345 let (root, scope, parent, sibling) = setup_parent_and_sibling();
346
347 const HTML: &str = r#"<p>paragraph</p>"#;
348 let elem = VNode::from_html_unchecked(HTML.into());
349 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
350 assert_braw(&mut elem);
351 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
352 elem.detach(&root, &parent, false);
353 assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT))
354 }
355
356 #[test]
357 fn braw_detach_works_empty_sibling_attached() {
358 let (root, scope, parent, sibling) = setup_parent_and_sibling();
359
360 const HTML: &str = "";
361 let elem = VNode::from_html_unchecked(HTML.into());
362 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
363 assert_braw(&mut elem);
364 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
365 elem.detach(&root, &parent, false);
366 assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT))
367 }
368
369 #[test]
370 fn braw_shift_works() {
371 let (root, scope, parent) = setup_parent();
372 const HTML: &str = r#"<p>paragraph</p>"#;
373
374 let elem = VNode::from_html_unchecked(HTML.into());
375 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
376 assert_braw(&mut elem);
377 assert_eq!(parent.inner_html(), HTML);
378
379 let new_parent = document().create_element("section").unwrap();
380 document().body().unwrap().append_child(&parent).unwrap();
381
382 elem.shift(&new_parent, DomSlot::at_end());
383
384 assert_eq!(new_parent.inner_html(), HTML);
385 assert_eq!(parent.inner_html(), "");
386 }
387
388 #[test]
389 fn braw_shift_with_sibling_works() {
390 let (root, scope, parent, sibling) = setup_parent_and_sibling();
391 const HTML: &str = r#"<p>paragraph</p>"#;
392
393 let elem = VNode::from_html_unchecked(HTML.into());
394 let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling);
395 assert_braw(&mut elem);
396 assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT));
397
398 let new_parent = document().create_element("section").unwrap();
399 document().body().unwrap().append_child(&parent).unwrap();
400
401 let new_sibling = document().create_text_node(SIBLING_CONTENT);
402 new_parent.append_child(&new_sibling).unwrap();
403 let new_sibling_ref = DomSlot::at(new_sibling.into());
404
405 elem.shift(&new_parent, new_sibling_ref);
406
407 assert_eq!(parent.inner_html(), SIBLING_CONTENT);
408
409 assert_eq!(
410 new_parent.inner_html(),
411 format!("{}{}", HTML, SIBLING_CONTENT)
412 );
413 }
414
415 #[test]
416 fn braw_shift_works_multi_node() {
417 let (root, scope, parent) = setup_parent();
418 const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
419
420 let elem = VNode::from_html_unchecked(HTML.into());
421 let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
422 assert_braw(&mut elem);
423 assert_eq!(parent.inner_html(), HTML);
424
425 let new_parent = document().create_element("section").unwrap();
426 document().body().unwrap().append_child(&parent).unwrap();
427
428 elem.shift(&new_parent, DomSlot::at_end());
429
430 assert_eq!(parent.inner_html(), "");
431 assert_eq!(new_parent.inner_html(), HTML);
432 }
433
434 fn assert_braw(node: &mut BNode) -> &mut BRaw {
435 if let BNode::Raw(braw) = node {
436 return braw;
437 }
438 panic!("should be braw");
439 }
440}