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    /// Lifts a `Vec<VirtualNode>` into a single [`VirtualNode`] via wrapping or unwrapping.
14    ///
15    /// # Arguments
16    ///
17    /// - `Vec<VirtualNode>` - Input value to convert from.
18    fn from(nodes: Vec<VirtualNode>) -> Self {
19        if nodes.is_empty() {
20            VirtualNode::Empty
21        } else {
22            VirtualNode::Fragment(nodes)
23        }
24    }
25}
26
27/// Converts an `Option<VirtualNode>` into a `VirtualNode`.
28///
29/// `Some(node)` returns the inner node, `None` returns `VirtualNode::Empty`.
30///
31/// # Returns
32///
33/// - `VirtualNode` - The inner node if `Some`, otherwise `VirtualNode::Empty`.
34impl From<Option<VirtualNode>> for VirtualNode {
35    /// Lifts an `Option<VirtualNode>` into a [`VirtualNode`] (unwraps or falls back to empty).
36    ///
37    /// # Arguments
38    ///
39    /// - `Option<VirtualNode>` - Input value to convert from.
40    fn from(node: Option<VirtualNode>) -> Self {
41        match node {
42            Some(node) => node,
43            None => VirtualNode::Empty,
44        }
45    }
46}
47
48/// Converts an `Option<Vec<VirtualNode>>` into a `VirtualNode`.
49///
50/// `Some(vec)` converts the vector into a `VirtualNode::Fragment` (or `Empty`
51/// if the vector is empty), `None` returns `VirtualNode::Empty`.
52///
53/// # Returns
54///
55/// - `VirtualNode` - A `VirtualNode::Fragment` if `Some` with nodes,
56///   `VirtualNode::Empty` if `None` or the vector is empty.
57impl From<Option<Vec<VirtualNode>>> for VirtualNode {
58    /// Lifts an `Option<Vec<VirtualNode>>` into a [`VirtualNode`] (unwraps or falls back to empty).
59    ///
60    /// # Arguments
61    ///
62    /// - `Option<Vec<VirtualNode>>` - Input value to convert from.
63    fn from(nodes: Option<Vec<VirtualNode>>) -> Self {
64        match nodes {
65            Some(nodes) => nodes.into(),
66            None => VirtualNode::Empty,
67        }
68    }
69}
70
71/// Wraps a `FnMut(&mut HookContext) -> VirtualNode` closure into a `DynamicNode`.
72///
73/// This enables writing `{move |_: &mut HookContext| html! { ... }}` directly in HTML markup
74/// without explicit `DynamicNode` construction.
75impl<F> From<F> for VirtualNode
76where
77    F: FnMut(&mut HookContext) -> VirtualNode + 'static,
78{
79    /// Wraps this closure into a `VirtualNode::Dynamic` with a fresh hook context.
80    ///
81    /// # Returns
82    ///
83    /// - `VirtualNode` - A dynamic virtual node wrapping this closure.
84    ///
85    /// # Arguments
86    ///
87    /// - `F` - Input value to convert from.
88    fn from(render_fn: F) -> Self {
89        VirtualNode::create_dynamic(render_fn)
90    }
91}
92
93/// Converts a `String` into a text virtual node.
94impl From<String> for VirtualNode {
95    /// Converts this string into a text virtual node.
96    ///
97    /// # Returns
98    ///
99    /// - `VirtualNode` - A text virtual node.
100    ///
101    /// # Arguments
102    ///
103    /// - `String` - Input value to convert from.
104    fn from(text: String) -> Self {
105        VirtualNode::Text(TextNode::new(text, None))
106    }
107}
108
109/// Converts a `&str` into a text virtual node.
110impl From<&str> for VirtualNode {
111    /// Converts this string slice into a text virtual node.
112    ///
113    /// # Returns
114    ///
115    /// - `VirtualNode` - A text virtual node.
116    ///
117    /// # Arguments
118    ///
119    /// - `&str` - Input value to convert from.
120    fn from(text: &str) -> Self {
121        VirtualNode::Text(TextNode::new(text.to_string(), None))
122    }
123}
124
125/// Converts an `i32` into a text virtual node.
126impl From<i32> for VirtualNode {
127    /// Converts this integer into a text virtual node.
128    ///
129    /// # Returns
130    ///
131    /// - `VirtualNode` - A text virtual node.
132    ///
133    /// # Arguments
134    ///
135    /// - `i32` - Input value to convert from.
136    fn from(value: i32) -> Self {
137        VirtualNode::Text(TextNode::new(value.to_string(), None))
138    }
139}
140
141/// Converts a `usize` into a text virtual node.
142impl From<usize> for VirtualNode {
143    /// Converts this unsigned integer into a text virtual node.
144    ///
145    /// # Returns
146    ///
147    /// - `VirtualNode` - A text virtual node.
148    ///
149    /// # Arguments
150    ///
151    /// - `usize` - Input value to convert from.
152    fn from(value: usize) -> Self {
153        VirtualNode::Text(TextNode::new(value.to_string(), None))
154    }
155}
156
157/// Converts a `bool` into a text virtual node.
158impl From<bool> for VirtualNode {
159    /// Converts this boolean into a text virtual node.
160    ///
161    /// # Returns
162    ///
163    /// - `VirtualNode` - A text virtual node.
164    ///
165    /// # Arguments
166    ///
167    /// - `bool` - Input value to convert from.
168    fn from(value: bool) -> Self {
169        VirtualNode::Text(TextNode::new(value.to_string(), None))
170    }
171}
172
173/// Converts a signal into a reactive text virtual node.
174impl<T> From<Signal<T>> for VirtualNode
175where
176    T: Clone + PartialEq + Display + 'static,
177{
178    /// Converts this signal into a reactive text virtual node.
179    ///
180    /// # Returns
181    ///
182    /// - `VirtualNode` - A reactive text virtual node.
183    ///
184    /// # Arguments
185    ///
186    /// - `Signal<T>` - Input value to convert from.
187    fn from(signal: Signal<T>) -> Self {
188        signal.as_reactive_text()
189    }
190}
191
192/// Converts a signal into a reactive text node with listener wiring.
193impl<T> AsReactiveText for Signal<T>
194where
195    T: Clone + PartialEq + Display + 'static,
196{
197    /// Creates a reactive text node that auto-updates when the signal changes.
198    ///
199    /// Internally creates a bridge `Signal<String>` that subscribes to the
200    /// source signal and updates the text content on every change.
201    ///
202    /// # Returns
203    ///
204    /// - `VirtualNode` - A text virtual node with reactive signal binding.
205    fn as_reactive_text(&self) -> VirtualNode {
206        let source: Signal<T> = *self;
207        let string_signal: Signal<String> = Signal::create(source.get().to_string());
208        let string_signal_clone: Signal<String> = string_signal;
209        source.subscribe(move || {
210            string_signal_clone.set(source.get().to_string());
211        });
212        // The closure above captures `string_signal_clone` (which aliases
213        // `string_signal`), so `source` now transitively keeps the bridge
214        // alive. Register that dependency so the bridge's heap allocation
215        // can be reclaimed once `source` is deactivated.
216        BridgeRefsCell::track(string_signal.get_inner(), source.get_inner());
217        VirtualNode::Text(TextNode::new(string_signal.get(), Some(string_signal)))
218    }
219}
220
221/// Constructs an `EventAdapter` that wraps any event-compatible value.
222impl<T> EventAdapter<T> {
223    /// Returns the inner wrapped value, consuming the adapter.
224    ///
225    /// # Returns
226    ///
227    /// - `T` - The inner value.
228    pub(crate) fn into_inner(self) -> T {
229        self.inner
230    }
231}
232
233/// Adapts a `FnMut(Event)` closure into an `AttributeValue::Event`.
234///
235/// Wraps the closure into a `NativeEventHandler` and returns it as an
236/// event attribute value. This replaces the `__EventWrapper<F>` type
237/// that was previously generated inline by the `html!` macro.
238impl<F> EventAdapter<F>
239where
240    F: FnMut(Event) + 'static,
241{
242    /// Converts the wrapped closure into an event `AttributeValue`.
243    ///
244    /// # Arguments
245    ///
246    /// - `&'static str` - The event name string to associate with the handler.
247    ///
248    /// # Returns
249    ///
250    /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
251    pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
252        AttributeValue::Event(NativeEventHandler::create(event_name, self.into_inner()))
253    }
254}
255
256/// Converts an event with a specific event name into an `AttributeValue`.
257impl<F> From<EventNamedAdapter<F>> for AttributeValue
258where
259    F: FnMut(Event) + 'static,
260{
261    /// Converts the wrapped closure with event name into an event `AttributeValue`.
262    ///
263    /// # Returns
264    ///
265    /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
266    ///
267    /// # Arguments
268    ///
269    /// - `EventNamedAdapter<F>` - Input value to convert from.
270    fn from(adapter: EventNamedAdapter<F>) -> Self {
271        AttributeValue::Event(NativeEventHandler::create(
272            adapter.get_event_name(),
273            adapter.inner,
274        ))
275    }
276}
277
278/// Converts an event named adapter with `NativeEventHandler` into an `AttributeValue`.
279impl From<EventNamedAdapter<NativeEventHandler>> for AttributeValue {
280    /// Converts the wrapped handler with event name into an event `AttributeValue`.
281    ///
282    /// # Returns
283    ///
284    /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
285    ///
286    /// # Arguments
287    ///
288    /// - `EventNamedAdapter<NativeEventHandler>` - Input value to convert from.
289    fn from(mut adapter: EventNamedAdapter<NativeEventHandler>) -> Self {
290        let event_name: &'static str = adapter.get_event_name();
291        adapter.get_mut_inner().set_event_name(event_name);
292        AttributeValue::Event(adapter.inner)
293    }
294}
295
296/// Converts an event named adapter with optional shared closure into an `AttributeValue`.
297///
298/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
299/// into a `NativeEventHandler` with the adapter's event name, and `None` becomes
300/// `AttributeValue::Text(String::new())`.
301impl From<EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>> for AttributeValue {
302    /// Converts the wrapped optional shared closure with event name into an event `AttributeValue`.
303    ///
304    /// # Returns
305    ///
306    /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
307    ///
308    /// # Arguments
309    ///
310    /// - `EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>` - Input value to convert from.
311    fn from(adapter: EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>) -> Self {
312        let event_name: &'static str = adapter.get_event_name();
313        match adapter.inner {
314            Some(callback) => AttributeValue::Event(NativeEventHandler::create(
315                event_name,
316                move |event: Event| {
317                    callback(event);
318                },
319            )),
320            None => AttributeValue::Text(String::new()),
321        }
322    }
323}
324
325/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
326///
327/// When the user already provides a `NativeEventHandler`, the handler is
328/// re-wrapped with the given `event_name` to ensure the DOM event listener
329/// is bound to the correct event type (e.g., "click" rather than "onclick").
330impl EventAdapter<NativeEventHandler> {
331    /// Converts the wrapped handler into an event `AttributeValue`.
332    ///
333    /// Re-wraps the handler with the provided `event_name` so that the
334    /// DOM event listener uses the correct event type string.
335    ///
336    /// # Arguments
337    ///
338    /// - `&'static str` - The event name to bind the handler to.
339    ///
340    /// # Returns
341    ///
342    /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
343    pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
344        let mut handler: NativeEventHandler = self.into_inner();
345        handler.set_event_name(event_name);
346        AttributeValue::Event(handler)
347    }
348}
349
350/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
351///
352/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with the
353/// given event name, and `None` becomes `AttributeValue::Text(String::new())`.
354impl EventAdapter<Option<NativeEventHandler>> {
355    /// Converts the wrapped optional handler into an attribute value.
356    ///
357    /// Re-wraps a `Some` handler with the provided `event_name` so that the
358    /// DOM event listener uses the correct event type string.
359    ///
360    /// # Arguments
361    ///
362    /// - `&'static str` - The event name to bind the handler to.
363    ///
364    /// # Returns
365    ///
366    /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
367    pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
368        match self.into_inner() {
369            Some(handler) => EventNamedAdapter::new(handler, event_name).into(),
370            None => AttributeValue::Text(String::new()),
371        }
372    }
373}
374
375/// Adapts an `Option<Rc<dyn Fn(Event)>>` into an `AttributeValue`.
376///
377/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
378/// into a `NativeEventHandler`, and `None` becomes `AttributeValue::Text(String::new())`.
379/// This supports component Props that use `Option<Rc<dyn Fn(Event)>>` for event callbacks.
380impl EventAdapter<Option<Rc<dyn Fn(Event)>>> {
381    /// Converts the wrapped optional shared closure into an attribute value.
382    ///
383    /// # Arguments
384    ///
385    /// - `&'static str` - The event name to bind the handler to.
386    ///
387    /// # Returns
388    ///
389    /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
390    pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
391        match self.into_inner() {
392            Some(callback) => AttributeValue::Event(NativeEventHandler::create(
393                event_name,
394                move |event: Event| {
395                    callback(event);
396                },
397            )),
398            None => AttributeValue::Text(String::new()),
399        }
400    }
401}
402
403/// Constructs an `AttrValueAdapter` that wraps any attribute-compatible value.
404impl<T> AttrValueAdapter<T> {
405    /// Returns the inner wrapped value, consuming the adapter.
406    ///
407    /// # Returns
408    ///
409    /// - `T` - The inner value.
410    pub(crate) fn into_inner(self) -> T {
411        self.inner
412    }
413}
414
415/// Constructs an `InnerHtmlAdapter` that wraps an `inner_html:` payload.
416impl<T> InnerHtmlAdapter<T> {
417    /// Returns the inner wrapped value, consuming the adapter.
418    ///
419    /// Mirrors [`AttrValueAdapter::into_inner`] so the html! macro can
420    /// use the same "wrap-then-into-inner" pattern for both adapter
421    /// kinds without diverging call sites.
422    ///
423    /// # Returns
424    ///
425    /// - `T` - The inner value.
426    pub(crate) fn into_inner(self) -> T {
427        self.inner
428    }
429}
430
431/// Adapts a `FnMut(Event)` closure into a callback `AttributeValue`.
432///
433/// This handles the case where a closure is used as a component callback prop.
434/// The closure is converted via `IntoCallbackAttribute::into_callback_attribute()`.
435impl<F> AttrValueAdapter<F>
436where
437    F: FnMut(Event) + 'static,
438{
439    /// Converts the wrapped closure into a callback `AttributeValue`.
440    ///
441    /// # Returns
442    ///
443    /// - `AttributeValue` - An event attribute value wrapping the adapted closure.
444    pub fn into_callback(self) -> AttributeValue {
445        self.into_inner().into()
446    }
447
448    /// Converts the wrapped closure into a callback `AttributeValue` with a
449    /// custom event name for component props.
450    ///
451    /// # Arguments
452    ///
453    /// - `&'static str` - The custom attribute name (e.g., "on-increment", "on-change").
454    ///
455    /// # Returns
456    ///
457    /// - `AttributeValue` - An event attribute value with the custom name.
458    pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
459        AttributeValue::Event(NativeEventHandler::create(name, self.into_inner()))
460    }
461}
462
463/// Converts a named callback adapter into an `AttributeValue`.
464impl<F> From<CallbackNamedAdapter<F>> for AttributeValue
465where
466    F: FnMut(Event) + 'static,
467{
468    /// Converts the wrapped closure with custom name into a callback `AttributeValue`.
469    ///
470    /// # Returns
471    ///
472    /// - `AttributeValue` - An event attribute value with the custom name.
473    ///
474    /// # Arguments
475    ///
476    /// - `CallbackNamedAdapter<F>` - Input value to convert from.
477    fn from(adapter: CallbackNamedAdapter<F>) -> Self {
478        AttributeValue::Event(NativeEventHandler::create(
479            adapter.get_name(),
480            adapter.inner,
481        ))
482    }
483}
484
485/// Generic-parameterised implementation for [`AttrValueAdapter`].
486/// Implements `impl AttrValueAdapter<NativeEventHandler>`.
487impl AttrValueAdapter<NativeEventHandler> {
488    /// Converts the wrapped handler into a callback `AttributeValue` with a
489    /// custom event name for component props.
490    ///
491    /// # Arguments
492    ///
493    /// - `&'static str` - The custom attribute name.
494    ///
495    /// # Returns
496    ///
497    /// - `AttributeValue` - An event attribute value with the custom name.
498    pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
499        let mut handler: NativeEventHandler = self.into_inner();
500        handler.set_event_name(name);
501        AttributeValue::Event(handler)
502    }
503}
504
505/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
506impl AttrValueAdapter<Option<NativeEventHandler>> {
507    /// Converts the wrapped optional handler into an attribute value.
508    ///
509    /// # Returns
510    ///
511    /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
512    pub fn into_callback(self) -> AttributeValue {
513        match self.into_inner() {
514            Some(handler) => AttrValueAdapter::new(handler).into(),
515            None => AttributeValue::Text(String::new()),
516        }
517    }
518
519    /// Converts this optional handler into a callback `AttributeValue` with a
520    /// custom event name for component props.
521    ///
522    /// # Arguments
523    ///
524    /// - `&'static str` - The custom attribute name.
525    ///
526    /// # Returns
527    ///
528    /// - `AttributeValue` - An event attribute with the custom name if `Some`,
529    ///   otherwise an empty text attribute.
530    pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
531        match self.into_inner() {
532            Some(handler) => AttrValueAdapter::new(handler).into_callback_named(name),
533            None => AttributeValue::Text(String::new()),
534        }
535    }
536}
537
538/// Adapts any type that implements `Into<AttributeValue>` into an `AttributeValue`.
539///
540/// This is the fallback path for non-closure attribute values (strings, signals,
541/// CSS classes, etc.).
542impl<T> From<AttrValueAdapter<T>> for AttributeValue
543where
544    T: Into<AttributeValue>,
545{
546    /// Converts the wrapped value into an `AttributeValue`.
547    ///
548    /// # Returns
549    ///
550    /// - `AttributeValue` - The reactive attribute value.
551    ///
552    /// # Arguments
553    ///
554    /// - `AttrValueAdapter<T>` - Input value to convert from.
555    fn from(adapter: AttrValueAdapter<T>) -> Self {
556        adapter.into_inner().into()
557    }
558}
559
560/// Adapts an `inner_html:` payload into the matching `AttributeValue`
561/// variant, routing through `set_inner_html` instead of the generic
562/// `Text` attribute path.
563///
564/// The two blanket impls below cover the user-visible call sites:
565///
566/// - `String` produces
567///   `AttributeValue::InnerHtml(String::from("..."))`.
568/// - `Signal<String>` produces
569///   `AttributeValue::InnerHtmlSignal(signal)`.
570///
571/// Each impl only requires its specific source type, so the compiler
572/// picks the right one based on the inferred `T` at the call site
573/// (no manual `.into()` annotation needed).
574impl From<InnerHtmlAdapter<String>> for AttributeValue {
575    /// Wraps the static `String` payload in an
576    /// `AttributeValue::InnerHtml` variant so the renderer can call
577    /// `Element::set_inner_html` on it.
578    ///
579    /// # Arguments
580    ///
581    /// - `InnerHtmlAdapter<String>` - Input value to convert from.
582    fn from(adapter: InnerHtmlAdapter<String>) -> Self {
583        AttributeValue::InnerHtml(adapter.into_inner())
584    }
585}
586
587/// `From` conversion into [`AttributeValue`].
588impl From<InnerHtmlAdapter<&str>> for AttributeValue {
589    /// Wraps the static `&str` payload by allocating a new `String`
590    /// so the renderer owns the data independently of the caller's
591    /// borrow lifetime.
592    ///
593    /// # Arguments
594    ///
595    /// - `InnerHtmlAdapter<&str>` - Input value to convert from.
596    fn from(adapter: InnerHtmlAdapter<&str>) -> Self {
597        AttributeValue::InnerHtml(adapter.into_inner().to_owned())
598    }
599}
600
601/// `From` conversion into [`AttributeValue`].
602impl From<InnerHtmlAdapter<Signal<String>>> for AttributeValue {
603    /// Wraps the reactive payload in an `AttributeValue::InnerHtmlSignal`
604    /// so the renderer subscribes to the signal and re-applies
605    /// `set_inner_html` on every change.
606    ///
607    /// # Arguments
608    ///
609    /// - `InnerHtmlAdapter<Signal<String>>` - Input value to convert from.
610    fn from(adapter: InnerHtmlAdapter<Signal<String>>) -> Self {
611        AttributeValue::InnerHtmlSignal(adapter.into_inner())
612    }
613}
614
615/// Converts a `NodeRef<T>` into an `AttributeValue::Ref`.
616///
617/// The type parameter `T` is erased at the `AttributeValue` layer (we
618/// store the underlying `JsValue` cell), so any concrete element type
619/// works. The `html!` macro relies on this to accept
620/// `html! { input { ref: my_ref } }` without the user needing to call
621/// `.into()` explicitly.
622impl<T: ?Sized> From<NodeRef<T>> for AttributeValue {
623    /// Wraps the ref cell as an `AttributeValue::Ref` so the renderer
624    /// can populate it after mount.
625    ///
626    /// # Returns
627    ///
628    /// - `AttributeValue` - A ref variant carrying the (still empty)
629    ///   handle.
630    ///
631    /// # Arguments
632    ///
633    /// - `NodeRef<T>` - Input value to convert from.
634    fn from(node_ref: NodeRef<T>) -> Self {
635        // Erase the typed phantom marker to a `NodeRef<JsValue>` so the
636        // `AttributeValue::Ref` payload is uniform. The `JsValue` cell
637        // is what the renderer actually stores and sets.
638        let erased: NodeRefDyn = NodeRefDyn {
639            inner: node_ref.inner.clone(),
640            _marker: PhantomData,
641        };
642        AttributeValue::Ref(erased)
643    }
644}