Skip to main content

euv_core/renderer/
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    /// # Arguments
8    ///
9    /// - `VirtualNode` - The virtual DOM tree to render.
10    pub fn render(&mut self, vnode: VirtualNode) {
11        let new_unwrapped: VirtualNode = self.unwrap_component(&vnode);
12        if let Some(old_vnode) = self.try_get_current_tree() {
13            let old_unwrapped: VirtualNode = self.unwrap_component(old_vnode);
14            self.patch_root(&old_unwrapped, &new_unwrapped);
15        } else {
16            let dom_node: Node = self.create_dom_node(&new_unwrapped);
17            while let Some(child) = self.get_root().first_child() {
18                self.get_root().remove_child(&child).unwrap();
19            }
20            self.get_root().append_child(&dom_node).unwrap();
21        }
22        self.set_current_tree(Some(vnode));
23    }
24
25    /// Patches the root DOM tree by replacing the single child of `self.root`.
26    fn patch_root(&mut self, old_node: &VirtualNode, new_node: &VirtualNode) {
27        let dom_child: Option<Node> = self.get_root().first_child();
28        let is_element: bool = if let Some(ref dom_child) = dom_child {
29            dom_child.dyn_ref::<Element>().is_some()
30        } else {
31            false
32        };
33        if is_element {
34            let element: Element = dom_child.unwrap().dyn_into::<Element>().unwrap();
35            self.patch_node(old_node, new_node, &element);
36        } else if let Some(dom_child) = dom_child {
37            let new_dom: Node = self.create_dom_node(new_node);
38            self.get_root().replace_child(&new_dom, &dom_child).unwrap();
39        } else {
40            let new_dom: Node = self.create_dom_node(new_node);
41            self.get_root().append_child(&new_dom).unwrap();
42        }
43    }
44
45    /// Patches an existing DOM node to match the new virtual node.
46    fn patch_node(
47        &mut self,
48        old_node: &VirtualNode,
49        new_node: &VirtualNode,
50        dom_element: &Element,
51    ) {
52        match (old_node, new_node) {
53            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => {
54                if old_text.get_content() != new_text.get_content() {
55                    dom_element.set_text_content(Some(new_text.get_content()));
56                }
57            }
58            (
59                VirtualNode::Element {
60                    tag: old_tag,
61                    attributes: old_attrs,
62                    children: old_children,
63                    key: _old_key,
64                },
65                VirtualNode::Element {
66                    tag: new_tag,
67                    attributes: new_attrs,
68                    children: new_children,
69                    key: _new_key,
70                },
71            ) => {
72                if !Self::tags_equal(old_tag, new_tag) {
73                    let new_dom: Node = self.create_dom_node(new_node);
74                    if let Some(parent) = dom_element.parent_node() {
75                        parent.replace_child(&new_dom, dom_element).unwrap();
76                    }
77                    return;
78                }
79                self.patch_attributes(dom_element, old_attrs, new_attrs);
80                self.patch_children(dom_element, old_children, new_children);
81            }
82            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
83                self.patch_children(dom_element, old_children, new_children);
84            }
85            (VirtualNode::Dynamic(new_dynamic), VirtualNode::Dynamic(_old_dynamic)) => {
86                while let Some(child) = dom_element.first_child() {
87                    dom_element.remove_child(&child).unwrap();
88                }
89                let dynamic_id: usize = Self::ensure_dynamic_id(dom_element);
90                let mut hook_context: HookContext = new_dynamic.get_hook_context();
91                hook_context.reset_hook_index();
92                let initial_vnode: VirtualNode = with_hook_context(hook_context, || {
93                    let mut borrowed: RefMut<dyn FnMut() -> VirtualNode> =
94                        new_dynamic.get_render_fn().borrow_mut();
95                    borrowed()
96                });
97                let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
98                let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
99                dom_element.append_child(&initial_dom).unwrap();
100                let render_fn_clone: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
101                    new_dynamic.get_render_fn().clone();
102                let placeholder_clone: Element = dom_element.clone();
103                let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
104                renderer_for_sub.set_current_tree(Some(initial_unwrapped));
105                let renderer_ref: Rc<RefCell<Renderer>> = Rc::new(RefCell::new(renderer_for_sub));
106                let renderer_ref_for_sub: Rc<RefCell<Renderer>> = Rc::clone(&renderer_ref);
107                let render_fn_for_sub: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
108                    Rc::clone(&render_fn_clone);
109                let re_render_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
110                    if placeholder_clone.parent_node().is_none() {
111                        return;
112                    }
113                    hook_context.reset_hook_index();
114                    let new_vnode: VirtualNode =
115                        with_hook_context(hook_context, || render_fn_for_sub.borrow_mut()());
116                    renderer_ref_for_sub.borrow_mut().render(new_vnode);
117                }));
118                register_dynamic_listener(dynamic_id, re_render_closure);
119            }
120            _ => {
121                let new_dom: Node = self.create_dom_node(new_node);
122                if let Some(parent) = dom_element.parent_node() {
123                    parent.replace_child(&new_dom, dom_element).unwrap();
124                }
125            }
126        }
127    }
128
129    /// Patches attributes of an element, adding, removing, or updating as needed.
130    fn patch_attributes(
131        &mut self,
132        element: &Element,
133        old_attrs: &[AttributeEntry],
134        new_attrs: &[AttributeEntry],
135    ) {
136        let mut old_map: HashMap<&str, &AttributeValue> = HashMap::new();
137        for attr in old_attrs {
138            old_map.insert(attr.get_name(), attr.get_value());
139        }
140        let mut new_map: HashMap<&str, &AttributeValue> = HashMap::new();
141        for attr in new_attrs {
142            new_map.insert(attr.get_name(), attr.get_value());
143        }
144        for name in old_map.keys() {
145            if !new_map.contains_key(*name) {
146                Self::remove_dom_attribute_or_property(element, name);
147            }
148        }
149        for attr in new_attrs {
150            let should_set: bool = match old_map.get(attr.get_name().as_str()) {
151                Some(old_value) => !Self::attribute_values_equal(old_value, attr.get_value()),
152                None => true,
153            };
154            if should_set {
155                match attr.get_value() {
156                    AttributeValue::Text(value) => {
157                        if value.is_empty() {
158                            Self::remove_dom_attribute_or_property(element, attr.get_name());
159                        } else {
160                            Self::set_dom_attribute_or_property(element, attr.get_name(), value);
161                        }
162                    }
163                    AttributeValue::Signal(signal) => {
164                        let value: String = signal.get();
165                        if value.is_empty() && !Self::is_boolean_property(attr.get_name()) {
166                            Self::remove_dom_attribute_or_property(element, attr.get_name());
167                        } else {
168                            Self::set_dom_attribute_or_property(element, attr.get_name(), &value);
169                        }
170                    }
171                    AttributeValue::Event(handler) => {
172                        self.attach_event_listener(element, handler);
173                    }
174                    AttributeValue::Dynamic(_) => {}
175                    AttributeValue::Css(css_class) => {
176                        css_class.inject_style();
177                        Self::set_dom_attribute_or_property(
178                            element,
179                            attr.get_name(),
180                            css_class.get_name(),
181                        );
182                    }
183                }
184            }
185        }
186    }
187
188    /// Returns true if the given attribute name is a boolean attribute that
189    /// requires DOM property-based manipulation instead of HTML attribute strings.
190    fn is_boolean_property(name: &str) -> bool {
191        matches!(name, "checked" | "disabled" | "selected" | "readonly")
192    }
193
194    /// Removes or clears a DOM attribute/property, depending on the attribute name.
195    ///
196    /// For `value`, sets the DOM property to an empty string rather than calling
197    /// `remove_attribute`, because `remove_attribute("value")` only removes the
198    /// HTML attribute and does not clear the displayed value of input elements.
199    /// For boolean properties (`checked`, `disabled`, `selected`, `readonly`),
200    /// sets the DOM property to `false` rather than calling `remove_attribute`,
201    /// because `remove_attribute` on a previously-set attribute may not correctly
202    /// reset the property in all browsers.
203    fn remove_dom_attribute_or_property(element: &Element, name: &str) {
204        if name == "value" {
205            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
206                input.set_value("");
207                return;
208            }
209            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
210                textarea.set_value("");
211                return;
212            }
213            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
214                select.set_value("");
215                return;
216            }
217        }
218        if name == "checked"
219            && let Some(input) = element.dyn_ref::<HtmlInputElement>()
220        {
221            input.set_checked(false);
222            return;
223        }
224        if name == "disabled" {
225            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
226                input.set_disabled(false);
227                return;
228            }
229            if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
230                button.set_disabled(false);
231                return;
232            }
233            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
234                select.set_disabled(false);
235                return;
236            }
237            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
238                textarea.set_disabled(false);
239                return;
240            }
241        }
242        if name == "selected"
243            && let Some(option) = element.dyn_ref::<HtmlOptionElement>()
244        {
245            option.set_selected(false);
246            return;
247        }
248        if name == "readonly" {
249            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
250                input.set_read_only(false);
251                return;
252            }
253            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
254                textarea.set_read_only(false);
255                return;
256            }
257        }
258        let _ = element.remove_attribute(name);
259    }
260
261    /// Sets a DOM attribute or property, depending on the attribute name.
262    ///
263    /// For `value`, uses the DOM property to ensure input elements update correctly.
264    /// For boolean attributes (`checked`, `disabled`, `selected`, `readonly`),
265    /// uses the DOM property so that the browser honors the value correctly
266    /// (HTML attributes are present-or-absent, not true/false strings).
267    /// For all other attributes, uses `set_attribute`.
268    fn set_dom_attribute_or_property(element: &Element, name: &str, value: &str) {
269        if name == "value" {
270            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
271                input.set_value(value);
272                return;
273            }
274            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
275                textarea.set_value(value);
276                return;
277            }
278            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
279                select.set_value(value);
280                return;
281            }
282        }
283        if name == "checked"
284            && let Some(input) = element.dyn_ref::<HtmlInputElement>()
285        {
286            input.set_checked(value == "true");
287            return;
288        }
289        if name == "disabled" {
290            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
291                input.set_disabled(value == "true");
292                return;
293            }
294            if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
295                button.set_disabled(value == "true");
296                return;
297            }
298            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
299                select.set_disabled(value == "true");
300                return;
301            }
302            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
303                textarea.set_disabled(value == "true");
304                return;
305            }
306        }
307        if name == "selected"
308            && let Some(option) = element.dyn_ref::<HtmlOptionElement>()
309        {
310            option.set_selected(value == "true");
311            return;
312        }
313        if name == "readonly" {
314            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
315                input.set_read_only(value == "true");
316                return;
317            }
318            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
319                textarea.set_read_only(value == "true");
320                return;
321            }
322        }
323        let _ = element.set_attribute(name, value);
324    }
325
326    /// Compares two tags for equality.
327    fn tags_equal(a: &Tag, b: &Tag) -> bool {
328        match (a, b) {
329            (Tag::Element(a_name), Tag::Element(b_name)) => a_name == b_name,
330            (Tag::Component(a_name), Tag::Component(b_name)) => a_name == b_name,
331            _ => false,
332        }
333    }
334
335    /// Compares two attribute values for equality.
336    ///
337    /// Event attributes are always considered unequal to ensure that
338    /// event listeners are re-bound on every patch. This is critical
339    /// because the underlying closure may capture different signal
340    /// references after a route change, even though the event name
341    /// remains the same.
342    fn attribute_values_equal(a: &AttributeValue, b: &AttributeValue) -> bool {
343        match (a, b) {
344            (AttributeValue::Text(a_val), AttributeValue::Text(b_val)) => a_val == b_val,
345            (AttributeValue::Signal(_a_sig), AttributeValue::Signal(_b_sig)) => false,
346            (AttributeValue::Event(_a_ev), AttributeValue::Event(_b_ev)) => false,
347            (AttributeValue::Dynamic(a_dyn), AttributeValue::Dynamic(b_dyn)) => a_dyn == b_dyn,
348            (AttributeValue::Css(a_css), AttributeValue::Css(b_css)) => {
349                a_css.get_name() == b_css.get_name()
350            }
351            _ => false,
352        }
353    }
354
355    /// Gets a child node at the given index by traversing child nodes.
356    fn get_child_node(parent: &Element, index: u32) -> Option<Node> {
357        let mut current: Option<Node> = parent.first_child();
358        let mut current_index: u32 = 0;
359        while let Some(node) = current {
360            if current_index == index {
361                return Some(node);
362            }
363            current = node.next_sibling();
364            current_index += 1;
365        }
366        None
367    }
368
369    /// Patches children of an element using a positional diff algorithm.
370    ///
371    /// For each position, patches the old child into the new child in-place.
372    /// Text nodes are updated by modifying their text content rather than
373    /// being replaced, which preserves any reactive signal subscriptions
374    /// already wired to the existing DOM text node.
375    /// Appends any extra new children, and removes any trailing old children.
376    fn patch_children(
377        &mut self,
378        parent: &Element,
379        old_children: &[VirtualNode],
380        new_children: &[VirtualNode],
381    ) {
382        let old_len: usize = old_children.len();
383        let new_len: usize = new_children.len();
384        let common_len: usize = old_len.min(new_len);
385        for index in 0..common_len {
386            let old_child: &VirtualNode = &old_children[index];
387            let new_child: &VirtualNode = &new_children[index];
388            if let Some(dom_child) = Self::get_child_node(parent, index as u32) {
389                if let Some(element) = dom_child.dyn_ref::<Element>() {
390                    self.patch_node(old_child, new_child, element);
391                } else if let (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) =
392                    (old_child, new_child)
393                {
394                    if old_text.get_content() != new_text.get_content() {
395                        dom_child.set_text_content(Some(new_text.get_content()));
396                    }
397                } else {
398                    let new_dom: Node = self.create_dom_node(new_child);
399                    if let Some(parent_node) = dom_child.parent_node() {
400                        let _ = parent_node.replace_child(&new_dom, &dom_child);
401                    }
402                }
403            }
404        }
405        if new_len > old_len {
406            for new_child in new_children.iter().skip(common_len) {
407                let new_dom: Node = self.create_dom_node(new_child);
408                parent.append_child(&new_dom).unwrap();
409            }
410        } else if old_len > new_len {
411            for _ in common_len..old_len {
412                if let Some(last_child) = parent.last_child() {
413                    parent.remove_child(&last_child).unwrap();
414                }
415            }
416        }
417    }
418
419    /// Creates a real DOM node from a virtual node.
420    fn create_dom_node(&mut self, node: &VirtualNode) -> Node {
421        match node {
422            VirtualNode::Element {
423                tag,
424                attributes,
425                children,
426                ..
427            } => {
428                let document: Document = window().unwrap().document().unwrap();
429                let element: Element = match tag {
430                    Tag::Element(name) => document.create_element(name).unwrap(),
431                    Tag::Component(_) => {
432                        let unwrapped: VirtualNode = self.unwrap_component(node);
433                        return self.create_dom_node(&unwrapped);
434                    }
435                };
436                for attr in attributes {
437                    match attr.get_value() {
438                        AttributeValue::Text(value) => {
439                            if !value.is_empty() || Self::is_boolean_property(attr.get_name()) {
440                                Self::set_dom_attribute_or_property(
441                                    &element,
442                                    attr.get_name(),
443                                    value,
444                                );
445                            }
446                        }
447                        AttributeValue::Signal(signal) => {
448                            let initial_value: String = signal.get();
449                            if !initial_value.is_empty()
450                                || Self::is_boolean_property(attr.get_name())
451                            {
452                                Self::set_dom_attribute_or_property(
453                                    &element,
454                                    attr.get_name(),
455                                    &initial_value,
456                                );
457                            }
458                            let attr_name: String = attr.get_name().clone();
459                            let element_clone: Element = element.clone();
460                            let signal_for_sub: Signal<String> = *signal;
461                            let signal_inner: Signal<String> = signal_for_sub;
462                            signal_for_sub.subscribe(move || {
463                                let new_value: String = signal_inner.get();
464                                if new_value.is_empty() && !Self::is_boolean_property(&attr_name) {
465                                    Self::remove_dom_attribute_or_property(
466                                        &element_clone,
467                                        &attr_name,
468                                    );
469                                } else {
470                                    Self::set_dom_attribute_or_property(
471                                        &element_clone,
472                                        &attr_name,
473                                        &new_value,
474                                    );
475                                }
476                            });
477                        }
478                        AttributeValue::Event(handler) => {
479                            self.attach_event_listener(&element, handler);
480                        }
481                        AttributeValue::Dynamic(_) => {}
482                        AttributeValue::Css(css_class) => {
483                            css_class.inject_style();
484                            Self::set_dom_attribute_or_property(
485                                &element,
486                                attr.get_name(),
487                                css_class.get_name(),
488                            );
489                        }
490                    }
491                }
492                for child in children {
493                    let child_node: Node = self.create_dom_node(child);
494                    element.append_child(&child_node).unwrap();
495                }
496                element.into()
497            }
498            VirtualNode::Text(text_node) => {
499                let document: Document = window().unwrap().document().unwrap();
500                let text: Text = document.create_text_node(text_node.get_content());
501                if let Some(signal) = text_node.try_get_signal() {
502                    let text_clone: Text = text.clone();
503                    let signal_clone: Signal<String> = *signal;
504                    signal_clone.subscribe({
505                        let signal_inner: Signal<String> = signal_clone;
506                        move || {
507                            let new_value: String = signal_inner.get();
508                            text_clone.set_text_content(Some(&new_value));
509                        }
510                    });
511                }
512                text.into()
513            }
514            VirtualNode::Fragment(children) => {
515                let document: Document = window().unwrap().document().unwrap();
516                let fragment: Element = document.create_element("div").unwrap();
517                for child in children {
518                    let child_node: Node = self.create_dom_node(child);
519                    fragment.append_child(&child_node).unwrap();
520                }
521                fragment.into()
522            }
523            VirtualNode::Dynamic(dynamic_node) => {
524                let document: Document = window().unwrap().document().unwrap();
525                let placeholder: Element = document.create_element("div").unwrap();
526                let style: &str = "display: contents;";
527                let _ = placeholder.set_attribute("style", style);
528                let dynamic_id: usize = Self::assign_dynamic_id(&placeholder);
529                let mut hook_context: HookContext = dynamic_node.get_hook_context();
530                hook_context.reset_hook_index();
531                let initial_vnode: VirtualNode = with_hook_context(hook_context, || {
532                    let mut borrowed: RefMut<dyn FnMut() -> VirtualNode> =
533                        dynamic_node.get_render_fn().borrow_mut();
534                    borrowed()
535                });
536                let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
537                let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
538                placeholder.append_child(&initial_dom).unwrap();
539                let render_fn_clone: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
540                    dynamic_node.get_render_fn().clone();
541                let placeholder_clone: Element = placeholder.clone();
542                let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
543                renderer_for_sub.set_current_tree(Some(initial_unwrapped));
544                let renderer_ref: Rc<RefCell<Renderer>> = Rc::new(RefCell::new(renderer_for_sub));
545                let renderer_ref_for_sub: Rc<RefCell<Renderer>> = Rc::clone(&renderer_ref);
546                let render_fn_for_sub: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
547                    Rc::clone(&render_fn_clone);
548                let closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
549                    if placeholder_clone.parent_node().is_none() {
550                        return;
551                    }
552                    hook_context.reset_hook_index();
553                    let new_vnode: VirtualNode =
554                        with_hook_context(hook_context, || render_fn_for_sub.borrow_mut()());
555                    {
556                        let renderer: Ref<'_, Renderer> = renderer_ref_for_sub.borrow();
557                        if let Some(old_vnode) = renderer.try_get_current_tree() {
558                            let new_unwrapped: VirtualNode = renderer.unwrap_component(&new_vnode);
559                            if old_vnode == &new_unwrapped {
560                                return;
561                            }
562                        }
563                    }
564                    renderer_ref_for_sub.borrow_mut().render(new_vnode);
565                }));
566                register_dynamic_listener(dynamic_id, closure);
567                placeholder.into()
568            }
569            VirtualNode::Empty => {
570                let document: Document = window().unwrap().document().unwrap();
571                document.create_text_node("").into()
572            }
573        }
574    }
575
576    /// Recursively unwraps component nodes into their rendered output.
577    fn unwrap_component(&self, node: &VirtualNode) -> VirtualNode {
578        match node {
579            VirtualNode::Element {
580                tag: Tag::Component(_),
581                children,
582                ..
583            } => {
584                if children.len() == 1 {
585                    self.unwrap_component(&children[0])
586                } else {
587                    VirtualNode::Fragment(children.clone())
588                }
589            }
590            VirtualNode::Element {
591                tag,
592                attributes,
593                children,
594                key,
595            } => {
596                let unwrapped_children: Vec<VirtualNode> = children
597                    .iter()
598                    .map(|child| self.unwrap_component(child))
599                    .collect();
600                VirtualNode::Element {
601                    tag: tag.clone(),
602                    attributes: attributes.clone(),
603                    children: unwrapped_children,
604                    key: key.clone(),
605                }
606            }
607            VirtualNode::Fragment(children) => {
608                let unwrapped_children: Vec<VirtualNode> = children
609                    .iter()
610                    .map(|child| self.unwrap_component(child))
611                    .collect();
612                VirtualNode::Fragment(unwrapped_children)
613            }
614            other => other.clone(),
615        }
616    }
617
618    /// Attaches an event listener to a DOM element.
619    ///
620    /// Uses a global auto-incrementing ID stored as `data-euv-id` on the element
621    /// to uniquely identify it in the handler registry. This avoids the bug where
622    /// `element.as_ref() as *const JsValue as usize` returns the address of the
623    /// Rust-side temporary `JsValue` wrapper rather than a stable JS object identity,
624    /// causing different DOM elements to collide on the same key.
625    ///
626    /// On first attach, allocates a new ID, creates a wrapper
627    /// `Rc<RefCell<Option<NativeEventHandler>>>`, and registers a DOM
628    /// `addEventListener` closure that reads from it. On subsequent patches
629    /// for the same element+event, only updates the wrapper content.
630    /// Assigns a new `data-euv-dynamic-id` to a newly created DynamicNode placeholder.
631    ///
632    /// # Arguments
633    ///
634    /// - `&Element` - The placeholder element to assign the ID to.
635    ///
636    /// # Returns
637    ///
638    /// - `usize` - The assigned dynamic ID.
639    fn assign_dynamic_id(placeholder: &Element) -> usize {
640        let dynamic_id: usize = NEXT_EUV_DYNAMIC_ID.fetch_add(1, Ordering::Relaxed);
641        let _ = placeholder.set_attribute("data-euv-dynamic-id", &dynamic_id.to_string());
642        dynamic_id
643    }
644
645    /// Reads or assigns the `data-euv-dynamic-id` of an existing DynamicNode placeholder.
646    ///
647    /// During `patch_node`, the placeholder element already exists in the DOM
648    /// and may already carry a `data-euv-dynamic-id`. If it does, the existing
649    /// ID is returned so the old listener can be looked up and removed. If not,
650    /// a new ID is assigned.
651    ///
652    /// # Arguments
653    ///
654    /// - `&Element` - The existing placeholder element.
655    ///
656    /// # Returns
657    ///
658    /// - `usize` - The existing or newly assigned dynamic ID.
659    fn ensure_dynamic_id(dom_element: &Element) -> usize {
660        match dom_element.get_attribute("data-euv-dynamic-id") {
661            Some(id_str) => id_str.parse::<usize>().unwrap_or_else(|_| {
662                let new_id: usize = NEXT_EUV_DYNAMIC_ID.fetch_add(1, Ordering::Relaxed);
663                let _ = dom_element.set_attribute("data-euv-dynamic-id", &new_id.to_string());
664                new_id
665            }),
666            None => {
667                let new_id: usize = NEXT_EUV_DYNAMIC_ID.fetch_add(1, Ordering::Relaxed);
668                let _ = dom_element.set_attribute("data-euv-dynamic-id", &new_id.to_string());
669                new_id
670            }
671        }
672    }
673
674    fn attach_event_listener(&self, element: &Element, handler: &NativeEventHandler) {
675        let euv_id: usize = match element.get_attribute("data-euv-id") {
676            Some(id_str) => id_str.parse::<usize>().unwrap_or_else(|_| {
677                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
678                let _ = element.set_attribute("data-euv-id", &new_id.to_string());
679                new_id
680            }),
681            None => {
682                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
683                let _ = element.set_attribute("data-euv-id", &new_id.to_string());
684                new_id
685            }
686        };
687        let event_name: String = handler.get_event_name().clone();
688        let key: (usize, String) = (euv_id, event_name.clone());
689        let registry: &mut HashMap<(usize, String), Rc<RefCell<Option<NativeEventHandler>>>> =
690            get_handler_registry();
691        if let Some(existing_wrapper) = registry.get(&key) {
692            let mut wrapper: RefMut<Option<NativeEventHandler>> = existing_wrapper.borrow_mut();
693            *wrapper = Some(handler.clone());
694        } else {
695            let handler_wrapper: Rc<RefCell<Option<NativeEventHandler>>> =
696                Rc::new(RefCell::new(Some(handler.clone())));
697            let wrapper_for_closure: Rc<RefCell<Option<NativeEventHandler>>> =
698                Rc::clone(&handler_wrapper);
699            let event_name_for_closure: String = event_name.clone();
700            let closure: Closure<dyn FnMut(Event)> =
701                Closure::wrap(Box::new(move |event: Event| {
702                    if let Some(active_handler) = wrapper_for_closure.borrow_mut().as_ref() {
703                        let euv_event: NativeEvent =
704                            convert_web_event(&event, &event_name_for_closure);
705                        active_handler.handle(euv_event);
706                    }
707                    event.stop_propagation();
708                }));
709            element
710                .add_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref())
711                .unwrap();
712            closure.forget();
713            registry.insert(key, handler_wrapper);
714        }
715    }
716}