euv_core/vdom/cast/impl.rs
1use super::*;
2
3/// Converts a `Vec<VirtualNode>` into a `VirtualNode::Fragment`.
4///
5/// This enables using a `Vec<VirtualNode>` directly in the `html!` macro
6/// without manually wrapping it in `VirtualNode::Fragment(...)`.
7///
8/// # Returns
9///
10/// - `VirtualNode` - A `VirtualNode::Fragment` containing the nodes, or
11/// `VirtualNode::Empty` if the vector is empty.
12impl From<Vec<VirtualNode>> for VirtualNode {
13 fn from(nodes: Vec<VirtualNode>) -> Self {
14 if nodes.is_empty() {
15 VirtualNode::Empty
16 } else {
17 VirtualNode::Fragment(nodes)
18 }
19 }
20}
21
22/// Converts an `Option<VirtualNode>` into a `VirtualNode`.
23///
24/// `Some(node)` returns the inner node, `None` returns `VirtualNode::Empty`.
25///
26/// # Returns
27///
28/// - `VirtualNode` - The inner node if `Some`, otherwise `VirtualNode::Empty`.
29impl From<Option<VirtualNode>> for VirtualNode {
30 fn from(node: Option<VirtualNode>) -> Self {
31 match node {
32 Some(node) => node,
33 None => VirtualNode::Empty,
34 }
35 }
36}
37
38/// Converts an `Option<Vec<VirtualNode>>` into a `VirtualNode`.
39///
40/// `Some(vec)` converts the vector into a `VirtualNode::Fragment` (or `Empty`
41/// if the vector is empty), `None` returns `VirtualNode::Empty`.
42///
43/// # Returns
44///
45/// - `VirtualNode` - A `VirtualNode::Fragment` if `Some` with nodes,
46/// `VirtualNode::Empty` if `None` or the vector is empty.
47impl From<Option<Vec<VirtualNode>>> for VirtualNode {
48 fn from(nodes: Option<Vec<VirtualNode>>) -> Self {
49 match nodes {
50 Some(nodes) => nodes.into(),
51 None => VirtualNode::Empty,
52 }
53 }
54}
55
56/// Wraps a `FnMut(&mut HookContext) -> VirtualNode` closure into a `DynamicNode`.
57///
58/// This enables writing `{move |_: &mut HookContext| html! { ... }}` directly in HTML markup
59/// without explicit `DynamicNode` construction.
60impl<F> From<F> for VirtualNode
61where
62 F: FnMut(&mut HookContext) -> VirtualNode + 'static,
63{
64 /// Wraps this closure into a `VirtualNode::Dynamic` with a fresh hook context.
65 ///
66 /// # Returns
67 ///
68 /// - `VirtualNode` - A dynamic virtual node wrapping this closure.
69 fn from(render_fn: F) -> Self {
70 VirtualNode::create_dynamic(render_fn)
71 }
72}
73
74/// Converts a `String` into a text virtual node.
75impl From<String> for VirtualNode {
76 /// Converts this string into a text virtual node.
77 ///
78 /// # Returns
79 ///
80 /// - `VirtualNode` - A text virtual node.
81 fn from(text: String) -> Self {
82 VirtualNode::Text(TextNode::new(text, None))
83 }
84}
85
86/// Converts a `&str` into a text virtual node.
87impl From<&str> for VirtualNode {
88 /// Converts this string slice into a text virtual node.
89 ///
90 /// # Returns
91 ///
92 /// - `VirtualNode` - A text virtual node.
93 fn from(text: &str) -> Self {
94 VirtualNode::Text(TextNode::new(text.to_string(), None))
95 }
96}
97
98/// Converts an `i32` into a text virtual node.
99impl From<i32> for VirtualNode {
100 /// Converts this integer into a text virtual node.
101 ///
102 /// # Returns
103 ///
104 /// - `VirtualNode` - A text virtual node.
105 fn from(value: i32) -> Self {
106 VirtualNode::Text(TextNode::new(value.to_string(), None))
107 }
108}
109
110/// Converts a `usize` into a text virtual node.
111impl From<usize> for VirtualNode {
112 /// Converts this unsigned integer into a text virtual node.
113 ///
114 /// # Returns
115 ///
116 /// - `VirtualNode` - A text virtual node.
117 fn from(value: usize) -> Self {
118 VirtualNode::Text(TextNode::new(value.to_string(), None))
119 }
120}
121
122/// Converts a `bool` into a text virtual node.
123impl From<bool> for VirtualNode {
124 /// Converts this boolean into a text virtual node.
125 ///
126 /// # Returns
127 ///
128 /// - `VirtualNode` - A text virtual node.
129 fn from(value: bool) -> Self {
130 VirtualNode::Text(TextNode::new(value.to_string(), None))
131 }
132}
133
134/// Converts a signal into a reactive text virtual node.
135impl<T> From<Signal<T>> for VirtualNode
136where
137 T: Clone + PartialEq + Display + 'static,
138{
139 /// Converts this signal into a reactive text virtual node.
140 ///
141 /// # Returns
142 ///
143 /// - `VirtualNode` - A reactive text virtual node.
144 fn from(signal: Signal<T>) -> Self {
145 signal.as_reactive_text()
146 }
147}
148
149/// Converts a signal into a reactive text node with listener wiring.
150impl<T> AsReactiveText for Signal<T>
151where
152 T: Clone + PartialEq + Display + 'static,
153{
154 /// Creates a reactive text node that auto-updates when the signal changes.
155 ///
156 /// Internally creates a bridge `Signal<String>` that subscribes to the
157 /// source signal and updates the text content on every change.
158 ///
159 /// # Returns
160 ///
161 /// - `VirtualNode` - A text virtual node with reactive signal binding.
162 fn as_reactive_text(&self) -> VirtualNode {
163 let source: Signal<T> = *self;
164 let string_signal: Signal<String> = Signal::create(source.get().to_string());
165 let string_signal_clone: Signal<String> = string_signal;
166 source.subscribe(move || {
167 string_signal_clone.set(source.get().to_string());
168 });
169 // The closure above captures `string_signal_clone` (which aliases
170 // `string_signal`), so `source` now transitively keeps the bridge
171 // alive. Register that dependency so the bridge's heap allocation
172 // can be reclaimed once `source` is deactivated.
173 BridgeRefsCell::track(string_signal.get_inner(), source.get_inner());
174 VirtualNode::Text(TextNode::new(string_signal.get(), Some(string_signal)))
175 }
176}
177
178/// Constructs an `EventAdapter` that wraps any event-compatible value.
179impl<T> EventAdapter<T> {
180 /// Returns the inner wrapped value, consuming the adapter.
181 ///
182 /// # Returns
183 ///
184 /// - `T` - The inner value.
185 pub(crate) fn into_inner(self) -> T {
186 self.inner
187 }
188}
189
190/// Adapts a `FnMut(Event)` closure into an `AttributeValue::Event`.
191///
192/// Wraps the closure into a `NativeEventHandler` and returns it as an
193/// event attribute value. This replaces the `__EventWrapper<F>` type
194/// that was previously generated inline by the `html!` macro.
195impl<F> EventAdapter<F>
196where
197 F: FnMut(Event) + 'static,
198{
199 /// Converts the wrapped closure into an event `AttributeValue`.
200 ///
201 /// # Arguments
202 ///
203 /// - `&'static str` - The event name string to associate with the handler.
204 ///
205 /// # Returns
206 ///
207 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
208 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
209 AttributeValue::Event(NativeEventHandler::create(event_name, self.into_inner()))
210 }
211}
212
213/// Converts an event with a specific event name into an `AttributeValue`.
214impl<F> From<EventNamedAdapter<F>> for AttributeValue
215where
216 F: FnMut(Event) + 'static,
217{
218 /// Converts the wrapped closure with event name into an event `AttributeValue`.
219 ///
220 /// # Returns
221 ///
222 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
223 fn from(adapter: EventNamedAdapter<F>) -> Self {
224 AttributeValue::Event(NativeEventHandler::create(
225 adapter.get_event_name(),
226 adapter.inner,
227 ))
228 }
229}
230
231/// Converts an event named adapter with `NativeEventHandler` into an `AttributeValue`.
232impl From<EventNamedAdapter<NativeEventHandler>> for AttributeValue {
233 /// Converts the wrapped handler with event name into an event `AttributeValue`.
234 ///
235 /// # Returns
236 ///
237 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
238 fn from(mut adapter: EventNamedAdapter<NativeEventHandler>) -> Self {
239 let event_name: &'static str = adapter.get_event_name();
240 adapter.get_mut_inner().set_event_name(event_name);
241 AttributeValue::Event(adapter.inner)
242 }
243}
244
245/// Converts an event named adapter with optional shared closure into an `AttributeValue`.
246///
247/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
248/// into a `NativeEventHandler` with the adapter's event name, and `None` becomes
249/// `AttributeValue::Text(String::new())`.
250impl From<EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>> for AttributeValue {
251 /// Converts the wrapped optional shared closure with event name into an event `AttributeValue`.
252 ///
253 /// # Returns
254 ///
255 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
256 fn from(adapter: EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>) -> Self {
257 let event_name: &'static str = adapter.get_event_name();
258 match adapter.inner {
259 Some(callback) => AttributeValue::Event(NativeEventHandler::create(
260 event_name,
261 move |event: Event| {
262 callback(event);
263 },
264 )),
265 None => AttributeValue::Text(String::new()),
266 }
267 }
268}
269
270/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
271///
272/// When the user already provides a `NativeEventHandler`, the handler is
273/// re-wrapped with the given `event_name` to ensure the DOM event listener
274/// is bound to the correct event type (e.g., "click" rather than "onclick").
275impl EventAdapter<NativeEventHandler> {
276 /// Converts the wrapped handler into an event `AttributeValue`.
277 ///
278 /// Re-wraps the handler with the provided `event_name` so that the
279 /// DOM event listener uses the correct event type string.
280 ///
281 /// # Arguments
282 ///
283 /// - `&'static str` - The event name to bind the handler to.
284 ///
285 /// # Returns
286 ///
287 /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
288 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
289 let mut handler: NativeEventHandler = self.into_inner();
290 handler.set_event_name(event_name);
291 AttributeValue::Event(handler)
292 }
293}
294
295/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
296///
297/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with the
298/// given event name, and `None` becomes `AttributeValue::Text(String::new())`.
299impl EventAdapter<Option<NativeEventHandler>> {
300 /// Converts the wrapped optional handler into an attribute value.
301 ///
302 /// Re-wraps a `Some` handler with the provided `event_name` so that the
303 /// DOM event listener uses the correct event type string.
304 ///
305 /// # Arguments
306 ///
307 /// - `&'static str` - The event name to bind the handler to.
308 ///
309 /// # Returns
310 ///
311 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
312 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
313 match self.into_inner() {
314 Some(handler) => EventNamedAdapter::new(handler, event_name).into(),
315 None => AttributeValue::Text(String::new()),
316 }
317 }
318}
319
320/// Adapts an `Option<Rc<dyn Fn(Event)>>` into an `AttributeValue`.
321///
322/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
323/// into a `NativeEventHandler`, and `None` becomes `AttributeValue::Text(String::new())`.
324/// This supports component Props that use `Option<Rc<dyn Fn(Event)>>` for event callbacks.
325impl EventAdapter<Option<Rc<dyn Fn(Event)>>> {
326 /// Converts the wrapped optional shared closure into an attribute value.
327 ///
328 /// # Arguments
329 ///
330 /// - `&'static str` - The event name to bind the handler to.
331 ///
332 /// # Returns
333 ///
334 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
335 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
336 match self.into_inner() {
337 Some(callback) => AttributeValue::Event(NativeEventHandler::create(
338 event_name,
339 move |event: Event| {
340 callback(event);
341 },
342 )),
343 None => AttributeValue::Text(String::new()),
344 }
345 }
346}
347
348/// Constructs an `AttrValueAdapter` that wraps any attribute-compatible value.
349impl<T> AttrValueAdapter<T> {
350 /// Returns the inner wrapped value, consuming the adapter.
351 ///
352 /// # Returns
353 ///
354 /// - `T` - The inner value.
355 pub(crate) fn into_inner(self) -> T {
356 self.inner
357 }
358}
359
360/// Adapts a `FnMut(Event)` closure into a callback `AttributeValue`.
361///
362/// This handles the case where a closure is used as a component callback prop.
363/// The closure is converted via `IntoCallbackAttribute::into_callback_attribute()`.
364impl<F> AttrValueAdapter<F>
365where
366 F: FnMut(Event) + 'static,
367{
368 /// Converts the wrapped closure into a callback `AttributeValue`.
369 ///
370 /// # Returns
371 ///
372 /// - `AttributeValue` - An event attribute value wrapping the adapted closure.
373 pub fn into_callback(self) -> AttributeValue {
374 self.into_inner().into()
375 }
376
377 /// Converts the wrapped closure into a callback `AttributeValue` with a
378 /// custom event name for component props.
379 ///
380 /// # Arguments
381 ///
382 /// - `&'static str` - The custom attribute name (e.g., "on-increment", "on-change").
383 ///
384 /// # Returns
385 ///
386 /// - `AttributeValue` - An event attribute value with the custom name.
387 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
388 AttributeValue::Event(NativeEventHandler::create(name, self.into_inner()))
389 }
390}
391
392/// Converts a named callback adapter into an `AttributeValue`.
393impl<F> From<CallbackNamedAdapter<F>> for AttributeValue
394where
395 F: FnMut(Event) + 'static,
396{
397 /// Converts the wrapped closure with custom name into a callback `AttributeValue`.
398 ///
399 /// # Returns
400 ///
401 /// - `AttributeValue` - An event attribute value with the custom name.
402 fn from(adapter: CallbackNamedAdapter<F>) -> Self {
403 AttributeValue::Event(NativeEventHandler::create(
404 adapter.get_name(),
405 adapter.inner,
406 ))
407 }
408}
409
410impl AttrValueAdapter<NativeEventHandler> {
411 /// Converts the wrapped handler into a callback `AttributeValue` with a
412 /// custom event name for component props.
413 ///
414 /// # Arguments
415 ///
416 /// - `&'static str` - The custom attribute name.
417 ///
418 /// # Returns
419 ///
420 /// - `AttributeValue` - An event attribute value with the custom name.
421 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
422 let mut handler: NativeEventHandler = self.into_inner();
423 handler.set_event_name(name);
424 AttributeValue::Event(handler)
425 }
426}
427
428/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
429impl AttrValueAdapter<Option<NativeEventHandler>> {
430 /// Converts the wrapped optional handler into an attribute value.
431 ///
432 /// # Returns
433 ///
434 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
435 pub fn into_callback(self) -> AttributeValue {
436 match self.into_inner() {
437 Some(handler) => AttrValueAdapter::new(handler).into(),
438 None => AttributeValue::Text(String::new()),
439 }
440 }
441
442 /// Converts this optional handler into a callback `AttributeValue` with a
443 /// custom event name for component props.
444 ///
445 /// # Arguments
446 ///
447 /// - `&'static str` - The custom attribute name.
448 ///
449 /// # Returns
450 ///
451 /// - `AttributeValue` - An event attribute with the custom name if `Some`,
452 /// otherwise an empty text attribute.
453 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
454 match self.into_inner() {
455 Some(handler) => AttrValueAdapter::new(handler).into_callback_named(name),
456 None => AttributeValue::Text(String::new()),
457 }
458 }
459}
460
461/// Adapts any type that implements `Into<AttributeValue>` into an `AttributeValue`.
462///
463/// This is the fallback path for non-closure attribute values (strings, signals,
464/// CSS classes, etc.).
465impl<T> From<AttrValueAdapter<T>> for AttributeValue
466where
467 T: Into<AttributeValue>,
468{
469 /// Converts the wrapped value into an `AttributeValue`.
470 ///
471 /// # Returns
472 ///
473 /// - `AttributeValue` - The reactive attribute value.
474 fn from(adapter: AttrValueAdapter<T>) -> Self {
475 adapter.into_inner().into()
476 }
477}