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        DynamicNode::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: DynamicNode = DynamicNode::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    /// - `VirtualNode` - 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    /// Creates a new element node with the given tag name.
113    ///
114    /// # Arguments
115    ///
116    /// - `&str`: The HTML tag name.
117    ///
118    /// # Returns
119    ///
120    /// - `VirtualNode`: A new element virtual node.
121    pub fn get_element_node(tag_name: &str) -> Self {
122        VirtualNode::Element {
123            tag: Tag::Element(tag_name.to_string()),
124            attributes: Vec::new(),
125            children: Vec::new(),
126            key: None,
127        }
128    }
129
130    /// Creates a new text node with the given content.
131    ///
132    /// # Arguments
133    ///
134    /// - `&str`: The text content.
135    ///
136    /// # Returns
137    ///
138    /// - `VirtualNode`: A new text virtual node.
139    pub fn get_text_node(content: &str) -> Self {
140        VirtualNode::Text(TextNode::new(content.to_string(), None))
141    }
142
143    /// Adds an attribute to this node if it is an element.
144    ///
145    /// # Arguments
146    ///
147    /// - `&str`: The attribute name.
148    /// - `AttributeValue`: The attribute value.
149    ///
150    /// # Returns
151    ///
152    /// - `Self`: This node with the attribute added.
153    pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
154        if let VirtualNode::Element {
155            ref mut attributes, ..
156        } = self
157        {
158            attributes.push(AttributeEntry::new(name.to_string(), value));
159        }
160        self
161    }
162
163    /// Adds a child node to this node if it is an element.
164    ///
165    /// # Arguments
166    ///
167    /// - `VirtualNode`: The child node to add.
168    ///
169    /// # Returns
170    ///
171    /// - `Self`: This node with the child added.
172    pub fn with_child(mut self, child: VirtualNode) -> Self {
173        if let VirtualNode::Element {
174            ref mut children, ..
175        } = self
176        {
177            children.push(child);
178        }
179        self
180    }
181
182    /// Returns true if this node is a component node.
183    ///
184    /// # Returns
185    ///
186    /// - `bool`: `true` if this is a component element.
187    pub fn is_component(&self) -> bool {
188        matches!(
189            self,
190            VirtualNode::Element {
191                tag: Tag::Component(_),
192                ..
193            }
194        )
195    }
196
197    /// Returns the tag name if this is an element or component node.
198    ///
199    /// # Returns
200    ///
201    /// - `Option<String>`: The tag name, or `None` if not an element.
202    pub fn tag_name(&self) -> Option<String> {
203        match self {
204            VirtualNode::Element { tag, .. } => match tag {
205                Tag::Element(name) => Some(name.clone()),
206                Tag::Component(name) => Some(name.clone()),
207            },
208            _ => None,
209        }
210    }
211
212    /// Extracts a string property from this node if it is an element with the named attribute.
213    ///
214    /// # Arguments
215    ///
216    /// - `&str`: The attribute name to look for.
217    ///
218    /// # Returns
219    ///
220    /// - `Option<String>`: The attribute value as a string, or `None`.
221    pub fn try_get_prop(&self, name: &str) -> Option<String> {
222        if let VirtualNode::Element { attributes, .. } = self {
223            for attr in attributes {
224                if attr.get_name() == name {
225                    match attr.get_value() {
226                        AttributeValue::Text(value) => return Some(value.clone()),
227                        AttributeValue::Signal(signal) => return Some(signal.get()),
228                        AttributeValue::Dynamic(value) => return Some(value.clone()),
229                        _ => {}
230                    }
231                }
232            }
233        }
234        None
235    }
236
237    /// Extracts a typed property from this node by parsing the attribute value string.
238    ///
239    /// # Arguments
240    ///
241    /// - `&str`: The attribute name to look for.
242    ///
243    /// # Returns
244    ///
245    /// - `Option<T>`: The parsed value, or `None` if not found or parsing fails.
246    pub fn try_get_typed_prop<T>(&self, name: &str) -> Option<T>
247    where
248        T: std::str::FromStr,
249    {
250        if let VirtualNode::Element { attributes, .. } = self {
251            for attr in attributes {
252                if attr.get_name() == name {
253                    let raw: String = match attr.get_value() {
254                        AttributeValue::Text(value) => value.clone(),
255                        AttributeValue::Signal(signal) => signal.get(),
256                        AttributeValue::Dynamic(value) => value.clone(),
257                        _ => continue,
258                    };
259                    return raw.parse::<T>().ok();
260                }
261            }
262        }
263        None
264    }
265
266    /// Extracts a signal 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<Signal<String>>`: The signal, or `None` if not found.
275    pub fn try_get_signal_prop(&self, name: &str) -> Option<Signal<String>> {
276        if let VirtualNode::Element { attributes, .. } = self {
277            for attr in attributes {
278                if attr.get_name() == name
279                    && let AttributeValue::Signal(signal) = attr.get_value()
280                {
281                    return Some(*signal);
282                }
283            }
284        }
285        None
286    }
287
288    /// Extracts children from this node if it is an element.
289    ///
290    /// # Returns
291    ///
292    /// - `Vec<VirtualNode>`: The children, or an empty vec if not an element.
293    pub fn get_children(&self) -> Vec<VirtualNode> {
294        if let VirtualNode::Element { children, .. } = self {
295            children.clone()
296        } else {
297            Vec::new()
298        }
299    }
300
301    /// Extracts text content from this node.
302    ///
303    /// # Returns
304    ///
305    /// - `Option<String>`: The text content, or `None` if unavailable.
306    pub fn try_get_text(&self) -> Option<String> {
307        match self {
308            VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
309            VirtualNode::Element { children, .. } => {
310                children.first().and_then(VirtualNode::try_get_text)
311            }
312            _ => None,
313        }
314    }
315
316    /// Extracts an event handler from this node if it is an element with the named event attribute.
317    ///
318    /// # Arguments
319    ///
320    /// - `&str`: The event attribute name to look for.
321    ///
322    /// # Returns
323    ///
324    /// - `Option<NativeEventHandler>`: The handler, or `None` if not found.
325    pub fn try_get_event(&self, name: &str) -> Option<NativeEventHandler> {
326        if let VirtualNode::Element { attributes, .. } = self {
327            for attr in attributes {
328                if attr.get_name() == name
329                    && let AttributeValue::Event(handler) = attr.get_value()
330                {
331                    return Some(handler.clone());
332                }
333            }
334        }
335        None
336    }
337
338    /// Extracts an event handler from this node by a custom attribute name.
339    ///
340    /// # Arguments
341    ///
342    /// - `&str`: The custom callback attribute name to look for.
343    ///
344    /// # Returns
345    ///
346    /// - `Option<NativeEventHandler>`: The handler, or `None` if not found.
347    pub fn try_get_callback(&self, name: &str) -> Option<NativeEventHandler> {
348        if let VirtualNode::Element { attributes, .. } = self {
349            for attr in attributes {
350                if attr.get_name() == name
351                    && let AttributeValue::Event(handler) = attr.get_value()
352                {
353                    return Some(handler.clone());
354                }
355            }
356        }
357        None
358    }
359}