1use 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
10pub(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 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 if m.node_type() == Node::TEXT_NODE {
108 let m = m.unchecked_into::<TextNode>();
109 fragment.pop_front();
111
112 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 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}