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