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