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 pub(crate) name: Cow<'static, str>,
20 /// The value of the attribute.
21 #[debug(skip)]
22 #[get(pub(crate))]
23 #[get_mut(pub(crate))]
24 #[set(pub(crate))]
25 pub(crate) value: AttributeValue,
26}
27
28/// Represents a CSS pseudo-class or pseudo-element rule attached to a class.
29///
30/// Each rule has a selector suffix (e.g., ":hover", "::before", ":focus")
31/// and a style declaration string. When injected into the DOM, it produces
32/// a rule like `.class-name:hover { background: red; }`.
33#[derive(Clone, Data, Debug, Default, Eq, Hash, New, PartialEq)]
34pub struct PseudoRule {
35 /// The CSS pseudo selector suffix appended to the class name
36 /// (e.g., ":hover", ":focus", ":active", ":disabled", "::before", "::after",
37 /// ":first-child", ":last-child", ":nth-child(2n)", etc.).
38 #[get(pub(crate))]
39 #[get_mut(pub(crate))]
40 #[set(pub(crate))]
41 selector: String,
42 /// The CSS style declarations for this pseudo rule
43 /// (e.g., "background: rgba(79, 70, 229, 0.04); color: #4f46e5;").
44 #[get(pub(crate))]
45 #[get_mut(pub(crate))]
46 #[set(pub(crate))]
47 style: String,
48}
49
50/// Represents a CSS class with a name, its style declarations, and optional pseudo rules.
51///
52/// Created by the `class!` macro and used in `html!` via the `class:` attribute.
53/// When the renderer encounters a `Css`, it injects the styles into the
54/// DOM's `<style>` element on first use and applies the class name to the element.
55#[derive(Clone, Data, Debug, Default, New)]
56pub struct Css {
57 /// The CSS class name used in the DOM.
58 #[get_mut(pub(crate))]
59 #[set(pub(crate))]
60 name: String,
61 /// The CSS style declarations (e.g., "max-width: 800px; margin: 0 auto;").
62 #[get_mut(pub(crate))]
63 #[set(pub(crate))]
64 style: String,
65 /// The pseudo-class and pseudo-element rules for this class
66 /// (e.g., ":hover", ":focus", ":active", "::before", etc.).
67 #[get_mut(pub(crate))]
68 #[set(pub(crate))]
69 pseudo_rules: Vec<PseudoRule>,
70 /// The media query rules for this class.
71 #[get_mut(pub(crate))]
72 #[set(pub(crate))]
73 media_rules: Vec<MediaRule>,
74}
75
76/// Represents a CSS @media rule attached to a class.
77///
78/// Each media rule has a query string (e.g., "(max-width: 767px)"),
79/// a style declaration string, and optional nested pseudo-element rules.
80/// When injected into the DOM, it produces a rule like:
81/// `@media (max-width: 767px) { .class-name { font-size: 14px; } .class-name::-webkit-scrollbar { width: 0px; } }`.
82#[derive(Clone, Data, Debug, Default, Eq, Hash, New, PartialEq)]
83pub struct MediaRule {
84 /// The media query condition string (e.g., "(max-width: 767px)").
85 #[get(pub(crate))]
86 #[get_mut(pub(crate))]
87 #[set(pub(crate))]
88 query: String,
89 /// The CSS style declarations inside this media rule
90 /// (e.g., "font-size: 14px; padding: 8px;").
91 #[get(pub(crate))]
92 #[get_mut(pub(crate))]
93 #[set(pub(crate))]
94 style: String,
95 /// The pseudo-element rules nested inside this media rule
96 /// (e.g., `::-webkit-scrollbar { width: "0px"; }`).
97 #[get(pub(crate))]
98 #[get_mut(pub(crate))]
99 #[set(pub(crate))]
100 pseudo_rules: Vec<PseudoRule>,
101}
102
103/// Adapts various event value types into an `AttributeValue` for event attributes.
104///
105/// The `html!` macro generates `EventAdapter::new(expr).into_attribute(event_name)`
106/// instead of inline trait dispatch boilerplate. This eliminates the per-attribute-site
107/// generation of `__EventWrapper`, `__IsClosure`, `__ClosurePicker`, `__ValuePicker`,
108/// `__FallbackHelper`, and `__dispatch` types, significantly reducing macro output size.
109///
110/// The adapter pattern handles three cases:
111/// - `FnMut(NativeEvent)` closure → `AttributeValue::Event` via `NativeEventHandler`
112/// - `NativeEventHandler` directly → `AttributeValue::Event` as-is
113/// - `Option<NativeEventHandler>` → `AttributeValue::Event` or `AttributeValue::Text`
114#[derive(Data, Debug, New)]
115pub struct EventAdapter<T> {
116 /// The wrapped value to be adapted into an attribute.
117 #[get(pub(crate))]
118 #[get_mut(pub(crate))]
119 #[set(pub(crate))]
120 pub(crate) inner: T,
121}
122
123/// Adapts an event with a specific event name into an `AttributeValue`.
124///
125/// This type wraps an event value and its event name, enabling
126/// `Into<AttributeValue>` trait implementation for events.
127/// Used by the `html!` macro for event attributes like `onclick`.
128#[derive(Data, Debug, New)]
129pub struct EventNamedAdapter<T> {
130 /// The wrapped event value to be adapted.
131 #[get(pub(crate))]
132 #[get_mut(pub(crate))]
133 #[set(pub(crate))]
134 pub(crate) inner: T,
135 /// The event name (e.g., "click", "mouseover").
136 #[get(pub, type(copy))]
137 #[get_mut(pub(crate))]
138 #[set(pub(crate))]
139 pub(crate) event_name: &'static str,
140}
141
142/// Adapts an arbitrary attribute value expression into an `AttributeValue`.
143///
144/// Handles the dispatch between event closures and reactive values without
145/// requiring the macro to generate inline trait hierarchies. The macro emits
146/// `AttrValueAdapter::new(expr).into_attribute_value()` instead of the
147/// `__IsClosure` / `__ClosurePicker` / `__ValuePicker` / `__FallbackHelper`
148/// / `__dispatch` boilerplate.
149///
150/// For event attributes (key starts with "on"), event closures are wrapped
151/// into `AttributeValue::Event`. For non-event attributes, values are
152/// converted via `IntoReactiveValue`.
153#[derive(Data, Debug, New)]
154pub struct AttrValueAdapter<T> {
155 /// The wrapped value to be adapted into an attribute.
156 #[get(pub(crate))]
157 #[get_mut(pub(crate))]
158 #[set(pub(crate))]
159 pub(crate) inner: T,
160}
161
162/// Adapts an `inner_html:` payload into the matching `AttributeValue`
163/// variant (`InnerHtml(String)` for static strings, `InnerHtmlSignal`
164/// for `Signal<String>`).
165///
166/// This is a sibling to [`AttrValueAdapter`] specialised for the
167/// `inner_html:` attribute key. The html! macro emits
168/// `InnerHtmlAdapter::new(expr).into()` whenever it sees an
169/// `inner_html: ...` binding, so that `inner_html: "raw"` and
170/// `inner_html: my_signal` route through `set_inner_html` rather than
171/// the generic `set_attribute_or_property` path used for ordinary
172/// `Text` attributes.
173///
174/// The actual `String` ↔ `Signal<String>` dispatch happens in the
175/// `From<InnerHtmlAdapter<T>> for AttributeValue` impl below, where
176/// the trait bounds on `T` decide which variant is produced.
177#[derive(Data, Debug, New)]
178pub struct InnerHtmlAdapter<T> {
179 /// The wrapped value to be adapted into an `AttributeValue` for
180 /// the `inner_html:` attribute.
181 #[get(pub(crate))]
182 #[get_mut(pub(crate))]
183 #[set(pub(crate))]
184 pub(crate) inner: T,
185}
186
187/// Adapts a callback with a custom name into an `AttributeValue`.
188///
189/// This type wraps a callback and its custom attribute name, enabling
190/// `Into<AttributeValue>` trait implementation for named callbacks.
191/// Used by the `html!` macro for component callback props.
192#[derive(Data, Debug, New)]
193pub struct CallbackNamedAdapter<T> {
194 /// The wrapped callback to be adapted.
195 #[get(pub(crate))]
196 #[get_mut(pub(crate))]
197 #[set(pub(crate))]
198 pub(crate) inner: T,
199 /// The custom attribute name (e.g., "on-increment", "on-change").
200 #[get(pub, type(copy))]
201 #[get_mut(pub(crate))]
202 #[set(pub(crate))]
203 pub(crate) name: &'static str,
204}
205
206/// A `Sync` wrapper for single-threaded global `HashSet` access.
207///
208/// SAFETY: This type is only safe to use in single-threaded contexts
209/// (e.g., WASM). It implements `Sync` to allow usage as a `static`
210/// variable, but concurrent access from multiple threads would be
211/// undefined behavior.
212#[derive(Data, Debug, New)]
213pub(crate) struct InjectedClassesCell(
214 /// Interior-mutable storage for the set of CSS class names already
215 /// injected into the DOM.
216 #[get(pub(crate))]
217 #[get_mut(pub(crate))]
218 #[set(pub(crate))]
219 pub UnsafeCell<HashSet<String>>,
220);