Skip to main content

euv_core/vdom/node/
impl.rs

1use crate::*;
2
3/// Visual equality comparison for text nodes.
4///
5/// Only compares the text content; the backing signal is not considered
6/// because it does not affect visual output.
7impl PartialEq for TextNode {
8    fn eq(&self, other: &Self) -> bool {
9        self.get_content() == other.get_content()
10    }
11}
12
13/// Visual equality comparison for virtual DOM nodes.
14///
15/// Used by DynamicNode re-rendering to skip unnecessary DOM patches when
16/// the rendered output has not changed. Event attributes are always
17/// considered equal because re-binding event listeners is handled
18/// separately by the handler registry and does not affect visual output.
19/// Dynamic nodes manage their own subtree re-rendering, so two Dynamic
20/// variants are always considered equal — the inner renderer handles
21/// patching when the dynamic content actually changes.
22impl PartialEq for VirtualNode {
23    fn eq(&self, other: &Self) -> bool {
24        match (self, other) {
25            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
26            (
27                VirtualNode::Element {
28                    tag: old_tag,
29                    attributes: old_attrs,
30                    children: old_children,
31                    ..
32                },
33                VirtualNode::Element {
34                    tag: new_tag,
35                    attributes: new_attrs,
36                    children: new_children,
37                    ..
38                },
39            ) => {
40                old_tag == new_tag
41                    && old_attrs.len() == new_attrs.len()
42                    && old_attrs
43                        .iter()
44                        .zip(new_attrs.iter())
45                        .all(|(old_attr, new_attr)| old_attr == new_attr)
46                    && old_children.len() == new_children.len()
47                    && old_children
48                        .iter()
49                        .zip(new_children.iter())
50                        .all(|(old_child, new_child)| old_child == new_child)
51            }
52            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
53                old_children.len() == new_children.len()
54                    && old_children
55                        .iter()
56                        .zip(new_children.iter())
57                        .all(|(old_child, new_child)| old_child == new_child)
58            }
59            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
60            (VirtualNode::Empty, VirtualNode::Empty) => true,
61            _ => false,
62        }
63    }
64}
65
66/// Provides a default empty dynamic node with a no-op render function.
67impl Default for DynamicNode {
68    fn default() -> Self {
69        let render_fn_inner: Rc<RefCell<RenderFnInner>> =
70            Rc::new(RefCell::new(RenderFnInner::new(Box::new(|| {
71                VirtualNode::Empty
72            }))));
73        Self::new(render_fn_inner, HookContext::default())
74    }
75}
76
77/// Clones a `DynamicNode` by cloning the shared references.
78impl Clone for DynamicNode {
79    fn clone(&self) -> Self {
80        let cloned: Self = Self::new(
81            self.get_render_fn().clone(),
82            self.get_hook_context().clone(),
83        );
84        cloned
85    }
86}
87
88/// Implementation of dynamic node accessor methods.
89impl DynamicNode {
90    /// Returns the hook context for this dynamic node.
91    ///
92    /// # Returns
93    ///
94    /// - `HookContext` - The hook context.
95    pub(crate) fn get_hook_context_value(&self) -> HookContext {
96        self.get_hook_context().clone()
97    }
98
99    /// Invokes the render closure and returns the produced virtual node.
100    ///
101    /// # Returns
102    ///
103    /// - `Self` - The virtual node produced by the render closure.
104    pub fn render(&self) -> VirtualNode {
105        let mut inner: RefMut<RenderFnInner> = self.get_render_fn().borrow_mut();
106        (inner.get_mut_render_fn())()
107    }
108}
109
110/// Implementation of virtual node construction and property extraction.
111impl VirtualNode {
112    /// Constructs a `Self::Dynamic` from a render closure with hook context management.
113    ///
114    /// # Arguments
115    ///
116    /// - `FnMut() -> Self + 'static` - The render closure that produces
117    ///   a virtual node tree. Called on initial render and on every signal update.
118    ///
119    /// # Returns
120    ///
121    /// - `Self` - A `Self::Dynamic` wrapping the render closure
122    ///   with a fresh `HookContext`.
123    pub fn create_dynamic<F>(mut render_fn: F) -> Self
124    where
125        F: FnMut() -> Self + 'static,
126    {
127        let hook_context: HookContext = create_hook_context();
128        let mut hook_context_for_closure: HookContext = hook_context.clone();
129        let inner: Rc<RefCell<RenderFnInner>> =
130            Rc::new(RefCell::new(RenderFnInner::new(Box::new(move || {
131                hook_context_for_closure.reset_hook_index();
132                render_fn()
133            }))));
134        let dynamic_node: DynamicNode = DynamicNode::new(inner, hook_context);
135        Self::Dynamic(dynamic_node)
136    }
137
138    /// Constructs a `Self::Dynamic` for match expressions where arm hook
139    /// isolation is required. The render closure receives a `&mut HookContext`
140    /// so it can call `set_arm_changed` before each arm body.
141    ///
142    /// # Arguments
143    ///
144    /// - `FnMut(&mut HookContext) -> Self + 'static` - The render closure
145    ///   that receives a mutable reference to the hook context.
146    ///
147    /// # Returns
148    ///
149    /// - `Self` - A `Self::Dynamic` wrapping the render closure
150    ///   with a fresh `HookContext`.
151    pub fn create_dynamic_with_context<F>(mut render_fn: F) -> Self
152    where
153        F: FnMut(&mut HookContext) -> Self + 'static,
154    {
155        let hook_context: HookContext = create_hook_context();
156        let mut hook_context_for_closure: HookContext = hook_context.clone();
157        let inner: Rc<RefCell<RenderFnInner>> =
158            Rc::new(RefCell::new(RenderFnInner::new(Box::new(move || {
159                hook_context_for_closure.reset_hook_index();
160                render_fn(&mut hook_context_for_closure)
161            }))));
162        let dynamic_node: DynamicNode = DynamicNode::new(inner, hook_context);
163        Self::Dynamic(dynamic_node)
164    }
165
166    /// Creates a new element node with the given tag name.
167    ///
168    /// # Arguments
169    ///
170    /// - `&str`- The HTML tag name.
171    ///
172    /// # Returns
173    ///
174    /// - `Self`- A new element virtual node.
175    pub fn get_element_node(tag_name: &str) -> Self {
176        Self::Element {
177            tag: Tag::Element(tag_name.to_string()),
178            attributes: Vec::new(),
179            children: Vec::new(),
180            key: None,
181        }
182    }
183
184    /// Creates a new text node with the given content.
185    ///
186    /// # Arguments
187    ///
188    /// - `&str`- The text content.
189    ///
190    /// # Returns
191    ///
192    /// - `Self`- A new text virtual node.
193    pub fn get_text_node(content: &str) -> Self {
194        Self::Text(TextNode::new(content.to_string(), None))
195    }
196
197    /// Adds an attribute to this node if it is an element.
198    ///
199    /// # Arguments
200    ///
201    /// - `&str`- The attribute name.
202    /// - `AttributeValue`- The attribute value.
203    ///
204    /// # Returns
205    ///
206    /// - `Self`- This node with the attribute added.
207    pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
208        if let Self::Element {
209            ref mut attributes, ..
210        } = self
211        {
212            attributes.push(AttributeEntry::new(name.to_string(), value));
213        }
214        self
215    }
216
217    /// Adds a child node to this node if it is an element.
218    ///
219    /// # Arguments
220    ///
221    /// - `VirtualNode`- The child node to add.
222    ///
223    /// # Returns
224    ///
225    /// - `Self`- This node with the child added.
226    pub fn with_child(mut self, child: VirtualNode) -> Self {
227        if let Self::Element {
228            ref mut children, ..
229        } = self
230        {
231            children.push(child);
232        }
233        self
234    }
235
236    /// Returns true if this node is a component node.
237    ///
238    /// # Returns
239    ///
240    /// - `bool`- `true` if this is a component element.
241    pub fn is_component(&self) -> bool {
242        matches!(
243            self,
244            Self::Element {
245                tag: Tag::Component(_),
246                ..
247            }
248        )
249    }
250
251    /// Returns the tag name if this is an element or component node.
252    ///
253    /// # Returns
254    ///
255    /// - `Option<String>`- The tag name, or `None` if not an element.
256    pub fn tag_name(&self) -> Option<String> {
257        match self {
258            Self::Element { tag, .. } => match tag {
259                Tag::Element(name) => Some(name.clone()),
260                Tag::Component(name) => Some(name.clone()),
261            },
262            _ => None,
263        }
264    }
265
266    /// Extracts a string property from this node if it is an element with the named attribute.
267    ///
268    /// # Arguments
269    ///
270    /// - `&str`- The attribute name to look for.
271    ///
272    /// # Returns
273    ///
274    /// - `Option<String>`- The attribute value as a string, or `None`.
275    pub fn try_get_prop(&self, name: &str) -> Option<String> {
276        if let Self::Element { attributes, .. } = self {
277            for attr in attributes {
278                if attr.get_name() == name {
279                    match attr.get_value() {
280                        AttributeValue::Text(value) => return Some(value.clone()),
281                        AttributeValue::Signal(signal) => return Some(signal.get()),
282                        AttributeValue::Dynamic(value) => return Some(value.clone()),
283                        _ => {}
284                    }
285                }
286            }
287        }
288        None
289    }
290
291    /// Extracts a typed property from this node by parsing the attribute value string.
292    ///
293    /// # Arguments
294    ///
295    /// - `&str`- The attribute name to look for.
296    ///
297    /// # Returns
298    ///
299    /// - `Option<T>`- The parsed value, or `None` if not found or parsing fails.
300    pub fn try_get_typed_prop<T>(&self, name: &str) -> Option<T>
301    where
302        T: FromStr,
303    {
304        if let Self::Element { attributes, .. } = self {
305            for attr in attributes {
306                if attr.get_name() == name {
307                    let raw: String = match attr.get_value() {
308                        AttributeValue::Text(value) => value.clone(),
309                        AttributeValue::Signal(signal) => signal.get(),
310                        AttributeValue::Dynamic(value) => value.clone(),
311                        _ => continue,
312                    };
313                    return raw.parse::<T>().ok();
314                }
315            }
316        }
317        None
318    }
319
320    /// Extracts a signal property from this node if it is an element with the named attribute.
321    ///
322    /// # Arguments
323    ///
324    /// - `&str`- The attribute name to look for.
325    ///
326    /// # Returns
327    ///
328    /// - `Option<Signal<String>>`- The signal, or `None` if not found.
329    pub fn try_get_signal_prop(&self, name: &str) -> Option<Signal<String>> {
330        if let Self::Element { attributes, .. } = self {
331            for attr in attributes {
332                if attr.get_name() == name
333                    && let AttributeValue::Signal(signal) = attr.get_value()
334                {
335                    return Some(*signal);
336                }
337            }
338        }
339        None
340    }
341
342    /// Extracts children from this node if it is an element.
343    ///
344    /// # Returns
345    ///
346    /// - `Vec<Self>`- The children, or an empty vec if not an element.
347    pub fn get_children(&self) -> Vec<Self> {
348        if let Self::Element { children, .. } = self {
349            children.clone()
350        } else {
351            Vec::new()
352        }
353    }
354
355    /// Extracts text content from this node.
356    ///
357    /// # Returns
358    ///
359    /// - `Option<String>`- The text content, or `None` if unavailable.
360    pub fn try_get_text(&self) -> Option<String> {
361        match self {
362            Self::Text(text_node) => Some(text_node.get_content().clone()),
363            Self::Element { children, .. } => children.first().and_then(Self::try_get_text),
364            _ => None,
365        }
366    }
367
368    /// Extracts an event handler from this node if it is an element with the named event attribute.
369    ///
370    /// # Arguments
371    ///
372    /// - `&str`- The event attribute name to look for.
373    ///
374    /// # Returns
375    ///
376    /// - `Option<NativeEventHandler>`- The handler, or `None` if not found.
377    pub fn try_get_event(&self, name: &str) -> Option<NativeEventHandler> {
378        if let Self::Element { attributes, .. } = self {
379            for attr in attributes {
380                if attr.get_name() == name
381                    && let AttributeValue::Event(handler) = attr.get_value()
382                {
383                    return Some(handler.clone());
384                }
385            }
386        }
387        None
388    }
389
390    /// Extracts an event handler from this node by a custom attribute name.
391    ///
392    /// # Arguments
393    ///
394    /// - `&str`- The custom callback attribute name to look for.
395    ///
396    /// # Returns
397    ///
398    /// - `Option<NativeEventHandler>`- The handler, or `None` if not found.
399    pub fn try_get_callback(&self, name: &str) -> Option<NativeEventHandler> {
400        if let Self::Element { attributes, .. } = self {
401            for attr in attributes {
402                if attr.get_name() == name
403                    && let AttributeValue::Event(handler) = attr.get_value()
404                {
405                    return Some(handler.clone());
406                }
407            }
408        }
409        None
410    }
411}