Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-core/src/vdom.rs
1//! Virtual DOM implementation with efficient diffing
2 
3use crate::types::NodeId;
4use std::collections::HashMap;
5use std::fmt;
6 
7/// Virtual DOM node types
8#[derive(Debug, Clone, PartialEq)]
9pub enum VNode {
10 /// Element node with tag, attributes, and children
11 Element {
12 tag: String,
13 attributes: HashMap<String, String>,
14 children: Vec<VNode>,
15 key: Option<String>,
16 },
17 /// Text node with content
18 Text(String),
19 /// Component node with props
20 Component {
21 name: String,
22 props: HashMap<String, String>,
23 children: Vec<VNode>,
24 key: Option<String>,
25 },
26 /// Fragment node (container for multiple children)
27 Fragment(Vec<VNode>),
28}
29 
30impl VNode {
31 /// Create a new element node
32 pub fn element(tag: impl Into<String>) -> Self {
33 VNode::Element {
34 tag: tag.into(),
35 attributes: HashMap::new(),
36 children: Vec::new(),
37 key: None,
38 }
39 }
40 
41 /// Create a new text node
42 pub fn text(content: impl Into<String>) -> Self {
43 VNode::Text(content.into())
44 }
45 
46 /// Create a new component node
47 pub fn component(name: impl Into<String>) -> Self {
48 VNode::Component {
49 name: name.into(),
50 props: HashMap::new(),
51 children: Vec::new(),
52 key: None,
53 }
54 }
55 
56 /// Create a fragment node
57 pub fn fragment(children: Vec<VNode>) -> Self {
58 VNode::Fragment(children)
59 }
60 
61 /// Set an attribute on an element
62 pub fn attr(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
63 match &mut self {
64 VNode::Element { attributes, .. } => {
65 attributes.insert(key.into(), value.into());
66 }
67 VNode::Component { props, .. } => {
68 props.insert(key.into(), value.into());
69 }
70 _ => {}
71 }
72 self
73 }
74 
75 /// Set a key for the node (used in diffing)
76 pub fn key(mut self, key: impl Into<String>) -> Self {
77 match &mut self {
78 VNode::Element { key: k, .. } | VNode::Component { key: k, .. } => {
79 *k = Some(key.into());
80 }
81 _ => {}
82 }
83 self
84 }
85 
86 /// Add children to the node
87 pub fn children(mut self, children: Vec<VNode>) -> Self {
88 match &mut self {
89 VNode::Element { children: c, .. } | VNode::Component { children: c, .. } => {
90 *c = children;
91 }
92 VNode::Fragment(c) => {
93 *c = children;
94 }
95 _ => {}
96 }
97 self
98 }
99 
100 /// Add a single child to the node
101 pub fn child(mut self, child: VNode) -> Self {
102 match &mut self {
103 VNode::Element { children, .. } | VNode::Component { children, .. } => {
104 children.push(child);
105 }
106 VNode::Fragment(children) => {
107 children.push(child);
108 }
109 _ => {}
110 }
111 self
112 }
113 
114 /// Get the key of the node
115 pub fn get_key(&self) -> Option<&str> {
116 match self {
117 VNode::Element { key, .. } | VNode::Component { key, .. } => key.as_deref(),
118 _ => None,
119 }
120 }
121 
122 /// Get the tag name for element nodes
123 pub fn get_tag(&self) -> Option<&str> {
124 match self {
125 VNode::Element { tag, .. } => Some(tag),
126 _ => None,
127 }
128 }
129 
130 /// Get the component name for component nodes
131 pub fn get_component_name(&self) -> Option<&str> {
132 match self {
133 VNode::Component { name, .. } => Some(name),
134 _ => None,
135 }
136 }
137 
138 /// Get text content for text nodes
139 pub fn get_text(&self) -> Option<&str> {
140 match self {
141 VNode::Text(content) => Some(content),
142 _ => None,
143 }
144 }
145 
146 /// Get children of the node
147 pub fn get_children(&self) -> &[VNode] {
148 match self {
149 VNode::Element { children, .. } | VNode::Component { children, .. } => children,
150 VNode::Fragment(children) => children,
151 VNode::Text(_) => &[],
152 }
153 }
154 
155 /// Get mutable children for element, component, and fragment nodes
156 pub fn get_children_mut(&mut self) -> &mut Vec<VNode> {
157 match self {
158 VNode::Element { children, .. } | VNode::Component { children, .. } => children,
159 VNode::Fragment(children) => children,
160 VNode::Text(_) => {
161 // This method shouldn't be called on Text nodes anyway
162 panic!("Cannot get mutable children from Text node")
163 }
164 }
165 }
166 
167 /// Get attributes for element nodes
168 pub fn get_attributes(&self) -> Option<&HashMap<String, String>> {
169 match self {
170 VNode::Element { attributes, .. } => Some(attributes),
171 _ => None,
172 }
173 }
174 
175 /// Get props for component nodes
176 pub fn get_props(&self) -> Option<&HashMap<String, String>> {
177 match self {
178 VNode::Component { props, .. } => Some(props),
179 _ => None,
180 }
181 }
182}
183 
184impl fmt::Display for VNode {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match self {
187 VNode::Element {
188 tag,
189 attributes,
190 children,
191 ..
192 } => {
193 write!(f, "<{}", tag)?;
194 for (key, value) in attributes {
195 write!(f, " {}=\"{}\"", key, value)?;
196 }
197 if children.is_empty() {
198 write!(f, " />")
199 } else {
200 write!(f, ">")?;
201 for child in children {
202 write!(f, "{}", child)?;
203 }
204 write!(f, "</{}>", tag)
205 }
206 }
207 VNode::Text(content) => write!(f, "{}", content),
208 VNode::Component {
209 name,
210 props,
211 children,
212 ..
213 } => {
214 write!(f, "<{}", name)?;
215 for (key, value) in props {
216 write!(f, " {}=\"{}\"", key, value)?;
217 }
218 if children.is_empty() {
219 write!(f, " />")
220 } else {
221 write!(f, ">")?;
222 for child in children {
223 write!(f, "{}", child)?;
224 }
225 write!(f, "</{}>", name)
226 }
227 }
228 VNode::Fragment(children) => {
229 for child in children {
230 write!(f, "{}", child)?;
231 }
232 Ok(())
233 }
234 }
235 }
236}
237 
238/// Diff operation types
239#[derive(Debug, Clone, PartialEq)]
240pub enum DiffOp {
241 /// Insert a new node at the given index
242 Insert { index: usize, node: VNode },
243 /// Remove a node at the given index
244 Remove { index: usize },
245 /// Replace a node at the given index
246 Replace { index: usize, node: VNode },
247 /// Update attributes of a node
248 UpdateAttributes {
249 index: usize,
250 attributes: HashMap<String, Option<String>>, // None means remove attribute
251 },
252 /// Update text content
253 UpdateText { index: usize, text: String },
254 /// Move a node from one index to another
255 Move { from: usize, to: usize },
256}
257 
258/// Virtual DOM differ
259pub struct VDomDiffer {
260 /// Current virtual DOM tree
261 current: Option<VNode>,
262}
263 
264impl VDomDiffer {
265 /// Create a new VDom differ
266 pub fn new() -> Self {
267 Self { current: None }
268 }
269 
270 /// Diff two virtual DOM trees and return the operations needed to transform old to new
271 pub fn diff(&mut self, new_tree: VNode) -> Vec<DiffOp> {
272 let ops = match &self.current {
273 Some(old_tree) => self.diff_nodes(old_tree, &new_tree, 0),
274 None => vec![DiffOp::Insert {
275 index: 0,
276 node: new_tree.clone(),
277 }],
278 };
279 
280 self.current = Some(new_tree);
281 ops
282 }
283 
284 /// Diff two individual nodes
285 fn diff_nodes(&self, old: &VNode, new: &VNode, index: usize) -> Vec<DiffOp> {
286 let mut ops = Vec::new();
287 
288 match (old, new) {
289 // Both are text nodes
290 (VNode::Text(old_text), VNode::Text(new_text)) => {
291 if old_text != new_text {
292 ops.push(DiffOp::UpdateText {
293 index,
294 text: new_text.clone(),
295 });
296 }
297 }
298 
299 // Both are elements with the same tag
300 (
301 VNode::Element {
302 tag: old_tag,
303 attributes: old_attrs,
304 children: old_children,
305 ..
306 },
307 VNode::Element {
308 tag: new_tag,
309 attributes: new_attrs,
310 children: new_children,
311 ..
312 },
313 ) if old_tag == new_tag => {
314 // Diff attributes
315 let attr_diff = self.diff_attributes(old_attrs, new_attrs);
316 if !attr_diff.is_empty() {
317 ops.push(DiffOp::UpdateAttributes {
318 index,
319 attributes: attr_diff,
320 });
321 }
322 
323 // Diff children
324 ops.extend(self.diff_children(old_children, new_children, index));
325 }
326 
327 // Both are components with the same name
328 (
329 VNode::Component {
330 name: old_name,
331 props: old_props,
332 children: old_children,
333 ..
334 },
335 VNode::Component {
336 name: new_name,
337 props: new_props,
338 children: new_children,
339 ..
340 },
341 ) if old_name == new_name => {
342 // Diff props (treated like attributes)
343 let prop_diff = self.diff_attributes(old_props, new_props);
344 if !prop_diff.is_empty() {
345 ops.push(DiffOp::UpdateAttributes {
346 index,
347 attributes: prop_diff,
348 });
349 }
350 
351 // Diff children
352 ops.extend(self.diff_children(old_children, new_children, index));
353 }
354 
355 // Both are fragments
356 (VNode::Fragment(old_children), VNode::Fragment(new_children)) => {
357 ops.extend(self.diff_children(old_children, new_children, index));
358 }
359 
360 // Different node types or different tags/names - replace entirely
361 _ => {
362 ops.push(DiffOp::Replace {
363 index,
364 node: new.clone(),
365 });
366 }
367 }
368 
369 ops
370 }
371 
372 /// Diff attributes/props
373 fn diff_attributes(
374 &self,
375 old_attrs: &HashMap<String, String>,
376 new_attrs: &HashMap<String, String>,
377 ) -> HashMap<String, Option<String>> {
378 let mut diff = HashMap::new();
379 
380 // Check for new or changed attributes
381 for (key, new_value) in new_attrs {
382 match old_attrs.get(key) {
383 Some(old_value) if old_value != new_value => {
384 diff.insert(key.clone(), Some(new_value.clone()));
385 }
386 None => {
387 diff.insert(key.clone(), Some(new_value.clone()));
388 }
389 _ => {} // No change
390 }
391 }
392 
393 // Check for removed attributes
394 for key in old_attrs.keys() {
395 if !new_attrs.contains_key(key) {
396 diff.insert(key.clone(), None);
397 }
398 }
399 
400 diff
401 }
402 
403 /// Diff children using a keyed diffing algorithm
404 fn diff_children(
405 &self,
406 old_children: &[VNode],
407 new_children: &[VNode],
408 parent_index: usize,
409 ) -> Vec<DiffOp> {
410 let mut ops = Vec::new();
411 
412 // Simple algorithm for now - can be optimized with keyed diffing later
413 let old_len = old_children.len();
414 let new_len = new_children.len();
415 let min_len = old_len.min(new_len);
416 
417 // Diff existing children
418 for i in 0..min_len {
419 ops.extend(self.diff_nodes(&old_children[i], &new_children[i], parent_index + i + 1));
420 }
421 
422 // Handle length differences
423 if new_len > old_len {
424 // Insert new children
425 for i in old_len..new_len {
426 ops.push(DiffOp::Insert {
427 index: parent_index + i + 1,
428 node: new_children[i].clone(),
429 });
430 }
431 } else if old_len > new_len {
432 // Remove extra children (in reverse order to maintain indices)
433 for i in (new_len..old_len).rev() {
434 ops.push(DiffOp::Remove {
435 index: parent_index + i + 1,
436 });
437 }
438 }
439 
440 ops
441 }
442 
443 /// Get the current virtual DOM tree
444 pub fn current(&self) -> Option<&VNode> {
445 self.current.as_ref()
446 }
447}
448 
449impl Default for VDomDiffer {
450 fn default() -> Self {
451 Self::new()
452 }
453}
454 
455/// Virtual DOM tree for managing the entire UI state
456pub struct VDomTree {
457 /// Root node of the tree
458 root: Option<VNode>,
459 /// Differ for computing changes
460 differ: VDomDiffer,
461 /// Node ID counter
462 next_id: NodeId,
463}
464 
465impl VDomTree {
466 /// Create a new virtual DOM tree
467 pub fn new() -> Self {
468 Self {
469 root: None,
470 differ: VDomDiffer::new(),
471 next_id: NodeId(0),
472 }
473 }
474 
475 /// Update the tree with a new root node and return diff operations
476 pub fn update(&mut self, new_root: VNode) -> Vec<DiffOp> {
477 let ops = self.differ.diff(new_root.clone());
478 self.root = Some(new_root);
479 ops
480 }
481 
482 /// Get the current root node
483 pub fn root(&self) -> Option<&VNode> {
484 self.root.as_ref()
485 }
486 
487 /// Generate a new unique node ID
488 pub fn next_node_id(&mut self) -> NodeId {
489 let id = self.next_id;
490 self.next_id.0 += 1;
491 id
492 }
493 
494 /// Render the tree to a string (for debugging)
495 pub fn render_to_string(&self) -> String {
496 match &self.root {
497 Some(node) => format!("{}", node),
498 None => String::new(),
499 }
500 }
501}
502 
503impl Default for VDomTree {
504 fn default() -> Self {
505 Self::new()
506 }
507}
508 
509#[cfg(test)]
510mod tests {
511 use super::*;
512 
513 #[test]
514 fn test_vnode_creation() {
515 let node = VNode::element("div")
516 .attr("class", "container")
517 .child(VNode::text("Hello, World!"));
518 
519 assert_eq!(node.get_tag(), Some("div"));
520 assert_eq!(node.get_children().len(), 1);
521 assert_eq!(node.get_children()[0].get_text(), Some("Hello, World!"));
522 }
523 
524 #[test]
525 fn test_vdom_diff_text_change() {
526 let mut differ = VDomDiffer::new();
527 
528 let old_tree = VNode::text("Hello");
529 let new_tree = VNode::text("World");
530 
531 differ.current = Some(old_tree);
532 let ops = differ.diff(new_tree);
533 
534 assert_eq!(ops.len(), 1);
535 match &ops[0] {
536 DiffOp::UpdateText { text, .. } => assert_eq!(text, "World"),
537 _ => panic!("Expected UpdateText operation"),
538 }
539 }
540 
541 #[test]
542 fn test_vdom_diff_attribute_change() {
543 let mut differ = VDomDiffer::new();
544 
545 let old_tree = VNode::element("div").attr("class", "old");
546 let new_tree = VNode::element("div").attr("class", "new");
547 
548 differ.current = Some(old_tree);
549 let ops = differ.diff(new_tree);
550 
551 assert_eq!(ops.len(), 1);
552 match &ops[0] {
553 DiffOp::UpdateAttributes { attributes, .. } => {
554 assert_eq!(attributes.get("class"), Some(&Some("new".to_string())));
555 }
556 _ => panic!("Expected UpdateAttributes operation"),
557 }
558 }
559 
560 #[test]
561 fn test_vdom_tree_update() {
562 let mut tree = VDomTree::new();
563 
564 let root = VNode::element("div").child(VNode::text("Hello"));
565 
566 let ops = tree.update(root);
567 assert_eq!(ops.len(), 1);
568 
569 match &ops[0] {
570 DiffOp::Insert { node, .. } => {
571 assert_eq!(node.get_tag(), Some("div"));
572 }
573 _ => panic!("Expected Insert operation"),
574 }
575 }
576}
577