Skip to main content

rue_core/node/
mod.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3use wasm_bindgen::prelude::*;
4use web_sys::*;
5
6pub mod mount;
7pub mod patch;
8pub mod children;
9
10// ---------------------------------------------------------------------------
11// VNodeType — classification of a VNode for diffing
12// ---------------------------------------------------------------------------
13
14/// The "type" of a VNode, used to decide whether two nodes can be patched
15/// in-place or must be replaced entirely.
16#[derive(Clone, Debug, PartialEq)]
17pub enum VNodeType {
18    Element(String),       // tag name
19    Text,
20    Fragment,
21    Dynamic,
22    Empty,
23}
24
25// ---------------------------------------------------------------------------
26// VNode
27// ---------------------------------------------------------------------------
28
29/// A virtual DOM node.
30pub enum VNode {
31    /// An HTML element with tag, attributes, event listeners, and children.
32    Element(VElement),
33    /// A text node.
34    Text(String),
35    /// A fragment containing multiple nodes.
36    Fragment(Vec<VNode>),
37    /// A dynamic node that re-renders when signals change.
38    Dynamic(
39        Rc<RefCell<Box<dyn Fn() -> VNode>>>,
40        Rc<RefCell<Option<web_sys::Node>>>,
41    ),
42    /// A placeholder for empty/false conditions.
43    Empty,
44}
45
46impl VNode {
47    /// Create an element node.
48    pub fn element(tag: &str) -> VElementBuilder {
49        VElementBuilder::new(tag)
50    }
51
52    /// Create a text node.
53    pub fn text(content: &str) -> Self {
54        VNode::Text(content.to_string())
55    }
56
57    /// Create a fragment node.
58    pub fn fragment(children: Vec<VNode>) -> Self {
59        VNode::Fragment(children)
60    }
61
62    /// Create an empty node.
63    pub fn empty() -> Self {
64        VNode::Empty
65    }
66
67    // -----------------------------------------------------------------------
68    // Virtual DOM helpers
69    // -----------------------------------------------------------------------
70
71    /// Return the `VNodeType` of this node (used for diffing).
72    pub fn node_type(&self) -> VNodeType {
73        match self {
74            VNode::Element(el) => VNodeType::Element(el.tag.clone()),
75            VNode::Text(_) => VNodeType::Text,
76            VNode::Fragment(_) => VNodeType::Fragment,
77            VNode::Dynamic(_, _) => VNodeType::Dynamic,
78            VNode::Empty => VNodeType::Empty,
79        }
80    }
81
82    /// Quick check: can we patch `self` (old) into `other` (new) in-place?
83    /// Two nodes can be patched if they have the same VNodeType (same variant,
84    /// same tag for elements).
85    pub fn same_type(&self, other: &VNode) -> bool {
86        match (self, other) {
87            (VNode::Element(a), VNode::Element(b)) => a.tag == b.tag,
88            (VNode::Text(_), VNode::Text(_)) => true,
89            (VNode::Fragment(_), VNode::Fragment(_)) => true,
90            (VNode::Dynamic(_, _), VNode::Dynamic(_, _)) => true,
91            (VNode::Empty, VNode::Empty) => true,
92            _ => false,
93        }
94    }
95
96    /// Return the rendering key (`:key` attribute) for this node, if any.
97    /// Only elements may have keys.
98    pub fn key(&self) -> Option<&str> {
99        match self {
100            VNode::Element(el) => el.key.as_deref(),
101            _ => None,
102        }
103    }
104
105    /// Return a reference to the actual DOM node if this VNode has been
106    /// mounted/rendered.
107    pub fn dom_node(&self) -> Option<web_sys::Node> {
108        match self {
109            VNode::Element(el) => el.dom_ref.borrow().clone(),
110            VNode::Dynamic(_, dom_ref) => dom_ref.borrow().clone(),
111            VNode::Text(_) | VNode::Fragment(_) | VNode::Empty => None,
112        }
113    }
114
115    /// Set the DOM node reference on this VNode (called during mount/patch).
116    pub fn set_dom_node(&self, node: web_sys::Node) {
117        match self {
118            VNode::Element(el) => {
119                *el.dom_ref.borrow_mut() = Some(node);
120            }
121            VNode::Dynamic(_, dom_ref) => {
122                *dom_ref.borrow_mut() = Some(node);
123            }
124            VNode::Text(_) | VNode::Fragment(_) | VNode::Empty => {
125                // Text nodes are created fresh each time; fragments don't store
126                // a single DOM ref. This is intentionally a no-op.
127            }
128        }
129    }
130
131    /// Recursively collect all `(child_index, VNode)` pairs that have a key.
132    /// Used by the keyed children reconciliation algorithm.
133    pub fn keyed_children(&self) -> Vec<(usize, &VNode)> {
134        match self {
135            VNode::Element(el) => el
136                .children
137                .iter()
138                .enumerate()
139                .filter(|(_, c)| c.key().is_some())
140                .collect(),
141            _ => vec![],
142        }
143    }
144}
145
146// ---------------------------------------------------------------------------
147// VElement
148// ---------------------------------------------------------------------------
149
150/// An HTML element representation.
151pub struct VElement {
152    pub tag: String,
153    pub attrs: Vec<(&'static str, String)>,
154    pub events: Vec<(&'static str, EventHandler)>,
155    pub children: Vec<VNode>,
156    pub key: Option<String>,
157    /// Reference to the actual DOM node (set during rendering).
158    pub dom_ref: Rc<RefCell<Option<web_sys::Node>>>,
159    /// Stored Closures for active event listeners, so they can be removed
160    /// during patching.
161    pub listener_closures: Rc<RefCell<Vec<(&'static str, Closure<dyn FnMut(web_sys::Event)>)>>>,
162}
163
164// ---------------------------------------------------------------------------
165// EventHandler
166// ---------------------------------------------------------------------------
167
168/// A clonable event handler using Rc.
169pub struct EventHandler(Rc<dyn Fn(web_sys::Event)>);
170
171impl EventHandler {
172    pub fn new<F: Fn(web_sys::Event) + 'static>(f: F) -> Self {
173        EventHandler(Rc::new(f))
174    }
175
176    pub fn call(&self, event: web_sys::Event) {
177        (self.0)(event)
178    }
179}
180
181impl Clone for EventHandler {
182    fn clone(&self) -> Self {
183        EventHandler(self.0.clone())
184    }
185}
186
187// ---------------------------------------------------------------------------
188// VElementBuilder
189// ---------------------------------------------------------------------------
190
191/// Builder for creating element VNodes.
192pub struct VElementBuilder {
193    tag: String,
194    attrs: Vec<(&'static str, String)>,
195    events: Vec<(&'static str, EventHandler)>,
196    children: Vec<VNode>,
197    key: Option<String>,
198}
199
200impl VElementBuilder {
201    fn new(tag: &str) -> Self {
202        VElementBuilder {
203            tag: tag.to_string(),
204            attrs: Vec::new(),
205            events: Vec::new(),
206            children: Vec::new(),
207            key: None,
208        }
209    }
210
211    /// Set an attribute.
212    pub fn attr(mut self, name: &'static str, value: &str) -> Self {
213        self.attrs.push((name, value.to_string()));
214        self
215    }
216
217    /// Set the "class" attribute.
218    pub fn class(mut self, value: &str) -> Self {
219        self.attrs.push(("class", value.to_string()));
220        self
221    }
222
223    /// Set the "id" attribute.
224    pub fn id(mut self, value: &str) -> Self {
225        self.attrs.push(("id", value.to_string()));
226        self
227    }
228
229    /// Set the rendering key (used for keyed children reconciliation).
230    pub fn key(mut self, value: &str) -> Self {
231        self.key = Some(value.to_string());
232        self
233    }
234
235    /// Add an event listener.
236    pub fn on<F: Fn(web_sys::Event) + 'static>(mut self, event: &'static str, handler: F) -> Self {
237        self.events.push((event, EventHandler::new(handler)));
238        self
239    }
240
241    /// Add a child node.
242    pub fn child(mut self, child: VNode) -> Self {
243        self.children.push(child);
244        self
245    }
246
247    /// Add multiple children.
248    pub fn children(mut self, children: Vec<VNode>) -> Self {
249        self.children.extend(children);
250        self
251    }
252
253    /// Add text content.
254    pub fn text(mut self, content: &str) -> Self {
255        self.children.push(VNode::Text(content.to_string()));
256        self
257    }
258
259    /// Build the element node.
260    pub fn build(self) -> VNode {
261        VNode::Element(VElement {
262            tag: self.tag,
263            attrs: self.attrs,
264            events: self.events,
265            children: self.children,
266            key: self.key,
267            dom_ref: Rc::new(RefCell::new(None)),
268            listener_closures: Rc::new(RefCell::new(Vec::new())),
269        })
270    }
271}
272
273// ---------------------------------------------------------------------------
274// Legacy render_to_dom — thin wrapper around mount::mount_to_dom
275// ---------------------------------------------------------------------------
276
277/// Render a VNode tree into actual DOM nodes and return the root node.
278/// This is a simple wrapper around `mount::mount_to_dom` for backward
279/// compatibility.
280pub fn render_to_dom(vnode: &VNode) -> Option<web_sys::Node> {
281    // We need a temporary parent to use mount_to_dom.
282    let document = web_sys::window()?.document()?;
283    let temp = document.create_element("div").ok()?;
284    let parent: web_sys::Node = temp.into();
285    mount::mount_to_dom(vnode, &parent, None)
286}