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