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