euv-core 0.5.2

A declarative, cross-platform UI framework for Rust with virtual DOM, reactive signals, and HTML macros for WebAssembly.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
use crate::*;

/// Visual equality comparison for text nodes.
///
/// Only compares the text content; the backing signal is not considered
/// because it does not affect visual output.
impl PartialEq for TextNode {
    fn eq(&self, other: &Self) -> bool {
        self.get_content() == other.get_content()
    }
}

/// Visual equality comparison for virtual DOM nodes.
///
/// Used by DynamicNode re-rendering to skip unnecessary DOM patches when
/// the rendered output has not changed. Event attributes are always
/// considered equal because re-binding event listeners is handled
/// separately by the handler registry and does not affect visual output.
/// Dynamic nodes manage their own subtree re-rendering, so two Dynamic
/// variants are always considered equal — the inner renderer handles
/// patching when the dynamic content actually changes.
impl PartialEq for VirtualNode {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
            (
                VirtualNode::Element {
                    tag: old_tag,
                    attributes: old_attrs,
                    children: old_children,
                    ..
                },
                VirtualNode::Element {
                    tag: new_tag,
                    attributes: new_attrs,
                    children: new_children,
                    ..
                },
            ) => {
                old_tag == new_tag
                    && old_attrs.len() == new_attrs.len()
                    && old_attrs
                        .iter()
                        .zip(new_attrs.iter())
                        .all(|(old_attr, new_attr)| old_attr == new_attr)
                    && old_children.len() == new_children.len()
                    && old_children
                        .iter()
                        .zip(new_children.iter())
                        .all(|(old_child, new_child)| old_child == new_child)
            }
            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
                old_children.len() == new_children.len()
                    && old_children
                        .iter()
                        .zip(new_children.iter())
                        .all(|(old_child, new_child)| old_child == new_child)
            }
            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
            (VirtualNode::Empty, VirtualNode::Empty) => true,
            _ => false,
        }
    }
}

/// Provides a default empty dynamic node with a no-op render function.
impl Default for DynamicNode {
    fn default() -> Self {
        let render_fn_inner: Rc<RefCell<RenderFnInner>> =
            Rc::new(RefCell::new(RenderFnInner::new(Box::new(|| {
                VirtualNode::Empty
            }))));
        Self::new(render_fn_inner, HookContext::default())
    }
}

/// Clones a `DynamicNode` by cloning the shared references.
impl Clone for DynamicNode {
    fn clone(&self) -> Self {
        let cloned: Self = Self::new(
            self.get_render_fn().clone(),
            self.get_hook_context().clone(),
        );
        cloned
    }
}

/// Implementation of dynamic node accessor methods.
impl DynamicNode {
    /// Returns the hook context for this dynamic node.
    ///
    /// # Returns
    ///
    /// - `HookContext` - The hook context.
    pub(crate) fn get_hook_context_value(&self) -> HookContext {
        self.get_hook_context().clone()
    }

    /// Invokes the render closure and returns the produced virtual node.
    ///
    /// # Returns
    ///
    /// - `Self` - The virtual node produced by the render closure.
    pub fn render(&self) -> VirtualNode {
        let mut inner: RefMut<RenderFnInner> = self.get_render_fn().borrow_mut();
        (inner.get_mut_render_fn())()
    }
}

/// Implementation of virtual node construction and property extraction.
impl VirtualNode {
    /// Constructs a `Self::Dynamic` from a render closure with hook context management.
    ///
    /// # Arguments
    ///
    /// - `FnMut() -> Self + 'static` - The render closure that produces
    ///   a virtual node tree. Called on initial render and on every signal update.
    ///
    /// # Returns
    ///
    /// - `Self` - A `Self::Dynamic` wrapping the render closure
    ///   with a fresh `HookContext`.
    pub fn create_dynamic<F>(mut render_fn: F) -> Self
    where
        F: FnMut() -> Self + 'static,
    {
        let hook_context: HookContext = create_hook_context();
        let mut hook_context_for_closure: HookContext = hook_context.clone();
        let inner: Rc<RefCell<RenderFnInner>> =
            Rc::new(RefCell::new(RenderFnInner::new(Box::new(move || {
                hook_context_for_closure.reset_hook_index();
                render_fn()
            }))));
        let dynamic_node: DynamicNode = DynamicNode::new(inner, hook_context);
        Self::Dynamic(dynamic_node)
    }

    /// Constructs a `Self::Dynamic` for match expressions where arm hook
    /// isolation is required. The render closure receives a `&mut HookContext`
    /// so it can call `set_arm_changed` before each arm body.
    ///
    /// # Arguments
    ///
    /// - `FnMut(&mut HookContext) -> Self + 'static` - The render closure
    ///   that receives a mutable reference to the hook context.
    ///
    /// # Returns
    ///
    /// - `Self` - A `Self::Dynamic` wrapping the render closure
    ///   with a fresh `HookContext`.
    pub fn create_dynamic_with_context<F>(mut render_fn: F) -> Self
    where
        F: FnMut(&mut HookContext) -> Self + 'static,
    {
        let hook_context: HookContext = create_hook_context();
        let mut hook_context_for_closure: HookContext = hook_context.clone();
        let inner: Rc<RefCell<RenderFnInner>> =
            Rc::new(RefCell::new(RenderFnInner::new(Box::new(move || {
                hook_context_for_closure.reset_hook_index();
                render_fn(&mut hook_context_for_closure)
            }))));
        let dynamic_node: DynamicNode = DynamicNode::new(inner, hook_context);
        Self::Dynamic(dynamic_node)
    }

