use gloo::utils::document;
use web_sys::{Element, Text as TextNode};
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::{AttrValue, VText};
pub(super) struct BText {
text: AttrValue,
text_node: TextNode,
}
impl ReconcileTarget for BText {
fn detach(self, _root: &BSubtree, parent: &Element, parent_to_detach: bool) {
if !parent_to_detach {
let result = parent.remove_child(&self.text_node);
if result.is_err() {
tracing::warn!("Node not found to remove VText");
}
}
}
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
slot.insert(next_parent, &self.text_node);
DomSlot::at(self.text_node.clone().into())
}
}
impl Reconcilable for VText {
type Bundle = BText;
fn attach(
self,
_root: &BSubtree,
_parent_scope: &AnyScope,
parent: &Element,
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let Self { text } = self;
let text_node = document().create_text_node(&text);
slot.insert(parent, &text_node);
let node_ref = DomSlot::at(text_node.clone().into());
(node_ref, BText { text, text_node })
}
fn reconcile_node(
self,
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
slot: DomSlot,
bundle: &mut BNode,
) -> DomSlot {
match bundle {
BNode::Text(btext) => self.reconcile(root, parent_scope, parent, slot, btext),
_ => self.replace(root, parent_scope, parent, slot, bundle),
}
}
fn reconcile(
self,
_root: &BSubtree,
_parent_scope: &AnyScope,
_parent: &Element,
_slot: DomSlot,
btext: &mut Self::Bundle,
) -> DomSlot {
let Self { text } = self;
let ancestor_text = std::mem::replace(&mut btext.text, text);
if btext.text != ancestor_text {
btext.text_node.set_node_value(Some(&btext.text));
}
DomSlot::at(btext.text_node.clone().into())
}
}
impl std::fmt::Debug for BText {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BText").field("text", &self.text).finish()
}
}
#[cfg(feature = "hydration")]
mod feat_hydration {
use wasm_bindgen::JsCast;
use web_sys::Node;
use super::*;
use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable};
impl Hydratable for VText {
fn hydrate(
self,
_root: &BSubtree,
_parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
previous_next_sibling: &mut Option<DynamicDomSlot>,
) -> Self::Bundle {
let create_at = |next_sibling: Option<Node>, text: AttrValue| {
let text_node = document().create_text_node(text.as_ref());
DomSlot::create(next_sibling).insert(parent, &text_node);
BText { text, text_node }
};
let btext = if let Some(m) = fragment.front().cloned() {
if m.node_type() == Node::TEXT_NODE {
let m = m.unchecked_into::<TextNode>();
fragment.pop_front();
m.set_node_value(Some(self.text.as_ref()));
BText {
text: self.text,
text_node: m,
}
} else {
create_at(Some(m), self.text)
}
} else {
create_at(fragment.sibling_at_end().cloned(), self.text)
};
if let Some(previous_next_sibling) = previous_next_sibling {
previous_next_sibling.reassign(DomSlot::at(btext.text_node.clone().into()));
}
*previous_next_sibling = None;
btext
}
}
}
#[cfg(test)]
mod test {
extern crate self as yew;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use crate::html;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
wasm_bindgen_test_configure!(run_in_browser);
#[test]
fn text_as_root() {
let _ = html! {
"Text Node As Root"
};
let _ = html! {
{ "Text Node As Root" }
};
}
}
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
#[cfg(test)]
mod layout_tests {
extern crate self as yew;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use crate::html;
use crate::tests::layout_tests::{diff_layouts, TestLayout};
wasm_bindgen_test_configure!(run_in_browser);
#[test]
fn diff() {
let layout1 = TestLayout {
name: "1",
node: html! { "a" },
expected: "a",
};
let layout2 = TestLayout {
name: "2",
node: html! { "b" },
expected: "b",
};
let layout3 = TestLayout {
name: "3",
node: html! {
<>
{"a"}
{"b"}
</>
},
expected: "ab",
};
let layout4 = TestLayout {
name: "4",
node: html! {
<>
{"b"}
{"a"}
</>
},
expected: "ba",
};
diff_layouts(vec![layout1, layout2, layout3, layout4]);
}
}