1use super::conditionals::{evaluate_value, find_matching_branch};
2use super::item_bindings::{replace_ir_node_item_bindings, replace_item_bindings};
3use super::keyed::generate_item_key;
4use super::resolve::{evaluate_binding, resolve_props_full};
5use super::{ControlFlowKind, InstanceTree, Patch};
6use crate::ir::{ConditionalBranch, Element, IRNode, NodeId, Props, Value};
7use crate::reactive::{Binding, DependencyGraph};
8use indexmap::IndexMap;
9
10type DataSources = indexmap::IndexMap<String, serde_json::Value>;
12
13struct ReconcileCtx<'a> {
17 tree: &'a mut InstanceTree,
18 state: &'a serde_json::Value,
19 patches: &'a mut Vec<Patch>,
20 dependencies: &'a mut DependencyGraph,
21 data_sources: Option<&'a DataSources>,
22}
23
24pub fn reconcile(
26 tree: &mut InstanceTree,
27 element: &Element,
28 parent_id: Option<NodeId>,
29 state: &serde_json::Value,
30 dependencies: &mut DependencyGraph,
31) -> Vec<Patch> {
32 reconcile_with_ds(tree, element, parent_id, state, dependencies, None)
33}
34
35pub fn reconcile_with_ds(
37 tree: &mut InstanceTree,
38 element: &Element,
39 parent_id: Option<NodeId>,
40 state: &serde_json::Value,
41 dependencies: &mut DependencyGraph,
42 data_sources: Option<&DataSources>,
43) -> Vec<Patch> {
44 let mut patches = Vec::new();
45
46 if tree.root().is_none() {
48 let node_id = create_tree(
49 tree,
50 element,
51 parent_id,
52 state,
53 &mut patches,
54 true,
55 dependencies,
56 data_sources,
57 );
58 tree.set_root(node_id);
59 return patches;
60 }
61
62 if let Some(root_id) = tree.root() {
64 reconcile_node(tree, root_id, element, state, &mut patches, dependencies, data_sources);
65 }
66
67 patches
68}
69
70pub fn reconcile_ir(
74 tree: &mut InstanceTree,
75 node: &IRNode,
76 parent_id: Option<NodeId>,
77 state: &serde_json::Value,
78 dependencies: &mut DependencyGraph,
79) -> Vec<Patch> {
80 reconcile_ir_with_ds(tree, node, parent_id, state, dependencies, None)
81}
82
83pub fn reconcile_ir_with_ds(
85 tree: &mut InstanceTree,
86 node: &IRNode,
87 parent_id: Option<NodeId>,
88 state: &serde_json::Value,
89 dependencies: &mut DependencyGraph,
90 data_sources: Option<&DataSources>,
91) -> Vec<Patch> {
92 let mut patches = Vec::new();
93
94 if tree.root().is_none() {
96 let node_id = create_ir_node_tree(
97 tree,
98 node,
99 parent_id,
100 state,
101 &mut patches,
102 true,
103 dependencies,
104 data_sources,
105 );
106 tree.set_root(node_id);
107 return patches;
108 }
109
110 if let Some(root_id) = tree.root() {
112 reconcile_ir_node(tree, root_id, node, state, &mut patches, dependencies, data_sources);
113 }
114
115 patches
116}
117
118#[allow(clippy::too_many_arguments)]
120pub fn create_tree(
121 tree: &mut InstanceTree,
122 element: &Element,
123 parent_id: Option<NodeId>,
124 state: &serde_json::Value,
125 patches: &mut Vec<Patch>,
126 is_root: bool,
127 dependencies: &mut DependencyGraph,
128 data_sources: Option<&DataSources>,
129) -> NodeId {
130 if let Some(Value::Binding(_)) = element.props.get("0") {
138 if !element.children.is_empty() {
139 return create_list_tree(
140 tree,
141 element,
142 parent_id,
143 state,
144 patches,
145 is_root,
146 dependencies,
147 data_sources,
148 );
149 }
150 }
151
152 let node_id = tree.create_node_full(element, state, data_sources);
154
155 for value in element.props.values() {
157 match value {
158 Value::Binding(binding) => {
159 dependencies.add_dependency(node_id, binding);
160 }
161 Value::TemplateString { bindings, .. } => {
162 for binding in bindings {
164 dependencies.add_dependency(node_id, binding);
165 }
166 }
167 _ => {}
168 }
169 }
170
171 let node = tree.get(node_id).unwrap();
173
174 let mut props = node.props.clone();
176 if let Some(Value::Static(val)) = element.props.get("__lazy") {
177 if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
178 let child_component = &element.children[0].element_type;
180 props.insert(
181 "__lazy_child".to_string(),
182 serde_json::json!(child_component),
183 );
184 }
185 }
186
187 patches.push(Patch::create(node_id, node.element_type.clone(), props));
188
189 if let Some(parent) = parent_id {
193 tree.add_child(parent, node_id, None);
194 patches.push(Patch::insert(parent, node_id, None));
195 } else if is_root {
196 patches.push(Patch::insert_root(node_id));
198 }
199
200 let is_lazy = element
202 .props
203 .get("__lazy")
204 .and_then(|v| {
205 if let Value::Static(val) = v {
206 val.as_bool()
207 } else {
208 None
209 }
210 })
211 .unwrap_or(false);
212
213 if !is_lazy {
214 for child_element in &element.children {
215 create_tree(
216 tree,
217 child_element,
218 Some(node_id),
219 state,
220 patches,
221 false,
222 dependencies,
223 data_sources,
224 );
225 }
226 }
227
228 node_id
229}
230
231#[allow(clippy::too_many_arguments)]
234fn create_list_tree(
235 tree: &mut InstanceTree,
236 element: &Element,
237 parent_id: Option<NodeId>,
238 state: &serde_json::Value,
239 patches: &mut Vec<Patch>,
240 is_root: bool,
241 dependencies: &mut DependencyGraph,
242 data_sources: Option<&DataSources>,
243) -> NodeId {
244 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
246 evaluate_binding(binding, 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" {
257 list_element.props.insert(key.clone(), value.clone());
258 }
259 }
260
261 let node_id = tree.create_node_full(&list_element, state, data_sources);
262
263 if let Some(Value::Binding(binding)) = element.props.get("0") {
266 dependencies.add_dependency(node_id, binding);
267 }
268
269 if let Some(node) = 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 = tree.get(node_id).unwrap();
278 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 tree.add_child(parent, node_id, None);
287 patches.push(Patch::insert(parent, node_id, None));
288 } else if is_root {
289 patches.push(Patch::insert_root(node_id));
290 }
291
292 if let serde_json::Value::Array(items) = &array {
294 for (index, item) in items.iter().enumerate() {
296 for child_template in &element.children {
297 let child_with_item = replace_item_bindings(child_template, item, index);
299 create_tree(
300 tree,
301 &child_with_item,
302 Some(node_id),
303 state,
304 patches,
305 false,
306 dependencies,
307 data_sources,
308 );
309 }
310 }
311 }
312
313 node_id
314}
315
316pub fn reconcile_node(
318 tree: &mut InstanceTree,
319 node_id: NodeId,
320 element: &Element,
321 state: &serde_json::Value,
322 patches: &mut Vec<Patch>,
323 dependencies: &mut DependencyGraph,
324 data_sources: Option<&DataSources>,
325) {
326 let node = tree.get(node_id).cloned();
327 if node.is_none() {
328 return;
329 }
330 let node = node.unwrap();
331
332 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
336
337 if is_iterable {
338 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
340 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
341 } else {
342 serde_json::Value::Array(vec![])
343 };
344
345 if let serde_json::Value::Array(items) = &array {
347 let old_children = node.children.clone();
348
349 let expected_children_count = items.len() * element.children.len();
351
352 if old_children.len() != expected_children_count {
354 for &old_child_id in &old_children {
355 patches.push(Patch::remove(old_child_id));
356 }
357
358 if let Some(node) = tree.get_mut(node_id) {
360 node.children.clear();
361 }
362
363 for (index, item) in items.iter().enumerate() {
365 for child_template in &element.children {
366 let child_with_item = replace_item_bindings(child_template, item, index);
367 create_tree(
368 tree,
369 &child_with_item,
370 Some(node_id),
371 state,
372 patches,
373 false,
374 dependencies,
375 data_sources,
376 );
377 }
378 }
379 } else {
380 let mut child_index = 0;
382 for (item_index, item) in items.iter().enumerate() {
383 for child_template in &element.children {
384 if let Some(&old_child_id) = old_children.get(child_index) {
385 let child_with_item =
386 replace_item_bindings(child_template, item, item_index);
387 reconcile_node(
388 tree,
389 old_child_id,
390 &child_with_item,
391 state,
392 patches,
393 dependencies,
394 data_sources,
395 );
396 }
397 child_index += 1;
398 }
399 }
400 }
401 }
402
403 return; }
405
406 if node.element_type != element.element_type {
408 replace_subtree(tree, node_id, &node, element, state, patches, dependencies, data_sources);
409 return;
410 }
411
412 for value in element.props.values() {
414 match value {
415 Value::Binding(binding) => {
416 dependencies.add_dependency(node_id, binding);
417 }
418 Value::TemplateString { bindings, .. } => {
419 for binding in bindings {
420 dependencies.add_dependency(node_id, binding);
421 }
422 }
423 _ => {}
424 }
425 }
426
427 let new_props = resolve_props_full(&element.props, state, None, data_sources);
429 let prop_patches = diff_props(node_id, &node.props, &new_props);
430 patches.extend(prop_patches);
431
432 if let Some(node) = tree.get_mut(node_id) {
434 node.props = new_props.clone();
435 node.raw_props = element.props.clone();
436 }
437
438 let is_lazy = element
440 .props
441 .get("__lazy")
442 .and_then(|v| {
443 if let Value::Static(val) = v {
444 val.as_bool()
445 } else {
446 None
447 }
448 })
449 .unwrap_or(false);
450
451 if !is_lazy {
452 let old_children = node.children.clone();
453 let new_children = &element.children;
454
455 for (i, new_child_element) in new_children.iter().enumerate() {
457 if let Some(&old_child_id) = old_children.get(i) {
458 reconcile_node(
460 tree,
461 old_child_id,
462 new_child_element,
463 state,
464 patches,
465 dependencies,
466 data_sources,
467 );
468 } else {
469 let new_child_id = create_tree(
471 tree,
472 new_child_element,
473 Some(node_id),
474 state,
475 patches,
476 false,
477 dependencies,
478 data_sources,
479 );
480 if let Some(node) = tree.get_mut(node_id) {
481 node.children.push_back(new_child_id);
482 }
483 }
484 }
485
486 if old_children.len() > new_children.len() {
488 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
489 let subtree_ids = collect_subtree_ids(tree, old_child_id);
491 for &id in &subtree_ids {
492 patches.push(Patch::remove(id));
493 dependencies.remove_node(id);
494 }
495 tree.remove_child(node_id, old_child_id);
497 tree.remove(old_child_id);
498 }
499 }
500 }
501}
502
503fn reconcile_element_with_ir_children(
507 tree: &mut InstanceTree,
508 node_id: NodeId,
509 element: &Element,
510 state: &serde_json::Value,
511 patches: &mut Vec<Patch>,
512 dependencies: &mut DependencyGraph,
513 data_sources: Option<&DataSources>,
514) {
515 let node = tree.get(node_id).cloned();
516 if node.is_none() {
517 return;
518 }
519 let node = node.unwrap();
520
521 if node.element_type != element.element_type {
523 replace_subtree(tree, node_id, &node, element, state, patches, dependencies, data_sources);
524 return;
525 }
526
527 for value in element.props.values() {
529 match value {
530 Value::Binding(binding) => {
531 dependencies.add_dependency(node_id, binding);
532 }
533 Value::TemplateString { bindings, .. } => {
534 for binding in bindings {
535 dependencies.add_dependency(node_id, binding);
536 }
537 }
538 _ => {}
539 }
540 }
541
542 let new_props = resolve_props_full(&element.props, state, None, data_sources);
544 let prop_patches = diff_props(node_id, &node.props, &new_props);
545 patches.extend(prop_patches);
546
547 if let Some(n) = tree.get_mut(node_id) {
549 n.props = new_props;
550 n.raw_props = element.props.clone();
551 }
552
553 let old_children = node.children.clone();
555 let new_children = &element.ir_children;
556 let common = old_children.len().min(new_children.len());
557
558 for (i, child_ir) in new_children.iter().enumerate().take(common) {
559 if let Some(&old_child_id) = old_children.get(i) {
560 reconcile_ir_node(tree, old_child_id, child_ir, state, patches, dependencies, data_sources);
561 }
562 }
563
564 for child_ir in &new_children[common..] {
566 create_ir_node_tree(
567 tree,
568 child_ir,
569 Some(node_id),
570 state,
571 patches,
572 false,
573 dependencies,
574 data_sources,
575 );
576 }
577
578 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
580 remove_subtree(tree, old_child_id, patches, dependencies);
581 if let Some(n) = tree.get_mut(node_id) {
582 n.children = n
583 .children
584 .iter()
585 .filter(|&&id| id != old_child_id)
586 .copied()
587 .collect();
588 }
589 }
590}
591
592#[allow(clippy::too_many_arguments)]
595fn replace_subtree(
596 tree: &mut InstanceTree,
597 old_node_id: NodeId,
598 old_node: &super::InstanceNode,
599 new_element: &Element,
600 state: &serde_json::Value,
601 patches: &mut Vec<Patch>,
602 dependencies: &mut DependencyGraph,
603 data_sources: Option<&DataSources>,
604) {
605 let parent_id = old_node.parent;
606
607 let old_position = if let Some(pid) = parent_id {
609 tree.get(pid)
610 .and_then(|parent| parent.children.iter().position(|&id| id == old_node_id))
611 } else {
612 None
613 };
614
615 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
617
618 for &id in &ids_to_remove {
620 patches.push(Patch::remove(id));
621 dependencies.remove_node(id);
622 }
623
624 if let Some(pid) = parent_id {
626 if let Some(parent) = tree.get_mut(pid) {
627 parent.children = parent
628 .children
629 .iter()
630 .filter(|&&id| id != old_node_id)
631 .copied()
632 .collect();
633 }
634 }
635
636 tree.remove(old_node_id);
638
639 let is_root = parent_id.is_none();
641 let new_node_id = create_tree(
642 tree,
643 new_element,
644 parent_id,
645 state,
646 patches,
647 is_root,
648 dependencies,
649 data_sources,
650 );
651
652 if is_root {
654 tree.set_root(new_node_id);
655 } else if let Some(pid) = parent_id {
656 if let Some(pos) = old_position {
657 if let Some(parent) = tree.get_mut(pid) {
658 let current_len = parent.children.len();
659 if pos < current_len - 1 {
662 let new_id = parent.children.pop_back().unwrap();
664 parent.children.insert(pos, new_id);
665
666 let next_sibling = parent.children.get(pos + 1).copied();
668 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
669 }
670 }
672 }
673 }
674}
675
676fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
679 let mut result = Vec::new();
680 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
681
682 while let Some((node_id, children_processed)) = stack.pop() {
683 if children_processed {
684 result.push(node_id);
686 } else {
687 stack.push((node_id, true));
689 if let Some(node) = tree.get(node_id) {
690 for &child_id in node.children.iter().rev() {
692 stack.push((child_id, false));
693 }
694 }
695 }
696 }
697
698 result
699}
700
701pub fn diff_props(
703 node_id: NodeId,
704 old_props: &IndexMap<String, serde_json::Value>,
705 new_props: &IndexMap<String, serde_json::Value>,
706) -> Vec<Patch> {
707 let mut patches = Vec::new();
708
709 for (key, new_value) in new_props {
711 if old_props.get(key) != Some(new_value) {
712 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
713 }
714 }
715
716 for key in old_props.keys() {
718 if !new_props.contains_key(key) {
719 patches.push(Patch::remove_prop(node_id, key.clone()));
720 }
721 }
722
723 patches
724}
725
726#[allow(clippy::too_many_arguments)]
732pub fn create_ir_node_tree(
733 tree: &mut InstanceTree,
734 node: &IRNode,
735 parent_id: Option<NodeId>,
736 state: &serde_json::Value,
737 patches: &mut Vec<Patch>,
738 is_root: bool,
739 dependencies: &mut DependencyGraph,
740 data_sources: Option<&DataSources>,
741) -> NodeId {
742 match node {
743 IRNode::Element(element) => {
744 if element.ir_children.is_empty() {
745 create_tree(
747 tree,
748 element,
749 parent_id,
750 state,
751 patches,
752 is_root,
753 dependencies,
754 data_sources,
755 )
756 } else {
757 create_element_with_ir_children(
759 tree,
760 element,
761 parent_id,
762 state,
763 patches,
764 is_root,
765 dependencies,
766 data_sources,
767 )
768 }
769 }
770 IRNode::ForEach {
771 source,
772 item_name,
773 key_path,
774 template,
775 props,
776 } => {
777 let mut ctx = ReconcileCtx {
778 tree,
779 state,
780 patches,
781 dependencies,
782 data_sources,
783 };
784 create_foreach_ir_tree(
785 &mut ctx,
786 source,
787 item_name,
788 key_path.as_deref(),
789 template,
790 props,
791 node,
792 parent_id,
793 is_root,
794 )
795 }
796 IRNode::Conditional {
797 value,
798 branches,
799 fallback,
800 } => {
801 let mut ctx = ReconcileCtx {
802 tree,
803 state,
804 patches,
805 dependencies,
806 data_sources,
807 };
808 create_conditional_tree(
809 &mut ctx,
810 value,
811 branches,
812 fallback.as_deref(),
813 node,
814 parent_id,
815 is_root,
816 )
817 }
818 }
819}
820
821fn create_element_with_ir_children(
825 tree: &mut InstanceTree,
826 element: &Element,
827 parent_id: Option<NodeId>,
828 state: &serde_json::Value,
829 patches: &mut Vec<Patch>,
830 is_root: bool,
831 dependencies: &mut DependencyGraph,
832 data_sources: Option<&DataSources>,
833) -> NodeId {
834 let node_id = tree.create_node(element, state);
836
837 for value in element.props.values() {
839 match value {
840 Value::Binding(binding) => {
841 dependencies.add_dependency(node_id, binding);
842 }
843 Value::TemplateString { bindings, .. } => {
844 for binding in bindings {
845 dependencies.add_dependency(node_id, binding);
846 }
847 }
848 _ => {}
849 }
850 }
851
852 let node = tree.get(node_id).unwrap();
854 patches.push(Patch::create(
855 node_id,
856 node.element_type.clone(),
857 node.props.clone(),
858 ));
859
860 if let Some(parent) = parent_id {
862 tree.add_child(parent, node_id, None);
863 patches.push(Patch::insert(parent, node_id, None));
864 } else if is_root {
865 patches.push(Patch::insert_root(node_id));
866 }
867
868 for child_ir in &element.ir_children {
870 create_ir_node_tree(
871 tree,
872 child_ir,
873 Some(node_id),
874 state,
875 patches,
876 false,
877 dependencies,
878 data_sources,
879 );
880 }
881
882 node_id
883}
884
885fn create_ir_node_tree_with_render_parent(
894 ctx: &mut ReconcileCtx,
895 node: &IRNode,
896 logical_parent: Option<NodeId>,
897 render_parent: Option<NodeId>,
898 is_root: bool,
899) -> NodeId {
900 match node {
901 IRNode::Element(element) => {
902 let node_id = ctx.tree.create_node_full(element, ctx.state, ctx.data_sources);
904
905 if let Some(parent) = logical_parent {
907 ctx.tree.add_child(parent, node_id, None);
908 }
909
910 ctx.patches.push(Patch::create(
912 node_id,
913 element.element_type.clone(),
914 ctx.tree
915 .get(node_id)
916 .map(|n| n.props.clone())
917 .unwrap_or_default(),
918 ));
919
920 if let Some(render_p) = render_parent {
921 ctx.patches.push(Patch::insert(render_p, node_id, None));
922 } else if is_root {
923 ctx.patches.push(Patch::insert_root(node_id));
924 }
925
926 for (_, value) in &element.props {
928 match value {
929 Value::Binding(binding) => {
930 ctx.dependencies.add_dependency(node_id, binding);
931 }
932 Value::TemplateString { bindings, .. } => {
933 for binding in bindings {
934 ctx.dependencies.add_dependency(node_id, binding);
935 }
936 }
937 _ => {}
938 }
939 }
940
941 let children_source: Vec<IRNode> = if !element.ir_children.is_empty() {
943 element.ir_children.clone()
944 } else {
945 element
946 .children
947 .iter()
948 .map(|child| IRNode::Element((**child).clone()))
949 .collect()
950 };
951 for child_ir in &children_source {
952 create_ir_node_tree(
953 ctx.tree,
954 child_ir,
955 Some(node_id),
956 ctx.state,
957 ctx.patches,
958 false,
959 ctx.dependencies,
960 ctx.data_sources,
961 );
962 }
963
964 node_id
965 }
966 IRNode::ForEach {
967 source,
968 item_name,
969 key_path,
970 template,
971 props,
972 } => {
973 create_foreach_ir_tree(
975 ctx,
976 source,
977 item_name,
978 key_path.as_deref(),
979 template,
980 props,
981 node,
982 logical_parent, is_root,
984 )
985 }
986 IRNode::Conditional {
987 value,
988 branches,
989 fallback,
990 } => {
991 create_conditional_tree(
993 ctx,
994 value,
995 branches,
996 fallback.as_deref(),
997 node,
998 logical_parent, is_root,
1000 )
1001 }
1002 }
1003}
1004
1005#[allow(clippy::too_many_arguments)]
1007fn create_foreach_ir_tree(
1008 ctx: &mut ReconcileCtx,
1009 source: &Binding,
1010 item_name: &str,
1011 key_path: Option<&str>,
1012 template: &[IRNode],
1013 props: &Props,
1014 original_node: &IRNode,
1015 parent_id: Option<NodeId>,
1016 is_root: bool,
1017) -> NodeId {
1018 let array = evaluate_binding(source, ctx.state).unwrap_or(serde_json::Value::Array(vec![]));
1020
1021 let resolved_props = resolve_props_full(props, ctx.state, None, ctx.data_sources);
1023
1024 let node_id = ctx.tree.create_control_flow_node(
1026 "__ForEach",
1027 resolved_props.clone(),
1028 props.clone(),
1029 ControlFlowKind::ForEach {
1030 item_name: item_name.to_string(),
1031 key_path: key_path.map(|s| s.to_string()),
1032 },
1033 original_node.clone(),
1034 );
1035
1036 ctx.dependencies.add_dependency(node_id, source);
1038
1039 if let Some(parent) = parent_id {
1042 ctx.tree.add_child(parent, node_id, None);
1043 }
1044
1045 let render_parent = parent_id; if let serde_json::Value::Array(items) = &array {
1050 for (index, item) in items.iter().enumerate() {
1051 let item_key = generate_item_key(item, key_path, item_name, index);
1052
1053 for child_template in template {
1055 let child_with_item = replace_ir_node_item_bindings(
1056 child_template,
1057 item,
1058 index,
1059 item_name,
1060 &item_key,
1061 );
1062 create_ir_node_tree_with_render_parent(
1064 ctx,
1065 &child_with_item,
1066 Some(node_id), render_parent, is_root && render_parent.is_none(),
1069 );
1070 }
1071 }
1072 }
1073
1074 node_id
1075}
1076
1077fn create_conditional_tree(
1079 ctx: &mut ReconcileCtx,
1080 value: &Value,
1081 branches: &[ConditionalBranch],
1082 fallback: Option<&[IRNode]>,
1083 original_node: &IRNode,
1084 parent_id: Option<NodeId>,
1085 is_root: bool,
1086) -> NodeId {
1087 let evaluated_value = evaluate_value(value, ctx.state, ctx.data_sources);
1089
1090 let mut raw_props = Props::new();
1092 raw_props.insert("__condition".to_string(), value.clone());
1093
1094 let node_id = ctx.tree.create_control_flow_node(
1095 "__Conditional",
1096 IndexMap::new(),
1097 raw_props,
1098 ControlFlowKind::Conditional,
1099 original_node.clone(),
1100 );
1101
1102 if let Value::Binding(binding) = value {
1104 ctx.dependencies.add_dependency(node_id, binding);
1105 } else if let Value::TemplateString { bindings, .. } = value {
1106 for binding in bindings {
1107 ctx.dependencies.add_dependency(node_id, binding);
1108 }
1109 }
1110
1111 if let Some(parent) = parent_id {
1114 ctx.tree.add_child(parent, node_id, None);
1115 }
1116
1117 let matched_children =
1119 find_matching_branch(&evaluated_value, branches, fallback, ctx.state, ctx.data_sources);
1120
1121 let render_parent = parent_id; if let Some(children) = matched_children {
1125 for child in children {
1126 create_ir_node_tree_with_render_parent(
1128 ctx,
1129 child,
1130 Some(node_id), render_parent, is_root && render_parent.is_none(),
1133 );
1134 }
1135 }
1136
1137 node_id
1138}
1139
1140pub fn reconcile_ir_node(
1142 tree: &mut InstanceTree,
1143 node_id: NodeId,
1144 node: &IRNode,
1145 state: &serde_json::Value,
1146 patches: &mut Vec<Patch>,
1147 dependencies: &mut DependencyGraph,
1148 data_sources: Option<&DataSources>,
1149) {
1150 let existing_node = tree.get(node_id).cloned();
1151 if existing_node.is_none() {
1152 return;
1153 }
1154 let existing = existing_node.unwrap();
1155
1156 match node {
1157 IRNode::Element(element) => {
1158 if element.ir_children.is_empty() {
1159 reconcile_node(tree, node_id, element, state, patches, dependencies, data_sources);
1161 } else {
1162 reconcile_element_with_ir_children(
1164 tree, node_id, element, state, patches, dependencies, data_sources,
1165 );
1166 }
1167 }
1168 IRNode::ForEach {
1169 source,
1170 item_name,
1171 key_path,
1172 template,
1173 props: _,
1174 } => {
1175 if !existing.is_foreach() {
1177 let parent_id = existing.parent;
1179 remove_subtree(tree, node_id, patches, dependencies);
1180 create_ir_node_tree(
1181 tree,
1182 node,
1183 parent_id,
1184 state,
1185 patches,
1186 parent_id.is_none(),
1187 dependencies,
1188 data_sources,
1189 );
1190 return;
1191 }
1192
1193 dependencies.add_dependency(node_id, source);
1195
1196 let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1198
1199 if let serde_json::Value::Array(items) = &array {
1200 let old_children = existing.children.clone();
1201 let expected_children_count = items.len() * template.len();
1202
1203 if old_children.len() != expected_children_count {
1205 for &old_child_id in &old_children {
1207 patches.push(Patch::remove(old_child_id));
1208 }
1209
1210 if let Some(node) = tree.get_mut(node_id) {
1211 node.children.clear();
1212 }
1213
1214 for (index, item) in items.iter().enumerate() {
1216 let item_key =
1217 generate_item_key(item, key_path.as_deref(), item_name, index);
1218
1219 for child_template in template {
1220 let child_with_item = replace_ir_node_item_bindings(
1221 child_template,
1222 item,
1223 index,
1224 item_name,
1225 &item_key,
1226 );
1227 create_ir_node_tree(
1228 tree,
1229 &child_with_item,
1230 Some(node_id),
1231 state,
1232 patches,
1233 false,
1234 dependencies,
1235 data_sources,
1236 );
1237 }
1238 }
1239 } else {
1240 let mut child_index = 0;
1242 for (item_index, item) in items.iter().enumerate() {
1243 let item_key =
1244 generate_item_key(item, key_path.as_deref(), item_name, item_index);
1245
1246 for child_template in template {
1247 if let Some(&old_child_id) = old_children.get(child_index) {
1248 let child_with_item = replace_ir_node_item_bindings(
1249 child_template,
1250 item,
1251 item_index,
1252 item_name,
1253 &item_key,
1254 );
1255 reconcile_ir_node(
1256 tree,
1257 old_child_id,
1258 &child_with_item,
1259 state,
1260 patches,
1261 dependencies,
1262 data_sources,
1263 );
1264 }
1265 child_index += 1;
1266 }
1267 }
1268 }
1269 }
1270 }
1271 IRNode::Conditional {
1272 value,
1273 branches,
1274 fallback,
1275 } => {
1276 if !existing.is_conditional() {
1278 let parent_id = existing.parent;
1280 remove_subtree(tree, node_id, patches, dependencies);
1281 create_ir_node_tree(
1282 tree,
1283 node,
1284 parent_id,
1285 state,
1286 patches,
1287 parent_id.is_none(),
1288 dependencies,
1289 data_sources,
1290 );
1291 return;
1292 }
1293
1294 if let Value::Binding(binding) = value {
1296 dependencies.add_dependency(node_id, binding);
1297 } else if let Value::TemplateString { bindings, .. } = value {
1298 for binding in bindings {
1299 dependencies.add_dependency(node_id, binding);
1300 }
1301 }
1302
1303 let evaluated_value = evaluate_value(value, state, data_sources);
1305 let matched_children =
1306 find_matching_branch(&evaluated_value, branches, fallback.as_deref(), state, data_sources);
1307
1308 let old_children = existing.children.clone();
1309 let old_len = old_children.len();
1310
1311 if let Some(children) = matched_children {
1312 let new_len = children.len();
1313 let common = old_len.min(new_len);
1314
1315 for (i, child) in children.iter().enumerate().take(common) {
1317 if let Some(&old_child_id) = old_children.get(i) {
1318 reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies, data_sources);
1319 }
1320 }
1321
1322 for i in common..old_len {
1324 if let Some(&old_child_id) = old_children.get(i) {
1325 remove_subtree(tree, old_child_id, patches, dependencies);
1326 if let Some(cond_node) = tree.get_mut(node_id) {
1327 cond_node.children = cond_node
1328 .children
1329 .iter()
1330 .filter(|&&id| id != old_child_id)
1331 .copied()
1332 .collect();
1333 }
1334 }
1335 }
1336
1337 for child in &children[common..] {
1339 create_ir_node_tree(
1340 tree,
1341 child,
1342 Some(node_id),
1343 state,
1344 patches,
1345 false,
1346 dependencies,
1347 data_sources,
1348 );
1349 }
1350 } else {
1351 for &old_child_id in &old_children {
1353 remove_subtree(tree, old_child_id, patches, dependencies);
1354 }
1355
1356 if let Some(cond_node) = tree.get_mut(node_id) {
1357 cond_node.children.clear();
1358 }
1359 }
1360 }
1361 }
1362}
1363
1364fn remove_subtree(
1366 tree: &mut InstanceTree,
1367 node_id: NodeId,
1368 patches: &mut Vec<Patch>,
1369 dependencies: &mut DependencyGraph,
1370) {
1371 let ids = collect_subtree_ids(tree, node_id);
1372 for &id in &ids {
1373 patches.push(Patch::remove(id));
1374 dependencies.remove_node(id);
1375 }
1376 tree.remove(node_id);
1377}
1378
1379#[cfg(test)]
1380mod tests {
1381 use super::*;
1382 use crate::ir::Value;
1383 use serde_json::json;
1384
1385 #[test]
1386 fn test_create_simple_tree() {
1387 use crate::reactive::DependencyGraph;
1388
1389 let mut tree = InstanceTree::new();
1390 let mut patches = Vec::new();
1391 let mut dependencies = DependencyGraph::new();
1392
1393 let element = Element::new("Column")
1394 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1395
1396 let state = json!({});
1397 create_tree(
1398 &mut tree,
1399 &element,
1400 None,
1401 &state,
1402 &mut patches,
1403 true,
1404 &mut dependencies,
1405 None,
1406 );
1407
1408 assert_eq!(patches.len(), 4);
1411
1412 let root_insert = patches
1414 .iter()
1415 .find(|p| matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root"));
1416 assert!(root_insert.is_some(), "Root insert patch should exist");
1417 }
1418
1419 #[test]
1420 fn test_diff_props() {
1421 let node_id = NodeId::default();
1422 let old = indexmap::indexmap! {
1423 "color".to_string() => json!("red"),
1424 "size".to_string() => json!(16),
1425 };
1426 let new = indexmap::indexmap! {
1427 "color".to_string() => json!("blue"),
1428 "size".to_string() => json!(16),
1429 };
1430
1431 let patches = diff_props(node_id, &old, &new);
1432
1433 assert_eq!(patches.len(), 1);
1435 }
1436}