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

yew/dom_bundle/
braw.rs

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            // we don't have a way to diff what's changed in the string so we remove the node and
131            // reattach it
132            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}