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