1use super::conditionals::{
2 evaluate_value, find_matching_branch, find_matching_route_with_key,
3};
4use super::tree::DEFAULT_ROUTER_CACHE_SIZE;
5use super::item_bindings::replace_ir_node_item_bindings;
6use super::keyed::{generate_item_key, reconcile_iterable_children};
7use super::resolve::{evaluate_binding, resolve_props_full};
8use super::{ControlFlowKind, InstanceTree, Patch};
9use crate::ir::{Element, IRNode, NodeId, Props, RouterRoute, Value};
10use crate::reactive::DependencyGraph;
11use indexmap::IndexMap;
12
13type DataSources = indexmap::IndexMap<String, serde_json::Value>;
15
16type Modules = indexmap::IndexMap<String, crate::lifecycle::ModuleInstance>;
18
19pub(crate) struct ReconcileCtx<'a> {
23 pub tree: &'a mut InstanceTree,
24 pub state: &'a serde_json::Value,
25 pub patches: &'a mut Vec<Patch>,
26 pub dependencies: &'a mut DependencyGraph,
27 pub data_sources: Option<&'a DataSources>,
28 pub modules: Option<&'a Modules>,
29}
30
31impl<'a> ReconcileCtx<'a> {
32 fn effective_scope<'s>(&self, raw: Option<&'s str>) -> Option<&'s str> {
40 raw.filter(|scope| {
41 self.modules
42 .map(|m| m.contains_key(*scope))
43 .unwrap_or(false)
44 })
45 }
46
47 fn effective_state(&self, raw_scope: Option<&str>) -> &'a serde_json::Value {
52 match self.effective_scope(raw_scope) {
53 Some(scope) => self
54 .modules
55 .and_then(|m| m.get(scope))
56 .map(|m| m.get_state())
57 .unwrap_or(self.state),
58 None => self.state,
59 }
60 }
61}
62
63pub fn reconcile_ir(
67 tree: &mut InstanceTree,
68 node: &IRNode,
69 parent_id: Option<NodeId>,
70 state: &serde_json::Value,
71 dependencies: &mut DependencyGraph,
72) -> Vec<Patch> {
73 reconcile_ir_with_ds(tree, node, parent_id, state, dependencies, None, None)
74}
75
76pub fn reconcile_ir_with_ds(
78 tree: &mut InstanceTree,
79 node: &IRNode,
80 parent_id: Option<NodeId>,
81 state: &serde_json::Value,
82 dependencies: &mut DependencyGraph,
83 data_sources: Option<&DataSources>,
84 modules: Option<&indexmap::IndexMap<String, crate::lifecycle::ModuleInstance>>,
85) -> Vec<Patch> {
86 let mut patches = Vec::new();
87
88 if tree.root().is_none() {
90 let mut ctx = ReconcileCtx {
91 tree,
92 state,
93 patches: &mut patches,
94 dependencies,
95 data_sources,
96 modules,
97 };
98 let node_id = create_ir_node_tree_impl(&mut ctx, node, parent_id, true);
99 ctx.tree.set_root(node_id);
100 return patches;
101 }
102
103 if let Some(root_id) = tree.root() {
105 let mut ctx = ReconcileCtx {
106 tree,
107 state,
108 patches: &mut patches,
109 dependencies,
110 data_sources,
111 modules,
112 };
113 reconcile_ir_node_impl(&mut ctx, root_id, node);
114 }
115
116 patches
117}
118
119fn create_element_node(
129 ctx: &mut ReconcileCtx,
130 element: &Element,
131 logical_parent: Option<NodeId>,
132 render_parent: Option<NodeId>,
133 is_root: bool,
134) -> NodeId {
135 let module_scope_ref = ctx.effective_scope(element.module_scope.as_deref());
136 let effective_state = ctx.effective_state(element.module_scope.as_deref());
137
138 if logical_parent == render_parent {
143 if let Some(Value::Binding(_)) = element.props.get("0") {
144 if !element.ir_children.is_empty() {
145 return create_list_tree_impl(ctx, element, logical_parent, is_root);
146 }
147 }
148 }
149
150 let node_id = ctx
153 .tree
154 .create_node_full(element, effective_state, ctx.data_sources);
155
156 for value in element.props.values() {
157 match value {
158 Value::Binding(binding) => {
159 ctx.dependencies
160 .add_dependency(node_id, binding, module_scope_ref);
161 }
162 Value::TemplateString { bindings, .. } => {
163 for binding in bindings {
164 ctx.dependencies
165 .add_dependency(node_id, binding, module_scope_ref);
166 }
167 }
168 _ => {}
169 }
170 }
171
172 let is_lazy = element
176 .props
177 .get("__lazy")
178 .and_then(|v| match v {
179 Value::Static(val) => val.as_bool(),
180 _ => None,
181 })
182 .unwrap_or(false);
183
184 let mut props = ctx
185 .tree
186 .get(node_id)
187 .map(|n| n.props.clone())
188 .unwrap_or_else(|| std::sync::Arc::new(indexmap::IndexMap::new()));
189 if is_lazy && !element.ir_children.is_empty() {
190 if let Some(IRNode::Element(first_child)) = element.ir_children.first() {
191 std::sync::Arc::make_mut(&mut props).insert(
194 "__lazy_child".to_string(),
195 serde_json::json!(first_child.element_type),
196 );
197 }
198 }
199 ctx.patches
200 .push(Patch::create(node_id, element.element_type.clone(), props));
201
202 if let Some(parent) = logical_parent {
204 ctx.tree.add_child(parent, node_id, None);
205 }
206
207 if let Some(rp) = render_parent {
214 ctx.patches.push(Patch::insert(rp, node_id, None));
215 } else if is_root {
216 ctx.patches.push(Patch::insert_root(node_id));
217 } else if let Some(lp) = logical_parent {
218 ctx.patches.push(Patch::insert(lp, node_id, None));
219 }
220
221 if !is_lazy {
225 let old_state = ctx.state;
226 ctx.state = effective_state;
227 for child_ir in &element.ir_children {
228 create_ir_node_tree_impl(ctx, child_ir, Some(node_id), false);
229 }
230 ctx.state = old_state;
231 }
232
233 node_id
234}
235
236fn create_list_tree_impl(
238 ctx: &mut ReconcileCtx,
239 element: &Element,
240 parent_id: Option<NodeId>,
241 is_root: bool,
242) -> NodeId {
243 let module_scope_ref = ctx.effective_scope(element.module_scope.as_deref());
244 let effective_state = ctx.effective_state(element.module_scope.as_deref());
245
246 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
248 evaluate_binding(binding, effective_state).unwrap_or(serde_json::Value::Array(vec![]))
249 } else {
250 serde_json::Value::Array(vec![])
251 };
252
253 let mut list_element = Element::new(&element.element_type);
256 for (key, value) in &element.props {
257 if key != "0" {
258 list_element.props.insert(key.clone(), value.clone());
259 }
260 }
261
262 let node_id = ctx
263 .tree
264 .create_node_full(&list_element, effective_state, ctx.data_sources);
265
266 if let Some(Value::Binding(binding)) = element.props.get("0") {
268 ctx.dependencies
269 .add_dependency(node_id, binding, module_scope_ref);
270 }
271
272 if let Some(node) = ctx.tree.get_mut(node_id) {
274 node.raw_props = element.props.clone();
275 node.element_template = Some(std::sync::Arc::new(element.clone()));
276 }
277
278 let node = ctx.tree.get(node_id).unwrap();
280 ctx.patches.push(Patch::create(
281 node_id,
282 node.element_type.clone(),
283 node.props.clone(),
284 ));
285
286 if let Some(parent) = parent_id {
288 ctx.tree.add_child(parent, node_id, None);
289 ctx.patches.push(Patch::insert(parent, node_id, None));
290 } else if is_root {
291 ctx.patches.push(Patch::insert_root(node_id));
292 }
293
294 if let serde_json::Value::Array(items) = &array {
298 let key_path = element.props.get("key.0").and_then(|v| match v {
299 Value::Static(serde_json::Value::String(s)) => Some(s.as_str()),
300 _ => None,
301 });
302 let multi_template = element.ir_children.len() > 1;
303
304 for (index, item) in items.iter().enumerate() {
305 let item_key = generate_item_key(item, key_path, "item", index);
306
307 for (template_idx, child_ir) in element.ir_children.iter().enumerate() {
308 let child_key = if multi_template {
309 format!("{}#{}", item_key, template_idx)
310 } else {
311 item_key.clone()
312 };
313 let child_with_item = replace_ir_node_item_bindings(
314 child_ir, item, index, "item", &item_key,
315 );
316 let child_id = create_ir_node_tree_impl(
317 ctx,
318 &child_with_item,
319 Some(node_id),
320 false,
321 );
322 if let Some(child_node) = ctx.tree.get_mut(child_id) {
323 child_node.key = Some(child_key);
324 }
325 }
326 }
327 }
328
329 node_id
330}
331
332fn reconcile_element_node(ctx: &mut ReconcileCtx, node_id: NodeId, element: &Element) {
338 let node = match ctx.tree.get(node_id).cloned() {
339 Some(n) => n,
340 None => return,
341 };
342
343 let effective_state = ctx.effective_state(element.module_scope.as_deref());
344 let module_scope_ref = element.module_scope.as_deref();
345
346 let is_iterable = element.props.get("0").is_some() && !element.ir_children.is_empty();
350
351 if is_iterable {
352 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
353 evaluate_binding(binding, effective_state).unwrap_or(serde_json::Value::Array(vec![]))
354 } else {
355 serde_json::Value::Array(vec![])
356 };
357
358 if let serde_json::Value::Array(items) = &array {
359 let key_path = element.props.get("key.0").and_then(|v| match v {
362 Value::Static(serde_json::Value::String(s)) => Some(s.as_str()),
363 _ => None,
364 });
365
366 reconcile_iterable_children(
367 ctx,
368 node_id,
369 items,
370 "item",
371 key_path,
372 &element.ir_children,
373 );
374 }
375
376 return;
377 }
378
379 if node.element_type != element.element_type {
381 replace_subtree_impl(ctx, node_id, &node, element);
382 return;
383 }
384
385 for value in element.props.values() {
387 match value {
388 Value::Binding(binding) => {
389 ctx.dependencies
390 .add_dependency(node_id, binding, module_scope_ref);
391 }
392 Value::TemplateString { bindings, .. } => {
393 for binding in bindings {
394 ctx.dependencies
395 .add_dependency(node_id, binding, module_scope_ref);
396 }
397 }
398 _ => {}
399 }
400 }
401
402 let new_props = resolve_props_full(&element.props, effective_state, None, ctx.data_sources);
404 let prop_patches = diff_props(node_id, &node.props, &new_props);
405 ctx.patches.extend(prop_patches);
406
407 if let Some(node) = ctx.tree.get_mut(node_id) {
408 node.props = new_props; node.raw_props = element.props.clone();
410 }
411
412 let is_lazy = element
415 .props
416 .get("__lazy")
417 .and_then(|v| match v {
418 Value::Static(val) => val.as_bool(),
419 _ => None,
420 })
421 .unwrap_or(false);
422
423 if !is_lazy {
424 let old_children = node.children.clone();
425 let new_children = &element.ir_children;
426
427 for (i, new_child_ir) in new_children.iter().enumerate() {
428 if let Some(&old_child_id) = old_children.get(i) {
429 reconcile_ir_node_impl(ctx, old_child_id, new_child_ir);
430 } else {
431 create_ir_node_tree_impl(ctx, new_child_ir, Some(node_id), false);
432 }
433 }
434
435 if old_children.len() > new_children.len() {
436 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
437 let subtree_ids = collect_subtree_ids(ctx.tree, old_child_id);
438 for &id in &subtree_ids {
439 ctx.patches.push(Patch::remove(id));
440 ctx.dependencies.remove_node(id);
441 }
442 ctx.tree.remove_child(node_id, old_child_id);
443 ctx.tree.remove(old_child_id);
444 }
445 }
446 }
447}
448
449fn replace_subtree_impl(
451 ctx: &mut ReconcileCtx,
452 old_node_id: NodeId,
453 old_node: &super::InstanceNode,
454 new_element: &Element,
455) {
456 let parent_id = old_node.parent;
457
458 let old_position = if let Some(pid) = parent_id {
459 ctx.tree
460 .get(pid)
461 .and_then(|parent| parent.children.iter().position(|&id| id == old_node_id))
462 } else {
463 None
464 };
465
466 let ids_to_remove = collect_subtree_ids(ctx.tree, old_node_id);
467
468 for &id in &ids_to_remove {
469 ctx.patches.push(Patch::remove(id));
470 ctx.dependencies.remove_node(id);
471 }
472
473 if let Some(pid) = parent_id {
474 if let Some(parent) = ctx.tree.get_mut(pid) {
475 parent.children = parent
476 .children
477 .iter()
478 .filter(|&&id| id != old_node_id)
479 .copied()
480 .collect();
481 }
482 }
483
484 ctx.tree.remove(old_node_id);
485
486 let is_root = parent_id.is_none();
487 let new_node_id = create_element_node(ctx, new_element, parent_id, parent_id, is_root);
488
489 if is_root {
490 ctx.tree.set_root(new_node_id);
491 } else if let Some(pid) = parent_id {
492 if let Some(pos) = old_position {
493 if let Some(parent) = ctx.tree.get_mut(pid) {
494 let current_len = parent.children.len();
495 if pos < current_len - 1 {
496 let new_id = parent.children.pop_back().unwrap();
497 parent.children.insert(pos, new_id);
498 let next_sibling = parent.children.get(pos + 1).copied();
499 ctx.patches
500 .push(Patch::move_node(pid, new_node_id, next_sibling));
501 }
502 }
503 }
504 }
505}
506
507fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
509 let mut result = Vec::new();
510 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
511
512 while let Some((node_id, children_processed)) = stack.pop() {
513 if children_processed {
514 result.push(node_id);
515 } else {
516 stack.push((node_id, true));
517 if let Some(node) = tree.get(node_id) {
518 for &child_id in node.children.iter().rev() {
519 stack.push((child_id, false));
520 }
521 }
522 }
523 }
524
525 result
526}
527
528pub fn diff_props(
530 node_id: NodeId,
531 old_props: &IndexMap<String, serde_json::Value>,
532 new_props: &IndexMap<String, serde_json::Value>,
533) -> Vec<Patch> {
534 let mut patches = Vec::new();
535
536 for (key, new_value) in new_props {
537 if old_props.get(key) != Some(new_value) {
538 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
539 }
540 }
541
542 for key in old_props.keys() {
543 if !new_props.contains_key(key) {
544 patches.push(Patch::remove_prop(node_id, key.clone()));
545 }
546 }
547
548 patches
549}
550
551pub(crate) fn create_ir_node_tree_impl(
560 ctx: &mut ReconcileCtx,
561 node: &IRNode,
562 parent_id: Option<NodeId>,
563 is_root: bool,
564) -> NodeId {
565 create_ir_node_tree_full(ctx, node, parent_id, parent_id, is_root)
566}
567
568fn create_ir_node_tree_full(
577 ctx: &mut ReconcileCtx,
578 node: &IRNode,
579 logical_parent: Option<NodeId>,
580 render_parent: Option<NodeId>,
581 is_root: bool,
582) -> NodeId {
583 match node {
584 IRNode::Element(element) => {
585 create_element_node(ctx, element, logical_parent, render_parent, is_root)
586 }
587 IRNode::ForEach { .. } | IRNode::Conditional { .. } | IRNode::Router { .. } => {
588 create_control_flow_tree(ctx, node, logical_parent, is_root)
591 }
592 }
593}
594
595fn create_control_flow_tree(
597 ctx: &mut ReconcileCtx,
598 node: &IRNode,
599 parent_id: Option<NodeId>,
600 is_root: bool,
601) -> NodeId {
602 match node {
603 IRNode::ForEach { .. } => create_foreach_ir_tree(
604 ctx,
605 node,
606 parent_id,
607 is_root,
608 ),
609 IRNode::Conditional {
610 value,
611 branches,
612 fallback,
613 ..
614 } => create_conditional_tree(
615 ctx,
616 value,
617 branches,
618 fallback.as_deref(),
619 node,
620 parent_id,
621 is_root,
622 ),
623 IRNode::Router {
624 location,
625 routes,
626 fallback,
627 ..
628 } => create_router_tree(
629 ctx,
630 location,
631 routes,
632 fallback.as_deref(),
633 node,
634 parent_id,
635 is_root,
636 ),
637 IRNode::Element(_) => unreachable!("create_control_flow_tree called with Element"),
638 }
639}
640
641fn create_foreach_ir_tree(
643 ctx: &mut ReconcileCtx,
644 node: &IRNode,
645 parent_id: Option<NodeId>,
646 is_root: bool,
647) -> NodeId {
648 let (source, item_name, key_path, template, props, raw_scope) = match node {
649 IRNode::ForEach {
650 source,
651 item_name,
652 key_path,
653 template,
654 props,
655 module_scope,
656 } => (
657 source,
658 item_name.as_str(),
659 key_path.as_deref(),
660 template.as_slice(),
661 props,
662 module_scope.as_deref(),
663 ),
664 _ => unreachable!("create_foreach_ir_tree called with non-ForEach node"),
665 };
666
667 let module_scope_ref = ctx.effective_scope(raw_scope);
669 let effective_state = ctx.effective_state(raw_scope);
670
671 let array =
672 evaluate_binding(source, effective_state).unwrap_or(serde_json::Value::Array(vec![]));
673
674 let resolved_props = resolve_props_full(props, effective_state, None, ctx.data_sources);
675
676 let node_id = ctx.tree.create_control_flow_node(
677 "__ForEach",
678 resolved_props,
679 props.clone(),
680 ControlFlowKind::ForEach {
681 item_name: item_name.to_string(),
682 key_path: key_path.map(|s| s.to_string()),
683 },
684 node.clone(),
685 );
686
687 ctx.dependencies
688 .add_dependency(node_id, source, module_scope_ref);
689
690 if let Some(parent) = parent_id {
691 ctx.tree.add_child(parent, node_id, None);
692 }
693
694 let render_parent = parent_id;
695
696 if let serde_json::Value::Array(items) = &array {
697 for (index, item) in items.iter().enumerate() {
698 let item_key = generate_item_key(item, key_path, item_name, index);
699
700 for child_template in template {
701 let child_with_item = replace_ir_node_item_bindings(
702 child_template,
703 item,
704 index,
705 item_name,
706 &item_key,
707 );
708 create_ir_node_tree_full(
709 ctx,
710 &child_with_item,
711 Some(node_id),
712 render_parent,
713 is_root && render_parent.is_none(),
714 );
715 }
716 }
717 }
718
719 node_id
720}
721
722fn create_conditional_tree(
724 ctx: &mut ReconcileCtx,
725 value: &Value,
726 branches: &[crate::ir::ConditionalBranch],
727 fallback: Option<&[IRNode]>,
728 original_node: &IRNode,
729 parent_id: Option<NodeId>,
730 is_root: bool,
731) -> NodeId {
732 let raw_scope = match original_node {
733 IRNode::Conditional { module_scope, .. } => module_scope.as_deref(),
734 _ => None,
735 };
736 let module_scope_ref = ctx.effective_scope(raw_scope);
737 let effective_state = ctx.effective_state(raw_scope);
738
739 let evaluated_value = evaluate_value(value, effective_state, ctx.data_sources);
740
741 let mut raw_props = Props::new();
742 raw_props.insert("__condition".to_string(), value.clone());
743
744 let node_id = ctx.tree.create_control_flow_node(
745 "__Conditional",
746 std::sync::Arc::new(IndexMap::new()),
747 raw_props,
748 ControlFlowKind::Conditional,
749 original_node.clone(),
750 );
751
752 if let Value::Binding(binding) = value {
753 ctx.dependencies
754 .add_dependency(node_id, binding, module_scope_ref);
755 } else if let Value::TemplateString { bindings, .. } = value {
756 for binding in bindings {
757 ctx.dependencies
758 .add_dependency(node_id, binding, module_scope_ref);
759 }
760 }
761
762 if let Some(parent) = parent_id {
763 ctx.tree.add_child(parent, node_id, None);
764 }
765
766 let matched_children =
767 find_matching_branch(&evaluated_value, branches, fallback, effective_state, ctx.data_sources);
768
769 let render_parent = parent_id;
770
771 if let Some(children) = matched_children {
772 for child in children {
773 create_ir_node_tree_full(
774 ctx,
775 child,
776 Some(node_id),
777 render_parent,
778 is_root && render_parent.is_none(),
779 );
780 }
781 }
782
783 node_id
784}
785
786fn create_router_tree(
794 ctx: &mut ReconcileCtx,
795 location: &Value,
796 routes: &[RouterRoute],
797 fallback: Option<&[IRNode]>,
798 original_node: &IRNode,
799 parent_id: Option<NodeId>,
800 is_root: bool,
801) -> NodeId {
802 let raw_scope = match original_node {
803 IRNode::Router { module_scope, .. } => module_scope.as_deref(),
804 _ => None,
805 };
806 let module_scope_ref = ctx.effective_scope(raw_scope);
807 let effective_state = ctx.effective_state(raw_scope);
808
809 let evaluated = evaluate_value(location, effective_state, ctx.data_sources);
812 let location_str = match &evaluated {
813 serde_json::Value::String(s) => s.clone(),
814 serde_json::Value::Null => String::new(),
815 other => other.to_string(),
816 };
817
818 let mut raw_props = Props::new();
819 raw_props.insert("__location".to_string(), location.clone());
820
821 let node_id = ctx.tree.create_control_flow_node(
824 "__Router",
825 std::sync::Arc::new(IndexMap::new()),
826 raw_props,
827 ControlFlowKind::Router {
828 cache: IndexMap::new(),
829 current_route_key: None,
830 max_cache_size: DEFAULT_ROUTER_CACHE_SIZE,
831 },
832 original_node.clone(),
833 );
834
835 if let Value::Binding(binding) = location {
838 ctx.dependencies
839 .add_dependency(node_id, binding, module_scope_ref);
840 } else if let Value::TemplateString { bindings, .. } = location {
841 for binding in bindings {
842 ctx.dependencies
843 .add_dependency(node_id, binding, module_scope_ref);
844 }
845 }
846
847 if let Some(parent) = parent_id {
848 ctx.tree.add_child(parent, node_id, None);
849 }
850
851 let matched = find_matching_route_with_key(&location_str, routes, fallback);
856 let render_parent = parent_id;
857
858 if let Some((route_key, children)) = matched.as_ref() {
859 for child in *children {
860 create_ir_node_tree_full(
861 ctx,
862 child,
863 Some(node_id),
864 render_parent,
865 is_root && render_parent.is_none(),
866 );
867 }
868
869 if let Some(router_node) = ctx.tree.get_mut(node_id) {
872 if let Some(ControlFlowKind::Router {
873 current_route_key, ..
874 }) = router_node.control_flow.as_mut()
875 {
876 *current_route_key = Some(route_key.clone());
877 }
878 }
879 }
880
881 node_id
882}
883
884pub(crate) fn reconcile_ir_node_impl(ctx: &mut ReconcileCtx, node_id: NodeId, node: &IRNode) {
886 let existing_node = ctx.tree.get(node_id).cloned();
887 if existing_node.is_none() {
888 return;
889 }
890 let existing = existing_node.unwrap();
891
892 match node {
893 IRNode::Element(element) => {
894 reconcile_element_node(ctx, node_id, element);
895 }
896 IRNode::ForEach {
897 source,
898 item_name,
899 key_path,
900 template,
901 props: _,
902 module_scope,
903 } => {
904 if !existing.is_foreach() {
905 let parent_id = existing.parent;
906 remove_subtree(ctx.tree, node_id, ctx.patches, ctx.dependencies);
907 create_ir_node_tree_impl(ctx, node, parent_id, parent_id.is_none());
908 return;
909 }
910
911 let module_scope_ref = ctx.effective_scope(module_scope.as_deref());
912 let effective_state = ctx.effective_state(module_scope.as_deref());
913
914 ctx.dependencies
915 .add_dependency(node_id, source, module_scope_ref);
916
917 let array = evaluate_binding(source, effective_state)
918 .unwrap_or(serde_json::Value::Array(vec![]));
919
920 if let serde_json::Value::Array(items) = &array {
921 let old_children = existing.children.clone();
922 let expected_children_count = items.len() * template.len();
923 let render_parent = existing.parent.unwrap_or(node_id);
924
925 if old_children.len() != expected_children_count {
926 for &old_child_id in &old_children {
927 ctx.patches.push(Patch::remove(old_child_id));
928 }
929
930 if let Some(node) = ctx.tree.get_mut(node_id) {
931 node.children.clear();
932 }
933
934 for (index, item) in items.iter().enumerate() {
935 let item_key =
936 generate_item_key(item, key_path.as_deref(), item_name, index);
937
938 for child_template in template {
939 let child_with_item = replace_ir_node_item_bindings(
940 child_template,
941 item,
942 index,
943 item_name,
944 &item_key,
945 );
946 create_ir_node_tree_impl(
947 ctx,
948 &child_with_item,
949 Some(render_parent),
950 false,
951 );
952 }
953 }
954 } else {
955 let mut child_index = 0;
956 for (item_index, item) in items.iter().enumerate() {
957 let item_key =
958 generate_item_key(item, key_path.as_deref(), item_name, item_index);
959
960 for child_template in template {
961 if let Some(&old_child_id) = old_children.get(child_index) {
962 let child_with_item = replace_ir_node_item_bindings(
963 child_template,
964 item,
965 item_index,
966 item_name,
967 &item_key,
968 );
969 reconcile_ir_node_impl(ctx, old_child_id, &child_with_item);
970 }
971 child_index += 1;
972 }
973 }
974 }
975 }
976 }
977 IRNode::Conditional {
978 value,
979 branches,
980 fallback,
981 module_scope,
982 } => {
983 if !existing.is_conditional() {
984 let parent_id = existing.parent;
985 remove_subtree(ctx.tree, node_id, ctx.patches, ctx.dependencies);
986 create_ir_node_tree_impl(ctx, node, parent_id, parent_id.is_none());
987 return;
988 }
989
990 let module_scope_ref = ctx.effective_scope(module_scope.as_deref());
991 let effective_state = ctx.effective_state(module_scope.as_deref());
992
993 if let Value::Binding(binding) = value {
994 ctx.dependencies
995 .add_dependency(node_id, binding, module_scope_ref);
996 } else if let Value::TemplateString { bindings, .. } = value {
997 for binding in bindings {
998 ctx.dependencies
999 .add_dependency(node_id, binding, module_scope_ref);
1000 }
1001 }
1002
1003 let evaluated_value = evaluate_value(value, effective_state, ctx.data_sources);
1004 let matched_children = find_matching_branch(
1005 &evaluated_value,
1006 branches,
1007 fallback.as_deref(),
1008 effective_state,
1009 ctx.data_sources,
1010 );
1011
1012 let old_children = existing.children.clone();
1013 let old_len = old_children.len();
1014 let render_parent = existing.parent;
1015
1016 if let Some(children) = matched_children {
1017 let new_len = children.len();
1018 let common = old_len.min(new_len);
1019
1020 for (i, child) in children.iter().enumerate().take(common) {
1021 if let Some(&old_child_id) = old_children.get(i) {
1022 reconcile_ir_node_impl(ctx, old_child_id, child);
1023 }
1024 }
1025
1026 for i in common..old_len {
1027 if let Some(&old_child_id) = old_children.get(i) {
1028 remove_subtree(ctx.tree, old_child_id, ctx.patches, ctx.dependencies);
1029 if let Some(cond_node) = ctx.tree.get_mut(node_id) {
1030 cond_node.children = cond_node
1031 .children
1032 .iter()
1033 .filter(|&&id| id != old_child_id)
1034 .copied()
1035 .collect();
1036 }
1037 }
1038 }
1039
1040 let children_is_root = render_parent.is_none();
1041 for child in &children[common..] {
1042 create_ir_node_tree_full(
1043 ctx,
1044 child,
1045 Some(node_id),
1046 render_parent,
1047 children_is_root,
1048 );
1049 }
1050 } else {
1051 for &old_child_id in &old_children {
1052 remove_subtree(ctx.tree, old_child_id, ctx.patches, ctx.dependencies);
1053 }
1054
1055 if let Some(cond_node) = ctx.tree.get_mut(node_id) {
1056 cond_node.children.clear();
1057 }
1058 }
1059 }
1060 IRNode::Router {
1061 location,
1062 routes,
1063 fallback,
1064 module_scope,
1065 } => {
1066 if !existing.is_router() {
1068 let parent_id = existing.parent;
1069 remove_subtree(ctx.tree, node_id, ctx.patches, ctx.dependencies);
1070 create_ir_node_tree_impl(ctx, node, parent_id, parent_id.is_none());
1071 return;
1072 }
1073
1074 let module_scope_ref = ctx.effective_scope(module_scope.as_deref());
1075 let effective_state = ctx.effective_state(module_scope.as_deref());
1076
1077 if let Value::Binding(binding) = location {
1079 ctx.dependencies
1080 .add_dependency(node_id, binding, module_scope_ref);
1081 } else if let Value::TemplateString { bindings, .. } = location {
1082 for binding in bindings {
1083 ctx.dependencies
1084 .add_dependency(node_id, binding, module_scope_ref);
1085 }
1086 }
1087
1088 let evaluated = evaluate_value(location, effective_state, ctx.data_sources);
1089 let location_str = match &evaluated {
1090 serde_json::Value::String(s) => s.clone(),
1091 serde_json::Value::Null => String::new(),
1092 other => other.to_string(),
1093 };
1094
1095 let (mut cache, prev_route_key, max_cache_size) =
1099 match existing.control_flow.as_ref() {
1100 Some(ControlFlowKind::Router {
1101 cache,
1102 current_route_key,
1103 max_cache_size,
1104 }) => (cache.clone(), current_route_key.clone(), *max_cache_size),
1105 _ => (IndexMap::new(), None, DEFAULT_ROUTER_CACHE_SIZE),
1106 };
1107
1108 let matched = find_matching_route_with_key(
1109 &location_str,
1110 routes,
1111 fallback.as_deref(),
1112 );
1113 let new_route_key = matched.as_ref().map(|(k, _)| k.clone());
1114
1115 if prev_route_key.is_some() && prev_route_key == new_route_key {
1119 return;
1120 }
1121
1122 let render_parent = existing.parent;
1123 let old_children: Vec<NodeId> = existing.children.iter().copied().collect();
1124
1125 if let Some(prev_key) = prev_route_key.as_ref() {
1132 for &child_id in &old_children {
1133 ctx.patches.push(Patch::detach(child_id));
1139 if let Some(child) = ctx.tree.get_mut(child_id) {
1140 child.parent = None;
1141 }
1142 }
1143 if !old_children.is_empty() {
1144 cache.shift_remove(prev_key);
1147 cache.insert(prev_key.clone(), old_children);
1148 }
1149 } else {
1150 for &old_child_id in &old_children {
1151 remove_subtree(ctx.tree, old_child_id, ctx.patches, ctx.dependencies);
1152 }
1153 }
1154
1155 if let Some(router_node) = ctx.tree.get_mut(node_id) {
1156 router_node.children.clear();
1157 }
1158
1159 while cache.len() > max_cache_size {
1163 let evicted_key = cache.keys().next().cloned();
1164 if let Some(evicted_key) = evicted_key {
1165 if let Some(evicted_ids) = cache.shift_remove(&evicted_key) {
1166 for evicted_id in evicted_ids {
1167 remove_subtree(
1168 ctx.tree,
1169 evicted_id,
1170 ctx.patches,
1171 ctx.dependencies,
1172 );
1173 }
1174 }
1175 } else {
1176 break;
1177 }
1178 }
1179
1180 if let Some(new_key) = new_route_key.as_ref() {
1184 if let Some(cached_ids) = cache.shift_remove(new_key) {
1185 for cached_id in &cached_ids {
1190 if let Some(child) = ctx.tree.get_mut(*cached_id) {
1191 child.parent = Some(node_id);
1192 }
1193 if let Some(router_node) = ctx.tree.get_mut(node_id) {
1194 router_node.children.push_back(*cached_id);
1195 }
1196 let attach_patch = match render_parent {
1197 Some(rp) => Patch::attach(rp, *cached_id, None),
1198 None => Patch::attach_root(*cached_id, None),
1199 };
1200 ctx.patches.push(attach_patch);
1201 }
1202 } else if let Some((_, children)) = matched.as_ref() {
1203 let children_is_root = render_parent.is_none();
1208 for child in *children {
1209 create_ir_node_tree_full(
1210 ctx,
1211 child,
1212 Some(node_id),
1213 render_parent,
1214 children_is_root,
1215 );
1216 }
1217 }
1218 }
1219
1220 if let Some(router_node) = ctx.tree.get_mut(node_id) {
1225 router_node.control_flow = Some(ControlFlowKind::Router {
1226 cache,
1227 current_route_key: new_route_key,
1228 max_cache_size,
1229 });
1230 }
1231 }
1232 }
1233}
1234
1235fn remove_subtree(
1237 tree: &mut InstanceTree,
1238 node_id: NodeId,
1239 patches: &mut Vec<Patch>,
1240 dependencies: &mut DependencyGraph,
1241) {
1242 let ids = collect_subtree_ids(tree, node_id);
1243 for &id in &ids {
1244 patches.push(Patch::remove(id));
1245 dependencies.remove_node(id);
1246 }
1247 tree.remove(node_id);
1248}
1249
1250#[cfg(test)]
1251mod tests {
1252 use super::*;
1253 use crate::ir::Value;
1254 use serde_json::json;
1255
1256 #[test]
1257 fn test_create_simple_tree() {
1258 use crate::reactive::DependencyGraph;
1259
1260 let mut tree = InstanceTree::new();
1261 let mut patches = Vec::new();
1262 let mut dependencies = DependencyGraph::new();
1263
1264 let element = Element::new("Column")
1265 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1266
1267 let state = json!({});
1268 let mut ctx = ReconcileCtx {
1269 tree: &mut tree,
1270 state: &state,
1271 patches: &mut patches,
1272 dependencies: &mut dependencies,
1273 data_sources: None,
1274 modules: None,
1275 };
1276 create_element_node(&mut ctx, &element, None, None, true);
1277
1278 assert_eq!(patches.len(), 4);
1281
1282 let root_insert = patches
1284 .iter()
1285 .find(|p| matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root"));
1286 assert!(root_insert.is_some(), "Root insert patch should exist");
1287 }
1288
1289 #[test]
1290 fn test_diff_props() {
1291 let node_id = NodeId::default();
1292 let old = indexmap::indexmap! {
1293 "color".to_string() => json!("red"),
1294 "size".to_string() => json!(16),
1295 };
1296 let new = indexmap::indexmap! {
1297 "color".to_string() => json!("blue"),
1298 "size".to_string() => json!(16),
1299 };
1300
1301 let patches = diff_props(node_id, &old, &new);
1302
1303 assert_eq!(patches.len(), 1);
1305 }
1306}