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 ///
18 /// `get_name` is public: `ui/tests/view_factories` collects attribute
19 /// names from outside this crate. `get_mut` / `set` stay crate-internal —
20 /// an attribute entry is built once by `New` and never rewritten.
21 #[get_mut(pub(crate))]
22 #[set(pub(crate))]
23 pub name: Cow<'static, str>,
24 /// The value of the attribute.
25 #[debug(skip)]
26 #[get(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 selector: String,
46 /// The CSS style declarations for this pseudo rule
47 /// (e.g., "background: rgba(79, 70, 229, 0.04); color: #4f46e5;").
48 #[get(pub(crate))]
49 #[get_mut(pub(crate))]
50 #[set(pub(crate))]
51 style: String,
52}
53
54/// Represents a CSS class with a name, its style declarations, and optional pseudo rules.
55///
56/// Created by the `class!` macro and used in `html!` via the `class:` attribute.
57/// When the renderer encounters a `Css`, it injects the styles into the
58/// DOM's `<style>` element on first use and applies the class name to the element.
59#[derive(Clone, Data, Debug, Default, New)]
60pub struct Css {
61 /// The CSS class name used in the DOM.
62 ///
63 /// Public: the `class!` macro expands `get_name()` into consumer
64 /// crates (`ui`, `example`), and the component views in `ui` read it
65 /// directly, so the getter cannot be crate-private. The field is
66 /// public to match that getter — a public getter over a restricted
67 /// field is the leak `public-accessor-leaks-restricted-field`
68 /// reports. Mutation stays crate-internal.
69 #[get_mut(pub(crate))]
70 #[set(pub(crate))]
71 pub name: String,
72 /// The CSS style declarations (e.g., "max-width: 800px; margin: 0 auto;").
73 ///
74 /// Public for the same reason as [`Css::name`]: the `class!` macro
75 /// expands `get_style()` into consumer crates when a class extends
76 /// another class. Mutation stays crate-internal.
77 #[get_mut(pub(crate))]
78 #[set(pub(crate))]
79 pub style: String,
80 /// The pseudo-class and pseudo-element rules for this class
81 /// (e.g., ":hover", ":focus", ":active", "::before", etc.).
82 ///
83 /// Public for the same reason as [`Css::name`]: the `class!` macro
84 /// expands `get_pseudo_rules()` into consumer crates when a class
85 /// extends another class. Mutation stays crate-internal.
86 #[get_mut(pub(crate))]
87 #[set(pub(crate))]
88 pub pseudo_rules: Vec<PseudoRule>,
89 /// The media query rules for this class.
90 ///
91 /// Public for the same reason as [`Css::name`]: the `class!` macro
92 /// expands `get_media_rules()` into consumer crates when a class
93 /// extends another class. Mutation stays crate-internal.
94 #[get_mut(pub(crate))]
95 #[set(pub(crate))]
96 pub media_rules: Vec<MediaRule>,
97}
98
99/// Represents a CSS @media rule attached to a class.
100///
101/// Each media rule has a query string (e.g., "(max-width: 767px)"),
102/// a style declaration string, and optional nested pseudo-element rules.
103/// When injected into the DOM, it produces a rule like:
104/// `@media (max-width: 767px) { .class-name { font-size: 14px; } .class-name::-webkit-scrollbar { width: 0px; } }`.
105#[derive(Clone, Data, Debug, Default, Eq, Hash, New, PartialEq)]
106pub struct MediaRule {
107 /// The media query condition string (e.g., "(max-width: 767px)").
108 #[get(pub(crate))]
109 #[get_mut(pub(crate))]
110 #[set(pub(crate))]
111 query: String,
112 /// The CSS style declarations inside this media rule
113 /// (e.g., "font-size: 14px; padding: 8px;").
114 #[get(pub(crate))]
115 #[get_mut(pub(crate))]
116 #[set(pub(crate))]
117 style: String,
118 /// The pseudo-element rules nested inside this media rule
119 /// (e.g., `::-webkit-scrollbar { width: "0px"; }`).
120 #[get(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_mut(pub(crate))]
141 #[set(pub(crate))]
142 pub inner: T,
143}
144
145/// Adapts an event with a specific event name into an `AttributeValue`.
146///
147/// This type wraps an event value and its event name, enabling
148/// `Into<AttributeValue>` trait implementation for events.
149/// Used by the `html!` macro for event attributes like `onclick`.
150#[derive(Data, Debug, New)]
151pub struct EventNamedAdapter<T> {
152 /// The wrapped event value to be adapted.
153 #[get_mut(pub(crate))]
154 #[set(pub(crate))]
155 pub inner: T,
156 /// The event name (e.g., "click", "mouseover").
157 #[get(type(copy), pub(crate))]
158 #[get_mut(pub(crate))]
159 #[set(pub(crate))]
160 pub(crate) event_name: &'static str,
161}
162
163/// Adapts an arbitrary attribute value expression into an `AttributeValue`.
164///
165/// Handles the dispatch between event closures and reactive values without
166/// requiring the macro to generate inline trait hierarchies. The macro emits
167/// `AttrValueAdapter::new(expr).into_attribute_value()` instead of the
168/// `__IsClosure` / `__ClosurePicker` / `__ValuePicker` / `__FallbackHelper`
169/// / `__dispatch` boilerplate.
170///
171/// For event attributes (key starts with "on"), event closures are wrapped
172/// into `AttributeValue::Event`. For non-event attributes, values are
173/// converted via `IntoReactiveValue`.
174#[derive(Data, Debug, New)]
175pub struct AttrValueAdapter<T> {
176 /// The wrapped value to be adapted into an attribute.
177 #[get_mut(pub(crate))]
178 #[set(pub(crate))]
179 pub inner: T,
180}
181
182/// Adapts an `inner_html:` payload into the matching `AttributeValue`
183/// variant (`InnerHtml(String)` for static strings, `InnerHtmlSignal`
184/// for `Signal<String>`).
185///
186/// This is a sibling to [`AttrValueAdapter`] specialised for the
187/// `inner_html:` attribute key. The html! macro emits
188/// `InnerHtmlAdapter::new(expr).into()` whenever it sees an
189/// `inner_html: ...` binding, so that `inner_html: "raw"` and
190/// `inner_html: my_signal` route through `set_inner_html` rather than
191/// the generic `set_attribute_or_property` path used for ordinary
192/// `Text` attributes.
193///
194/// The actual `String` ↔ `Signal<String>` dispatch happens in the
195/// `From<InnerHtmlAdapter<T>> for AttributeValue` impl below, where
196/// the trait bounds on `T` decide which variant is produced.
197#[derive(Data, Debug, New)]
198pub struct InnerHtmlAdapter<T> {
199 /// The wrapped value to be adapted into an `AttributeValue` for
200 /// the `inner_html:` attribute.
201 #[get_mut(pub(crate))]
202 #[set(pub(crate))]
203 pub inner: T,
204}
205
206/// Adapts a callback with a custom name into an `AttributeValue`.
207///
208/// This type wraps a callback and its custom attribute name, enabling
209/// `Into<AttributeValue>` trait implementation for named callbacks.
210/// Used by the `html!` macro for component callback props.
211#[derive(Data, Debug, New)]
212pub struct CallbackNamedAdapter<T> {
213 /// The wrapped callback to be adapted.
214 #[get_mut(pub(crate))]
215 #[set(pub(crate))]
216 pub inner: T,
217 /// The custom attribute name (e.g., "on-increment", "on-change").
218 #[get(type(copy))]
219 #[get_mut(pub(crate))]
220 #[set(pub(crate))]
221 pub name: &'static str,
222}
223
224/// Namespace for the injected-class registry accessors.
225///
226/// The registry itself lives in `INJECTED_CLASSES` (an `RwLock<HashSet>`
227/// behind a `LazyLock`). This type only carries the associated functions so
228/// the accessors keep a `Type::method()` shape.
229///
230/// It previously wrapped a `UnsafeCell<HashSet<String>>` and was marked
231/// `unsafe impl Sync` so it could sit in a `static mut`. That was undefined
232/// behaviour as soon as two threads touched it, which is exactly what
233/// parallel test runs do.
234#[derive(Debug, Default)]
235pub(crate) struct InjectedClassesCell;