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