Skip to main content

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}