Skip to main content

rue_core/node/
mount.rs

1use wasm_bindgen::prelude::*;
2use wasm_bindgen::JsCast;
3use web_sys::*;
4
5use super::VNode;
6
7/// SVG namespace URI
8const SVG_NS: &str = "http://www.w3.org/2000/svg";
9
10/// Mount a VNode tree into the DOM by creating real DOM nodes.
11///
12/// * `vnode` — the virtual node tree to mount
13/// * `parent` — the DOM node to attach children to
14/// * `anchor` — optional child node to insert before (if None, append)
15///
16/// Returns the first DOM node created (the "root" of the rendered subtree).
17/// For elements this is the element itself; for text nodes it's the text node;
18/// for fragments it's the first child; for empty it's None.
19pub fn mount_to_dom(
20    vnode: &VNode,
21    parent: &web_sys::Node,
22    anchor: Option<&web_sys::Node>,
23) -> Option<web_sys::Node> {
24    mount_to_dom_inner(vnode, parent, anchor, None)
25}
26
27/// Internal helper that carries namespace context for SVG support.
28fn mount_to_dom_inner(
29    vnode: &VNode,
30    parent: &web_sys::Node,
31    anchor: Option<&web_sys::Node>,
32    namespace: Option<&str>,
33) -> Option<web_sys::Node> {
34    match vnode {
35        VNode::Element(el) => {
36            let document = web_sys::window()?.document()?;
37
38            // Determine the namespace for this element.
39            // <svg> always uses the SVG namespace. Other elements inherit
40            // from their parent (SVG children stay in SVG namespace).
41            let ns = if el.tag == "svg" {
42                Some(SVG_NS)
43            } else {
44                namespace
45            };
46
47            // Create the element with the correct namespace
48            let elem: web_sys::Element = match ns {
49                Some(ns_str) => document.create_element_ns(Some(ns_str), &el.tag).ok()?,
50                None => document.create_element(&el.tag).ok()?,
51            };
52
53            // Set attributes
54            for (name, value) in &el.attrs {
55                let _ = elem.set_attribute(name, value);
56            }
57
58            // Set event listeners using Closure — store for cleanup
59            for (event_name, handler) in &el.events {
60                let handler_cls = {
61                    let handler = handler.clone();
62                    Closure::wrap(Box::new(move |event: web_sys::Event| {
63                        handler.call(event);
64                    }) as Box<dyn FnMut(web_sys::Event)>)
65                };
66
67                let js_func: &js_sys::Function = handler_cls.as_ref().unchecked_ref();
68                let _ = elem.add_event_listener_with_callback(event_name, js_func);
69
70                // Store the closure so we can remove it later during patching
71                el.listener_closures
72                    .borrow_mut()
73                    .push((event_name, handler_cls));
74            }
75
76            // Mount children — propagate the namespace
77            for child in &el.children {
78                if let Some(child_node) = mount_to_dom_inner(child, &elem, None, ns) {
79                    let _ = elem.append_child(&child_node);
80                }
81            }
82
83            let node: web_sys::Node = elem.into();
84            *el.dom_ref.borrow_mut() = Some(node.clone());
85
86            // Insert into parent at the right position
87            if let Some(ref anchor_node) = anchor {
88                let _ = parent.insert_before(&node, Some(anchor_node));
89            } else {
90                let _ = parent.append_child(&node);
91            }
92
93            Some(node)
94        }
95
96        VNode::Text(text) => {
97            let document = web_sys::window()?.document()?;
98            let text_node = document.create_text_node(text);
99            let node: web_sys::Node = text_node.into();
100
101            if let Some(ref anchor_node) = anchor {
102                let _ = parent.insert_before(&node, Some(anchor_node));
103            } else {
104                let _ = parent.append_child(&node);
105            }
106
107            Some(node)
108        }
109
110        VNode::Fragment(children) => {
111            let mut first_child = None;
112            for child in children {
113                if let Some(child_node) = mount_to_dom_inner(child, parent, anchor, namespace) {
114                    if first_child.is_none() {
115                        first_child = Some(child_node);
116                    }
117                }
118            }
119            first_child
120        }
121
122        VNode::Dynamic(render_fn, dom_ref) => {
123            let f = render_fn.borrow();
124            let new_vnode = f();
125            drop(f);
126
127            if let Some(node) = mount_to_dom_inner(&new_vnode, parent, anchor, namespace) {
128                *dom_ref.borrow_mut() = Some(node.clone());
129                Some(node)
130            } else {
131                None
132            }
133        }
134
135        VNode::Empty => None,
136    }
137}