Skip to main content

euv_core/vdom/node/
impl.rs

1use super::*;
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    /// Returns `true` when `self` and `other` are equivalent by the [`PartialEq`] contract.
9    ///
10    /// # Arguments
11    ///
12    /// - `&Self` - The other value to compare against `self`.
13    ///
14    /// # Returns
15    ///
16    /// - `bool` - `true` when `self` and `other` are equivalent by the trait contract.
17    fn eq(&self, other: &Self) -> bool {
18        self.get_content() == other.get_content()
19    }
20}
21
22/// Clones a `VirtualNode<T>` by deep-copying all fields.
23impl<T: Clone> Clone for VirtualNode<T> {
24    /// Clones the [`VirtualNode`] by reusing shared, cheap-to-clone state where possible.
25    fn clone(&self) -> Self {
26        match self {
27            Self::Element {
28                tag,
29                attributes,
30                children,
31                key,
32                props,
33            } => Self::Element {
34                tag: tag.clone(),
35                attributes: attributes.clone(),
36                children: children.clone(),
37                key: key.clone(),
38                props: props.clone(),
39            },
40            Self::Text(text_node) => Self::Text(text_node.clone()),
41            Self::Fragment(children) => Self::Fragment(children.clone()),
42            Self::Dynamic(dynamic_node) => Self::Dynamic(dynamic_node.clone()),
43            Self::Empty => Self::Empty,
44        }
45    }
46}
47
48/// Debug formatting for `VirtualNode<T>`.
49///
50/// Skips `Dynamic` inner details and `props` for brevity.
51impl<T: Debug> Debug for VirtualNode<T> {
52    /// Formats the [`VirtualNode`] via the supplied formatter.
53    ///
54    /// # Arguments
55    ///
56    /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
57    ///
58    /// # Returns
59    ///
60    /// - `fmt::Result` - Result of the formatting operation.
61    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
62        match self {
63            Self::Element {
64                tag,
65                attributes,
66                children,
67                key,
68                props,
69            } => formatter
70                .debug_struct("Element")
71                .field("tag", tag)
72                .field("attributes", attributes)
73                .field("children", children)
74                .field("key", key)
75                .field("props", props)
76                .finish(),
77            Self::Text(text_node) => formatter.debug_tuple("Text").field(text_node).finish(),
78            Self::Fragment(children) => formatter.debug_tuple("Fragment").field(children).finish(),
79            Self::Dynamic(_) => formatter.debug_tuple("Dynamic").finish(),
80            Self::Empty => formatter.debug_tuple("Empty").finish(),
81        }
82    }
83}
84
85/// Default implementation returns `VirtualNode::Empty`.
86impl<T> Default for VirtualNode<T> {
87    /// Constructs a default [`VirtualNode`] value.
88    fn default() -> Self {
89        Self::Empty
90    }
91}
92
93/// Visual equality comparison for virtual DOM nodes.
94///
95/// Used by DynamicNode re-rendering to skip unnecessary DOM patches when
96/// the rendered output has not changed. Event attributes are always
97/// considered equal because re-binding event listeners is handled
98/// separately by the handler registry and does not affect visual output.
99/// Dynamic nodes manage their own subtree re-rendering, so two Dynamic
100/// variants are always considered equal — the inner renderer handles
101/// patching when the dynamic content actually changes.
102impl<T: PartialEq> PartialEq for VirtualNode<T> {
103    /// Returns `true` when `self` and `other` are equivalent by the [`PartialEq`] contract.
104    ///
105    /// # Arguments
106    ///
107    /// - `&Self` - The other value to compare against `self`.
108    ///
109    /// # Returns
110    ///
111    /// - `bool` - `true` when `self` and `other` are equivalent by the trait contract.
112    fn eq(&self, other: &Self) -> bool {
113        match (self, other) {
114            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
115            (
116                VirtualNode::Element {
117                    tag: old_tag,
118                    attributes: old_attrs,
119                    children: old_children,
120                    props: old_props,
121                    ..
122                },
123                VirtualNode::Element {
124                    tag: new_tag,
125                    attributes: new_attrs,
126                    children: new_children,
127                    props: new_props,
128                    ..
129                },
130            ) => {
131                old_tag == new_tag
132                    && old_attrs.len() == new_attrs.len()
133                    && old_attrs.iter().zip(new_attrs.iter()).all(
134                        |(old_attr, new_attr): (&AttributeEntry, &AttributeEntry)| {
135                            old_attr == new_attr
136                        },
137                    )
138                    && old_children.len() == new_children.len()
139                    && old_children.iter().zip(new_children.iter()).all(
140                        |(old_child, new_child): (&VirtualNode, &VirtualNode)| {
141                            old_child == new_child
142                        },
143                    )
144                    && old_props == new_props
145            }
146            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
147                old_children.len() == new_children.len()
148                    && old_children.iter().zip(new_children.iter()).all(
149                        |(old_child, new_child): (&VirtualNode, &VirtualNode)| {
150                            old_child == new_child
151                        },
152                    )
153            }
154            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
155            (VirtualNode::Empty, VirtualNode::Empty) => true,
156            _ => false,
157        }
158    }
159}
160
161/// Provides a default empty dynamic node with a no-op render function.
162impl Default for DynamicNode {
163    /// Constructs a default [`DynamicNode`] value.
164    fn default() -> Self {
165        let render_fn_inner: Rc<UnsafeCell<RenderFnInner>> = Rc::new(UnsafeCell::new(
166            RenderFnInner::new(Box::new(|_: &mut HookContext| VirtualNode::Empty)),
167        ));
168        Self::new(render_fn_inner, HookContext::default())
169    }
170}
171
172/// Implementation of dynamic node accessor methods.
173impl DynamicNode {
174    /// Invokes the render closure and returns the produced virtual node.
175    ///
176    /// # Safety
177    ///
178    /// Must only be called from the main thread. Guaranteed in WASM
179    /// single-threaded context. No concurrent access is possible.
180    ///
181    /// # Arguments
182    ///
183    /// - `&mut HookContext` - The hook context to pass to the render closure.
184    ///
185    /// # Returns
186    ///
187    /// - `VirtualNode` - The virtual node produced by the render closure.
188    pub fn render(&self, hook_context: &mut HookContext) -> VirtualNode {
189        let inner: &mut RenderFnInner = unsafe { &mut *self.get_render_fn().get() };
190        (inner.get_mut_render_fn())(hook_context)
191    }
192}
193
194/// Implementation of virtual node construction and property extraction.
195impl<T> VirtualNode<T> {
196    /// Returns the tag name if this is an element or component node.
197    ///
198    /// # Returns
199    ///
200    /// - `Option<String>` - The tag name, or `None` if not an element.
201    pub fn try_get_tag_name(&self) -> Option<String> {
202        match self {
203            Self::Element { tag, .. } => match tag {
204                // OPT 2: `Cow::to_string()` allocates only for the
205                // `Owned` branch. The common `Borrowed("div")` path
206                // performs one string slice clone (no heap).
207                Tag::Element(name) => Some(name.to_string()),
208                Tag::Component(name) => Some(name.to_string()),
209                // Portals do not contribute a tag name to the
210                // declared position in the DOM tree — their content
211                // is rendered into a separate target, and the
212                // marker is an internal implementation detail.
213                // Returning `None` here keeps callers that use
214                // `try_get_tag_name` for "what tag is this?" away
215                // from the portal sentinel.
216                Tag::Portal(_) => None,
217            },
218            _ => None,
219        }
220    }
221
222    /// Returns a reference to the children of this node, if it has any.
223    ///
224    /// Returns `Some` for `Element` and `Fragment` variants, `None` otherwise.
225    ///
226    /// # Returns
227    ///
228    /// - `Option<&Vec<VirtualNode>>` - The children, or `None`.
229    pub fn try_get_children(&self) -> Option<&Vec<VirtualNode>> {
230        match self {
231            Self::Element { children, .. } => Some(children),
232            Self::Fragment(children) => Some(children),
233            _ => None,
234        }
235    }
236
237    /// Returns `true` if this node has non-empty children.
238    ///
239    /// # Returns
240    ///
241    /// - `bool` - Whether this node has children.
242    pub fn has_children(&self) -> bool {
243        self.try_get_children()
244            .is_some_and(|children: &Vec<VirtualNode>| !children.is_empty())
245    }
246
247    /// Clones the props of this node.
248    ///
249    /// # Returns
250    ///
251    /// - `Option<T>` - The cloned props, or `None` if this node has no props.
252    pub fn try_get_props(&self) -> Option<T>
253    where
254        T: Clone,
255    {
256        match self {
257            Self::Element { props, .. } => props.as_deref().cloned(),
258            _ => None,
259        }
260    }
261
262    /// Returns the children of this node as a virtual node.
263    ///
264    /// Returns `VirtualNode::Empty` when there are no children, a single child
265    /// when there is exactly one, or `VirtualNode::Fragment` when there are
266    /// multiple children.
267    ///
268    /// # Returns
269    ///
270    /// - `Option<VirtualNode>` - The children as a virtual node.
271    pub fn try_get_child_node(&self) -> Option<VirtualNode> {
272        match self.try_get_children() {
273            Some(children) => match children.len() {
274                0 => None,
275                1 => children.first().cloned(),
276                _ => Some(VirtualNode::Fragment(children.clone())),
277            },
278            None => None,
279        }
280    }
281
282    /// Extends this node's attribute list with the given entries, then
283    /// returns the node. If the node is not an `Element` variant, the
284    /// entries are dropped and the node is returned unchanged.
285    ///
286    /// Used by the `html!` macro to splice `class` / `style` / event
287    /// handler attributes onto a component-returned node without forcing
288    /// a `let mut` binding in the generated code.
289    ///
290    /// # Arguments
291    ///
292    /// - `I: IntoIterator<Item = AttributeEntry>` - The extra entries to append.
293    ///
294    /// # Returns
295    ///
296    /// - `Self` - The node with extended attributes (or unchanged).
297    pub fn extend_attributes<I>(self, extra: I) -> Self
298    where
299        I: IntoIterator<Item = AttributeEntry>,
300    {
301        match self {
302            Self::Element {
303                tag,
304                attributes,
305                children,
306                key,
307                props,
308            } => {
309                let mut attrs: Vec<AttributeEntry> = attributes;
310                attrs.extend(extra);
311                Self::Element {
312                    tag,
313                    attributes: attrs,
314                    children,
315                    key,
316                    props,
317                }
318            }
319            other => other,
320        }
321    }
322
323    /// Returns the children of this node as a virtual node.
324    ///
325    /// Returns `VirtualNode::Empty` when there are no children, a single child
326    /// when there is exactly one, or `VirtualNode::Fragment` when there are
327    /// multiple children.
328    ///
329    /// # Returns
330    ///
331    /// - `VirtualNode` - The children as a virtual node.
332    pub fn get_child_node(&self) -> VirtualNode {
333        self.try_get_child_node().unwrap_or_default()
334    }
335
336    /// Returns the diffing key of this node, if it has one.
337    ///
338    /// Recognizes keys on `Element` variants. Other variants
339    /// (`Text`, `Fragment`, `Dynamic`, `Empty`) do not have keys.
340    /// This matches the renderer's `get_node_key` semantics
341    /// in `core/src/renderer/render/impl.rs`.
342    ///
343    /// # Returns
344    ///
345    /// - `Option<&str>` - The key, or `None` if this node has no key
346    ///   or is not an `Element` variant.
347    pub fn key(&self) -> Option<&str> {
348        match self {
349            Self::Element { key, .. } => key.as_deref(),
350            _ => None,
351        }
352    }
353
354    /// Returns `true` if this node has a non-`None` diffing key.
355    ///
356    /// # Returns
357    ///
358    /// - `bool` - `true` if `key()` returns `Some`, `false` otherwise.
359    pub fn has_key(&self) -> bool {
360        self.key().is_some()
361    }
362}
363
364/// Implementation of virtual node construction for `VirtualNode<()>`.
365impl VirtualNode<()> {
366    /// Creates a dynamic node with the given render function.
367    ///
368    /// # Arguments
369    ///
370    /// - `F: FnMut(&mut HookContext) -> Self + 'static` - The render function.
371    ///
372    /// # Returns
373    ///
374    /// - `Self` - The dynamic node.
375    pub fn create_dynamic<F>(render_fn: F) -> Self
376    where
377        F: FnMut(&mut HookContext) -> Self + 'static,
378    {
379        let hook_context: HookContext = HookContext::default();
380        let inner: Rc<UnsafeCell<RenderFnInner>> =
381            Rc::new(UnsafeCell::new(RenderFnInner::new(Box::new(render_fn))));
382        Self::Dynamic(DynamicNode::new(inner, hook_context))
383    }
384}