euv_core/vdom/cast/impl.rs
1use crate::*;
2
3/// Converts a `VirtualNode` reference into an owned node.
4impl AsNode for VirtualNode {
5 /// Clones this virtual node into an owned `Option<VirtualNode>`.
6 ///
7 /// # Returns
8 ///
9 /// - `Option<VirtualNode>` - Always `Some` with the cloned node.
10 fn as_node(&self) -> Option<VirtualNode> {
11 Some(self.clone())
12 }
13}
14
15/// Converts a `VirtualNode` reference into an owned node.
16impl AsNode for &VirtualNode {
17 /// Clones this virtual node reference into an owned `Option<VirtualNode>`.
18 ///
19 /// # Returns
20 ///
21 /// - `Option<VirtualNode>` - Always `Some` with the cloned node.
22 fn as_node(&self) -> Option<VirtualNode> {
23 Some((*self).clone())
24 }
25}
26
27/// Converts a `String` into a text virtual node.
28impl AsNode for String {
29 /// Converts this string into a text virtual node.
30 ///
31 /// # Returns
32 ///
33 /// - `Option<VirtualNode>` - Always `Some` with a text node.
34 fn as_node(&self) -> Option<VirtualNode> {
35 Some(VirtualNode::Text(TextNode::new(self.clone(), None)))
36 }
37}
38
39/// Converts a string slice into a text virtual node.
40impl AsNode for &str {
41 /// Converts this string slice into a text virtual node.
42 ///
43 /// # Returns
44 ///
45 /// - `Option<VirtualNode>` - Always `Some` with a text node.
46 fn as_node(&self) -> Option<VirtualNode> {
47 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
48 }
49}
50
51/// Converts an `i32` into a text virtual node.
52impl AsNode for i32 {
53 /// Converts this integer into a text virtual node.
54 ///
55 /// # Returns
56 ///
57 /// - `Option<VirtualNode>` - Always `Some` with a text node.
58 fn as_node(&self) -> Option<VirtualNode> {
59 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
60 }
61}
62
63/// Converts an `i64` into a text virtual node.
64impl AsNode for i64 {
65 /// Converts this integer into a text virtual node.
66 ///
67 /// # Returns
68 ///
69 /// - `Option<VirtualNode>` - Always `Some` with a text node.
70 fn as_node(&self) -> Option<VirtualNode> {
71 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
72 }
73}
74
75/// Converts a `usize` into a text virtual node.
76impl AsNode for usize {
77 /// Converts this unsigned integer into a text virtual node.
78 ///
79 /// # Returns
80 ///
81 /// - `Option<VirtualNode>` - Always `Some` with a text node.
82 fn as_node(&self) -> Option<VirtualNode> {
83 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
84 }
85}
86
87/// Converts an `f32` into a text virtual node.
88impl AsNode for f32 {
89 /// Converts this float into a text virtual node.
90 ///
91 /// # Returns
92 ///
93 /// - `Option<VirtualNode>` - Always `Some` with a text node.
94 fn as_node(&self) -> Option<VirtualNode> {
95 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
96 }
97}
98
99/// Converts an `f64` into a text virtual node.
100impl AsNode for f64 {
101 /// Converts this float into a text virtual node.
102 ///
103 /// # Returns
104 ///
105 /// - `Option<VirtualNode>` - Always `Some` with a text node.
106 fn as_node(&self) -> Option<VirtualNode> {
107 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
108 }
109}
110
111/// Converts a `bool` into a text virtual node.
112impl AsNode for bool {
113 /// Converts this boolean into a text virtual node.
114 ///
115 /// # Returns
116 ///
117 /// - `Option<VirtualNode>` - Always `Some` with a text node.
118 fn as_node(&self) -> Option<VirtualNode> {
119 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
120 }
121}
122
123/// Converts a signal into a reactive text virtual node.
124impl<T> AsNode for Signal<T>
125where
126 T: Clone + PartialEq + std::fmt::Display + 'static,
127{
128 /// Converts this signal into a reactive text virtual node.
129 ///
130 /// # Returns
131 ///
132 /// - `Option<VirtualNode>` - Always `Some` with a reactive text node.
133 fn as_node(&self) -> Option<VirtualNode> {
134 Some(self.as_reactive_text())
135 }
136}
137
138/// Converts a `VirtualNode` into itself via `IntoNode`.
139impl IntoNode for VirtualNode {
140 /// Returns this virtual node as-is.
141 ///
142 /// # Returns
143 ///
144 /// - `VirtualNode` - This same virtual node.
145 fn into_node(self) -> VirtualNode {
146 self
147 }
148}
149
150/// Wraps a `FnMut() -> VirtualNode` closure into a `DynamicNode` via `IntoNode`.
151///
152/// This enables writing `{move || html! { ... }}` directly in HTML markup
153/// without explicit `DynamicNode` construction.
154impl<F> IntoNode for F
155where
156 F: FnMut() -> VirtualNode + 'static,
157{
158 /// Wraps this closure into a `VirtualNode::Dynamic` with a fresh hook context.
159 ///
160 /// # Returns
161 ///
162 /// - `VirtualNode` - A dynamic virtual node wrapping this closure.
163 fn into_node(self) -> VirtualNode {
164 let inner: Box<RenderFnInner> = Box::new(RenderFnInner {
165 render_fn: Box::new(self),
166 });
167 VirtualNode::Dynamic(DynamicNode {
168 render_fn: Box::leak(inner) as *mut RenderFnInner,
169 hook_context: crate::reactive::create_hook_context(),
170 })
171 }
172}
173
174/// Converts a `String` into a text virtual node via `IntoNode`.
175impl IntoNode for String {
176 /// Converts this string into a text virtual node.
177 ///
178 /// # Returns
179 ///
180 /// - `VirtualNode` - A text virtual node.
181 fn into_node(self) -> VirtualNode {
182 VirtualNode::Text(TextNode::new(self, None))
183 }
184}
185
186/// Converts a `&str` into a text virtual node via `IntoNode`.
187impl IntoNode for &str {
188 /// Converts this string slice into a text virtual node.
189 ///
190 /// # Returns
191 ///
192 /// - `VirtualNode` - A text virtual node.
193 fn into_node(self) -> VirtualNode {
194 VirtualNode::Text(TextNode::new(self.to_string(), None))
195 }
196}
197
198/// Converts an `i32` into a text virtual node via `IntoNode`.
199impl IntoNode for i32 {
200 /// Converts this integer into a text virtual node.
201 ///
202 /// # Returns
203 ///
204 /// - `VirtualNode` - A text virtual node.
205 fn into_node(self) -> VirtualNode {
206 VirtualNode::Text(TextNode::new(self.to_string(), None))
207 }
208}
209
210/// Converts a `usize` into a text virtual node via `IntoNode`.
211impl IntoNode for usize {
212 /// Converts this unsigned integer into a text virtual node.
213 ///
214 /// # Returns
215 ///
216 /// - `VirtualNode` - A text virtual node.
217 fn into_node(self) -> VirtualNode {
218 VirtualNode::Text(TextNode::new(self.to_string(), None))
219 }
220}
221
222/// Converts a `bool` into a text virtual node via `IntoNode`.
223impl IntoNode for bool {
224 /// Converts this boolean into a text virtual node.
225 ///
226 /// # Returns
227 ///
228 /// - `VirtualNode` - A text virtual node.
229 fn into_node(self) -> VirtualNode {
230 VirtualNode::Text(TextNode::new(self.to_string(), None))
231 }
232}
233
234/// Converts a signal into a reactive text virtual node via `IntoNode`.
235impl<T> IntoNode for Signal<T>
236where
237 T: Clone + PartialEq + std::fmt::Display + 'static,
238{
239 /// Converts this signal into a reactive text virtual node.
240 ///
241 /// # Returns
242 ///
243 /// - `VirtualNode` - A reactive text virtual node.
244 fn into_node(self) -> VirtualNode {
245 self.as_reactive_text()
246 }
247}
248
249/// Converts a signal into a reactive text node with listener wiring.
250impl<T> AsReactiveText for Signal<T>
251where
252 T: Clone + PartialEq + std::fmt::Display + 'static,
253{
254 /// Creates a reactive text node that auto-updates when the signal changes.
255 ///
256 /// Internally creates a bridge `Signal<String>` that subscribes to the
257 /// source signal and updates the text content on every change.
258 ///
259 /// # Returns
260 ///
261 /// - `VirtualNode` - A text virtual node with reactive signal binding.
262 fn as_reactive_text(&self) -> VirtualNode {
263 let signal: Signal<T> = *self;
264 let initial: String = signal.get().to_string();
265 let string_signal: Signal<String> = {
266 let boxed: Box<SignalInner<String>> = Box::new(SignalInner::new(initial.clone()));
267 let ptr: *mut SignalInner<String> = Box::leak(boxed) as *mut SignalInner<String>;
268 let address: usize = ptr as usize;
269 address.into()
270 };
271 let source_signal: Signal<T> = *self;
272 let string_signal_clone: Signal<String> = string_signal;
273 source_signal.subscribe({
274 let source_signal: Signal<T> = source_signal;
275 move || {
276 let new_value: String = source_signal.get().to_string();
277 string_signal_clone.set(new_value);
278 }
279 });
280 VirtualNode::Text(TextNode::new(initial, Some(string_signal)))
281 }
282}
283
284/// Constructs an `EventAdapter` that wraps any event-compatible value.
285impl<T> EventAdapter<T> {
286 /// Creates a new `EventAdapter` wrapping the given value.
287 ///
288 /// # Arguments
289 ///
290 /// - `T` - The value to wrap for event attribute adaptation.
291 ///
292 /// # Returns
293 ///
294 /// - `EventAdapter<T>` - A new adapter wrapping the value.
295 pub fn new(inner: T) -> Self {
296 EventAdapter { inner }
297 }
298
299 /// Returns the inner wrapped value, consuming the adapter.
300 ///
301 /// # Returns
302 ///
303 /// - `T` - The inner value.
304 pub(crate) fn into_inner(self) -> T {
305 self.inner
306 }
307}
308
309/// Adapts a `FnMut(Event)` closure into an `AttributeValue::Event`.
310///
311/// Wraps the closure into a `NativeEventHandler` and returns it as an
312/// event attribute value. This replaces the `__EventWrapper<F>` type
313/// that was previously generated inline by the `html!` macro.
314impl<F> EventAdapter<F>
315where
316 F: FnMut(Event) + 'static,
317{
318 /// Converts the wrapped closure into an event `AttributeValue`.
319 ///
320 /// # Arguments
321 ///
322 /// - `NativeEventName` - The event name enum variant to associate with the handler.
323 ///
324 /// # Returns
325 ///
326 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
327 pub fn into_attribute(self, event_name: NativeEventName) -> AttributeValue {
328 AttributeValue::Event(NativeEventHandler::new(event_name, self.into_inner()))
329 }
330}
331
332/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
333///
334/// When the user already provides a `NativeEventHandler`, the handler is
335/// re-wrapped with the given `event_name` to ensure the DOM event listener
336/// is bound to the correct event type (e.g., "click" rather than "onclick").
337/// This replaces the `impl __EventWrapper<NativeEventHandler>` that was
338/// previously generated inline.
339impl EventAdapter<NativeEventHandler> {
340 /// Converts the wrapped handler into an event `AttributeValue`.
341 ///
342 /// Re-wraps the handler with the provided `event_name` so that the
343 /// DOM event listener uses the correct event type string.
344 ///
345 /// # Arguments
346 ///
347 /// - `NativeEventName` - The event name to bind the handler to.
348 ///
349 /// # Returns
350 ///
351 /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
352 pub fn into_attribute(self, event_name: NativeEventName) -> AttributeValue {
353 let handler: NativeEventHandler = self.into_inner();
354 AttributeValue::Event(NativeEventHandler::new(event_name, move |event: Event| {
355 handler.handle(event);
356 }))
357 }
358}
359
360/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
361///
362/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with the
363/// given event name, and `None` becomes `AttributeValue::Text(String::new())`.
364/// This replaces the `impl __EventWrapper<Option<NativeEventHandler>>` that was
365/// previously generated inline by the `html!` macro.
366impl EventAdapter<Option<NativeEventHandler>> {
367 /// Converts the wrapped optional handler into an attribute value.
368 ///
369 /// Re-wraps a `Some` handler with the provided `event_name` so that the
370 /// DOM event listener uses the correct event type string.
371 ///
372 /// # Arguments
373 ///
374 /// - `NativeEventName` - The event name to bind the handler to.
375 ///
376 /// # Returns
377 ///
378 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
379 pub fn into_attribute(self, event_name: NativeEventName) -> AttributeValue {
380 match self.into_inner() {
381 Some(handler) => EventAdapter::new(handler).into_attribute(event_name),
382 None => AttributeValue::Text(String::new()),
383 }
384 }
385}
386
387/// Constructs an `AttrValueAdapter` that wraps any attribute-compatible value.
388impl<T> AttrValueAdapter<T> {
389 /// Creates a new `AttrValueAdapter` wrapping the given value.
390 ///
391 /// # Arguments
392 ///
393 /// - `T` - The value to wrap for attribute adaptation.
394 ///
395 /// # Returns
396 ///
397 /// - `AttrValueAdapter<T>` - A new adapter wrapping the value.
398 pub fn new(inner: T) -> Self {
399 AttrValueAdapter { inner }
400 }
401
402 /// Returns the inner wrapped value, consuming the adapter.
403 ///
404 /// # Returns
405 ///
406 /// - `T` - The inner value.
407 pub(crate) fn into_inner(self) -> T {
408 self.inner
409 }
410}
411
412/// Adapts a `FnMut(Event)` closure into a callback `AttributeValue`.
413///
414/// This handles the case where a closure is used as a component callback prop.
415/// The closure is converted via `IntoCallbackAttribute::into_callback_attribute()`.
416/// This replaces the `__IsClosure for F` impl that was previously generated inline.
417impl<F> AttrValueAdapter<F>
418where
419 F: FnMut(Event) + 'static,
420{
421 /// Converts the wrapped closure into a callback `AttributeValue`.
422 ///
423 /// # Returns
424 ///
425 /// - `AttributeValue` - An event attribute value wrapping the adapted closure.
426 pub fn into_callback_attribute_value(self) -> AttributeValue {
427 self.into_inner().into_callback_attribute()
428 }
429
430 /// Converts the wrapped closure into a callback `AttributeValue` with a
431 /// custom event name for component props.
432 ///
433 /// # Arguments
434 ///
435 /// - `String` - The custom attribute name (e.g., "on-increment", "on-change").
436 ///
437 /// # Returns
438 ///
439 /// - `AttributeValue` - An event attribute value with the custom name.
440 pub fn into_callback_attribute_value_with_name(self, name: String) -> AttributeValue {
441 AttributeValue::Event(NativeEventHandler::new(
442 NativeEventName::Other(name),
443 self.into_inner(),
444 ))
445 }
446}
447
448/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
449///
450/// When the user already provides a `NativeEventHandler`, the handler is
451/// re-wrapped with a generic "callback" event name so that subsequent
452/// `EventAdapter::into_attribute` calls can override it with the correct
453/// DOM event type. This replaces the `__IsClosure for NativeEventHandler`
454/// impl that was previously generated inline by the `html!` macro.
455impl AttrValueAdapter<NativeEventHandler> {
456 /// Converts the wrapped handler into an event `AttributeValue`.
457 ///
458 /// Re-wraps the handler with a generic callback event name so that the
459 /// DOM event type can be correctly resolved when the handler is later
460 /// bound to a real element via `EventAdapter::into_attribute`.
461 ///
462 /// # Returns
463 ///
464 /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
465 pub fn into_callback_attribute_value(self) -> AttributeValue {
466 let handler: NativeEventHandler = self.into_inner();
467 AttributeValue::Event(NativeEventHandler::new(
468 NativeEventName::Other("callback".to_string()),
469 move |event: Event| {
470 handler.handle(event);
471 },
472 ))
473 }
474
475 /// Converts the wrapped handler into a callback `AttributeValue` with a
476 /// custom event name for component props.
477 ///
478 /// # Arguments
479 ///
480 /// - `String` - The custom attribute name.
481 ///
482 /// # Returns
483 ///
484 /// - `AttributeValue` - An event attribute value with the custom name.
485 pub fn into_callback_attribute_value_with_name(self, name: String) -> AttributeValue {
486 let handler: NativeEventHandler = self.into_inner();
487 AttributeValue::Event(NativeEventHandler::new(
488 NativeEventName::Other(name),
489 move |event: Event| {
490 handler.handle(event);
491 },
492 ))
493 }
494}
495
496/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
497///
498/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with
499/// a generic callback event name, and `None` becomes `AttributeValue::Text(String::new())`.
500/// This replaces the `__IsClosure for Option<NativeEventHandler>` impl that was
501/// previously generated inline by the `html!` macro.
502impl AttrValueAdapter<Option<NativeEventHandler>> {
503 /// Converts the wrapped optional handler into an attribute value.
504 ///
505 /// Re-wraps a `Some` handler with a generic callback event name so that the
506 /// DOM event type can be correctly resolved when the handler is later bound
507 /// to a real element via `EventAdapter::into_attribute`.
508 ///
509 /// # Returns
510 ///
511 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
512 pub fn into_callback_attribute_value(self) -> AttributeValue {
513 match self.into_inner() {
514 Some(handler) => AttrValueAdapter::new(handler).into_callback_attribute_value(),
515 None => AttributeValue::Text(String::new()),
516 }
517 }
518
519 /// Converts this optional handler into a callback `AttributeValue` with a
520 /// custom event name for component props.
521 ///
522 /// # Arguments
523 ///
524 /// - `String` - The custom attribute name.
525 ///
526 /// # Returns
527 ///
528 /// - `AttributeValue` - An event attribute with the custom name if `Some`,
529 /// otherwise an empty text attribute.
530 pub fn into_callback_attribute_value_with_name(self, name: String) -> AttributeValue {
531 match self.into_inner() {
532 Some(handler) => {
533 AttrValueAdapter::new(handler).into_callback_attribute_value_with_name(name)
534 }
535 None => AttributeValue::Text(String::new()),
536 }
537 }
538}
539
540/// Adapts any `IntoReactiveValue` type into an `AttributeValue`.
541///
542/// This is the fallback path for non-closure attribute values (strings, signals,
543/// CSS classes, etc.). The value is converted via `IntoReactiveValue::into_reactive_value()`.
544/// This replaces the `__ValuePicker` / `__FallbackHelper` hierarchy that was previously
545/// generated inline by the `html!` macro.
546impl<T> AttrValueAdapter<T>
547where
548 T: IntoReactiveValue,
549{
550 /// Converts the wrapped value into an `AttributeValue` via reactive value adaptation.
551 ///
552 /// # Returns
553 ///
554 /// - `AttributeValue` - The reactive attribute value.
555 pub fn into_reactive_attribute_value(self) -> AttributeValue {
556 self.into_inner().into_reactive_value()
557 }
558}