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