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