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

yew/dom_bundle/
btext.rs

1//! This module contains the bundle implementation of text [BText].
2
3use gloo::utils::document;
4use web_sys::{Element, Text as TextNode};
5
6use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
7use crate::html::AnyScope;
8use crate::virtual_dom::{AttrValue, VText};
9
10/// The bundle implementation to [VText]
11pub(super) struct BText {
12    text: AttrValue,
13    text_node: TextNode,
14}
15
16impl ReconcileTarget for BText {
17    fn detach(self, _root: &BSubtree, parent: &Element, parent_to_detach: bool) {
18        if !parent_to_detach {
19            let result = parent.remove_child(&self.text_node);
20
21            if result.is_err() {
22                tracing::warn!("Node not found to remove VText");
23            }
24        }
25    }
26
27    fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
28        slot.insert(next_parent, &self.text_node);
29
30        DomSlot::at(self.text_node.clone().into())
31    }
32}
33
34impl Reconcilable for VText {
35    type Bundle = BText;
36
37    fn attach(
38        self,
39        _root: &BSubtree,
40        _parent_scope: &AnyScope,
41        parent: &Element,
42        slot: DomSlot,
43    ) -> (DomSlot, Self::Bundle) {
44        let Self { text } = self;
45        let text_node = document().create_text_node(&text);
46        slot.insert(parent, &text_node);
47        let node_ref = DomSlot::at(text_node.clone().into());
48        (node_ref, BText { text, text_node })
49    }
50
51    /// Renders virtual node over existing `TextNode`, but only if value of text has changed.
52    fn reconcile_node(
53        self,
54        root: &BSubtree,
55        parent_scope: &AnyScope,
56        parent: &Element,
57        slot: DomSlot,
58        bundle: &mut BNode,
59    ) -> DomSlot {
60        match bundle {
61            BNode::Text(btext) => self.reconcile(root, parent_scope, parent, slot, btext),
62            _ => self.replace(root, parent_scope, parent, slot, bundle),
63        }
64    }
65
66    fn reconcile(
67        self,
68        _root: &BSubtree,
69        _parent_scope: &AnyScope,
70        _parent: &Element,
71        _slot: DomSlot,
72        btext: &mut Self::Bundle,
73    ) -> DomSlot {
74        let Self { text } = self;
75        let ancestor_text = std::mem::replace(&mut btext.text, text);
76        if btext.text != ancestor_text {
77            btext.text_node.set_node_value(Some(&btext.text));
78        }
79        DomSlot::at(btext.text_node.clone().into())
80    }
81}
82
83impl std::fmt::Debug for BText {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("BText").field("text", &self.text).finish()
86    }
87}
88
89#[cfg(feature = "hydration")]
90mod feat_hydration {
91    use wasm_bindgen::JsCast;
92    use web_sys::Node;
93
94    use super::*;
95    use crate::dom_bundle::{Fragment, Hydratable};
96
97    impl Hydratable for VText {
98        fn hydrate(
99            self,
100            _root: &BSubtree,
101            _parent_scope: &AnyScope,
102            parent: &Element,
103            fragment: &mut Fragment,
104        ) -> Self::Bundle {
105            let next_sibling = if let Some(m) = fragment.front().cloned() {
106                // better safe than sorry.
107                if m.node_type() == Node::TEXT_NODE {
108                    let m = m.unchecked_into::<TextNode>();
109                    // pop current node.
110                    fragment.pop_front();
111
112                    // TODO: It may make sense to assert the text content in the text node
113                    // against the VText when #[cfg(debug_assertions)]
114                    // is true, but this may be complicated.
115                    // We always replace the text value for now.
116                    //
117                    // Please see the next comment for a detailed explanation.
118                    m.set_node_value(Some(self.text.as_ref()));
119
120                    return BText {
121                        text: self.text,
122                        text_node: m,
123                    };
124                }
125                Some(m)
126            } else {
127                fragment.sibling_at_end().cloned()
128            };
129
130            // If there are multiple text nodes placed back-to-back in SSR, it may be parsed as a
131            // single text node by browser, hence we need to add extra text nodes here
132            // if the next node is not a text node. Similarly, the value of the text
133            // node may be a combination of multiple VText vnodes. So we always need to
134            // override their values.
135            let text_node = document().create_text_node("");
136            DomSlot::create(next_sibling).insert(parent, &text_node);
137            BText {
138                text: "".into(),
139                text_node,
140            }
141        }
142    }
143}
144
145#[cfg(test)]
146mod test {
147    extern crate self as yew;
148
149    #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
150    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
151
152    use crate::html;
153
154    #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
155    wasm_bindgen_test_configure!(run_in_browser);
156
157    #[test]
158    fn text_as_root() {
159        let _ = html! {
160            "Text Node As Root"
161        };
162
163        let _ = html! {
164            { "Text Node As Root" }
165        };
166    }
167}
168
169#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
170#[cfg(test)]
171mod layout_tests {
172    extern crate self as yew;
173
174    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
175
176    use crate::html;
177    use crate::tests::layout_tests::{diff_layouts, TestLayout};
178
179    wasm_bindgen_test_configure!(run_in_browser);
180
181    #[test]
182    fn diff() {
183        let layout1 = TestLayout {
184            name: "1",
185            node: html! { "a" },
186            expected: "a",
187        };
188
189        let layout2 = TestLayout {
190            name: "2",
191            node: html! { "b" },
192            expected: "b",
193        };
194
195        let layout3 = TestLayout {
196            name: "3",
197            node: html! {
198                <>
199                    {"a"}
200                    {"b"}
201                </>
202            },
203            expected: "ab",
204        };
205
206        let layout4 = TestLayout {
207            name: "4",
208            node: html! {
209                <>
210                    {"b"}
211                    {"a"}
212                </>
213            },
214            expected: "ba",
215        };
216
217        diff_layouts(vec![layout1, layout2, layout3, layout4]);
218    }
219}