kozan_core/dom/text.rs
1//! `Text` — a leaf node containing text content.
2//!
3//! Like Chrome's `Text` class: inherits from `CharacterData → Node`.
4//! Text nodes are NOT elements — they have no tag name, no attributes,
5//! and cannot have children.
6//!
7//! `text.append(child)` is a **compile error** because `Text`
8//! only implements `Node`, not `ContainerNode`.
9
10use kozan_macros::Node;
11
12use crate::Handle;
13
14/// A text node. Leaf only — cannot have children.
15///
16/// Implements `Node` but NOT `ContainerNode` or `Element`.
17/// Access text content via `content()` / `set_content()`.
18#[derive(Copy, Clone, Node)]
19pub struct Text(Handle);
20
21/// Text content storage in `DataStorage`.
22#[derive(Default, Clone)]
23pub struct TextData {
24 pub content: String,
25}
26
27impl Text {
28 /// Wrap a Handle into a Text node. Called by `Document::create_text`.
29 pub(crate) fn from_raw(handle: Handle) -> Self {
30 Self(handle)
31 }
32
33 /// Get the text content.
34 #[inline]
35 #[must_use]
36 pub fn content(&self) -> String {
37 self.0
38 .read_data::<TextData, _>(|d| d.content.clone())
39 .unwrap_or_default()
40 }
41
42 /// Set the text content. No-op if value is unchanged.
43 ///
44 /// Chrome: `CharacterData::setData()` — compares old vs new, skips if same.
45 /// When text changes, marks parent for restyle + sets `needs_layout`.
46 pub fn set_content(&self, value: impl Into<String>) {
47 let value = value.into();
48 let changed = self
49 .0
50 .write_data::<TextData, _>(|d| {
51 if d.content == value {
52 return false; // Same value — no-op.
53 }
54 d.content = value;
55 true
56 })
57 .unwrap_or(false);
58
59 if changed {
60 // Text content changed → parent needs relayout (text affects sizing).
61 // Text nodes have no ElementData, so mark the parent element.
62 self.0.mark_parent_needs_layout();
63 }
64 }
65}