Skip to main content

euv_core/vdom/cast/
impl.rs

1use crate::*;
2
3/// Converts a `VirtualNode` reference into an owned node.
4impl AsNode for VirtualNode {
5    /// Clones this virtual node into an owned `Option<VirtualNode>`.
6    ///
7    /// # Returns
8    ///
9    /// - `Option<VirtualNode>` - Always `Some` with the cloned node.
10    fn as_node(&self) -> Option<VirtualNode> {
11        Some(self.clone())
12    }
13}
14
15/// Converts a `VirtualNode` reference into an owned node.
16impl AsNode for &VirtualNode {
17    /// Clones this virtual node reference into an owned `Option<VirtualNode>`.
18    ///
19    /// # Returns
20    ///
21    /// - `Option<VirtualNode>` - Always `Some` with the cloned node.
22    fn as_node(&self) -> Option<VirtualNode> {
23        Some((*self).clone())
24    }
25}
26
27/// Converts a `String` into a text virtual node.
28impl AsNode for String {
29    /// Converts this string into a text virtual node.
30    ///
31    /// # Returns
32    ///
33    /// - `Option<VirtualNode>` - Always `Some` with a text node.
34    fn as_node(&self) -> Option<VirtualNode> {
35        Some(VirtualNode::Text(TextNode::new(self.clone(), None)))
36    }
37}
38
39/// Converts a string slice into a text virtual node.
40impl AsNode for &str {
41    /// Converts this string slice into a text virtual node.
42    ///
43    /// # Returns
44    ///
45    /// - `Option<VirtualNode>` - Always `Some` with a text node.
46    fn as_node(&self) -> Option<VirtualNode> {
47        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
48    }
49}
50
51/// Converts an `i32` into a text virtual node.
52impl AsNode for i32 {
53    /// Converts this integer into a text virtual node.
54    ///
55    /// # Returns
56    ///
57    /// - `Option<VirtualNode>` - Always `Some` with a text node.
58    fn as_node(&self) -> Option<VirtualNode> {
59        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
60    }
61}
62
63/// Converts an `i64` into a text virtual node.
64impl AsNode for i64 {
65    /// Converts this integer into a text virtual node.
66    ///
67    /// # Returns
68    ///
69    /// - `Option<VirtualNode>` - Always `Some` with a text node.
70    fn as_node(&self) -> Option<VirtualNode> {
71        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
72    }
73}
74
75/// Converts a `usize` into a text virtual node.
76impl AsNode for usize {
77    /// Converts this unsigned integer into a text virtual node.
78    ///
79    /// # Returns
80    ///
81    /// - `Option<VirtualNode>` - Always `Some` with a text node.
82    fn as_node(&self) -> Option<VirtualNode> {
83        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
84    }
85}
86
87/// Converts an `f32` into a text virtual node.
88impl AsNode for f32 {
89    /// Converts this float into a text virtual node.
90    ///
91    /// # Returns
92    ///
93    /// - `Option<VirtualNode>` - Always `Some` with a text node.
94    fn as_node(&self) -> Option<VirtualNode> {
95        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
96    }
97}
98
99/// Converts an `f64` into a text virtual node.
100impl AsNode for f64 {
101    /// Converts this float into a text virtual node.
102    ///
103    /// # Returns
104    ///
105    /// - `Option<VirtualNode>` - Always `Some` with a text node.
106    fn as_node(&self) -> Option<VirtualNode> {
107        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
108    }
109}
110
111/// Converts a `bool` into a text virtual node.
112impl AsNode for bool {
113    /// Converts this boolean into a text virtual node.
114    ///
115    /// # Returns
116    ///
117    /// - `Option<VirtualNode>` - Always `Some` with a text node.
118    fn as_node(&self) -> Option<VirtualNode> {
119        Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
120    }
121}
122
123/// Converts a signal into a reactive text virtual node.
124impl<T> AsNode for Signal<T>
125where
126    T: Clone + PartialEq + std::fmt::Display + 'static,
127{
128    /// Converts this signal into a reactive text virtual node.
129    ///
130    /// # Returns
131    ///
132    /// - `Option<VirtualNode>` - Always `Some` with a reactive text node.
133    fn as_node(&self) -> Option<VirtualNode> {
134        Some(self.as_reactive_text())
135    }
136}
137
138/// Converts a `VirtualNode` into itself via `IntoNode`.
139impl IntoNode for VirtualNode {
140    /// Returns this virtual node as-is.
141    ///
142    /// # Returns
143    ///
144    /// - `VirtualNode` - This same virtual node.
145    fn into_node(self) -> VirtualNode {
146        self
147    }
148}
149
150/// Wraps a `FnMut() -> VirtualNode` closure into a `DynamicNode` via `IntoNode`.
151///
152/// This enables writing `{move || html! { ... }}` directly in HTML markup
153/// without explicit `DynamicNode` construction.
154impl<F> IntoNode for F
155where
156    F: FnMut() -> VirtualNode + 'static,
157{
158    /// Wraps this closure into a `VirtualNode::Dynamic` with a fresh hook context.
159    ///
160    /// # Returns
161    ///
162    /// - `VirtualNode` - A dynamic virtual node wrapping this closure.
163    fn into_node(self) -> VirtualNode {
164        VirtualNode::Dynamic(DynamicNode {
165            render_fn: Rc::new(RefCell::new(self)),
166            hook_context: crate::reactive::create_hook_context(),
167        })
168    }
169}
170
171/// Converts a `String` into a text virtual node via `IntoNode`.
172impl IntoNode for String {
173    /// Converts this string into a text virtual node.
174    ///
175    /// # Returns
176    ///
177    /// - `VirtualNode` - A text virtual node.
178    fn into_node(self) -> VirtualNode {
179        VirtualNode::Text(TextNode::new(self, None))
180    }
181}
182
183/// Converts a `&str` into a text virtual node via `IntoNode`.
184impl IntoNode for &str {
185    /// Converts this string slice into a text virtual node.
186    ///
187    /// # Returns
188    ///
189    /// - `VirtualNode` - A text virtual node.
190    fn into_node(self) -> VirtualNode {
191        VirtualNode::Text(TextNode::new(self.to_string(), None))
192    }
193}
194
195/// Converts an `i32` into a text virtual node via `IntoNode`.
196impl IntoNode for i32 {
197    /// Converts this integer into a text virtual node.
198    ///
199    /// # Returns
200    ///
201    /// - `VirtualNode` - A text virtual node.
202    fn into_node(self) -> VirtualNode {
203        VirtualNode::Text(TextNode::new(self.to_string(), None))
204    }
205}
206
207/// Converts a `usize` into a text virtual node via `IntoNode`.
208impl IntoNode for usize {
209    /// Converts this unsigned integer into a text virtual node.
210    ///
211    /// # Returns
212    ///
213    /// - `VirtualNode` - A text virtual node.
214    fn into_node(self) -> VirtualNode {
215        VirtualNode::Text(TextNode::new(self.to_string(), None))
216    }
217}
218
219/// Converts a `bool` into a text virtual node via `IntoNode`.
220impl IntoNode for bool {
221    /// Converts this boolean into a text virtual node.
222    ///
223    /// # Returns
224    ///
225    /// - `VirtualNode` - A text virtual node.
226    fn into_node(self) -> VirtualNode {
227        VirtualNode::Text(TextNode::new(self.to_string(), None))
228    }
229}
230
231/// Converts a signal into a reactive text virtual node via `IntoNode`.
232impl<T> IntoNode for Signal<T>
233where
234    T: Clone + PartialEq + std::fmt::Display + 'static,
235{
236    /// Converts this signal into a reactive text virtual node.
237    ///
238    /// # Returns
239    ///
240    /// - `VirtualNode` - A reactive text virtual node.
241    fn into_node(self) -> VirtualNode {
242        self.as_reactive_text()
243    }
244}
245
246/// Converts a signal into a reactive text node with listener wiring.
247impl<T> AsReactiveText for Signal<T>
248where
249    T: Clone + PartialEq + std::fmt::Display + 'static,
250{
251    /// Creates a reactive text node that auto-updates when the signal changes.
252    ///
253    /// Internally creates a bridge `Signal<String>` that subscribes to the
254    /// source signal and updates the text content on every change.
255    ///
256    /// # Returns
257    ///
258    /// - `VirtualNode` - A text virtual node with reactive signal binding.
259    fn as_reactive_text(&self) -> VirtualNode {
260        let signal: Signal<T> = *self;
261        let initial: String = signal.get().to_string();
262        let string_signal: Signal<String> = {
263            let boxed: Box<SignalInner<String>> = Box::new(SignalInner::new(initial.clone()));
264            Signal::from_inner(Box::leak(boxed) as *mut SignalInner<String>)
265        };
266        let source_signal: Signal<T> = *self;
267        let string_signal_clone: Signal<String> = string_signal;
268        source_signal.subscribe({
269            let source_signal: Signal<T> = source_signal;
270            move || {
271                let new_value: String = source_signal.get().to_string();
272                string_signal_clone.set(new_value);
273            }
274        });
275        VirtualNode::Text(TextNode::new(initial, Some(string_signal)))
276    }
277}
278
279/// Constructs an `EventAdapter` that wraps any event-compatible value.
280impl<T> EventAdapter<T> {
281    /// Creates a new `EventAdapter` wrapping the given value.
282    ///
283    /// # Arguments
284    ///
285    /// - `T` - The value to wrap for event attribute adaptation.
286    ///
287    /// # Returns
288    ///
289    /// - `EventAdapter<T>` - A new adapter wrapping the value.
290    pub fn new(inner: T) -> Self {
291        EventAdapter { inner }
292    }
293
294    /// Returns the inner wrapped value, consuming the adapter.
295    ///
296    /// # Returns
297    ///
298    /// - `T` - The inner value.
299    pub(crate) fn into_inner(self) -> T {
300        self.inner
301    }
302}
303
304/// Adapts a `FnMut(NativeEvent)` closure into an `AttributeValue::Event`.
305///
306/// Wraps the closure into a `NativeEventHandler` and returns it as an
307/// event attribute value. This replaces the `__EventWrapper<F>` type
308/// that was previously generated inline by the `html!` macro.
309impl<F> EventAdapter<F>
310where
311    F: FnMut(NativeEvent) + 'static,
312{
313    /// Converts the wrapped closure into an event `AttributeValue`.
314    ///
315    /// # Arguments
316    ///
317    /// - `NativeEventName` - The event name enum variant to associate with the handler.
318    ///
319    /// # Returns
320    ///
321    /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
322    pub fn into_attribute(self, event_name: NativeEventName) -> AttributeValue {
323        AttributeValue::Event(NativeEventHandler::new(event_name, self.into_inner()))
324    }
325}
326
327/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
328///
329/// When the user already provides a `NativeEventHandler`, the handler is
330/// re-wrapped with the given `event_name` to ensure the DOM event listener
331/// is bound to the correct event type (e.g., "click" rather than "onclick").
332/// This replaces the `impl __EventWrapper<NativeEventHandler>` that was
333/// previously generated inline.
334impl EventAdapter<NativeEventHandler> {
335    /// Converts the wrapped handler into an event `AttributeValue`.
336    ///
337    /// Re-wraps the handler with the provided `event_name` so that the
338    /// DOM event listener uses the correct event type string.
339    ///
340    /// # Arguments
341    ///
342    /// - `NativeEventName` - The event name to bind the handler to.
343    ///
344    /// # Returns
345    ///
346    /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
347    pub fn into_attribute(self, event_name: NativeEventName) -> AttributeValue {
348        let handler: NativeEventHandler = self.into_inner();
349        AttributeValue::Event(NativeEventHandler::new(
350            event_name,
351            move |event: NativeEvent| {
352                handler.handle(event);
353            },
354        ))
355    }
356}
357
358/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
359///
360/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with the
361/// given event name, and `None` becomes `AttributeValue::Text(String::new())`.
362/// This replaces the `impl __EventWrapper<Option<NativeEventHandler>>` that was
363/// previously generated inline by the `html!` macro.
364impl EventAdapter<Option<NativeEventHandler>> {
365    /// Converts the wrapped optional handler into an attribute value.
366    ///
367    /// Re-wraps a `Some` handler with the provided `event_name` so that the
368    /// DOM event listener uses the correct event type string.
369    ///
370    /// # Arguments
371    ///
372    /// - `NativeEventName` - The event name to bind the handler to.
373    ///
374    /// # Returns
375    ///
376    /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
377    pub fn into_attribute(self, event_name: NativeEventName) -> AttributeValue {
378        match self.into_inner() {
379            Some(handler) => EventAdapter::new(handler).into_attribute(event_name),
380            None => AttributeValue::Text(String::new()),
381        }
382    }
383}
384
385/// Constructs an `AttrValueAdapter` that wraps any attribute-compatible value.
386impl<T> AttrValueAdapter<T> {
387    /// Creates a new `AttrValueAdapter` wrapping the given value.
388    ///
389    /// # Arguments
390    ///
391    /// - `T` - The value to wrap for attribute adaptation.
392    ///
393    /// # Returns
394    ///
395    /// - `AttrValueAdapter<T>` - A new adapter wrapping the value.
396    pub fn new(inner: T) -> Self {
397        AttrValueAdapter { inner }
398    }
399
400    /// Returns the inner wrapped value, consuming the adapter.
401    ///
402    /// # Returns
403    ///
404    /// - `T` - The inner value.
405    pub(crate) fn into_inner(self) -> T {
406        self.inner
407    }
408}
409
410/// Adapts a `FnMut(NativeEvent)` closure into a callback `AttributeValue`.
411///
412/// This handles the case where a closure is used as a component callback prop.
413/// The closure is converted via `IntoCallbackAttribute::into_callback_attribute()`.
414/// This replaces the `__IsClosure for F` impl that was previously generated inline.
415impl<F> AttrValueAdapter<F>
416where
417    F: FnMut(NativeEvent) + 'static,
418{
419    /// Converts the wrapped closure into a callback `AttributeValue`.
420    ///
421    /// # Returns
422    ///
423    /// - `AttributeValue` - An event attribute value wrapping the adapted closure.
424    pub fn into_callback_attribute_value(self) -> AttributeValue {
425        self.into_inner().into_callback_attribute()
426    }
427
428    /// Converts the wrapped closure into a callback `AttributeValue` with a
429    /// custom event name for component props.
430    ///
431    /// # Arguments
432    ///
433    /// - `String` - The custom attribute name (e.g., "on-increment", "on-change").
434    ///
435    /// # Returns
436    ///
437    /// - `AttributeValue` - An event attribute value with the custom name.
438    pub fn into_callback_attribute_value_with_name(self, name: String) -> AttributeValue {
439        AttributeValue::Event(NativeEventHandler::new(
440            NativeEventName::Other(name),
441            self.into_inner(),
442        ))
443    }
444}
445
446/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
447///
448/// When the user already provides a `NativeEventHandler`, the handler is
449/// re-wrapped with a generic "callback" event name so that subsequent
450/// `EventAdapter::into_attribute` calls can override it with the correct
451/// DOM event type. This replaces the `__IsClosure for NativeEventHandler`
452/// impl that was previously generated inline by the `html!` macro.
453impl AttrValueAdapter<NativeEventHandler> {
454    /// Converts the wrapped handler into an event `AttributeValue`.
455    ///
456    /// Re-wraps the handler with a generic callback event name so that the
457    /// DOM event type can be correctly resolved when the handler is later
458    /// bound to a real element via `EventAdapter::into_attribute`.
459    ///
460    /// # Returns
461    ///
462    /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
463    pub fn into_callback_attribute_value(self) -> AttributeValue {
464        let handler: NativeEventHandler = self.into_inner();
465        AttributeValue::Event(NativeEventHandler::new(
466            NativeEventName::Other("callback".to_string()),
467            move |event: NativeEvent| {
468                handler.handle(event);
469            },
470        ))
471    }
472
473    /// Converts the wrapped handler into a callback `AttributeValue` with a
474    /// custom event name for component props.
475    ///
476    /// # Arguments
477    ///
478    /// - `String` - The custom attribute name.
479    ///
480    /// # Returns
481    ///
482    /// - `AttributeValue` - An event attribute value with the custom name.
483    pub fn into_callback_attribute_value_with_name(self, name: String) -> AttributeValue {
484        let handler: NativeEventHandler = self.into_inner();
485        AttributeValue::Event(NativeEventHandler::new(
486            NativeEventName::Other(name),
487            move |event: NativeEvent| {
488                handler.handle(event);
489            },
490        ))
491    }
492}
493
494/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
495///
496/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with
497/// a generic callback event name, and `None` becomes `AttributeValue::Text(String::new())`.
498/// This replaces the `__IsClosure for Option<NativeEventHandler>` impl that was
499/// previously generated inline by the `html!` macro.
500impl AttrValueAdapter<Option<NativeEventHandler>> {
501    /// Converts the wrapped optional handler into an attribute value.
502    ///
503    /// Re-wraps a `Some` handler with a generic callback event name so that the
504    /// DOM event type can be correctly resolved when the handler is later bound
505    /// to a real element via `EventAdapter::into_attribute`.
506    ///
507    /// # Returns
508    ///
509    /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
510    pub fn into_callback_attribute_value(self) -> AttributeValue {
511        match self.into_inner() {
512            Some(handler) => AttrValueAdapter::new(handler).into_callback_attribute_value(),
513            None => AttributeValue::Text(String::new()),
514        }
515    }
516
517    /// Converts this optional handler into a callback `AttributeValue` with a
518    /// custom event name for component props.
519    ///
520    /// # Arguments
521    ///
522    /// - `String` - The custom attribute name.
523    ///
524    /// # Returns
525    ///
526    /// - `AttributeValue` - An event attribute with the custom name if `Some`,
527    ///   otherwise an empty text attribute.
528    pub fn into_callback_attribute_value_with_name(self, name: String) -> AttributeValue {
529        match self.into_inner() {
530            Some(handler) => {
531                AttrValueAdapter::new(handler).into_callback_attribute_value_with_name(name)
532            }
533            None => AttributeValue::Text(String::new()),
534        }
535    }
536}
537
538/// Adapts any `IntoReactiveValue` type into an `AttributeValue`.
539///
540/// This is the fallback path for non-closure attribute values (strings, signals,
541/// CSS classes, etc.). The value is converted via `IntoReactiveValue::into_reactive_value()`.
542/// This replaces the `__ValuePicker` / `__FallbackHelper` hierarchy that was previously
543/// generated inline by the `html!` macro.
544impl<T> AttrValueAdapter<T>
545where
546    T: IntoReactiveValue,
547{
548    /// Converts the wrapped value into an `AttributeValue` via reactive value adaptation.
549    ///
550    /// # Returns
551    ///
552    /// - `AttributeValue` - The reactive attribute value.
553    pub fn into_reactive_attribute_value(self) -> AttributeValue {
554        self.into_inner().into_reactive_value()
555    }
556}