kozan_core/dom/traits.rs
1//! DOM trait hierarchy — Chrome's class hierarchy as Rust traits.
2//!
3//! ```text
4//! HasHandle ← fn handle() -> Handle (base, derives generate this)
5//! EventTarget ← on(), off(), dispatch() [in events/ module]
6//! Node ← parent, siblings, raw() [Chrome: core/dom/node.h]
7//! ContainerNode ← append, children [Chrome: core/dom/container_node.h]
8//! Element ← attributes, tag name [Chrome: core/dom/element.h]
9//! ```
10//!
11//! `HasHandle` lives here. `EventTarget` lives in `events/`.
12//! Both `Node` and `Window` extend `EventTarget` (same as Chrome).
13
14use crate::dom::handle::Handle;
15use crate::dom::node::NodeType;
16use crate::events::EventTarget;
17use crate::id::RawId;
18
19/// The single base trait: provides access to the inner [`Handle`].
20///
21/// This is the ONE method that derive macros generate.
22/// Every other trait in the hierarchy builds on top with default
23/// implementations — zero boilerplate for implementors.
24///
25/// # Implementors
26///
27/// - All DOM node types (`Text`, `HtmlDivElement`, `HtmlButtonElement`, ...)
28/// - `Window` (future)
29/// - Custom elements defined by users
30pub trait HasHandle: Copy + 'static {
31 /// Get the inner [`Handle`] for this node/target.
32 ///
33 /// The Handle provides low-level access to the document's parallel arenas.
34 /// Most users should call trait methods instead of using the Handle directly.
35 fn handle(&self) -> Handle;
36}
37
38/// All DOM node types: Text, Element, Document.
39///
40/// Like Chrome's `Node` class (`core/dom/node.h`).
41/// Extends [`EventTarget`] — every node can receive events.
42/// Provides tree position (parent, siblings) and identity (raw ID, node kind).
43///
44/// # What implements `Node`
45///
46/// - [`Text`](crate::Text) — text content, no children
47/// - All elements via [`Element`] → [`ContainerNode`] → `Node`
48/// - Document root (accessed via [`Document::root()`](crate::Document::root))
49pub trait Node: EventTarget {
50 /// Check if this node is still alive in the document.
51 ///
52 /// Returns `false` after [`destroy()`](Self::destroy) is called.
53 /// All other methods return defaults (None, empty, false) on dead nodes.
54 fn is_alive(&self) -> bool {
55 self.handle().is_alive()
56 }
57
58 /// Get the parent node, or `None` if this is the root or detached.
59 fn parent(&self) -> Option<Handle> {
60 self.handle().parent()
61 }
62
63 /// Get the next sibling in the parent's child list.
64 fn next_sibling(&self) -> Option<Handle> {
65 self.handle().next_sibling()
66 }
67
68 /// Get the previous sibling in the parent's child list.
69 fn prev_sibling(&self) -> Option<Handle> {
70 self.handle().prev_sibling()
71 }
72
73 /// Detach this node from its parent. The node stays alive but is no longer in the tree.
74 fn detach(&self) {
75 self.handle().detach();
76 }
77
78 /// Destroy this node. Frees the slot — all handles to it become stale.
79 fn destroy(&self) {
80 self.handle().destroy();
81 }
82
83 /// Get the raw ID for cross-thread communication.
84 ///
85 /// [`RawId`] is `Send` — use it to reference nodes from background tasks.
86 /// Resolve back to a Handle via [`Document::resolve()`](crate::Document::resolve).
87 fn raw(&self) -> RawId {
88 self.handle().raw()
89 }
90
91 /// Get the DOM node type (Element, Text, Document, etc.).
92 fn node_kind(&self) -> Option<NodeType> {
93 self.handle().node_kind()
94 }
95
96 /// Is this an element node?
97 fn is_element(&self) -> bool {
98 self.handle().is_element()
99 }
100
101 /// Is this a text node?
102 fn is_text(&self) -> bool {
103 self.handle().is_text()
104 }
105
106 /// Is this the document root?
107 fn is_document(&self) -> bool {
108 self.handle().is_document()
109 }
110
111 // ---- Style ----
112
113 /// Per-property style access — like JavaScript's `element.style`.
114 fn style(&self) -> crate::styling::builder::StyleAccess {
115 self.handle().style()
116 }
117
118 /// Apply styles via closure and return self for chaining.
119 ///
120 /// ```ignore
121 /// doc.div()
122 /// .styled(|s| s.width(px(200.0)).height(px(100.0)).background_color(rgb8(255, 0, 0)))
123 /// .child(doc.div().styled(|s| s.width(px(50.0)).height(px(50.0))))
124 /// ```
125 fn styled(self, f: impl FnOnce(&mut crate::styling::builder::StyleAccess)) -> Self {
126 f(&mut self.handle().style());
127 self
128 }
129}
130
131/// Nodes that can have children: Element and Document.
132///
133/// Like Chrome's `ContainerNode` (`core/dom/container_node.h`).
134/// Text nodes do NOT implement this — `text.append(child)` is a **compile error**.
135pub trait ContainerNode: Node {
136 /// Append `child` as the last child of this node.
137 ///
138 /// If `child` is already attached elsewhere, it is detached first (reparenting).
139 fn append(&self, child: impl HasHandle) {
140 self.handle().append(child.handle());
141 }
142
143 /// Append a child and return self for chaining (GPUI-style).
144 ///
145 /// Elements are `Copy` — zero-cost value return.
146 ///
147 /// ```ignore
148 /// doc.root().child(
149 /// doc.div()
150 /// .child(doc.div().bg(rgb8(255, 0, 0)))
151 /// .child(doc.div().bg(rgb8(0, 255, 0)))
152 /// );
153 /// ```
154 fn child(self, child: impl HasHandle) -> Self {
155 self.handle().append(child.handle());
156 self
157 }
158
159 /// Append multiple children at once.
160 fn add_children<I, C>(self, items: I) -> Self
161 where
162 I: IntoIterator<Item = C>,
163 C: HasHandle,
164 {
165 for c in items {
166 self.handle().append(c.handle());
167 }
168 self
169 }
170
171 /// Insert `child` before this node in the parent's child list.
172 fn insert_before(&self, child: impl HasHandle) {
173 self.handle().insert_before(child.handle());
174 }
175
176 /// Get the first child, or `None` if empty.
177 fn first_child(&self) -> Option<Handle> {
178 self.handle().first_child()
179 }
180
181 /// Get the last child, or `None` if empty.
182 fn last_child(&self) -> Option<Handle> {
183 self.handle().last_child()
184 }
185
186 /// Collect all children as Handles.
187 fn children(&self) -> Vec<Handle> {
188 self.handle().children()
189 }
190}
191
192/// Element nodes — attributes, tag name, class, id.
193///
194/// Like Chrome's `Element` (`core/dom/element.h`).
195/// Text and Document do NOT implement this.
196pub trait Element: ContainerNode {
197 /// Element-specific data type (e.g., `ButtonData`). Use `()` if none.
198 type Data: Default + 'static;
199
200 /// The default tag name (e.g., `"div"`, `"button"`, `"input"`).
201 ///
202 /// Empty string means this element type has no fixed tag and MUST be
203 /// created via `Document::create_with_tag()` (e.g., `HtmlHeadingElement`
204 /// represents h1-h6 — the tag varies at runtime).
205 ///
206 /// Chrome: tag name is always runtime data on `Element::tagName()`.
207 /// This const is a Kozan convenience for single-tag elements.
208 const TAG_NAME: &'static str = "";
209
210 /// Whether this element is focusable by default.
211 const IS_FOCUSABLE: bool = false;
212
213 /// Wrap a raw Handle into this element's typed handle.
214 ///
215 /// Called by [`Document::create()`](crate::Document::create).
216 fn from_handle(handle: Handle) -> Self;
217
218 /// Get the tag name (runtime — reads from `ElementData`).
219 ///
220 /// Usually matches `TAG_NAME`, but can differ for elements created
221 /// via `Document::create_with_tag()` (e.g., `HtmlHeadingElement` represents h1-h6).
222 /// Chrome: `Element::tagName()` always returns the actual tag.
223 fn tag_name(&self) -> &'static str {
224 self.handle().tag_name().unwrap_or(Self::TAG_NAME)
225 }
226
227 /// Get the `id` attribute.
228 fn id(&self) -> String {
229 self.handle().id()
230 }
231
232 /// Set the `id` attribute.
233 fn set_id(&self, id: impl Into<String>) {
234 self.handle().set_id(id);
235 }
236
237 /// Get the `class` attribute.
238 fn class_name(&self) -> String {
239 self.handle().class_name()
240 }
241
242 /// Set the `class` attribute.
243 fn set_class_name(&self, class: impl Into<String>) {
244 self.handle().set_class_name(class);
245 }
246
247 // ── ClassList — Chrome: element.classList (DOMTokenList) ──
248
249 /// Add a CSS class. No-op if already present.
250 /// Chrome: `element.classList.add("name")`.
251 fn class_add(&self, name: &str) {
252 self.handle().class_add(name);
253 }
254
255 /// Remove a CSS class. No-op if absent.
256 /// Chrome: `element.classList.remove("name")`.
257 fn class_remove(&self, name: &str) {
258 self.handle().class_remove(name);
259 }
260
261 /// Toggle a CSS class. Returns true if now present.
262 /// Chrome: `element.classList.toggle("name")`.
263 fn class_toggle(&self, name: &str) -> bool {
264 self.handle().class_toggle(name)
265 }
266
267 /// Check if an element has a CSS class.
268 /// Chrome: `element.classList.contains("name")`.
269 fn class_contains(&self, name: &str) -> bool {
270 self.handle().class_contains(name)
271 }
272
273 // ── Attributes ──
274
275 /// Returns the attribute value for `name`, or `None` if absent.
276 fn attribute(&self, name: &str) -> Option<String> {
277 self.handle().attribute(name)
278 }
279
280 /// Set an attribute.
281 fn set_attribute(&self, name: &str, value: impl Into<String>) {
282 self.handle().set_attribute(name, value);
283 }
284
285 /// Remove an attribute. Returns the old value.
286 fn remove_attribute(&self, name: &str) -> Option<String> {
287 self.handle().remove_attribute(name)
288 }
289}