    /// Creates a new element node with the given tag name.
    ///
    /// # Arguments
    ///
    /// - `&str`- The HTML tag name.
    ///
    /// # Returns
    ///
    /// - `Self`- A new element virtual node.
    pub fn get_element_node(tag_name: &str) -> Self {
        Self::Element {
            tag: Tag::Element(tag_name.to_string()),
            attributes: Vec::new(),
            children: Vec::new(),
            key: None,
        }
    }

    /// Creates a new text node with the given content.
    ///
    /// # Arguments
    ///
    /// - `&str`- The text content.
    ///
    /// # Returns
    ///
    /// - `Self`- A new text virtual node.
    pub fn get_text_node(content: &str) -> Self {
        Self::Text(TextNode::new(content.to_string(), None))
    }

    /// Adds an attribute to this node if it is an element.
    ///
    /// # Arguments
    ///
    /// - `&str`- The attribute name.
    /// - `AttributeValue`- The attribute value.
    ///
    /// # Returns
    ///
    /// - `Self`- This node with the attribute added.
    pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
        if let Self::Element {
            ref mut attributes, ..
        } = self
        {
            attributes.push(AttributeEntry::new(name.to_string(), value));
        }
        self
    }

    /// Adds a child node to this node if it is an element.
    ///
    /// # Arguments
    ///
    /// - `VirtualNode`- The child node to add.
    ///
    /// # Returns
    ///
    /// - `Self`- This node with the child added.
    pub fn with_child(mut self, child: VirtualNode) -> Self {
        if let Self::Element {
            ref mut children, ..
        } = self
        {
            children.push(child);
        }
        self
    }

    /// Returns true if this node is a component node.
    ///
    /// # Returns
    ///
    /// - `bool`- `true` if this is a component element.
    pub fn is_component(&self) -> bool {
        matches!(
            self,
            Self::Element {
                tag: Tag::Component(_),
                ..
            }
        )
    }

    /// Returns the tag name if this is an element or component node.
    ///
    /// # Returns
    ///
    /// - `Option<String>`- The tag name, or `None` if not an element.
    pub fn tag_name(&self) -> Option<String> {
        match self {
            Self::Element { tag, .. } => match tag {
                Tag::Element(name) => Some(name.clone()),
                Tag::Component(name) => Some(name.clone()),
            },
            _ => None,
        }
    }

    /// Extracts a string property from this node if it is an element with the named attribute.
    ///
    /// # Arguments
    ///
    /// - `&str`- The attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<String>`- The attribute value as a string, or `None`.
    pub fn try_get_prop(&self, name: &str) -> Option<String> {
        if let Self::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name {
                    match attr.get_value() {
                        AttributeValue::Text(value) => return Some(value.clone()),
                        AttributeValue::Signal(signal) => return Some(signal.get()),
                        AttributeValue::Dynamic(value) => return Some(value.clone()),
                        _ => {}
                    }
                }
            }
        }
        None
    }

    /// Extracts a typed property from this node by parsing the attribute value string.
    ///
    /// # Arguments
    ///
    /// - `&str`- The attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<T>`- The parsed value, or `None` if not found or parsing fails.
    pub fn try_get_typed_prop<T>(&self, name: &str) -> Option<T>
    where
        T: FromStr,
    {
        if let Self::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name {
                    let raw: String = match attr.get_value() {
                        AttributeValue::Text(value) => value.clone(),
                        AttributeValue::Signal(signal) => signal.get(),
                        AttributeValue::Dynamic(value) => value.clone(),
                        _ => continue,
                    };
                    return raw.parse::<T>().ok();
                }
            }
        }
        None
    }

    /// Extracts a signal property from this node if it is an element with the named attribute.
    ///
    /// # Arguments
    ///
    /// - `&str`- The attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<Signal<String>>`- The signal, or `None` if not found.
    pub fn try_get_signal_prop(&self, name: &str) -> Option<Signal<String>> {
        if let Self::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name
                    && let AttributeValue::Signal(signal) = attr.get_value()
                {
                    return Some(*signal);
                }
            }
        }
        None
    }

    /// Extracts children from this node if it is an element.
    ///
    /// # Returns
    ///
    /// - `Vec<Self>`- The children, or an empty vec if not an element.
    pub fn get_children(&self) -> Vec<Self> {
        if let Self::Element { children, .. } = self {
            children.clone()
        } else {
            Vec::new()
        }
    }

    /// Extracts text content from this node.
    ///
    /// # Returns
    ///
    /// - `Option<String>`- The text content, or `None` if unavailable.
    pub fn try_get_text(&self) -> Option<String> {
        match self {
            Self::Text(text_node) => Some(text_node.get_content().clone()),
            Self::Element { children, .. } => children.first().and_then(Self::try_get_text),
            _ => None,
        }
    }

    /// Extracts an event handler from this node if it is an element with the named event attribute.
    ///
    /// # Arguments
    ///
    /// - `&str`- The event attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<NativeEventHandler>`- The handler, or `None` if not found.
    pub fn try_get_event(&self, name: &str) -> Option<NativeEventHandler> {
        if let Self::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name
                    && let AttributeValue::Event(handler) = attr.get_value()
                {
                    return Some(handler.clone());
                }
            }
        }
        None
    }

    /// Extracts an event handler from this node by a custom attribute name.
    ///
    /// # Arguments
    ///
    /// - `&str`- The custom callback attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<NativeEventHandler>`- The handler, or `None` if not found.
    pub fn try_get_callback(&self, name: &str) -> Option<NativeEventHandler> {
        if let Self::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name
                    && let AttributeValue::Event(handler) = attr.get_value()
                {
                    return Some(handler.clone());
                }
            }
        }
        None
    }
}