Skip to main content

euv_core/renderer/render/
impl.rs

1use crate::*;
2
3/// Implementation of the virtual DOM renderer.
4impl Renderer {
5    /// Renders the given virtual DOM tree into the real DOM.
6    ///
7    /// If a previous tree exists, patches the existing DOM to match the new tree.
8    /// Otherwise, creates new DOM nodes from scratch and appends them to the root.
9    ///
10    /// # Arguments
11    ///
12    /// - `VirtualNode`: The new virtual DOM tree to render.
13    pub fn render(&mut self, vnode: VirtualNode) {
14        let new_unwrapped: VirtualNode = self.unwrap_component(&vnode);
15        let old_tree: Option<VirtualNode> = self.try_get_current_tree().clone();
16        if let Some(old_vnode) = old_tree {
17            self.patch_root(&old_vnode, &new_unwrapped);
18        } else {
19            while let Some(child) = self.get_root().first_child() {
20                if let Some(element) = child.dyn_ref::<Element>() {
21                    self.cleanup_dom_subtree(element);
22                }
23                self.get_root().remove_child(&child).unwrap();
24            }
25            let dom_node: Node = self.create_dom_node(&new_unwrapped);
26            self.get_root().append_child(&dom_node).unwrap();
27        }
28        self.set_current_tree(Some(new_unwrapped));
29    }
30
31    /// Renders the given virtual DOM tree into the real DOM by fully replacing
32    /// all existing content. Used when a match arm switch occurs (e.g. route
33    /// change) where incremental patching would incorrectly align unrelated
34    /// child nodes from the previous arm.
35    ///
36    /// # Arguments
37    ///
38    /// - `VirtualNode`: The new virtual DOM tree to render.
39    pub fn render_full_replace(&mut self, vnode: VirtualNode) {
40        let new_unwrapped: VirtualNode = self.unwrap_component(&vnode);
41        while let Some(child) = self.get_root().first_child() {
42            if let Some(element) = child.dyn_ref::<Element>() {
43                self.cleanup_dom_subtree(element);
44            }
45            self.get_root().remove_child(&child).unwrap();
46        }
47        let dom_node: Node = self.create_dom_node(&new_unwrapped);
48        self.get_root().append_child(&dom_node).unwrap();
49        self.set_current_tree(Some(new_unwrapped));
50    }
51
52    /// Patches the root DOM tree by replacing the single child of `self.root`.
53    ///
54    /// # Arguments
55    ///
56    /// - `&VirtualNode`: The old virtual node to patch from.
57    /// - `&VirtualNode`: The new virtual node to patch to.
58    fn patch_root(&mut self, old_node: &VirtualNode, new_node: &VirtualNode) {
59        let dom_child: Option<Node> = self.get_root().first_child();
60        let is_element: bool = if let Some(ref dom_child) = dom_child {
61            dom_child.dyn_ref::<Element>().is_some()
62        } else {
63            false
64        };
65        if is_element {
66            let element: Element = dom_child.unwrap().dyn_into::<Element>().unwrap();
67            self.patch_node(old_node, new_node, &element);
68        } else if let Some(dom_child) = dom_child {
69            if let Some(element) = dom_child.dyn_ref::<Element>() {
70                self.cleanup_dom_subtree(element);
71            }
72            let new_dom: Node = self.create_dom_node(new_node);
73            self.get_root().replace_child(&new_dom, &dom_child).unwrap();
74        } else {
75            let new_dom: Node = self.create_dom_node(new_node);
76            self.get_root().append_child(&new_dom).unwrap();
77        }
78    }
79
80    /// Patches an existing DOM node to match the new virtual node.
81    ///
82    /// # Arguments
83    ///
84    /// - `&VirtualNode`: The old virtual node.
85    /// - `&VirtualNode`: The new virtual node.
86    /// - `&Element`: The real DOM element to patch.
87    fn patch_node(
88        &mut self,
89        old_node: &VirtualNode,
90        new_node: &VirtualNode,
91        dom_element: &Element,
92    ) {
93        match (old_node, new_node) {
94            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => {
95                if old_text != new_text {
96                    dom_element.set_text_content(Some(new_text.get_content()));
97                }
98            }
99            (
100                VirtualNode::Element {
101                    tag: old_tag,
102                    attributes: old_attrs,
103                    children: old_children,
104                    key: _old_key,
105                },
106                VirtualNode::Element {
107                    tag: new_tag,
108                    attributes: new_attrs,
109                    children: new_children,
110                    key: _new_key,
111                },
112            ) => {
113                if old_tag != new_tag {
114                    let new_dom: Node = self.create_dom_node(new_node);
115                    if let Some(parent) = dom_element.parent_node() {
116                        self.cleanup_dom_subtree(dom_element);
117                        parent.replace_child(&new_dom, dom_element).unwrap();
118                    }
119                    return;
120                }
121                self.patch_attributes(dom_element, old_attrs, new_attrs);
122                self.patch_children(dom_element, old_children, new_children);
123            }
124            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
125                self.patch_children(dom_element, old_children, new_children);
126            }
127            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => {}
128            (VirtualNode::Dynamic(_), _) => {
129                let new_dom: Node = self.create_dom_node(new_node);
130                if let Some(parent) = dom_element.parent_node() {
131                    self.cleanup_dom_subtree(dom_element);
132                    parent.replace_child(&new_dom, dom_element).unwrap();
133                }
134            }
135            (_, VirtualNode::Dynamic(_)) => {
136                let new_dom: Node = self.create_dom_node(new_node);
137                if let Some(parent) = dom_element.parent_node() {
138                    self.cleanup_dom_subtree(dom_element);
139                    parent.replace_child(&new_dom, dom_element).unwrap();
140                }
141            }
142            _ => {
143                let new_dom: Node = self.create_dom_node(new_node);
144                if let Some(parent) = dom_element.parent_node() {
145                    self.cleanup_dom_subtree(dom_element);
146                    parent.replace_child(&new_dom, dom_element).unwrap();
147                }
148            }
149        }
150    }
151
152    /// Patches attributes of an element, adding, removing, or updating as needed.
153    ///
154    /// # Arguments
155    ///
156    /// - `&Element`: The DOM element whose attributes to patch.
157    /// - `&[AttributeEntry]`: The old attribute list.
158    /// - `&[AttributeEntry]`: The new attribute list.
159    fn patch_attributes(
160        &mut self,
161        element: &Element,
162        old_attrs: &[AttributeEntry],
163        new_attrs: &[AttributeEntry],
164    ) {
165        let old_map: HashMap<&str, &AttributeValue> = old_attrs
166            .iter()
167            .map(|attr: &AttributeEntry| (attr.get_name().as_str(), attr.get_value()))
168            .collect();
169        let new_map: HashMap<&str, ()> = new_attrs
170            .iter()
171            .map(|attr: &AttributeEntry| (attr.get_name().as_str(), ()))
172            .collect();
173        for old_attr in old_attrs {
174            if !new_map.contains_key(old_attr.get_name().as_str()) {
175                if let AttributeValue::Event(_) = old_attr.get_value()
176                    && let Some(euv_id_str) = element.get_attribute(DATA_EUV_ID)
177                    && let Ok(euv_id) = euv_id_str.parse::<usize>()
178                {
179                    cleanup_event_handler(euv_id, old_attr.get_name());
180                }
181                remove_dom_attribute_or_property(element, old_attr.get_name());
182            }
183        }
184        for new_attr in new_attrs {
185            let old_value: Option<&AttributeValue> =
186                old_map.get(new_attr.get_name().as_str()).copied();
187            let should_set: bool = match old_value {
188                Some(old_val) => old_val != new_attr.get_value(),
189                None => true,
190            };
191            if should_set {
192                match new_attr.get_value() {
193                    AttributeValue::Text(value) => {
194                        if value.is_empty() {
195                            remove_dom_attribute_or_property(element, new_attr.get_name());
196                        } else {
197                            set_dom_attribute_or_property(element, new_attr.get_name(), value);
198                        }
199                    }
200                    AttributeValue::Signal(signal) => {
201                        let value: String = signal.get();
202                        if value.is_empty() && !is_boolean_property(new_attr.get_name()) {
203                            remove_dom_attribute_or_property(element, new_attr.get_name());
204                        } else {
205                            set_dom_attribute_or_property(element, new_attr.get_name(), &value);
206                        }
207                    }
208                    AttributeValue::Event(handler) => {
209                        self.attach_event_listener(element, handler);
210                    }
211                    AttributeValue::Dynamic(_) => {}
212                    AttributeValue::Css(css_class) => {
213                        css_class.inject_style();
214                        set_dom_attribute_or_property(
215                            element,
216                            new_attr.get_name(),
217                            css_class.get_name(),
218                        );
219                    }
220                }
221            }
222        }
223    }
224
225    /// Gets a child node at the given index.
226    ///
227    /// # Arguments
228    ///
229    /// - `&Element`: The parent element.
230    /// - `u32`: The child index.
231    ///
232    /// # Returns
233    ///
234    /// - `Option<Node>`: The child node at the given index, if it exists.
235    fn get_child_node(parent: &Element, index: u32) -> Option<Node> {
236        parent.child_nodes().get(index)
237    }
238
239    /// Patches children of an element using a keyed diff algorithm when keys
240    /// are available, falling back to positional diff when no keys exist.
241    ///
242    /// When all children in both old and new lists have keys, this method
243    /// builds a key-to-index map and applies a minimal set of DOM moves,
244    /// insertions, and removals. This avoids the O(N) per-child re-patch
245    /// that the naive positional algorithm incurs when items are reordered.
246    ///
247    /// # Arguments
248    ///
249    /// - `&Element`: The parent DOM element.
250    /// - `&[VirtualNode]`: The old children list.
251    /// - `&[VirtualNode]`: The new children list.
252    fn patch_children(
253        &mut self,
254        parent: &Element,
255        old_children: &[VirtualNode],
256        new_children: &[VirtualNode],
257    ) {
258        let old_has_keys: bool =
259            !old_children.is_empty() && old_children.iter().all(Self::node_has_key);
260        let new_has_keys: bool =
261            !new_children.is_empty() && new_children.iter().all(Self::node_has_key);
262        if old_has_keys && new_has_keys {
263            self.patch_children_keyed(parent, old_children, new_children);
264        } else {
265            self.patch_children_positional(parent, old_children, new_children);
266        }
267    }
268
269    /// Returns `true` if the virtual node has a non-empty key.
270    ///
271    /// # Arguments
272    ///
273    /// - `&VirtualNode`: The node to check.
274    ///
275    /// # Returns
276    ///
277    /// - `bool`: Whether the node has a key.
278    fn node_has_key(node: &VirtualNode) -> bool {
279        match node {
280            VirtualNode::Element { key, .. } => key.is_some(),
281            _ => false,
282        }
283    }
284
285    /// Extracts the key from a virtual node.
286    ///
287    /// # Arguments
288    ///
289    /// - `&VirtualNode`: The node to extract the key from.
290    ///
291    /// # Returns
292    ///
293    /// - `Option<&str>`: The key string, if present.
294    fn get_node_key(node: &VirtualNode) -> Option<&str> {
295        match node {
296            VirtualNode::Element { key, .. } => key.as_deref(),
297            _ => None,
298        }
299    }
300
301    /// Keyed diffing algorithm that minimizes DOM operations.
302    ///
303    /// Builds a mapping from old keys to their DOM indices, then walks the
304    /// new children list. For each new child:
305    ///
306    /// - If its key existed in the old list, patches the existing DOM node.
307    /// - Otherwise, creates a new DOM node.
308    ///
309    /// After processing all new children, removes any old DOM nodes whose
310    /// keys are no longer present in the new list.
311    ///
312    /// # Arguments
313    ///
314    /// - `&Element`: The parent DOM element.
315    /// - `&[VirtualNode]`: The old children list.
316    /// - `&[VirtualNode]`: The new children list.
317    fn patch_children_keyed(
318        &mut self,
319        parent: &Element,
320        old_children: &[VirtualNode],
321        new_children: &[VirtualNode],
322    ) {
323        let mut old_key_map: HashMap<&str, usize> = HashMap::with_capacity(old_children.len());
324        for (index, old_child) in old_children.iter().enumerate() {
325            if let Some(key) = Self::get_node_key(old_child) {
326                old_key_map.insert(key, index);
327            }
328        }
329        let mut reused_indices: HashSet<usize> = HashSet::with_capacity(new_children.len());
330        let child_nodes: NodeList = parent.child_nodes();
331        let dom_child_count: u32 = child_nodes.length();
332        for (new_index, new_child) in new_children.iter().enumerate() {
333            let new_key: &str = Self::get_node_key(new_child).unwrap_or("");
334            if let Some(&old_index) = old_key_map.get(new_key) {
335                reused_indices.insert(old_index);
336                let old_child: &VirtualNode = &old_children[old_index];
337                let mapped_dom_index: u32 = old_index as u32;
338                if mapped_dom_index < dom_child_count
339                    && let Some(dom_node) = child_nodes.get(mapped_dom_index)
340                    && let Some(element) = dom_node.dyn_ref::<Element>()
341                {
342                    self.patch_node(old_child, new_child, element);
343                }
344                let current_dom_index: u32 = new_index as u32;
345                if mapped_dom_index != current_dom_index
346                    && current_dom_index < dom_child_count
347                    && let Some(dom_node) = child_nodes.get(mapped_dom_index)
348                {
349                    if let Some(reference_node) = child_nodes.get(current_dom_index) {
350                        let _ = parent.insert_before(&dom_node, Some(&reference_node));
351                    } else {
352                        let _ = parent.append_child(&dom_node);
353                    }
354                }
355            } else {
356                let new_dom: Node = self.create_dom_node(new_child);
357                if (new_index as u32) < dom_child_count
358                    && let Some(reference_node) = child_nodes.get(new_index as u32)
359                {
360                    let _ = parent.insert_before(&new_dom, Some(&reference_node));
361                } else {
362                    let _ = parent.append_child(&new_dom);
363                }
364            }
365        }
366        let mut indices_to_remove: Vec<usize> = Vec::new();
367        for (index, old_child) in old_children.iter().enumerate() {
368            if !reused_indices.contains(&index) {
369                indices_to_remove.push(index);
370            }
371            let _ = old_child;
372        }
373        indices_to_remove.sort_unstable_by(|a: &usize, b: &usize| b.cmp(a));
374        for old_index in indices_to_remove {
375            let mapped_dom_index: u32 = old_index as u32;
376            if mapped_dom_index < parent.child_nodes().length()
377                && let Some(dom_node) = parent.child_nodes().get(mapped_dom_index)
378            {
379                if let Some(element) = dom_node.dyn_ref::<Element>() {
380                    self.cleanup_dom_subtree(element);
381                }
382                let _ = parent.remove_child(&dom_node);
383            }
384        }
385    }
386
387    /// Positional diffing algorithm (original behavior).
388    ///
389    /// Patches children by index position. Used as a fallback when keys
390    /// are not available on all children.
391    ///
392    /// # Arguments
393    ///
394    /// - `&Element`: The parent DOM element.
395    /// - `&[VirtualNode]`: The old children list.
396    /// - `&[VirtualNode]`: The new children list.
397    fn patch_children_positional(
398        &mut self,
399        parent: &Element,
400        old_children: &[VirtualNode],
401        new_children: &[VirtualNode],
402    ) {
403        let old_len: usize = old_children.len();
404        let new_len: usize = new_children.len();
405        let common_len: usize = old_len.min(new_len);
406        for index in 0..common_len {
407            let old_child: &VirtualNode = &old_children[index];
408            let new_child: &VirtualNode = &new_children[index];
409            if let Some(dom_child) = Self::get_child_node(parent, index as u32) {
410                if let Some(element) = dom_child.dyn_ref::<Element>() {
411                    self.patch_node(old_child, new_child, element);
412                } else if let (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) =
413                    (old_child, new_child)
414                {
415                    if old_text != new_text {
416                        dom_child.set_text_content(Some(new_text.get_content()));
417                    }
418                } else {
419                    let new_dom: Node = self.create_dom_node(new_child);
420                    if let Some(parent_node) = dom_child.parent_node() {
421                        if let Some(child_element) = dom_child.dyn_ref::<Element>() {
422                            self.cleanup_dom_subtree(child_element);
423                        }
424                        let _ = parent_node.replace_child(&new_dom, &dom_child);
425                    }
426                }
427            }
428        }
429        if new_len > old_len {
430            for new_child in new_children.iter().skip(common_len) {
431                let new_dom: Node = self.create_dom_node(new_child);
432                parent.append_child(&new_dom).unwrap();
433            }
434        } else if old_len > new_len {
435            for _ in common_len..old_len {
436                if let Some(last_child) = parent.last_child()
437                    && let Some(element) = last_child.dyn_ref::<Element>()
438                {
439                    self.cleanup_dom_subtree(element);
440                }
441                if let Some(last_child) = parent.last_child() {
442                    parent.remove_child(&last_child).unwrap();
443                }
444            }
445        }
446    }
447
448    /// Creates a real DOM node from a virtual node.
449    ///
450    /// # Arguments
451    ///
452    /// - `&VirtualNode`: The virtual node to materialize.
453    ///
454    /// # Returns
455    ///
456    /// - `Node`: The created DOM node.
457    ///
458    /// # Panics
459    ///
460    /// Panics if `window()` or `document()` is unavailable.
461    fn create_dom_node(&mut self, node: &VirtualNode) -> Node {
462        let document: Document = window().unwrap().document().unwrap();
463        self.create_dom_node_with_document(node, &document)
464    }
465
466    /// Creates a real DOM node using a pre-acquired document reference.
467    ///
468    /// # Arguments
469    ///
470    /// - `&VirtualNode`: The virtual node to materialize.
471    /// - `&Document`: The document reference for creating DOM elements.
472    ///
473    /// # Returns
474    ///
475    /// - `Node`: The created DOM node.
476    fn create_dom_node_with_document(&mut self, node: &VirtualNode, document: &Document) -> Node {
477        match node {
478            VirtualNode::Element {
479                tag,
480                attributes,
481                children,
482                ..
483            } => {
484                let element: Element = match tag {
485                    Tag::Element(name) => document.create_element(name).unwrap(),
486                    Tag::Component(_) => {
487                        let unwrapped: VirtualNode = self.unwrap_component(node);
488                        return self.create_dom_node_with_document(&unwrapped, document);
489                    }
490                };
491                for attr in attributes {
492                    match attr.get_value() {
493                        AttributeValue::Text(value) => {
494                            if !value.is_empty() || is_boolean_property(attr.get_name()) {
495                                set_dom_attribute_or_property(&element, attr.get_name(), value);
496                            }
497                        }
498                        AttributeValue::Signal(signal) => {
499                            let initial_value: String = signal.get();
500                            if !initial_value.is_empty() || is_boolean_property(attr.get_name()) {
501                                set_dom_attribute_or_property(
502                                    &element,
503                                    attr.get_name(),
504                                    &initial_value,
505                                );
506                            }
507                            let signal_addr: usize = signal.get_inner_addr();
508                            let existing_addrs: String = element
509                                .get_attribute("data-euv-signal-addrs")
510                                .unwrap_or_default();
511                            let updated_addrs: String = if existing_addrs.is_empty() {
512                                signal_addr.to_string()
513                            } else {
514                                format!("{},{}", existing_addrs, signal_addr)
515                            };
516                            let _ = element.set_attribute("data-euv-signal-addrs", &updated_addrs);
517                            let attr_name: String = attr.get_name().clone();
518                            let element_clone: Element = element.clone();
519                            let signal_for_sub: Signal<String> = *signal;
520                            let sub_signal: Signal<String> = signal_for_sub;
521                            signal_for_sub.replace_subscribe(move || {
522                                if !is_node_connected(&element_clone) {
523                                    sub_signal.clear_listeners();
524                                    return;
525                                }
526                                let new_value: String = sub_signal.get();
527                                if new_value.is_empty() && !is_boolean_property(&attr_name) {
528                                    remove_dom_attribute_or_property(&element_clone, &attr_name);
529                                } else {
530                                    set_dom_attribute_or_property(
531                                        &element_clone,
532                                        &attr_name,
533                                        &new_value,
534                                    );
535                                }
536                            });
537                        }
538                        AttributeValue::Event(handler) => {
539                            self.attach_event_listener(&element, handler);
540                        }
541                        AttributeValue::Dynamic(_) => {}
542                        AttributeValue::Css(css_class) => {
543                            css_class.inject_style();
544                            set_dom_attribute_or_property(
545                                &element,
546                                attr.get_name(),
547                                css_class.get_name(),
548                            );
549                        }
550                    }
551                }
552                for child in children {
553                    let child_node: Node = self.create_dom_node_with_document(child, document);
554                    element.append_child(&child_node).unwrap();
555                }
556                element.into()
557            }
558            VirtualNode::Text(text_node) => {
559                let text: Text = document.create_text_node(text_node.get_content());
560                if let Some(signal) = text_node.try_get_signal() {
561                    let text_clone: Text = text.clone();
562                    let signal_clone: Signal<String> = *signal;
563                    let sub_signal: Signal<String> = signal_clone;
564                    signal_clone.replace_subscribe({
565                        move || {
566                            if !is_node_connected(&text_clone) {
567                                sub_signal.clear_listeners();
568                                return;
569                            }
570                            let new_value: String = sub_signal.get();
571                            text_clone.set_text_content(Some(&new_value));
572                        }
573                    });
574                }
575                text.into()
576            }
577            VirtualNode::Fragment(children) => {
578                let fragment: Element = document.create_element("slot").unwrap();
579                let _ = fragment.set_attribute("style", "display:contents");
580                for child in children {
581                    let child_node: Node = self.create_dom_node_with_document(child, document);
582                    fragment.append_child(&child_node).unwrap();
583                }
584                fragment.into()
585            }
586            VirtualNode::Dynamic(dynamic_node) => {
587                let placeholder: Element = document.create_element("div").unwrap();
588                let style: &str = "display: contents;";
589                let _ = placeholder.set_attribute("style", style);
590                let dynamic_id: usize = Self::assign_dynamic_id(&placeholder);
591                let initial_dom: Node =
592                    self.setup_dynamic_node(dynamic_node, dynamic_id, &placeholder, true);
593                placeholder.append_child(&initial_dom).unwrap();
594                placeholder.into()
595            }
596            VirtualNode::Empty => document.create_text_node("").into(),
597        }
598    }
599
600    /// Initializes a DynamicNode: runs the initial render, creates a sub-renderer,
601    /// and registers the re-render callback in the signal update registry.
602    ///
603    /// # Arguments
604    ///
605    /// - `&DynamicNode`: The dynamic node to set up.
606    /// - `usize`: The unique dynamic ID assigned to the placeholder.
607    /// - `&Element`: The placeholder DOM element.
608    /// - `bool`: Whether to skip rendering if the output is unchanged.
609    ///
610    /// # Returns
611    ///
612    /// - `Node`: The initial rendered DOM node.
613    fn setup_dynamic_node(
614        &mut self,
615        dynamic_node: &DynamicNode,
616        dynamic_id: usize,
617        placeholder: &Element,
618        skip_equal: bool,
619    ) -> Node {
620        let mut hook_context: HookContext = dynamic_node.get_hook_context_value();
621        hook_context.reset_hook_index();
622        let initial_vnode: VirtualNode =
623            with_hook_context(hook_context.clone(), || dynamic_node.render());
624        let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
625        let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
626        let render_fn: Rc<RefCell<RenderFnInner>> = dynamic_node.get_render_fn().clone();
627        let placeholder_clone: Element = placeholder.clone();
628        let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
629        renderer_for_sub.set_current_tree(Some(initial_unwrapped));
630        let renderer_rc: Rc<RefCell<Renderer>> = Rc::new(RefCell::new(renderer_for_sub));
631        let initial_arm: usize = hook_context.get_inner().borrow().get_arm_changed();
632        let last_arm: Rc<RefCell<usize>> = Rc::new(RefCell::new(initial_arm));
633        let callback: Box<dyn FnMut()> = Box::new(move || {
634            if placeholder_clone.parent_node().is_none() {
635                return;
636            }
637            hook_context.reset_hook_index();
638            let prev_arm: usize = *last_arm.borrow();
639            let new_vnode: VirtualNode = with_hook_context(hook_context.clone(), || {
640                let mut inner: RefMut<RenderFnInner> = render_fn.borrow_mut();
641                (inner.get_mut_render_fn())()
642            });
643            let current_arm: usize = hook_context.get_inner().borrow().get_arm_changed();
644            let arm_switched: bool = prev_arm != current_arm;
645            *last_arm.borrow_mut() = current_arm;
646            if skip_equal && !arm_switched {
647                let renderer_ref: Ref<Renderer> = renderer_rc.borrow();
648                if let Some(old_vnode) = renderer_ref.try_get_current_tree() {
649                    let new_unwrapped: VirtualNode = Renderer::unwrap_component_static(&new_vnode);
650                    if Renderer::visual_eq(old_vnode, &new_unwrapped) {
651                        return;
652                    }
653                }
654            }
655            let mut renderer_mut: RefMut<Renderer> = renderer_rc.borrow_mut();
656            if arm_switched {
657                renderer_mut.render_full_replace(new_vnode);
658            } else {
659                renderer_mut.render(new_vnode);
660            }
661        });
662        register_dynamic_listener(dynamic_id, callback);
663        initial_dom
664    }
665
666    /// Recursively unwraps component nodes into their rendered output.
667    ///
668    /// # Arguments
669    ///
670    /// - `&VirtualNode`: The virtual node to unwrap.
671    ///
672    /// # Returns
673    ///
674    /// - `VirtualNode`: The unwrapped virtual node with all components expanded.
675    fn unwrap_component(&self, node: &VirtualNode) -> VirtualNode {
676        match node {
677            VirtualNode::Element {
678                tag: Tag::Component(_),
679                children,
680                ..
681            } => {
682                if children.len() == 1 {
683                    self.unwrap_component(&children[0])
684                } else {
685                    VirtualNode::Fragment(children.clone())
686                }
687            }
688            VirtualNode::Element {
689                tag,
690                attributes,
691                children,
692                key,
693            } => {
694                if !children.iter().any(Self::subtree_has_component) {
695                    return node.clone();
696                }
697                let unwrapped_children: Vec<VirtualNode> = children
698                    .iter()
699                    .map(|child| self.unwrap_component(child))
700                    .collect();
701                VirtualNode::Element {
702                    tag: tag.clone(),
703                    attributes: attributes.clone(),
704                    children: unwrapped_children,
705                    key: key.clone(),
706                }
707            }
708            VirtualNode::Fragment(children) => {
709                if !children.iter().any(Self::subtree_has_component) {
710                    return node.clone();
711                }
712                let unwrapped_children: Vec<VirtualNode> = children
713                    .iter()
714                    .map(|child| self.unwrap_component(child))
715                    .collect();
716                VirtualNode::Fragment(unwrapped_children)
717            }
718            other => other.clone(),
719        }
720    }
721
722    /// Static version of `unwrap_component`.
723    ///
724    /// # Arguments
725    ///
726    /// - `&VirtualNode`: The virtual node to unwrap.
727    ///
728    /// # Returns
729    ///
730    /// - `VirtualNode`: The unwrapped virtual node with all components expanded.
731    fn unwrap_component_static(node: &VirtualNode) -> VirtualNode {
732        match node {
733            VirtualNode::Element {
734                tag: Tag::Component(_),
735                children,
736                ..
737            } => {
738                if children.len() == 1 {
739                    Self::unwrap_component_static(&children[0])
740                } else {
741                    VirtualNode::Fragment(children.clone())
742                }
743            }
744            VirtualNode::Element {
745                tag,
746                attributes,
747                children,
748                key,
749            } => {
750                if !children.iter().any(Self::subtree_has_component) {
751                    return node.clone();
752                }
753                let unwrapped_children: Vec<VirtualNode> =
754                    children.iter().map(Self::unwrap_component_static).collect();
755                VirtualNode::Element {
756                    tag: tag.clone(),
757                    attributes: attributes.clone(),
758                    children: unwrapped_children,
759                    key: key.clone(),
760                }
761            }
762            VirtualNode::Fragment(children) => {
763                if !children.iter().any(Self::subtree_has_component) {
764                    return node.clone();
765                }
766                let unwrapped_children: Vec<VirtualNode> =
767                    children.iter().map(Self::unwrap_component_static).collect();
768                VirtualNode::Fragment(unwrapped_children)
769            }
770            other => other.clone(),
771        }
772    }
773
774    /// Returns `true` if the given subtree contains any `Tag::Component` nodes
775    /// that need unwrapping.
776    ///
777    /// # Arguments
778    ///
779    /// - `&VirtualNode`: The virtual node to check.
780    ///
781    /// # Returns
782    ///
783    /// - `bool`: `true` if the subtree contains a component node.
784    fn subtree_has_component(node: &VirtualNode) -> bool {
785        match node {
786            VirtualNode::Element {
787                tag: Tag::Component(_),
788                ..
789            } => true,
790            VirtualNode::Element { children, .. } => {
791                children.iter().any(Self::subtree_has_component)
792            }
793            VirtualNode::Fragment(children) => children.iter().any(Self::subtree_has_component),
794            _ => false,
795        }
796    }
797
798    /// Performs a visual equality comparison between two virtual node trees.
799    ///
800    /// Unlike `PartialEq`, this method recursively unwraps `VirtualNode::Dynamic`
801    /// nodes by rendering their inner content and comparing the visual output.
802    /// This is used by the `skip_equal` optimization in `setup_dynamic_node`
803    /// to avoid unnecessary DOM patches when the rendered output is unchanged.
804    ///
805    /// # Arguments
806    ///
807    /// - `&VirtualNode`: The old virtual node.
808    /// - `&VirtualNode`: The new virtual node.
809    ///
810    /// # Returns
811    ///
812    /// - `bool`: `true` if the two nodes produce the same visual output.
813    fn visual_eq(old_node: &VirtualNode, new_node: &VirtualNode) -> bool {
814        match (old_node, new_node) {
815            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
816            (
817                VirtualNode::Element {
818                    tag: old_tag,
819                    attributes: old_attrs,
820                    children: old_children,
821                    ..
822                },
823                VirtualNode::Element {
824                    tag: new_tag,
825                    attributes: new_attrs,
826                    children: new_children,
827                    ..
828                },
829            ) => {
830                old_tag == new_tag
831                    && old_attrs.len() == new_attrs.len()
832                    && old_attrs
833                        .iter()
834                        .zip(new_attrs.iter())
835                        .all(|(old_attr, new_attr)| old_attr == new_attr)
836                    && old_children.len() == new_children.len()
837                    && old_children
838                        .iter()
839                        .zip(new_children.iter())
840                        .all(|(old_child, new_child)| Self::visual_eq(old_child, new_child))
841            }
842            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
843                old_children.len() == new_children.len()
844                    && old_children
845                        .iter()
846                        .zip(new_children.iter())
847                        .all(|(old_child, new_child)| Self::visual_eq(old_child, new_child))
848            }
849            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => true,
850            (VirtualNode::Empty, VirtualNode::Empty) => true,
851            _ => false,
852        }
853    }
854
855    /// Assigns a new `data-euv-dynamic-id` to a newly created DynamicNode placeholder.
856    ///
857    /// # Arguments
858    ///
859    /// - `&Element`: The placeholder DOM element.
860    ///
861    /// # Returns
862    ///
863    /// - `usize`: The assigned dynamic ID.
864    fn assign_dynamic_id(placeholder: &Element) -> usize {
865        let dynamic_id: usize = NEXT_EUV_DYNAMIC_ID.fetch_add(1, Ordering::Relaxed);
866        let _ = placeholder.set_attribute("data-euv-dynamic-id", &dynamic_id.to_string());
867        dynamic_id
868    }
869
870    /// Recursively cleans up framework resources associated with a DOM subtree.
871    ///
872    /// Removes event handlers, dynamic node listeners, and signal listeners
873    /// for the given element and all of its descendants.
874    ///
875    /// # Arguments
876    ///
877    /// - `&Element`: The DOM element to clean up.
878    fn cleanup_dom_subtree(&self, element: &Element) {
879        if let Some(euv_id_str) = element.get_attribute(DATA_EUV_ID)
880            && let Ok(euv_id) = euv_id_str.parse::<usize>()
881        {
882            cleanup_element_handlers(euv_id);
883        }
884        if let Some(dynamic_id_str) = element.get_attribute("data-euv-dynamic-id")
885            && let Ok(dynamic_id) = dynamic_id_str.parse::<usize>()
886        {
887            cleanup_dynamic_node(dynamic_id);
888        }
889        if let Some(signal_addrs_str) = element.get_attribute("data-euv-signal-addrs") {
890            for addr_str in signal_addrs_str.split(',') {
891                if let Ok(addr) = addr_str.parse::<usize>() {
892                    clear_signal_listeners_by_addr(addr);
893                }
894            }
895        }
896        let child_nodes: NodeList = element.child_nodes();
897        let length: u32 = child_nodes.length();
898        for i in 0..length {
899            if let Some(child) = child_nodes.get(i) {
900                if let Some(child_element) = child.dyn_ref::<Element>() {
901                    self.cleanup_dom_subtree(child_element);
902                } else if let Some(text) = child.dyn_ref::<Text>() {
903                    cleanup_text_signal_listeners(text);
904                }
905            }
906        }
907    }
908
909    /// Registers an event handler for a DOM element using global event delegation.
910    ///
911    /// # Arguments
912    ///
913    /// - `&Element`: The DOM element to attach the handler to.
914    /// - `&NativeEventHandler`: The event handler to register.
915    fn attach_event_listener(&self, element: &Element, handler: &NativeEventHandler) {
916        let euv_id: usize = match element.get_attribute(DATA_EUV_ID) {
917            Some(id_str) => id_str.parse::<usize>().unwrap_or_else(|_| {
918                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
919                let _ = element.set_attribute(DATA_EUV_ID, &new_id.to_string());
920                new_id
921            }),
922            None => {
923                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
924                let _ = element.set_attribute(DATA_EUV_ID, &new_id.to_string());
925                new_id
926            }
927        };
928        let event_name: Cow<'static, str> = handler.get_event_name().clone();
929        if !NativeEventName::DELEGATABLE_EVENT_NAMES.contains(&&*event_name) {
930            ensure_delegated_listener(event_name.clone());
931        }
932        let key: (usize, Cow<'static, str>) = (euv_id, event_name);
933        let registry_ref: &mut HashMap<(usize, Cow<'static, str>), HandlerEntry> =
934            ensure_handler_registry_mut();
935        if let Some(existing_entry) = registry_ref.get(&key) {
936            let mut slot: RefMut<HandlerSlot> = existing_entry.borrow_mut();
937            slot.set_handler(Some(handler.clone()));
938        } else {
939            let handler_slot: HandlerEntry =
940                Rc::new(RefCell::new(HandlerSlot::new(Some(handler.clone()))));
941            registry_ref.insert(key, handler_slot);
942        }
943    }
944}