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}