1use super::conditionals::{evaluate_value, find_matching_branch, find_matching_route};
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::{ControlFlowKind, InstanceTree, Patch};
6use crate::ir::{Element, IRNode, NodeId, Props, RouterRoute, Value};
7use crate::reactive::DependencyGraph;
8use indexmap::IndexMap;
9
10type DataSources = indexmap::IndexMap<String, serde_json::Value>;
12
13type Modules = indexmap::IndexMap<String, crate::lifecycle::ModuleInstance>;
15
16pub(crate) struct ReconcileCtx<'a> {
20 pub tree: &'a mut InstanceTree,
21 pub state: &'a serde_json::Value,
22 pub patches: &'a mut Vec<Patch>,
23 pub dependencies: &'a mut DependencyGraph,
24 pub data_sources: Option<&'a DataSources>,
25 pub modules: Option<&'a Modules>,
26}
27
28impl<'a> ReconcileCtx<'a> {
29 fn effective_scope<'s>(&self, raw: Option<&'s str>) -> Option<&'s str> {
37 raw.filter(|scope| {
38 self.modules
39 .map(|m| m.contains_key(*scope))
40 .unwrap_or(false)
41 })
42 }
43
44 fn effective_state(&self, raw_scope: Option<&str>) -> &'a serde_json::Value {
49 match self.effective_scope(raw_scope) {
50 Some(scope) => self
51 .modules
52 .and_then(|m| m.get(scope))
53 .map(|m| m.get_state())
54 .unwrap_or(self.state),
55 None => self.state,
56 }
57 }
58}
59
60pub fn reconcile_ir(
64 tree: &mut InstanceTree,
65 node: &IRNode,
66 parent_id: Option<NodeId>,
67 state: &serde_json::Value,
68 dependencies: &mut DependencyGraph,
69) -> Vec<Patch> {
70 reconcile_ir_with_ds(tree, node, parent_id, state, dependencies, None, None)
71}
72
73pub fn reconcile_ir_with_ds(
75 tree: &mut InstanceTree,
76 node: &IRNode,
77 parent_id: Option<NodeId>,
78 state: &serde_json::Value,
79 dependencies: &mut DependencyGraph,
80 data_sources: Option<&DataSources>,
81 modules: Option<&indexmap::IndexMap<String, crate::lifecycle::ModuleInstance>>,
82) -> Vec<Patch> {
83 let mut patches = Vec::new();
84
85 if tree.root().is_none() {
87 let mut ctx = ReconcileCtx {
88 tree,
89 state,
90 patches: &mut patches,
91 dependencies,
92 data_sources,
93 modules,
94 };
95 let node_id = create_ir_node_tree_impl(&mut ctx, node, parent_id, true);
96 ctx.tree.set_root(node_id);
97 return patches;
98 }
99
100 if let Some(root_id) = tree.root() {
102 let mut ctx = ReconcileCtx {
103 tree,
104 state,
105 patches: &mut patches,
106 dependencies,
107 data_sources,
108 modules,
109 };
110 reconcile_ir_node_impl(&mut ctx, root_id, node);
111 }
112
113 patches
114}
115
116fn create_element_node(
126 ctx: &mut ReconcileCtx,
127 element: &Element,
128 logical_parent: Option<NodeId>,
129 render_parent: Option<NodeId>,
130 is_root: bool,
131) -> NodeId {
132 let module_scope_ref = ctx.effective_scope(element.module_scope.as_deref());
133 let effective_state = ctx.effective_state(element.module_scope.as_deref());
134
135 if logical_parent == render_parent {
140 if let Some(Value::Binding(_)) = element.props.get("0") {
141 if !element.ir_children.is_empty() {
142 return create_list_tree_impl(ctx, element, logical_parent, is_root);
143 }
144 }
145 }
146
147 let node_id = ctx
150 .tree
151 .create_node_full(element, effective_state, ctx.data_sources);
152
153 for value in element.props.values() {
154 match value {
155 Value::Binding(binding) => {
156 ctx.dependencies
157 .add_dependency(node_id, binding, module_scope_ref);
158 }
159 Value::TemplateString { bindings, .. } => {
160 for binding in bindings {
161 ctx.dependencies
162 .add_dependency(node_id, binding, module_scope_ref);
163 }
164 }
165 _ => {}
166 }
167 }
168
169 let is_lazy = element
173 .props
174 .get("__lazy")
175 .and_then(|v| match v {
176 Value::Static(val) => val.as_bool(),
177 _ => None,
178 })
179 .unwrap_or(false);
180
181 let mut props = ctx
182 .tree
183 .get(node_id)
184 .map(|n| n.props.clone())
185 .unwrap_or_default();
186 if is_lazy && !element.ir_children.is_empty() {
187 if let Some(IRNode::Element(first_child)) = element.ir_children.first() {
188 props.insert(
189 "__lazy_child".to_string(),
190 serde_json::json!(first_child.element_type),
191 );
192 }
193 }
194 ctx.patches
195 .push(Patch::create(node_id, element.element_type.clone(), props));
196
197 if let Some(parent) = logical_parent {
199 ctx.tree.add_child(parent, node_id, None);
200 }
201
202 if let Some(rp) = render_parent.or(logical_parent) {
205 ctx.patches.push(Patch::insert(rp, node_id, None));
206 } else if is_root {
207 ctx.patches.push(Patch::insert_root(node_id));
208 }
209
210 if !is_lazy {
214 let old_state = ctx.state;
215 ctx.state = effective_state;
216 for child_ir in &element.ir_children {
217 create_ir_node_tree_impl(ctx, child_ir, Some(node_id), false);
218 }
219 ctx.state = old_state;
220 }
221
222 node_id
223}
224
225fn create_list_tree_impl(
227 ctx: &mut ReconcileCtx,
228 element: &Element,
229 parent_id: Option<NodeId>,
230 is_root: bool,
231) -> NodeId {
232 let module_scope_ref = ctx.effective_scope(element.module_scope.as_deref());
233 let effective_state = ctx.effective_state(element.module_scope.as_deref());
234
235 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
237 evaluate_binding(binding, effective_state).unwrap_or(serde_json::Value::Array(vec![]))
238 } else {
239 serde_json::Value::Array(vec![])
240 };
241
242 let mut list_element = Element::new(&element.element_type);
245 for (key, value) in &element.props {
246 if key != "0" {
247 list_element.props.insert(key.clone(), value.clone());
248 }
249 }
250
251 let node_id = ctx
252 .tree
253 .create_node_full(&list_element, effective_state, ctx.data_sources);
254
255 if let Some(Value::Binding(binding)) = element.props.get("0") {
257 ctx.dependencies
258 .add_dependency(node_id, binding, module_scope_ref);
259 }
260
261 if let Some(node) = ctx.tree.get_mut(node_id) {
263 node.raw_props = element.props.clone();
264 node.element_template = Some(std::sync::Arc::new(element.clone()));
265 }
266
267 let node = ctx.tree.get(node_id).unwrap();
269 ctx.patches.push(Patch::create(
270 node_id,
271 node.element_type.clone(),
272 node.props.clone(),
273 ));
274
275 if let Some(parent) = parent_id {
277 ctx.tree.add_child(parent, node_id, None);
278 ctx.patches.push(Patch::insert(parent, node_id, None));
279 } else if is_root {
280 ctx.patches.push(Patch::insert_root(node_id));
281 }
282
283 if let serde_json::Value::Array(items) = &array {
287 let key_path = element.props.get("key.0").and_then(|v| match v {
288 Value::Static(serde_json::Value::String(s)) => Some(s.as_str()),
289 _ => None,
290 });
291 let multi_template = element.ir_children.len() > 1;
292
293 for (index, item) in items.iter().enumerate() {
294 let item_key = generate_item_key(item, key_path, "item", index);
295
296 for (template_idx, child_ir) in element.ir_children.iter().enumerate() {
297 let child_key = if multi_template {
298 format!("{}#{}", item_key, template_idx)
299 } else {
300 item_key.clone()
301 };
302 let child_with_item = replace_ir_node_item_bindings(
303 child_ir, item, index, "item", &item_key,
304 );
305 let child_id = create_ir_node_tree_impl(
306 ctx,
307 &child_with_item,
308 Some(node_id),
309 false,
310 );
311 if let Some(child_node) = ctx.tree.get_mut(child_id) {
312 child_node.key = Some(child_key);
313 }
314 }
315 }
316 }
317
318 node_id
319}
320
321fn reconcile_element_node(ctx: &mut ReconcileCtx, node_id: NodeId, element: &Element) {
327 let node = match ctx.tree.get(node_id).cloned() {
328 Some(n) => n,
329 None => return,
330 };
331
332 let effective_state = ctx.effective_state(element.module_scope.as_deref());
333 let module_scope_ref = element.module_scope.as_deref();
334
335 let is_iterable = element.props.get("0").is_some() && !element.ir_children.is_empty();
339
340 if is_iterable {
341 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
342 evaluate_binding(binding, effective_state).unwrap_or(serde_json::Value::Array(vec![]))
343 } else {
344 serde_json::Value::Array(vec![])
345 };
346
347 if let serde_json::Value::Array(items) = &array {
348 let key_path = element.props.get("key.0").and_then(|v| match v {
351 Value::Static(serde_json::Value::String(s)) => Some(s.as_str()),
352 _ => None,
353 });
354
355 reconcile_iterable_children(
356 ctx,
357 node_id,
358 items,
359 "item",
360 key_path,
361 &element.ir_children,
362 );
363 }
364
365 return;
366 }
367
368 if node.element_type != element.element_type {
370 replace_subtree_impl(ctx, node_id, &node, element);
371 return;
372 }
373
374 for value in element.props.values() {
376 match value {
377 Value::Binding(binding) => {
378 ctx.dependencies
379 .add_dependency(node_id, binding, module_scope_ref);
380 }
381 Value::TemplateString { bindings, .. } => {
382 for binding in bindings {
383 ctx.dependencies
384 .add_dependency(node_id, binding, module_scope_ref);
385 }
386 }
387 _ => {}
388 }
389 }
390
391 let new_props = resolve_props_full(&element.props, effective_state, None, ctx.data_sources);
393 let prop_patches = diff_props(node_id, &node.props, &new_props);
394 ctx.patches.extend(prop_patches);
395
396 if let Some(node) = ctx.tree.get_mut(node_id) {
397 node.props = new_props.clone();
398 node.raw_props = element.props.clone();
399 }
400
401 let is_lazy = element
404 .props
405 .get("__lazy")
406 .and_then(|v| match v {
407 Value::Static(val) => val.as_bool(),
408 _ => None,
409 })
410 .unwrap_or(false);
411
412 if !is_lazy {
413 let old_children = node.children.clone();
414 let new_children = &element.ir_children;
415
416 for (i, new_child_ir) in new_children.iter().enumerate() {
417 if let Some(&old_child_id) = old_children.get(i) {
418 reconcile_ir_node_impl(ctx, old_child_id, new_child_ir);
419 } else {
420 create_ir_node_tree_impl(ctx, new_child_ir, Some(node_id), false);
421 }
422 }
423
424 if old_children.len() > new_children.len() {
425 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
426 let subtree_ids = collect_subtree_ids(ctx.tree, old_child_id);
427 for &id in &subtree_ids {
428 ctx.patches.push(Patch::remove(id));
429 ctx.dependencies.remove_node(id);
430 }
431 ctx.tree.remove_child(node_id, old_child_id);
432 ctx.tree.remove(old_child_id);
433 }
434 }
435 }
436}
437
438fn replace_subtree_impl(
440 ctx: &mut ReconcileCtx,
441 old_node_id: NodeId,
442 old_node: &super::InstanceNode,
443 new_element: &Element,
444) {
445 let parent_id = old_node.parent;
446
447 let old_position = if let Some(pid) = parent_id {
448 ctx.tree
449 .get(pid)
450 .and_then(|parent| parent.children.iter().position(|&id| id == old_node_id))
451 } else {
452 None
453 };
454
455 let ids_to_remove = collect_subtree_ids(ctx.tree, old_node_id);
456
457 for &id in &ids_to_remove {
458 ctx.patches.push(Patch::remove(id));
459 ctx.dependencies.remove_node(id);
460 }
461
462 if let Some(pid) = parent_id {
463 if let Some(parent) = ctx.tree.get_mut(pid) {
464 parent.children = parent
465 .children
466 .iter()
467 .filter(|&&id| id != old_node_id)
468 .copied()
469 .collect();
470 }
471 }
472
473 ctx.tree.remove(old_node_id);
474
475 let is_root = parent_id.is_none();
476 let new_node_id = create_element_node(ctx, new_element, parent_id, parent_id, is_root);
477
478 if is_root {
479 ctx.tree.set_root(new_node_id);
480 } else if let Some(pid) = parent_id {
481 if let Some(pos) = old_position {
482 if let Some(parent) = ctx.tree.get_mut(pid) {
483 let current_len = parent.children.len();
484 if pos < current_len - 1 {
485 let new_id = parent.children.pop_back().unwrap();
486 parent.children.insert(pos, new_id);
487 let next_sibling = parent.children.get(pos + 1).copied();
488 ctx.patches
489 .push(Patch::move_node(pid, new_node_id, next_sibling));
490 }
491 }
492 }
493 }
494}
495
496fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
498 let mut result = Vec::new();
499 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
500
501 while let Some((node_id, children_processed)) = stack.pop() {
502 if children_processed {
503 result.push(node_id);
504 } else {
505 stack.push((node_id, true));
506 if let Some(node) = tree.get(node_id) {
507 for &child_id in node.children.iter().rev() {
508 stack.push((child_id, false));
509 }
510 }
511 }
512 }
513
514 result
515}
516
517pub fn diff_props(
519 node_id: NodeId,
520 old_props: &IndexMap<String, serde_json::Value>,
521 new_props: &IndexMap<String, serde_json::Value>,
522) -> Vec<Patch> {
523 let mut patches = Vec::new();
524
525 for (key, new_value) in new_props {
526 if old_props.get(key) != Some(new_value) {
527 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
528 }
529 }
530
531 for key in old_props.keys() {
532 if !new_props.contains_key(key) {
533 patches.push(Patch::remove_prop(node_id, key.clone()));
534 }
535 }
536
537 patches
538}
539
540pub(crate) fn create_ir_node_tree_impl(
549 ctx: &mut ReconcileCtx,
550 node: &IRNode,
551 parent_id: Option<NodeId>,
552 is_root: bool,
553) -> NodeId {
554 create_ir_node_tree_full(ctx, node, parent_id, parent_id, is_root)
555}
556
557fn create_ir_node_tree_full(
566 ctx: &mut ReconcileCtx,
567 node: &IRNode,
568 logical_parent: Option<NodeId>,
569 render_parent: Option<NodeId>,
570 is_root: bool,
571) -> NodeId {
572 match node {
573 IRNode::Element(element) => {
574 create_element_node(ctx, element, logical_parent, render_parent, is_root)
575 }
576 IRNode::ForEach { .. } | IRNode::Conditional { .. } | IRNode::Router { .. } => {
577 create_control_flow_tree(ctx, node, logical_parent, is_root)
580 }
581 }
582}
583
584fn create_control_flow_tree(
586 ctx: &mut ReconcileCtx,
587 node: &IRNode,
588 parent_id: Option<NodeId>,
589 is_root: bool,
590) -> NodeId {
591 match node {
592 IRNode::ForEach { .. } => create_foreach_ir_tree(
593 ctx,
594 node,
595 parent_id,
596 is_root,
597 ),
598 IRNode::Conditional {
599 value,
600 branches,
601 fallback,
602 ..
603 } => create_conditional_tree(
604 ctx,
605 value,
606 branches,
607 fallback.as_deref(),
608 node,
609 parent_id,
610 is_root,
611 ),
612 IRNode::Router {
613 location,
614 routes,
615 fallback,
616 ..
617 } => create_router_tree(
618 ctx,
619 location,
620 routes,
621 fallback.as_deref(),
622 node,
623 parent_id,
624 is_root,
625 ),
626 IRNode::Element(_) => unreachable!("create_control_flow_tree called with Element"),
627 }
628}
629
630fn create_foreach_ir_tree(
632 ctx: &mut ReconcileCtx,
633 node: &IRNode,
634 parent_id: Option<NodeId>,
635 is_root: bool,
636) -> NodeId {
637 let (source, item_name, key_path, template, props, raw_scope) = match node {
638 IRNode::ForEach {
639 source,
640 item_name,
641 key_path,
642 template,
643 props,
644 module_scope,
645 } => (
646 source,
647 item_name.as_str(),
648 key_path.as_deref(),
649 template.as_slice(),
650 props,
651 module_scope.as_deref(),
652 ),
653 _ => unreachable!("create_foreach_ir_tree called with non-ForEach node"),
654 };
655
656 let module_scope_ref = ctx.effective_scope(raw_scope);
658 let effective_state = ctx.effective_state(raw_scope);
659
660 let array =
661 evaluate_binding(source, effective_state).unwrap_or(serde_json::Value::Array(vec![]));
662
663 let resolved_props = resolve_props_full(props, effective_state, None, ctx.data_sources);
664
665 let node_id = ctx.tree.create_control_flow_node(
666 "__ForEach",
667 resolved_props.clone(),
668 props.clone(),
669 ControlFlowKind::ForEach {
670 item_name: item_name.to_string(),
671 key_path: key_path.map(|s| s.to_string()),
672 },
673 node.clone(),
674 );
675
676 ctx.dependencies
677 .add_dependency(node_id, source, module_scope_ref);
678
679 if let Some(parent) = parent_id {
680 ctx.tree.add_child(parent, node_id, None);
681 }
682
683 let render_parent = parent_id;
684
685 if let serde_json::Value::Array(items) = &array {
686 for (index, item) in items.iter().enumerate() {
687 let item_key = generate_item_key(item, key_path, item_name, index);
688
689 for child_template in template {
690 let child_with_item = replace_ir_node_item_bindings(
691 child_template,
692 item,
693 index,
694 item_name,
695 &item_key,
696 );
697 create_ir_node_tree_full(
698 ctx,
699 &child_with_item,
700 Some(node_id),
701 render_parent,
702 is_root && render_parent.is_none(),
703 );
704 }
705 }
706 }
707
708 node_id
709}
710
711fn create_conditional_tree(
713 ctx: &mut ReconcileCtx,
714 value: &Value,
715 branches: &[crate::ir::ConditionalBranch],
716 fallback: Option<&[IRNode]>,
717 original_node: &IRNode,
718 parent_id: Option<NodeId>,
719 is_root: bool,
720) -> NodeId {
721 let raw_scope = match original_node {
722 IRNode::Conditional { module_scope, .. } => module_scope.as_deref(),
723 _ => None,
724 };
725 let module_scope_ref = ctx.effective_scope(raw_scope);
726 let effective_state = ctx.effective_state(raw_scope);
727
728 let evaluated_value = evaluate_value(value, effective_state, ctx.data_sources);
729
730 let mut raw_props = Props::new();
731 raw_props.insert("__condition".to_string(), value.clone());
732
733 let node_id = ctx.tree.create_control_flow_node(
734 "__Conditional",
735 IndexMap::new(),
736 raw_props,
737 ControlFlowKind::Conditional,
738 original_node.clone(),
739 );
740
741 if let Value::Binding(binding) = value {
742 ctx.dependencies
743 .add_dependency(node_id, binding, module_scope_ref);
744 } else if let Value::TemplateString { bindings, .. } = value {
745 for binding in bindings {
746 ctx.dependencies
747 .add_dependency(node_id, binding, module_scope_ref);
748 }
749 }
750
751 if let Some(parent) = parent_id {
752 ctx.tree.add_child(parent, node_id, None);
753 }
754
755 let matched_children =
756 find_matching_branch(&evaluated_value, branches, fallback, effective_state, ctx.data_sources);
757
758 let render_parent = parent_id;
759
760 if let Some(children) = matched_children {
761 for child in children {
762 create_ir_node_tree_full(
763 ctx,
764 child,
765 Some(node_id),
766 render_parent,
767 is_root && render_parent.is_none(),
768 );
769 }
770 }
771
772 node_id
773}
774
775fn create_router_tree(
783 ctx: &mut ReconcileCtx,
784 location: &Value,
785 routes: &[RouterRoute],
786 fallback: Option<&[IRNode]>,
787 original_node: &IRNode,
788 parent_id: Option<NodeId>,
789 is_root: bool,
790) -> NodeId {
791 let raw_scope = match original_node {
792 IRNode::Router { module_scope, .. } => module_scope.as_deref(),
793 _ => None,
794 };
795 let module_scope_ref = ctx.effective_scope(raw_scope);
796 let effective_state = ctx.effective_state(raw_scope);
797
798 let evaluated = evaluate_value(location, effective_state, ctx.data_sources);
801 let location_str = match &evaluated {
802 serde_json::Value::String(s) => s.clone(),
803 serde_json::Value::Null => String::new(),
804 other => other.to_string(),
805 };
806
807 let mut raw_props = Props::new();
808 raw_props.insert("__location".to_string(), location.clone());
809
810 let node_id = ctx.tree.create_control_flow_node(
811 "__Router",
812 IndexMap::new(),
813 raw_props,
814 ControlFlowKind::Router,
815 original_node.clone(),
816 );
817
818 if let Value::Binding(binding) = location {
821 ctx.dependencies
822 .add_dependency(node_id, binding, module_scope_ref);
823 } else if let Value::TemplateString { bindings, .. } = location {
824 for binding in bindings {
825 ctx.dependencies
826 .add_dependency(node_id, binding, module_scope_ref);
827 }
828 }
829
830 if let Some(parent) = parent_id {
831 ctx.tree.add_child(parent, node_id, None);
832 }
833
834 let matched_children = find_matching_route(&location_str, routes, fallback);
835 let render_parent = parent_id;
836
837 if let Some(children) = matched_children {
838 for child in children {
839 create_ir_node_tree_full(
840 ctx,
841 child,
842 Some(node_id),
843 render_parent,
844 is_root && render_parent.is_none(),
845 );
846 }
847 }
848
849 node_id
850}
851
852pub(crate) fn reconcile_ir_node_impl(ctx: &mut ReconcileCtx, node_id: NodeId, node: &IRNode) {
854 let existing_node = ctx.tree.get(node_id).cloned();
855 if existing_node.is_none() {
856 return;
857 }
858 let existing = existing_node.unwrap();
859
860 match node {
861 IRNode::Element(element) => {
862 reconcile_element_node(ctx, node_id, element);
863 }
864 IRNode::ForEach {
865 source,
866 item_name,
867 key_path,
868 template,
869 props: _,
870 module_scope,
871 } => {
872 if !existing.is_foreach() {
873 let parent_id = existing.parent;
874 remove_subtree(ctx.tree, node_id, ctx.patches, ctx.dependencies);
875 create_ir_node_tree_impl(ctx, node, parent_id, parent_id.is_none());
876 return;
877 }
878
879 let module_scope_ref = ctx.effective_scope(module_scope.as_deref());
880 let effective_state = ctx.effective_state(module_scope.as_deref());
881
882 ctx.dependencies
883 .add_dependency(node_id, source, module_scope_ref);
884
885 let array = evaluate_binding(source, effective_state)
886 .unwrap_or(serde_json::Value::Array(vec![]));
887
888 if let serde_json::Value::Array(items) = &array {
889 let old_children = existing.children.clone();
890 let expected_children_count = items.len() * template.len();
891 let render_parent = existing.parent.unwrap_or(node_id);
892
893 if old_children.len() != expected_children_count {
894 for &old_child_id in &old_children {
895 ctx.patches.push(Patch::remove(old_child_id));
896 }
897
898 if let Some(node) = ctx.tree.get_mut(node_id) {
899 node.children.clear();
900 }
901
902 for (index, item) in items.iter().enumerate() {
903 let item_key =
904 generate_item_key(item, key_path.as_deref(), item_name, index);
905
906 for child_template in template {
907 let child_with_item = replace_ir_node_item_bindings(
908 child_template,
909 item,
910 index,
911 item_name,
912 &item_key,
913 );
914 create_ir_node_tree_impl(
915 ctx,
916 &child_with_item,
917 Some(render_parent),
918 false,
919 );
920 }
921 }
922 } else {
923 let mut child_index = 0;
924 for (item_index, item) in items.iter().enumerate() {
925 let item_key =
926 generate_item_key(item, key_path.as_deref(), item_name, item_index);
927
928 for child_template in template {
929 if let Some(&old_child_id) = old_children.get(child_index) {
930 let child_with_item = replace_ir_node_item_bindings(
931 child_template,
932 item,
933 item_index,
934 item_name,
935 &item_key,
936 );
937 reconcile_ir_node_impl(ctx, old_child_id, &child_with_item);
938 }
939 child_index += 1;
940 }
941 }
942 }
943 }
944 }
945 IRNode::Conditional {
946 value,
947 branches,
948 fallback,
949 module_scope,
950 } => {
951 if !existing.is_conditional() {
952 let parent_id = existing.parent;
953 remove_subtree(ctx.tree, node_id, ctx.patches, ctx.dependencies);
954 create_ir_node_tree_impl(ctx, node, parent_id, parent_id.is_none());
955 return;
956 }
957
958 let module_scope_ref = ctx.effective_scope(module_scope.as_deref());
959 let effective_state = ctx.effective_state(module_scope.as_deref());
960
961 if let Value::Binding(binding) = value {
962 ctx.dependencies
963 .add_dependency(node_id, binding, module_scope_ref);
964 } else if let Value::TemplateString { bindings, .. } = value {
965 for binding in bindings {
966 ctx.dependencies
967 .add_dependency(node_id, binding, module_scope_ref);
968 }
969 }
970
971 let evaluated_value = evaluate_value(value, effective_state, ctx.data_sources);
972 let matched_children = find_matching_branch(
973 &evaluated_value,
974 branches,
975 fallback.as_deref(),
976 effective_state,
977 ctx.data_sources,
978 );
979
980 let old_children = existing.children.clone();
981 let old_len = old_children.len();
982 let render_parent = existing.parent;
983
984 if let Some(children) = matched_children {
985 let new_len = children.len();
986 let common = old_len.min(new_len);
987
988 for (i, child) in children.iter().enumerate().take(common) {
989 if let Some(&old_child_id) = old_children.get(i) {
990 reconcile_ir_node_impl(ctx, old_child_id, child);
991 }
992 }
993
994 for i in common..old_len {
995 if let Some(&old_child_id) = old_children.get(i) {
996 remove_subtree(ctx.tree, old_child_id, ctx.patches, ctx.dependencies);
997 if let Some(cond_node) = ctx.tree.get_mut(node_id) {
998 cond_node.children = cond_node
999 .children
1000 .iter()
1001 .filter(|&&id| id != old_child_id)
1002 .copied()
1003 .collect();
1004 }
1005 }
1006 }
1007
1008 for child in &children[common..] {
1009 create_ir_node_tree_full(
1010 ctx,
1011 child,
1012 Some(node_id),
1013 render_parent,
1014 false,
1015 );
1016 }
1017 } else {
1018 for &old_child_id in &old_children {
1019 remove_subtree(ctx.tree, old_child_id, ctx.patches, ctx.dependencies);
1020 }
1021
1022 if let Some(cond_node) = ctx.tree.get_mut(node_id) {
1023 cond_node.children.clear();
1024 }
1025 }
1026 }
1027 IRNode::Router {
1028 location,
1029 routes,
1030 fallback,
1031 module_scope,
1032 } => {
1033 if !existing.is_router() {
1035 let parent_id = existing.parent;
1036 remove_subtree(ctx.tree, node_id, ctx.patches, ctx.dependencies);
1037 create_ir_node_tree_impl(ctx, node, parent_id, parent_id.is_none());
1038 return;
1039 }
1040
1041 let module_scope_ref = ctx.effective_scope(module_scope.as_deref());
1042 let effective_state = ctx.effective_state(module_scope.as_deref());
1043
1044 if let Value::Binding(binding) = location {
1046 ctx.dependencies
1047 .add_dependency(node_id, binding, module_scope_ref);
1048 } else if let Value::TemplateString { bindings, .. } = location {
1049 for binding in bindings {
1050 ctx.dependencies
1051 .add_dependency(node_id, binding, module_scope_ref);
1052 }
1053 }
1054
1055 let evaluated = evaluate_value(location, effective_state, ctx.data_sources);
1056 let location_str = match &evaluated {
1057 serde_json::Value::String(s) => s.clone(),
1058 serde_json::Value::Null => String::new(),
1059 other => other.to_string(),
1060 };
1061
1062 let matched_children =
1063 find_matching_route(&location_str, routes, fallback.as_deref());
1064
1065 let old_children = existing.children.clone();
1077 for &old_child_id in &old_children {
1078 remove_subtree(ctx.tree, old_child_id, ctx.patches, ctx.dependencies);
1079 }
1080
1081 if let Some(router_node) = ctx.tree.get_mut(node_id) {
1082 router_node.children.clear();
1083 }
1084
1085 let render_parent = existing.parent;
1086
1087 if let Some(children) = matched_children {
1088 for child in children {
1089 create_ir_node_tree_full(
1090 ctx,
1091 child,
1092 Some(node_id),
1093 render_parent,
1094 false,
1095 );
1096 }
1097 }
1098 }
1099 }
1100}
1101
1102fn remove_subtree(
1104 tree: &mut InstanceTree,
1105 node_id: NodeId,
1106 patches: &mut Vec<Patch>,
1107 dependencies: &mut DependencyGraph,
1108) {
1109 let ids = collect_subtree_ids(tree, node_id);
1110 for &id in &ids {
1111 patches.push(Patch::remove(id));
1112 dependencies.remove_node(id);
1113 }
1114 tree.remove(node_id);
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119 use super::*;
1120 use crate::ir::Value;
1121 use serde_json::json;
1122
1123 #[test]
1124 fn test_create_simple_tree() {
1125 use crate::reactive::DependencyGraph;
1126
1127 let mut tree = InstanceTree::new();
1128 let mut patches = Vec::new();
1129 let mut dependencies = DependencyGraph::new();
1130
1131 let element = Element::new("Column")
1132 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1133
1134 let state = json!({});
1135 let mut ctx = ReconcileCtx {
1136 tree: &mut tree,
1137 state: &state,
1138 patches: &mut patches,
1139 dependencies: &mut dependencies,
1140 data_sources: None,
1141 modules: None,
1142 };
1143 create_element_node(&mut ctx, &element, None, None, true);
1144
1145 assert_eq!(patches.len(), 4);
1148
1149 let root_insert = patches
1151 .iter()
1152 .find(|p| matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root"));
1153 assert!(root_insert.is_some(), "Root insert patch should exist");
1154 }
1155
1156 #[test]
1157 fn test_diff_props() {
1158 let node_id = NodeId::default();
1159 let old = indexmap::indexmap! {
1160 "color".to_string() => json!("red"),
1161 "size".to_string() => json!(16),
1162 };
1163 let new = indexmap::indexmap! {
1164 "color".to_string() => json!("blue"),
1165 "size".to_string() => json!(16),
1166 };
1167
1168 let patches = diff_props(node_id, &old, &new);
1169
1170 assert_eq!(patches.len(), 1);
1172 }
1173}