euv_core/vdom/cast/impl.rs
1use crate::*;
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 fn from(nodes: Vec<VirtualNode>) -> Self {
14 if nodes.is_empty() {
15 VirtualNode::Empty
16 } else {
17 VirtualNode::Fragment(nodes)
18 }
19 }
20}
21
22/// Converts an `Option<VirtualNode>` into a `VirtualNode`.
23///
24/// `Some(node)` returns the inner node, `None` returns `VirtualNode::Empty`.
25///
26/// # Returns
27///
28/// - `VirtualNode` - The inner node if `Some`, otherwise `VirtualNode::Empty`.
29impl From<Option<VirtualNode>> for VirtualNode {
30 fn from(node: Option<VirtualNode>) -> Self {
31 match node {
32 Some(node) => node,
33 None => VirtualNode::Empty,
34 }
35 }
36}
37
38/// Converts an `Option<Vec<VirtualNode>>` into a `VirtualNode`.
39///
40/// `Some(vec)` converts the vector into a `VirtualNode::Fragment` (or `Empty`
41/// if the vector is empty), `None` returns `VirtualNode::Empty`.
42///
43/// # Returns
44///
45/// - `VirtualNode` - A `VirtualNode::Fragment` if `Some` with nodes,
46/// `VirtualNode::Empty` if `None` or the vector is empty.
47impl From<Option<Vec<VirtualNode>>> for VirtualNode {
48 fn from(nodes: Option<Vec<VirtualNode>>) -> Self {
49 match nodes {
50 Some(nodes) => nodes.into(),
51 None => VirtualNode::Empty,
52 }
53 }
54}
55
56/// Wraps a `FnMut(&mut HookContext) -> VirtualNode` closure into a `DynamicNode`.
57///
58/// This enables writing `{move |_: &mut HookContext| html! { ... }}` directly in HTML markup
59/// without explicit `DynamicNode` construction.
60impl<F> From<F> for VirtualNode
61where
62 F: FnMut(&mut HookContext) -> VirtualNode + 'static,
63{
64 /// Wraps this closure into a `VirtualNode::Dynamic` with a fresh hook context.
65 ///
66 /// # Returns
67 ///
68 /// - `VirtualNode` - A dynamic virtual node wrapping this closure.
69 fn from(render_fn: F) -> Self {
70 VirtualNode::create_dynamic(render_fn)
71 }
72}
73
74/// Converts a `String` into a text virtual node.
75impl From<String> for VirtualNode {
76 /// Converts this string into a text virtual node.
77 ///
78 /// # Returns
79 ///
80 /// - `VirtualNode` - A text virtual node.
81 fn from(text: String) -> Self {
82 VirtualNode::Text(TextNode::new(text, None))
83 }
84}
85
86/// Converts a `&str` into a text virtual node.
87impl From<&str> for VirtualNode {
88 /// Converts this string slice into a text virtual node.
89 ///
90 /// # Returns
91 ///
92 /// - `VirtualNode` - A text virtual node.
93 fn from(text: &str) -> Self {
94 VirtualNode::Text(TextNode::new(text.to_string(), None))
95 }
96}
97
98/// Converts an `i32` into a text virtual node.
99impl From<i32> for VirtualNode {
100 /// Converts this integer into a text virtual node.
101 ///
102 /// # Returns
103 ///
104 /// - `VirtualNode` - A text virtual node.
105 fn from(value: i32) -> Self {
106 VirtualNode::Text(TextNode::new(value.to_string(), None))
107 }
108}
109
110/// Converts a `usize` into a text virtual node.
111impl From<usize> for VirtualNode {
112 /// Converts this unsigned integer into a text virtual node.
113 ///
114 /// # Returns
115 ///
116 /// - `VirtualNode` - A text virtual node.
117 fn from(value: usize) -> Self {
118 VirtualNode::Text(TextNode::new(value.to_string(), None))
119 }
120}
121
122/// Converts a `bool` into a text virtual node.
123impl From<bool> for VirtualNode {
124 /// Converts this boolean into a text virtual node.
125 ///
126 /// # Returns
127 ///
128 /// - `VirtualNode` - A text virtual node.
129 fn from(value: bool) -> Self {
130 VirtualNode::Text(TextNode::new(value.to_string(), None))
131 }
132}
133
134/// Converts a signal into a reactive text virtual node.
135impl<T> From<Signal<T>> for VirtualNode
136where
137 T: Clone + PartialEq + Display + 'static,
138{
139 /// Converts this signal into a reactive text virtual node.
140 ///
141 /// # Returns
142 ///
143 /// - `VirtualNode` - A reactive text virtual node.
144 fn from(signal: Signal<T>) -> Self {
145 signal.as_reactive_text()
146 }
147}
148
149/// Converts a signal into a reactive text node with listener wiring.
150impl<T> AsReactiveText for Signal<T>
151where
152 T: Clone + PartialEq + Display + 'static,
153{
154 /// Creates a reactive text node that auto-updates when the signal changes.
155 ///
156 /// Internally creates a bridge `Signal<String>` that subscribes to the
157 /// source signal and updates the text content on every change.
158 ///
159 /// # Returns
160 ///
161 /// - `VirtualNode` - A text virtual node with reactive signal binding.
162 fn as_reactive_text(&self) -> VirtualNode {
163 let source: Signal<T> = *self;
164 let string_signal: Signal<String> = Signal::create(source.get().to_string());
165 let string_signal_clone: Signal<String> = string_signal;
166 source.subscribe(move || {
167 string_signal_clone.set(source.get().to_string());
168 });
169 VirtualNode::Text(TextNode::new(string_signal.get(), Some(string_signal)))
170 }
171}
172
173/// Constructs an `EventAdapter` that wraps any event-compatible value.
174impl<T> EventAdapter<T> {
175 /// Returns the inner wrapped value, consuming the adapter.
176 ///
177 /// # Returns
178 ///
179 /// - `T` - The inner value.
180 pub(crate) fn into_inner(self) -> T {
181 self.inner
182 }
183}
184
185/// Adapts a `FnMut(Event)` closure into an `AttributeValue::Event`.
186///
187/// Wraps the closure into a `NativeEventHandler` and returns it as an
188/// event attribute value. This replaces the `__EventWrapper<F>` type
189/// that was previously generated inline by the `html!` macro.
190impl<F> EventAdapter<F>
191where
192 F: FnMut(Event) + 'static,
193{
194 /// Converts the wrapped closure into an event `AttributeValue`.
195 ///
196 /// # Arguments
197 ///
198 /// - `&'static str` - The event name string to associate with the handler.
199 ///
200 /// # Returns
201 ///
202 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
203 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
204 AttributeValue::Event(NativeEventHandler::create(event_name, self.into_inner()))
205 }
206}
207
208/// Converts an event with a specific event name into an `AttributeValue`.
209impl<F> From<EventNamedAdapter<F>> for AttributeValue
210where
211 F: FnMut(Event) + 'static,
212{
213 /// Converts the wrapped closure with event name into an event `AttributeValue`.
214 ///
215 /// # Returns
216 ///
217 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
218 fn from(adapter: EventNamedAdapter<F>) -> Self {
219 AttributeValue::Event(NativeEventHandler::create(
220 adapter.get_event_name(),
221 adapter.inner,
222 ))
223 }
224}
225
226/// Converts an event named adapter with `NativeEventHandler` into an `AttributeValue`.
227impl From<EventNamedAdapter<NativeEventHandler>> for AttributeValue {
228 /// Converts the wrapped handler with event name into an event `AttributeValue`.
229 ///
230 /// # Returns
231 ///
232 /// - `AttributeValue` - An `AttributeValue::Event` wrapping the handler.
233 fn from(mut adapter: EventNamedAdapter<NativeEventHandler>) -> Self {
234 let event_name: &'static str = adapter.get_event_name();
235 adapter.get_mut_inner().set_event_name(event_name);
236 AttributeValue::Event(adapter.inner)
237 }
238}
239
240/// Converts an event named adapter with optional shared closure into an `AttributeValue`.
241///
242/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
243/// into a `NativeEventHandler` with the adapter's event name, and `None` becomes
244/// `AttributeValue::Text(String::new())`.
245impl From<EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>> for AttributeValue {
246 /// Converts the wrapped optional shared closure with event name into an event `AttributeValue`.
247 ///
248 /// # Returns
249 ///
250 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
251 fn from(adapter: EventNamedAdapter<Option<Rc<dyn Fn(Event)>>>) -> Self {
252 let event_name: &'static str = adapter.get_event_name();
253 match adapter.inner {
254 Some(callback) => AttributeValue::Event(NativeEventHandler::create(
255 event_name,
256 move |event: Event| {
257 callback(event);
258 },
259 )),
260 None => AttributeValue::Text(String::new()),
261 }
262 }
263}
264
265/// Adapts an owned `NativeEventHandler` into an `AttributeValue::Event` directly.
266///
267/// When the user already provides a `NativeEventHandler`, the handler is
268/// re-wrapped with the given `event_name` to ensure the DOM event listener
269/// is bound to the correct event type (e.g., "click" rather than "onclick").
270impl EventAdapter<NativeEventHandler> {
271 /// Converts the wrapped handler into an event `AttributeValue`.
272 ///
273 /// Re-wraps the handler with the provided `event_name` so that the
274 /// DOM event listener uses the correct event type string.
275 ///
276 /// # Arguments
277 ///
278 /// - `&'static str` - The event name to bind the handler to.
279 ///
280 /// # Returns
281 ///
282 /// - `AttributeValue` - An `AttributeValue::Event` containing the re-wrapped handler.
283 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
284 let mut handler: NativeEventHandler = self.into_inner();
285 handler.set_event_name(event_name);
286 AttributeValue::Event(handler)
287 }
288}
289
290/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
291///
292/// `Some(handler)` becomes `AttributeValue::Event(handler)` re-wrapped with the
293/// given event name, and `None` becomes `AttributeValue::Text(String::new())`.
294impl EventAdapter<Option<NativeEventHandler>> {
295 /// Converts the wrapped optional handler into an attribute value.
296 ///
297 /// Re-wraps a `Some` handler with the provided `event_name` so that the
298 /// DOM event listener uses the correct event type string.
299 ///
300 /// # Arguments
301 ///
302 /// - `&'static str` - The event name to bind the handler to.
303 ///
304 /// # Returns
305 ///
306 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
307 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
308 match self.into_inner() {
309 Some(handler) => EventNamedAdapter::new(handler, event_name).into(),
310 None => AttributeValue::Text(String::new()),
311 }
312 }
313}
314
315/// Adapts an `Option<Rc<dyn Fn(Event)>>` into an `AttributeValue`.
316///
317/// `Some(callback)` becomes `AttributeValue::Event` by wrapping the shared closure
318/// into a `NativeEventHandler`, and `None` becomes `AttributeValue::Text(String::new())`.
319/// This supports component Props that use `Option<Rc<dyn Fn(Event)>>` for event callbacks.
320impl EventAdapter<Option<Rc<dyn Fn(Event)>>> {
321 /// Converts the wrapped optional shared closure into an attribute value.
322 ///
323 /// # Arguments
324 ///
325 /// - `&'static str` - The event name to bind the handler to.
326 ///
327 /// # Returns
328 ///
329 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
330 pub fn into_attribute(self, event_name: &'static str) -> AttributeValue {
331 match self.into_inner() {
332 Some(callback) => AttributeValue::Event(NativeEventHandler::create(
333 event_name,
334 move |event: Event| {
335 callback(event);
336 },
337 )),
338 None => AttributeValue::Text(String::new()),
339 }
340 }
341}
342
343/// Constructs an `AttrValueAdapter` that wraps any attribute-compatible value.
344impl<T> AttrValueAdapter<T> {
345 /// Returns the inner wrapped value, consuming the adapter.
346 ///
347 /// # Returns
348 ///
349 /// - `T` - The inner value.
350 pub(crate) fn into_inner(self) -> T {
351 self.inner
352 }
353}
354
355/// Adapts a `FnMut(Event)` closure into a callback `AttributeValue`.
356///
357/// This handles the case where a closure is used as a component callback prop.
358/// The closure is converted via `IntoCallbackAttribute::into_callback_attribute()`.
359impl<F> AttrValueAdapter<F>
360where
361 F: FnMut(Event) + 'static,
362{
363 /// Converts the wrapped closure into a callback `AttributeValue`.
364 ///
365 /// # Returns
366 ///
367 /// - `AttributeValue` - An event attribute value wrapping the adapted closure.
368 pub fn into_callback(self) -> AttributeValue {
369 self.into_inner().into()
370 }
371
372 /// Converts the wrapped closure into a callback `AttributeValue` with a
373 /// custom event name for component props.
374 ///
375 /// # Arguments
376 ///
377 /// - `&'static str` - The custom attribute name (e.g., "on-increment", "on-change").
378 ///
379 /// # Returns
380 ///
381 /// - `AttributeValue` - An event attribute value with the custom name.
382 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
383 AttributeValue::Event(NativeEventHandler::create(name, self.into_inner()))
384 }
385}
386
387/// Converts a named callback adapter into an `AttributeValue`.
388impl<F> From<CallbackNamedAdapter<F>> for AttributeValue
389where
390 F: FnMut(Event) + 'static,
391{
392 /// Converts the wrapped closure with custom name into a callback `AttributeValue`.
393 ///
394 /// # Returns
395 ///
396 /// - `AttributeValue` - An event attribute value with the custom name.
397 fn from(adapter: CallbackNamedAdapter<F>) -> Self {
398 AttributeValue::Event(NativeEventHandler::create(
399 adapter.get_name(),
400 adapter.inner,
401 ))
402 }
403}
404
405impl AttrValueAdapter<NativeEventHandler> {
406 /// Converts the wrapped handler into a callback `AttributeValue` with a
407 /// custom event name for component props.
408 ///
409 /// # Arguments
410 ///
411 /// - `&'static str` - The custom attribute name.
412 ///
413 /// # Returns
414 ///
415 /// - `AttributeValue` - An event attribute value with the custom name.
416 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
417 let mut handler: NativeEventHandler = self.into_inner();
418 handler.set_event_name(name);
419 AttributeValue::Event(handler)
420 }
421}
422
423/// Adapts an `Option<NativeEventHandler>` into an `AttributeValue`.
424impl AttrValueAdapter<Option<NativeEventHandler>> {
425 /// Converts the wrapped optional handler into an attribute value.
426 ///
427 /// # Returns
428 ///
429 /// - `AttributeValue` - An event attribute if `Some`, otherwise an empty text attribute.
430 pub fn into_callback(self) -> AttributeValue {
431 match self.into_inner() {
432 Some(handler) => AttrValueAdapter::new(handler).into(),
433 None => AttributeValue::Text(String::new()),
434 }
435 }
436
437 /// Converts this optional handler into a callback `AttributeValue` with a
438 /// custom event name for component props.
439 ///
440 /// # Arguments
441 ///
442 /// - `&'static str` - The custom attribute name.
443 ///
444 /// # Returns
445 ///
446 /// - `AttributeValue` - An event attribute with the custom name if `Some`,
447 /// otherwise an empty text attribute.
448 pub fn into_callback_named(self, name: &'static str) -> AttributeValue {
449 match self.into_inner() {
450 Some(handler) => AttrValueAdapter::new(handler).into_callback_named(name),
451 None => AttributeValue::Text(String::new()),
452 }
453 }
454}
455
456/// Adapts any type that implements `Into<AttributeValue>` into an `AttributeValue`.
457///
458/// This is the fallback path for non-closure attribute values (strings, signals,
459/// CSS classes, etc.).
460impl<T> From<AttrValueAdapter<T>> for AttributeValue
461where
462 T: Into<AttributeValue>,
463{
464 /// Converts the wrapped value into an `AttributeValue`.
465 ///
466 /// # Returns
467 ///
468 /// - `AttributeValue` - The reactive attribute value.
469 fn from(adapter: AttrValueAdapter<T>) -> Self {
470 adapter.into_inner().into()
471 }
472}