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 DynamicNode::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: DynamicNode = DynamicNode::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 /// - `VirtualNode` - 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 /// Creates a new element node with the given tag name.
113 ///
114 /// # Arguments
115 ///
116 /// - `&str`: The HTML tag name.
117 ///
118 /// # Returns
119 ///
120 /// - `VirtualNode`: A new element virtual node.
121 pub fn get_element_node(tag_name: &str) -> Self {
122 VirtualNode::Element {
123 tag: Tag::Element(tag_name.to_string()),
124 attributes: Vec::new(),
125 children: Vec::new(),
126 key: None,
127 }
128 }
129
130 /// Creates a new text node with the given content.
131 ///
132 /// # Arguments
133 ///
134 /// - `&str`: The text content.
135 ///
136 /// # Returns
137 ///
138 /// - `VirtualNode`: A new text virtual node.
139 pub fn get_text_node(content: &str) -> Self {
140 VirtualNode::Text(TextNode::new(content.to_string(), None))
141 }
142
143 /// Adds an attribute to this node if it is an element.
144 ///
145 /// # Arguments
146 ///
147 /// - `&str`: The attribute name.
148 /// - `AttributeValue`: The attribute value.
149 ///
150 /// # Returns
151 ///
152 /// - `Self`: This node with the attribute added.
153 pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
154 if let VirtualNode::Element {
155 ref mut attributes, ..
156 } = self
157 {
158 attributes.push(AttributeEntry::new(name.to_string(), value));
159 }
160 self
161 }
162
163 /// Adds a child node to this node if it is an element.
164 ///
165 /// # Arguments
166 ///
167 /// - `VirtualNode`: The child node to add.
168 ///
169 /// # Returns
170 ///
171 /// - `Self`: This node with the child added.
172 pub fn with_child(mut self, child: VirtualNode) -> Self {
173 if let VirtualNode::Element {
174 ref mut children, ..
175 } = self
176 {
177 children.push(child);
178 }
179 self
180 }
181
182 /// Returns true if this node is a component node.
183 ///
184 /// # Returns
185 ///
186 /// - `bool`: `true` if this is a component element.
187 pub fn is_component(&self) -> bool {
188 matches!(
189 self,
190 VirtualNode::Element {
191 tag: Tag::Component(_),
192 ..
193 }
194 )
195 }
196
197 /// Returns the tag name if this is an element or component node.
198 ///
199 /// # Returns
200 ///
201 /// - `Option<String>`: The tag name, or `None` if not an element.
202 pub fn tag_name(&self) -> Option<String> {
203 match self {
204 VirtualNode::Element { tag, .. } => match tag {
205 Tag::Element(name) => Some(name.clone()),
206 Tag::Component(name) => Some(name.clone()),
207 },
208 _ => None,
209 }
210 }
211
212 /// Extracts a string property from this node if it is an element with the named attribute.
213 ///
214 /// # Arguments
215 ///
216 /// - `&str`: The attribute name to look for.
217 ///
218 /// # Returns
219 ///
220 /// - `Option<String>`: The attribute value as a string, or `None`.
221 pub fn try_get_prop(&self, name: &str) -> Option<String> {
222 if let VirtualNode::Element { attributes, .. } = self {
223 for attr in attributes {
224 if attr.get_name() == name {
225 match attr.get_value() {
226 AttributeValue::Text(value) => return Some(value.clone()),
227 AttributeValue::Signal(signal) => return Some(signal.get()),
228 AttributeValue::Dynamic(value) => return Some(value.clone()),
229 _ => {}
230 }
231 }
232 }
233 }
234 None
235 }
236
237 /// Extracts a typed property from this node by parsing the attribute value string.
238 ///
239 /// # Arguments
240 ///
241 /// - `&str`: The attribute name to look for.
242 ///
243 /// # Returns
244 ///
245 /// - `Option<T>`: The parsed value, or `None` if not found or parsing fails.
246 pub fn try_get_typed_prop<T>(&self, name: &str) -> Option<T>
247 where
248 T: std::str::FromStr,
249 {
250 if let VirtualNode::Element { attributes, .. } = self {
251 for attr in attributes {
252 if attr.get_name() == name {
253 let raw: String = match attr.get_value() {
254 AttributeValue::Text(value) => value.clone(),
255 AttributeValue::Signal(signal) => signal.get(),
256 AttributeValue::Dynamic(value) => value.clone(),
257 _ => continue,
258 };
259 return raw.parse::<T>().ok();
260 }
261 }
262 }
263 None
264 }
265
266 /// Extracts a signal 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<Signal<String>>`: The signal, or `None` if not found.
275 pub fn try_get_signal_prop(&self, name: &str) -> Option<Signal<String>> {
276 if let VirtualNode::Element { attributes, .. } = self {
277 for attr in attributes {
278 if attr.get_name() == name
279 && let AttributeValue::Signal(signal) = attr.get_value()
280 {
281 return Some(*signal);
282 }
283 }
284 }
285 None
286 }
287
288 /// Extracts children from this node if it is an element.
289 ///
290 /// # Returns
291 ///
292 /// - `Vec<VirtualNode>`: The children, or an empty vec if not an element.
293 pub fn get_children(&self) -> Vec<VirtualNode> {
294 if let VirtualNode::Element { children, .. } = self {
295 children.clone()
296 } else {
297 Vec::new()
298 }
299 }
300
301 /// Extracts text content from this node.
302 ///
303 /// # Returns
304 ///
305 /// - `Option<String>`: The text content, or `None` if unavailable.
306 pub fn try_get_text(&self) -> Option<String> {
307 match self {
308 VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
309 VirtualNode::Element { children, .. } => {
310 children.first().and_then(VirtualNode::try_get_text)
311 }
312 _ => None,
313 }
314 }
315
316 /// Extracts an event handler from this node if it is an element with the named event attribute.
317 ///
318 /// # Arguments
319 ///
320 /// - `&str`: The event attribute name to look for.
321 ///
322 /// # Returns
323 ///
324 /// - `Option<NativeEventHandler>`: The handler, or `None` if not found.
325 pub fn try_get_event(&self, name: &str) -> Option<NativeEventHandler> {
326 if let VirtualNode::Element { attributes, .. } = self {
327 for attr in attributes {
328 if attr.get_name() == name
329 && let AttributeValue::Event(handler) = attr.get_value()
330 {
331 return Some(handler.clone());
332 }
333 }
334 }
335 None
336 }
337
338 /// Extracts an event handler from this node by a custom attribute name.
339 ///
340 /// # Arguments
341 ///
342 /// - `&str`: The custom callback attribute name to look for.
343 ///
344 /// # Returns
345 ///
346 /// - `Option<NativeEventHandler>`: The handler, or `None` if not found.
347 pub fn try_get_callback(&self, name: &str) -> Option<NativeEventHandler> {
348 if let VirtualNode::Element { attributes, .. } = self {
349 for attr in attributes {
350 if attr.get_name() == name
351 && let AttributeValue::Event(handler) = attr.get_value()
352 {
353 return Some(handler.clone());
354 }
355 }
356 }
357 None
358 }
359}