Skip to main content

kozan_core/html/
html_element.rs

1//! `HtmlElement` — shared behavior for ALL HTML elements.
2//!
3//! Like Chrome's `HTMLElement` (500+ lines of shared logic).
4//! Every HTML element (Div, Button, Input, etc.) implements this trait.
5//! SVG/MathML elements would NOT implement it.
6//!
7//! All methods have **default implementations** — concrete elements
8//! get them for free. Override only what differs (like Chrome's virtual methods).
9//!
10//! # Chrome equivalence
11//!
12//! | Chrome method             | Kozan trait method      |
13//! |---------------------------|-------------------------|
14//! | `hidden()`                | `hidden()`              |
15//! | `title()`                 | `title()`               |
16//! | `lang()`                  | `lang()`                |
17//! | `dir()`                   | `dir()`                 |
18//! | `tabIndex`                | `tab_index()`           |
19//! | `click()`                 | `click()`               |
20//! | `focus()`                 | `focus()`               |
21//! | `blur()`                  | `blur()`                |
22//! | `draggable`               | `draggable()`           |
23//! | `spellcheck`              | `spellcheck()`          |
24//! | `CollectStyleForPresAttr` | `presentation_style()`  |
25
26use crate::dom::traits::Element;
27
28/// Shared behavior for all HTML elements.
29///
30/// Every method has a default implementation that reads/writes attributes
31/// through the `Element` trait. Concrete elements inherit all behavior
32/// and override only what they need.
33///
34/// `HtmlDivElement` overrides: `presentation_style()` (for legacy `align`).
35/// `HtmlButtonElement` overrides: nothing (adds props via `ButtonData` instead).
36pub trait HtmlElement: Element {
37    // ---- Global HTML attributes (default impls read/write from attributes) ----
38
39    /// The `hidden` attribute. Elements with `hidden` should not be rendered.
40    fn hidden(&self) -> bool {
41        self.attribute("hidden").is_some()
42    }
43
44    fn set_hidden(&self, hidden: bool) {
45        if hidden {
46            self.set_attribute("hidden", "");
47        } else {
48            self.remove_attribute("hidden");
49        }
50    }
51
52    /// The `title` attribute (tooltip text).
53    fn title(&self) -> String {
54        self.attribute("title").unwrap_or_default()
55    }
56
57    fn set_title(&self, title: impl Into<String>) {
58        self.set_attribute("title", title);
59    }
60
61    /// The `lang` attribute (language code).
62    fn lang(&self) -> String {
63        self.attribute("lang").unwrap_or_default()
64    }
65
66    fn set_lang(&self, lang: impl Into<String>) {
67        self.set_attribute("lang", lang);
68    }
69
70    /// The `dir` attribute (text direction: "ltr", "rtl", "auto").
71    fn dir(&self) -> String {
72        self.attribute("dir").unwrap_or_default()
73    }
74
75    fn set_dir(&self, dir: impl Into<String>) {
76        self.set_attribute("dir", dir);
77    }
78
79    /// The `tabindex` attribute. Controls focus order.
80    /// Returns the element's default if not explicitly set.
81    fn tab_index(&self) -> i32 {
82        self.attribute("tabindex")
83            .and_then(|v| v.parse().ok())
84            .unwrap_or(if Self::IS_FOCUSABLE { 0 } else { -1 })
85    }
86
87    fn set_tab_index(&self, index: i32) {
88        self.set_attribute("tabindex", index.to_string());
89    }
90
91    /// The `draggable` attribute.
92    fn draggable(&self) -> bool {
93        self.attribute("draggable").is_some_and(|v| v == "true")
94    }
95
96    fn set_draggable(&self, draggable: bool) {
97        self.set_attribute("draggable", if draggable { "true" } else { "false" });
98    }
99
100    /// The `spellcheck` attribute.
101    fn spellcheck(&self) -> bool {
102        self.attribute("spellcheck").is_none_or(|v| v != "false")
103    }
104
105    fn set_spellcheck(&self, spellcheck: bool) {
106        self.set_attribute("spellcheck", if spellcheck { "true" } else { "false" });
107    }
108
109    // ---- Behavioral hooks (override in concrete elements) ----
110
111    /// Map presentation attributes to a `PropertyDeclarationBlock`.
112    ///
113    /// Like Chrome's `CollectStyleForPresentationAttribute`. Override in elements
114    /// that expose legacy presentation attributes (e.g. `align`, `width`, `color`).
115    fn collect_presentation_styles(&self) {
116        // Default: no presentation attributes to map.
117    }
118
119    /// Called when an attribute changes.
120    ///
121    /// Like Chrome's `HTMLElement::ParseAttribute`.
122    /// Default: no-op. Override to react to attribute changes.
123    fn attribute_changed(&self, _name: &str, _old: Option<&str>, _new: Option<&str>) {
124        // Default: no special handling.
125    }
126
127    // ---- Actions ----
128
129    /// Programmatically click this element.
130    fn click(&self) {
131        // Future: dispatch ClickEvent through the event system.
132    }
133
134    /// Focus this element.
135    fn focus(&self) {
136        // Future: focus management through the document.
137    }
138
139    /// Blur (unfocus) this element.
140    fn blur(&self) {
141        // Future: focus management through the document.
142    }
143}