Skip to main content

euv_core/vdom/attribute/
struct.rs

1use super::*;
2
3/// Represents a single attribute on a virtual DOM node.
4///
5/// Combines an attribute name with its corresponding value.
6///
7/// OPT 2: attribute names are `Cow<'static, str>`. The `html!` macro
8/// emits `Cow::Borrowed("class")` for the common literal case so
9/// attribute keys share a single static slice across the whole rendered
10/// DOM, removing the per-attribute `String` heap allocation. The
11/// `AttributeValue::Text(String)` payload keeps its owned allocation
12/// (text values are user-supplied strings that almost always come
13/// from string interpolation or runtime formatting).
14#[derive(Clone, CustomDebug, Data, New)]
15pub struct AttributeEntry {
16    /// The name of the attribute.
17    #[get_mut(pub(crate))]
18    #[set(pub(crate))]
19    #[get_mut(pub(crate))]
20    #[set(pub(crate))]
21    pub(crate) name: Cow<'static, str>,
22    /// The value of the attribute.
23    #[debug(skip)]
24    #[get(pub(crate))]
25    #[get_mut(pub(crate))]
26    #[set(pub(crate))]
27    #[get_mut(pub(crate))]
28    #[set(pub(crate))]
29    pub(crate) value: AttributeValue,
30}
31
32/// Represents a CSS pseudo-class or pseudo-element rule attached to a class.
33///
34/// Each rule has a selector suffix (e.g., ":hover", "::before", ":focus")
35/// and a style declaration string. When injected into the DOM, it produces
36/// a rule like `.class-name:hover { background: red; }`.
37#[derive(Clone, Data, Debug, Default, Eq, Hash, New, PartialEq)]
38pub struct PseudoRule {
39    /// The CSS pseudo selector suffix appended to the class name
40    /// (e.g., ":hover", ":focus", ":active", ":disabled", "::before", "::after",
41    /// ":first-child", ":last-child", ":nth-child(2n)", etc.).
42    #[get(pub(crate))]
43    #[get_mut(pub(crate))]
44    #[set(pub(crate))]
45    #[get_mut(pub(crate))]
46    #[set(pub(crate))]
47    selector: String,
48    /// The CSS style declarations for this pseudo rule
49    /// (e.g., "background: rgba(79, 70, 229, 0.04); color: #4f46e5;").
50    #[get(pub(crate))]
51    #[get_mut(pub(crate))]
52    #[set(pub(crate))]
53    #[get_mut(pub(crate))]
54    #[set(pub(crate))]
55    style: String,
56}
57
58/// Represents a CSS class with a name, its style declarations, and optional pseudo rules.
59///
60/// Created by the `class!` macro and used in `html!` via the `class:` attribute.
61/// When the renderer encounters a `Css`, it injects the styles into the
62/// DOM's `<style>` element on first use and applies the class name to the element.
63#[derive(Clone, Data, Debug, Default, New)]
64pub struct Css {
65    /// The CSS class name used in the DOM.
66    #[get_mut(pub(crate))]
67    #[set(pub(crate))]
68    #[get_mut(pub(crate))]
69    #[set(pub(crate))]
70    name: String,
71    /// The CSS style declarations (e.g., "max-width: 800px; margin: 0 auto;").
72    #[get_mut(pub(crate))]
73    #[set(pub(crate))]
74    #[get_mut(pub(crate))]
75    #[set(pub(crate))]
76    style: String,
77    /// The pseudo-class and pseudo-element rules for this class
78    /// (e.g., ":hover", ":focus", ":active", "::before", etc.).
79    #[get_mut(pub(crate))]
80    #[set(pub(crate))]
81    #[get_mut(pub(crate))]
82    #[set(pub(crate))]
83    pseudo_rules: Vec<PseudoRule>,
84    /// The media query rules for this class.
85    #[get_mut(pub(crate))]
86    #[set(pub(crate))]
87    #[get_mut(pub(crate))]
88    #[set(pub(crate))]
89    media_rules: Vec<MediaRule>,
90}
91
92/// Represents a CSS @media rule attached to a class.
93///
94/// Each media rule has a query string (e.g., "(max-width: 767px)"),
95/// a style declaration string, and optional nested pseudo-element rules.
96/// When injected into the DOM, it produces a rule like:
97/// `@media (max-width: 767px) { .class-name { font-size: 14px; } .class-name::-webkit-scrollbar { width: 0px; } }`.
98#[derive(Clone, Data, Debug, Default, Eq, Hash, New, PartialEq)]
99pub struct MediaRule {
100    /// The media query condition string (e.g., "(max-width: 767px)").
101    #[get(pub(crate))]
102    #[get_mut(pub(crate))]
103    #[set(pub(crate))]
104    #[get(pub(crate))]
105    #[get_mut(pub(crate))]
106    #[set(pub(crate))]
107    query: String,
108    /// The CSS style declarations inside this media rule
109    /// (e.g., "font-size: 14px; padding: 8px;").
110    #[get(pub(crate))]
111    #[get_mut(pub(crate))]
112    #[set(pub(crate))]
113    #[get_mut(pub(crate))]
114    #[set(pub(crate))]
115    style: String,
116    /// The pseudo-element rules nested inside this media rule
117    /// (e.g., `::-webkit-scrollbar { width: "0px"; }`).
118    #[get(pub(crate))]
119    #[get_mut(pub(crate))]
120    #[set(pub(crate))]
121    #[get_mut(pub(crate))]
122    #[set(pub(crate))]
123    pseudo_rules: Vec<PseudoRule>,
124}
125
126/// Adapts various event value types into an `AttributeValue` for event attributes.
127///
128/// The `html!` macro generates `EventAdapter::new(expr).into_attribute(event_name)`
129/// instead of inline trait dispatch boilerplate. This eliminates the per-attribute-site
130/// generation of `__EventWrapper`, `__IsClosure`, `__ClosurePicker`, `__ValuePicker`,
131/// `__FallbackHelper`, and `__dispatch` types, significantly reducing macro output size.
132///
133/// The adapter pattern handles three cases:
134/// - `FnMut(NativeEvent)` closure → `AttributeValue::Event` via `NativeEventHandler`
135/// - `NativeEventHandler` directly → `AttributeValue::Event` as-is
136/// - `Option<NativeEventHandler>` → `AttributeValue::Event` or `AttributeValue::Text`
137#[derive(Data, Debug, New)]
138pub struct EventAdapter<T> {
139    /// The wrapped value to be adapted into an attribute.
140    #[get(pub(crate))]
141    #[get_mut(pub(crate))]
142    #[set(pub(crate))]
143    #[get(pub(crate))]
144    #[get_mut(pub(crate))]
145    #[set(pub(crate))]
146    pub(crate) inner: T,
147}
148
149/// Adapts an event with a specific event name into an `AttributeValue`.
150///
151/// This type wraps an event value and its event name, enabling
152/// `Into<AttributeValue>` trait implementation for events.
153/// Used by the `html!` macro for event attributes like `onclick`.
154#[derive(Data, Debug, New)]
155pub struct EventNamedAdapter<T> {
156    /// The wrapped event value to be adapted.
157    #[get(pub(crate))]
158    #[get_mut(pub(crate))]
159    #[set(pub(crate))]
160    #[get(pub(crate))]
161    #[get_mut(pub(crate))]
162    #[set(pub(crate))]
163    pub(crate) inner: T,
164    /// The event name (e.g., "click", "mouseover").
165    #[get(type(copy))]
166    #[get_mut(pub(crate))]
167    #[set(pub(crate))]
168    #[get(pub(crate))]
169    #[get_mut(pub(crate))]
170    #[set(pub(crate))]
171    pub(crate) event_name: &'static str,
172}
173
174/// Adapts an arbitrary attribute value expression into an `AttributeValue`.
175///
176/// Handles the dispatch between event closures and reactive values without
177/// requiring the macro to generate inline trait hierarchies. The macro emits
178/// `AttrValueAdapter::new(expr).into_attribute_value()` instead of the
179/// `__IsClosure` / `__ClosurePicker` / `__ValuePicker` / `__FallbackHelper`
180/// / `__dispatch` boilerplate.
181///
182/// For event attributes (key starts with "on"), event closures are wrapped
183/// into `AttributeValue::Event`. For non-event attributes, values are
184/// converted via `IntoReactiveValue`.
185#[derive(Data, Debug, New)]
186pub struct AttrValueAdapter<T> {
187    /// The wrapped value to be adapted into an attribute.
188    #[get(pub(crate))]
189    #[get_mut(pub(crate))]
190    #[set(pub(crate))]
191    #[get(pub(crate))]
192    #[get_mut(pub(crate))]
193    #[set(pub(crate))]
194    pub(crate) inner: T,
195}
196
197/// Adapts an `inner_html:` payload into the matching `AttributeValue`
198/// variant (`InnerHtml(String)` for static strings, `InnerHtmlSignal`
199/// for `Signal<String>`).
200///
201/// This is a sibling to [`AttrValueAdapter`] specialised for the
202/// `inner_html:` attribute key. The html! macro emits
203/// `InnerHtmlAdapter::new(expr).into()` whenever it sees an
204/// `inner_html: ...` binding, so that `inner_html: "raw"` and
205/// `inner_html: my_signal` route through `set_inner_html` rather than
206/// the generic `set_attribute_or_property` path used for ordinary
207/// `Text` attributes.
208///
209/// The actual `String` ↔ `Signal<String>` dispatch happens in the
210/// `From<InnerHtmlAdapter<T>> for AttributeValue` impl below, where
211/// the trait bounds on `T` decide which variant is produced.
212#[derive(Data, Debug, New)]
213pub struct InnerHtmlAdapter<T> {
214    /// The wrapped value to be adapted into an `AttributeValue` for
215    /// the `inner_html:` attribute.
216    #[get(pub(crate))]
217    #[get_mut(pub(crate))]
218    #[set(pub(crate))]
219    #[get(pub(crate))]
220    #[get_mut(pub(crate))]
221    #[set(pub(crate))]
222    pub(crate) inner: T,
223}
224
225/// Adapts a callback with a custom name into an `AttributeValue`.
226///
227/// This type wraps a callback and its custom attribute name, enabling
228/// `Into<AttributeValue>` trait implementation for named callbacks.
229/// Used by the `html!` macro for component callback props.
230#[derive(Data, Debug, New)]
231pub struct CallbackNamedAdapter<T> {
232    /// The wrapped callback to be adapted.
233    #[get(pub(crate))]
234    #[get_mut(pub(crate))]
235    #[set(pub(crate))]
236    #[get(pub(crate))]
237    #[get_mut(pub(crate))]
238    #[set(pub(crate))]
239    pub(crate) inner: T,
240    /// The custom attribute name (e.g., "on-increment", "on-change").
241    #[get(type(copy))]
242    #[get_mut(pub(crate))]
243    #[set(pub(crate))]
244    pub(crate) name: &'static str,
245}
246
247/// Namespace for the injected-class registry accessors.
248///
249/// The registry itself lives in `INJECTED_CLASSES` (an `RwLock<HashSet>`
250/// behind a `LazyLock`). This type only carries the associated functions so
251/// the accessors keep a `Type::method()` shape.
252///
253/// It previously wrapped a `UnsafeCell<HashSet<String>>` and was marked
254/// `unsafe impl Sync` so it could sit in a `static mut`. That was undefined
255/// behaviour as soon as two threads touched it, which is exactly what
256/// parallel test runs do.
257#[derive(Debug, Default)]
258pub(crate) struct InjectedClassesCell;