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