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    ///
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;