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 self.inner
283 }
284}
285
286/// Adapts a `FnMut(Event)` closure into an `AttributeValue::Event`.
287///
288/// Wraps the closure into a `NativeEventHandler` and returns it as an
289/// event attribute value. This replaces the `__EventWrapper<F>` type
290/// that was previously generated inline by the `html!` macro.
291impl<F> EventAdapter<F>
292where
293 F: FnMut(Event) + 'static,
294{
295 /// Converts the wrapped closure into an event `AttributeValue`.
296 ///
297 /// # Arguments
298 ///
299 /// - `&'static str` - The event name string to associate with the handler.
300 ///
301 /// # Returns
302 ///
303 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
304 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
305 AttributeValue::Event(NativeEventHandler::create(event_name, self.into_inner()))
306 }
307}
308
309/// Converts an event with a specific event name into an `AttributeValue`.
310impl<F> From<EventNamedAdapter<F>> for AttributeValue
311where
312 F: FnMut(Event) + 'static,
313{
314 /// Converts the wrapped closure with event name into an event `AttributeValue`.
315 ///
316 /// # Returns
317 ///
318 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
319 ///
320 /// # Arguments
321 ///
322 /// - `EventNamedAdapter<F>` - Input value to convert from.
323 fn from(adapter: EventNamedAdapter<F>) -> Self {
324 AttributeValue::Event(NativeEventHandler::create(
325 adapter.get_event_name(),
326 adapter.inner,
327 ))
328 }
329}
330
331/// Converts an event named adapter with `NativeEventHandler` into an `AttributeValue`.
332impl From<EventNamedAdapter<NativeEventHandler>> for AttributeValue {
333 /// Converts the wrapped handler with event name into an event `AttributeValue`.
334 ///
335 /// # Returns
336 ///
337 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
338 ///
339 /// # Arguments
340 ///
341 /// - `EventNamedAdapter<NativeEventHandler>` - Input value to convert from.
342 fn from(mut adapter: EventNamedAdapter<NativeEventHandler>) -> Self {
343 let event_name: &'static str = adapter.get_event_name();
344 adapter.get_mut_inner().set_event_name(event_name);
345 AttributeValue::Event(adapter.inner)
346 }
347}
348
349/// Converts an event named adapter with optional shared closure into an `AttributeValue`.
350///
351/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
352/// into a `NativeEventHandler` with the adapter's event name, and `None` becomes
353/// `AttributeValue::Text(String::new())`.
354impl From<EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>> for AttributeValue {
355 /// Converts the wrapped optional shared closure with event name into an event `AttributeValue`.
356 ///
357 /// # Returns
358 ///
359 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
360 ///
361 /// # Arguments
362 ///
363 /// - `EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>` - Input value to convert from.
364 fn from(adapter: EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>) -> Self {
365 let event_name: &'static str = adapter.get_event_name();
366 match adapter.inner {
367 Some(callback) => AttributeValue::Event(NativeEventHandler::create(
368 event_name,
369 move |event: Event| {
370 callback(event);
371 },
372 )),
373 None => AttributeValue::Text(String::new()),
374 }
375 }
376}
377
378/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
379///
380/// When the user already provides a `NativeEventHandler`, the handler is
381/// re-wrapped with the given `event_name` to ensure the DOM event listener
382/// is bound to the correct event type (e.g., "click" rather than "onclick").
383impl EventAdapter<NativeEventHandler> {
384 /// Converts the wrapped handler into an event `AttributeValue`.
385 ///
386 /// Re-wraps the handler with the provided `event_name` so that the
387 /// DOM event listener uses the correct event type string.
388 ///
389 /// # Arguments
390 ///
391 /// - `&'static str` - The event name to bind the handler to.
392 ///
393 /// # Returns
394 ///
395 /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
396 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
397 let mut handler: NativeEventHandler = self.into_inner();
398 handler.set_event_name(event_name);
399 AttributeValue::Event(handler)
400 }
401}
402
403/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
404///
405/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with the
406/// given event name, and `None` becomes `AttributeValue::Text(String::new())`.
407impl EventAdapter<Option<NativeEventHandler>> {
408 /// Converts the wrapped optional handler into an attribute value.
409 ///
410 /// Re-wraps a `Some` handler with the provided `event_name` so that the
411 /// DOM event listener uses the correct event type string.
412 ///
413 /// # Arguments
414 ///
415 /// - `&'static str` - The event name to bind the handler to.
416 ///
417 /// # Returns
418 ///
419 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
420 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
421 match self.into_inner() {
422 Some(handler) => EventNamedAdapter::new(handler, event_name).into(),
423 None => AttributeValue::Text(String::new()),
424 }
425 }
426}
427
428/// Adapts an `Option<Rc<dyn Fn(Event)>>` into an `AttributeValue`.
429///
430/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
431/// into a `NativeEventHandler`, and `None` becomes `AttributeValue::Text(String::new())`.
432/// This supports component Props that use `Option<Rc<dyn Fn(Event)>>` for event callbacks.
433impl EventAdapter<Option<Rc<dyn Fn(Event)>>> {
434 /// Converts the wrapped optional shared closure into an attribute value.
435 ///
436 /// # Arguments
437 ///
438 /// - `&'static str` - The event name to bind the handler to.
439 ///
440 /// # Returns
441 ///
442 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
443 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
444 match self.into_inner() {
445 Some(callback) => AttributeValue::Event(NativeEventHandler::create(
446 event_name,
447 move |event: Event| {
448 callback(event);
449 },
450 )),
451 None => AttributeValue::Text(String::new()),
452 }
453 }
454}
455
456/// Constructs an `AttrValueAdapter` that wraps any attribute-compatible value.
457impl<T> AttrValueAdapter<T> {
458 /// Returns the inner wrapped value, consuming the adapter.
459 ///
460 /// # Returns
461 ///
462 /// - `T` - The inner value.
463 pub(crate) fn into_inner(self) -> T {
464 self.inner
465 }
466}
467
468/// Constructs an `InnerHtmlAdapter` that wraps an `inner_html:` payload.
469impl<T> InnerHtmlAdapter<T> {
470 /// Returns the inner wrapped value, consuming the adapter.
471 ///
472 /// Mirrors [`AttrValueAdapter::into_inner`] so the html! macro can
473 /// use the same "wrap-then-into-inner" pattern for both adapter
474 /// kinds without diverging call sites.
475 ///
476 /// # Returns
477 ///
478 /// - `T` - The inner value.
479 pub(crate) fn into_inner(self) -> T {
480 self.inner
481 }
482}
483
484/// Adapts a `FnMut(Event)` closure into a callback `AttributeValue`.
485///
486/// This handles the case where a closure is used as a component callback prop.
487/// The closure is converted via `IntoCallbackAttribute::into_callback_attribute()`.
488impl<F> AttrValueAdapter<F>
489where
490 F: FnMut(Event) + 'static,
491{
492 /// Converts the wrapped closure into a callback `AttributeValue`.
493 ///
494 /// # Returns
495 ///
496 /// - `AttributeValue` - An event attribute value wrapping the adapted closure.
497 pub fn into_callback(self) -> AttributeValue {
498 self.into_inner().into()
499 }
500
501 /// Converts the wrapped closure into a callback `AttributeValue` with a
502 /// custom event name for component props.
503 ///
504 /// # Arguments
505 ///
506 /// - `&'static str` - The custom attribute name (e.g., "on-increment", "on-change").
507 ///
508 /// # Returns
509 ///
510 /// - `AttributeValue` - An event attribute value with the custom name.
511 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
512 AttributeValue::Event(NativeEventHandler::create(name, self.into_inner()))
513 }
514}
515
516/// Converts a named callback adapter into an `AttributeValue`.
517impl<F> From<CallbackNamedAdapter<F>> for AttributeValue
518where
519 F: FnMut(Event) + 'static,
520{
521 /// Converts the wrapped closure with custom name into a callback `AttributeValue`.
522 ///
523 /// # Returns
524 ///
525 /// - `AttributeValue` - An event attribute value with the custom name.
526 ///
527 /// # Arguments
528 ///
529 /// - `CallbackNamedAdapter<F>` - Input value to convert from.
530 fn from(adapter: CallbackNamedAdapter<F>) -> Self {
531 AttributeValue::Event(NativeEventHandler::create(
532 adapter.get_name(),
533 adapter.inner,
534 ))
535 }
536}
537
538/// Generic-parameterised implementation for [`AttrValueAdapter`].
539/// Implements `impl AttrValueAdapter<NativeEventHandler>`.
540impl AttrValueAdapter<NativeEventHandler> {
541 /// Converts the wrapped handler into a callback `AttributeValue` with a
542 /// custom event name for component props.
543 ///
544 /// # Arguments
545 ///
546 /// - `&'static str` - The custom attribute name.
547 ///
548 /// # Returns
549 ///
550 /// - `AttributeValue` - An event attribute value with the custom name.
551 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
552 let mut handler: NativeEventHandler = self.into_inner();
553 handler.set_event_name(name);
554 AttributeValue::Event(handler)
555 }
556}
557
558/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
559impl AttrValueAdapter<Option<NativeEventHandler>> {
560 /// Converts the wrapped optional handler into an attribute value.
561 ///
562 /// # Returns
563 ///
564 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
565 pub fn into_callback(self) -> AttributeValue {
566 match self.into_inner() {
567 Some(handler) => AttrValueAdapter::new(handler).into(),
568 None => AttributeValue::Text(String::new()),
569 }
570 }
571
572 /// Converts this optional handler into a callback `AttributeValue` with a
573 /// custom event name for component props.
574 ///
575 /// # Arguments
576 ///
577 /// - `&'static str` - The custom attribute name.
578 ///
579 /// # Returns
580 ///
581 /// - `AttributeValue` - An event attribute with the custom name if `Some`,
582 /// otherwise an empty text attribute.
583 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
584 match self.into_inner() {
585 Some(handler) => AttrValueAdapter::new(handler).into_callback_named(name),
586 None => AttributeValue::Text(String::new()),
587 }
588 }
589}
590
591/// Adapts any type that implements `Into<AttributeValue>` into an `AttributeValue`.
592///
593/// This is the fallback path for non-closure attribute values (strings, signals,
594/// CSS classes, etc.).
595impl<T> From<AttrValueAdapter<T>> for AttributeValue
596where
597 T: Into<AttributeValue>,
598{
599 /// Converts the wrapped value into an `AttributeValue`.
600 ///
601 /// # Returns
602 ///
603 /// - `AttributeValue` - The reactive attribute value.
604 ///
605 /// # Arguments
606 ///
607 /// - `AttrValueAdapter<T>` - Input value to convert from.
608 fn from(adapter: AttrValueAdapter<T>) -> Self {
609 adapter.into_inner().into()
610 }
611}
612
613/// Adapts an `inner_html:` payload into the matching `AttributeValue`
614/// variant, routing through `set_inner_html` instead of the generic
615/// `Text` attribute path.
616///
617/// The two blanket impls below cover the user-visible call sites:
618///
619/// - `String` produces
620/// `AttributeValue::InnerHtml(String::from("..."))`.
621/// - `Signal<String>` produces
622/// `AttributeValue::InnerHtmlSignal(signal)`.
623///
624/// Each impl only requires its specific source type, so the compiler
625/// picks the right one based on the inferred `T` at the call site
626/// (no manual `.into()` annotation needed).
627impl From<InnerHtmlAdapter<String>> for AttributeValue {
628 /// Wraps the static `String` payload in an
629 /// `AttributeValue::InnerHtml` variant so the renderer can call
630 /// `Element::set_inner_html` on it.
631 ///
632 /// # Arguments
633 ///
634 /// - `InnerHtmlAdapter<String>` - Input value to convert from.
635 fn from(adapter: InnerHtmlAdapter<String>) -> Self {
636 AttributeValue::InnerHtml(adapter.into_inner())
637 }
638}
639
640/// `From` conversion into [`AttributeValue`].
641impl From<InnerHtmlAdapter<&str>> for AttributeValue {
642 /// Wraps the static `&str` payload by allocating a new `String`
643 /// so the renderer owns the data independently of the caller's
644 /// borrow lifetime.
645 ///
646 /// # Arguments
647 ///
648 /// - `InnerHtmlAdapter<&str>` - Input value to convert from.
649 fn from(adapter: InnerHtmlAdapter<&str>) -> Self {
650 AttributeValue::InnerHtml(adapter.into_inner().to_owned())
651 }
652}
653
654/// `From` conversion into [`AttributeValue`].
655impl From<InnerHtmlAdapter<Signal<String>>> for AttributeValue {
656 /// Wraps the reactive payload in an `AttributeValue::InnerHtmlSignal`
657 /// so the renderer subscribes to the signal and re-applies
658 /// `set_inner_html` on every change.
659 ///
660 /// # Arguments
661 ///
662 /// - `InnerHtmlAdapter<Signal<String>>` - Input value to convert from.
663 fn from(adapter: InnerHtmlAdapter<Signal<String>>) -> Self {
664 AttributeValue::InnerHtmlSignal(adapter.into_inner())
665 }
666}
667
668/// Converts a `NodeRef<T>` into an `AttributeValue::Ref`.
669///
670/// The type parameter `T` is erased at the `AttributeValue` layer (we
671/// store the underlying `JsValue` cell), so any concrete element type
672/// works. The `html!` macro relies on this to accept
673/// `html! { input { ref: my_ref } }` without the user needing to call
674/// `.into()` explicitly.
675impl<T: ?Sized> From<NodeRef<T>> for AttributeValue {
676 /// Wraps the ref cell as an `AttributeValue::Ref` so the renderer
677 /// can populate it after mount.
678 ///
679 /// # Returns
680 ///
681 /// - `AttributeValue` - A ref variant carrying the (still empty)
682 /// handle.
683 ///
684 /// # Arguments
685 ///
686 /// - `NodeRef<T>` - Input value to convert from.
687 fn from(node_ref: NodeRef<T>) -> Self {
688 // Erase the typed phantom marker to a `NodeRef<JsValue>` so the
689 // `AttributeValue::Ref` payload is uniform. The `JsValue` cell
690 // is what the renderer actually stores and sets.
691 let erased: NodeRefDyn = NodeRefDyn {
692 inner: node_ref.inner.clone(),
693 _marker: PhantomData,
694 };
695 AttributeValue::Ref(erased)
696 }
697}