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}