use super::conditionals::{evaluate_value, find_matching_branch};
use super::item_bindings::{replace_ir_node_item_bindings, replace_item_bindings};
use super::keyed::generate_item_key;
use super::resolve::{evaluate_binding, resolve_props_full};
use super::{ControlFlowKind, InstanceTree, Patch};
use crate::ir::{ConditionalBranch, Element, IRNode, NodeId, Props, Value};
use crate::reactive::{Binding, DependencyGraph};
use indexmap::IndexMap;
type DataSources = indexmap::IndexMap<String, serde_json::Value>;
struct ReconcileCtx<'a> {
tree: &'a mut InstanceTree,
state: &'a serde_json::Value,
patches: &'a mut Vec<Patch>,
dependencies: &'a mut DependencyGraph,
data_sources: Option<&'a DataSources>,
}
pub fn reconcile(
tree: &mut InstanceTree,
element: &Element,
parent_id: Option<NodeId>,
state: &serde_json::Value,
dependencies: &mut DependencyGraph,
) -> Vec<Patch> {
reconcile_with_ds(tree, element, parent_id, state, dependencies, None)
}
pub fn reconcile_with_ds(
tree: &mut InstanceTree,
element: &Element,
parent_id: Option<NodeId>,
state: &serde_json::Value,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> Vec<Patch> {
let mut patches = Vec::new();
if tree.root().is_none() {
let node_id = create_tree(
tree,
element,
parent_id,
state,
&mut patches,
true,
dependencies,
data_sources,
);
tree.set_root(node_id);
return patches;
}
if let Some(root_id) = tree.root() {
reconcile_node(tree, root_id, element, state, &mut patches, dependencies, data_sources);
}
patches
}
pub fn reconcile_ir(
tree: &mut InstanceTree,
node: &IRNode,
parent_id: Option<NodeId>,
state: &serde_json::Value,
dependencies: &mut DependencyGraph,
) -> Vec<Patch> {
reconcile_ir_with_ds(tree, node, parent_id, state, dependencies, None)
}
pub fn reconcile_ir_with_ds(
tree: &mut InstanceTree,
node: &IRNode,
parent_id: Option<NodeId>,
state: &serde_json::Value,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> Vec<Patch> {
let mut patches = Vec::new();
if tree.root().is_none() {
let node_id = create_ir_node_tree(
tree,
node,
parent_id,
state,
&mut patches,
true,
dependencies,
data_sources,
);
tree.set_root(node_id);
return patches;
}
if let Some(root_id) = tree.root() {
reconcile_ir_node(tree, root_id, node, state, &mut patches, dependencies, data_sources);
}
patches
}
#[allow(clippy::too_many_arguments)]
pub fn create_tree(
tree: &mut InstanceTree,
element: &Element,
parent_id: Option<NodeId>,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
is_root: bool,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> NodeId {
if let Some(Value::Binding(_)) = element.props.get("0") {
if !element.children.is_empty() {
return create_list_tree(
tree,
element,
parent_id,
state,
patches,
is_root,
dependencies,
data_sources,
);
}
}
let node_id = tree.create_node_full(element, state, data_sources);
for value in element.props.values() {
match value {
Value::Binding(binding) => {
dependencies.add_dependency(node_id, binding);
}
Value::TemplateString { bindings, .. } => {
for binding in bindings {
dependencies.add_dependency(node_id, binding);
}
}
_ => {}
}
}
let node = tree.get(node_id).unwrap();
let mut props = node.props.clone();
if let Some(Value::Static(val)) = element.props.get("__lazy") {
if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
let child_component = &element.children[0].element_type;
props.insert(
"__lazy_child".to_string(),
serde_json::json!(child_component),
);
}
}
patches.push(Patch::create(node_id, node.element_type.clone(), props));
if let Some(parent) = parent_id {
tree.add_child(parent, node_id, None);
patches.push(Patch::insert(parent, node_id, None));
} else if is_root {
patches.push(Patch::insert_root(node_id));
}
let is_lazy = element
.props
.get("__lazy")
.and_then(|v| {
if let Value::Static(val) = v {
val.as_bool()
} else {
None
}
})
.unwrap_or(false);
if !is_lazy {
for child_element in &element.children {
create_tree(
tree,
child_element,
Some(node_id),
state,
patches,
false,
dependencies,
data_sources,
);
}
}
node_id
}
#[allow(clippy::too_many_arguments)]
fn create_list_tree(
tree: &mut InstanceTree,
element: &Element,
parent_id: Option<NodeId>,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
is_root: bool,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> NodeId {
let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
} else {
serde_json::Value::Array(vec![])
};
let mut list_element = Element::new(&element.element_type);
for (key, value) in &element.props {
if key != "0" {
list_element.props.insert(key.clone(), value.clone());
}
}
let node_id = tree.create_node_full(&list_element, state, data_sources);
if let Some(Value::Binding(binding)) = element.props.get("0") {
dependencies.add_dependency(node_id, binding);
}
if let Some(node) = tree.get_mut(node_id) {
node.raw_props = element.props.clone();
node.element_template = Some(std::sync::Arc::new(element.clone()));
}
let node = tree.get(node_id).unwrap();
patches.push(Patch::create(
node_id,
node.element_type.clone(),
node.props.clone(),
));
if let Some(parent) = parent_id {
tree.add_child(parent, node_id, None);
patches.push(Patch::insert(parent, node_id, None));
} else if is_root {
patches.push(Patch::insert_root(node_id));
}
if let serde_json::Value::Array(items) = &array {
for (index, item) in items.iter().enumerate() {
for child_template in &element.children {
let child_with_item = replace_item_bindings(child_template, item, index);
create_tree(
tree,
&child_with_item,
Some(node_id),
state,
patches,
false,
dependencies,
data_sources,
);
}
}
}
node_id
}
pub fn reconcile_node(
tree: &mut InstanceTree,
node_id: NodeId,
element: &Element,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) {
let node = tree.get(node_id).cloned();
if node.is_none() {
return;
}
let node = node.unwrap();
let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
if is_iterable {
let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
} else {
serde_json::Value::Array(vec![])
};
if let serde_json::Value::Array(items) = &array {
let old_children = node.children.clone();
let expected_children_count = items.len() * element.children.len();
if old_children.len() != expected_children_count {
for &old_child_id in &old_children {
patches.push(Patch::remove(old_child_id));
}
if let Some(node) = tree.get_mut(node_id) {
node.children.clear();
}
for (index, item) in items.iter().enumerate() {
for child_template in &element.children {
let child_with_item = replace_item_bindings(child_template, item, index);
create_tree(
tree,
&child_with_item,
Some(node_id),
state,
patches,
false,
dependencies,
data_sources,
);
}
}
} else {
let mut child_index = 0;
for (item_index, item) in items.iter().enumerate() {
for child_template in &element.children {
if let Some(&old_child_id) = old_children.get(child_index) {
let child_with_item =
replace_item_bindings(child_template, item, item_index);
reconcile_node(
tree,
old_child_id,
&child_with_item,
state,
patches,
dependencies,
data_sources,
);
}
child_index += 1;
}
}
}
}
return; }
if node.element_type != element.element_type {
replace_subtree(tree, node_id, &node, element, state, patches, dependencies, data_sources);
return;
}
for value in element.props.values() {
match value {
Value::Binding(binding) => {
dependencies.add_dependency(node_id, binding);
}
Value::TemplateString { bindings, .. } => {
for binding in bindings {
dependencies.add_dependency(node_id, binding);
}
}
_ => {}
}
}
let new_props = resolve_props_full(&element.props, state, None, data_sources);
let prop_patches = diff_props(node_id, &node.props, &new_props);
patches.extend(prop_patches);
if let Some(node) = tree.get_mut(node_id) {
node.props = new_props.clone();
node.raw_props = element.props.clone();
}
let is_lazy = element
.props
.get("__lazy")
.and_then(|v| {
if let Value::Static(val) = v {
val.as_bool()
} else {
None
}
})
.unwrap_or(false);
if !is_lazy {
let old_children = node.children.clone();
let new_children = &element.children;
for (i, new_child_element) in new_children.iter().enumerate() {
if let Some(&old_child_id) = old_children.get(i) {
reconcile_node(
tree,
old_child_id,
new_child_element,
state,
patches,
dependencies,
data_sources,
);
} else {
let new_child_id = create_tree(
tree,
new_child_element,
Some(node_id),
state,
patches,
false,
dependencies,
data_sources,
);
if let Some(node) = tree.get_mut(node_id) {
node.children.push_back(new_child_id);
}
}
}
if old_children.len() > new_children.len() {
for old_child_id in old_children.iter().skip(new_children.len()).copied() {
let subtree_ids = collect_subtree_ids(tree, old_child_id);
for &id in &subtree_ids {
patches.push(Patch::remove(id));
dependencies.remove_node(id);
}
tree.remove_child(node_id, old_child_id);
tree.remove(old_child_id);
}
}
}
}
fn reconcile_element_with_ir_children(
tree: &mut InstanceTree,
node_id: NodeId,
element: &Element,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) {
let node = tree.get(node_id).cloned();
if node.is_none() {
return;
}
let node = node.unwrap();
if node.element_type != element.element_type {
replace_subtree(tree, node_id, &node, element, state, patches, dependencies, data_sources);
return;
}
for value in element.props.values() {
match value {
Value::Binding(binding) => {
dependencies.add_dependency(node_id, binding);
}
Value::TemplateString { bindings, .. } => {
for binding in bindings {
dependencies.add_dependency(node_id, binding);
}
}
_ => {}
}
}
let new_props = resolve_props_full(&element.props, state, None, data_sources);
let prop_patches = diff_props(node_id, &node.props, &new_props);
patches.extend(prop_patches);
if let Some(n) = tree.get_mut(node_id) {
n.props = new_props;
n.raw_props = element.props.clone();
}
let old_children = node.children.clone();
let new_children = &element.ir_children;
let common = old_children.len().min(new_children.len());
for (i, child_ir) in new_children.iter().enumerate().take(common) {
if let Some(&old_child_id) = old_children.get(i) {
reconcile_ir_node(tree, old_child_id, child_ir, state, patches, dependencies, data_sources);
}
}
for child_ir in &new_children[common..] {
create_ir_node_tree(
tree,
child_ir,
Some(node_id),
state,
patches,
false,
dependencies,
data_sources,
);
}
for old_child_id in old_children.iter().skip(new_children.len()).copied() {
remove_subtree(tree, old_child_id, patches, dependencies);
if let Some(n) = tree.get_mut(node_id) {
n.children = n
.children
.iter()
.filter(|&&id| id != old_child_id)
.copied()
.collect();
}
}
}
#[allow(clippy::too_many_arguments)]
fn replace_subtree(
tree: &mut InstanceTree,
old_node_id: NodeId,
old_node: &super::InstanceNode,
new_element: &Element,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) {
let parent_id = old_node.parent;
let old_position = if let Some(pid) = parent_id {
tree.get(pid)
.and_then(|parent| parent.children.iter().position(|&id| id == old_node_id))
} else {
None
};
let ids_to_remove = collect_subtree_ids(tree, old_node_id);
for &id in &ids_to_remove {
patches.push(Patch::remove(id));
dependencies.remove_node(id);
}
if let Some(pid) = parent_id {
if let Some(parent) = tree.get_mut(pid) {
parent.children = parent
.children
.iter()
.filter(|&&id| id != old_node_id)
.copied()
.collect();
}
}
tree.remove(old_node_id);
let is_root = parent_id.is_none();
let new_node_id = create_tree(
tree,
new_element,
parent_id,
state,
patches,
is_root,
dependencies,
data_sources,
);
if is_root {
tree.set_root(new_node_id);
} else if let Some(pid) = parent_id {
if let Some(pos) = old_position {
if let Some(parent) = tree.get_mut(pid) {
let current_len = parent.children.len();
if pos < current_len - 1 {
let new_id = parent.children.pop_back().unwrap();
parent.children.insert(pos, new_id);
let next_sibling = parent.children.get(pos + 1).copied();
patches.push(Patch::move_node(pid, new_node_id, next_sibling));
}
}
}
}
}
fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
let mut result = Vec::new();
let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
while let Some((node_id, children_processed)) = stack.pop() {
if children_processed {
result.push(node_id);
} else {
stack.push((node_id, true));
if let Some(node) = tree.get(node_id) {
for &child_id in node.children.iter().rev() {
stack.push((child_id, false));
}
}
}
}
result
}
pub fn diff_props(
node_id: NodeId,
old_props: &IndexMap<String, serde_json::Value>,
new_props: &IndexMap<String, serde_json::Value>,
) -> Vec<Patch> {
let mut patches = Vec::new();
for (key, new_value) in new_props {
if old_props.get(key) != Some(new_value) {
patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
}
}
for key in old_props.keys() {
if !new_props.contains_key(key) {
patches.push(Patch::remove_prop(node_id, key.clone()));
}
}
patches
}
#[allow(clippy::too_many_arguments)]
pub fn create_ir_node_tree(
tree: &mut InstanceTree,
node: &IRNode,
parent_id: Option<NodeId>,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
is_root: bool,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> NodeId {
match node {
IRNode::Element(element) => {
if element.ir_children.is_empty() {
create_tree(
tree,
element,
parent_id,
state,
patches,
is_root,
dependencies,
data_sources,
)
} else {
create_element_with_ir_children(
tree,
element,
parent_id,
state,
patches,
is_root,
dependencies,
data_sources,
)
}
}
IRNode::ForEach {
source,
item_name,
key_path,
template,
props,
} => {
let mut ctx = ReconcileCtx {
tree,
state,
patches,
dependencies,
data_sources,
};
create_foreach_ir_tree(
&mut ctx,
source,
item_name,
key_path.as_deref(),
template,
props,
node,
parent_id,
is_root,
)
}
IRNode::Conditional {
value,
branches,
fallback,
} => {
let mut ctx = ReconcileCtx {
tree,
state,
patches,
dependencies,
data_sources,
};
create_conditional_tree(
&mut ctx,
value,
branches,
fallback.as_deref(),
node,
parent_id,
is_root,
)
}
}
}
fn create_element_with_ir_children(
tree: &mut InstanceTree,
element: &Element,
parent_id: Option<NodeId>,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
is_root: bool,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> NodeId {
let node_id = tree.create_node(element, state);
for value in element.props.values() {
match value {
Value::Binding(binding) => {
dependencies.add_dependency(node_id, binding);
}
Value::TemplateString { bindings, .. } => {
for binding in bindings {
dependencies.add_dependency(node_id, binding);
}
}
_ => {}
}
}
let node = tree.get(node_id).unwrap();
patches.push(Patch::create(
node_id,
node.element_type.clone(),
node.props.clone(),
));
if let Some(parent) = parent_id {
tree.add_child(parent, node_id, None);
patches.push(Patch::insert(parent, node_id, None));
} else if is_root {
patches.push(Patch::insert_root(node_id));
}
for child_ir in &element.ir_children {
create_ir_node_tree(
tree,
child_ir,
Some(node_id),
state,
patches,
false,
dependencies,
data_sources,
);
}
node_id
}
fn create_ir_node_tree_with_render_parent(
ctx: &mut ReconcileCtx,
node: &IRNode,
logical_parent: Option<NodeId>,
render_parent: Option<NodeId>,
is_root: bool,
) -> NodeId {
match node {
IRNode::Element(element) => {
let node_id = ctx.tree.create_node_full(element, ctx.state, ctx.data_sources);
if let Some(parent) = logical_parent {
ctx.tree.add_child(parent, node_id, None);
}
ctx.patches.push(Patch::create(
node_id,
element.element_type.clone(),
ctx.tree
.get(node_id)
.map(|n| n.props.clone())
.unwrap_or_default(),
));
if let Some(render_p) = render_parent {
ctx.patches.push(Patch::insert(render_p, node_id, None));
} else if is_root {
ctx.patches.push(Patch::insert_root(node_id));
}
for (_, value) in &element.props {
match value {
Value::Binding(binding) => {
ctx.dependencies.add_dependency(node_id, binding);
}
Value::TemplateString { bindings, .. } => {
for binding in bindings {
ctx.dependencies.add_dependency(node_id, binding);
}
}
_ => {}
}
}
let children_source: Vec<IRNode> = if !element.ir_children.is_empty() {
element.ir_children.clone()
} else {
element
.children
.iter()
.map(|child| IRNode::Element((**child).clone()))
.collect()
};
for child_ir in &children_source {
create_ir_node_tree(
ctx.tree,
child_ir,
Some(node_id),
ctx.state,
ctx.patches,
false,
ctx.dependencies,
ctx.data_sources,
);
}
node_id
}
IRNode::ForEach {
source,
item_name,
key_path,
template,
props,
} => {
create_foreach_ir_tree(
ctx,
source,
item_name,
key_path.as_deref(),
template,
props,
node,
logical_parent, is_root,
)
}
IRNode::Conditional {
value,
branches,
fallback,
} => {
create_conditional_tree(
ctx,
value,
branches,
fallback.as_deref(),
node,
logical_parent, is_root,
)
}
}
}
#[allow(clippy::too_many_arguments)]
fn create_foreach_ir_tree(
ctx: &mut ReconcileCtx,
source: &Binding,
item_name: &str,
key_path: Option<&str>,
template: &[IRNode],
props: &Props,
original_node: &IRNode,
parent_id: Option<NodeId>,
is_root: bool,
) -> NodeId {
let array = evaluate_binding(source, ctx.state).unwrap_or(serde_json::Value::Array(vec![]));
let resolved_props = resolve_props_full(props, ctx.state, None, ctx.data_sources);
let node_id = ctx.tree.create_control_flow_node(
"__ForEach",
resolved_props.clone(),
props.clone(),
ControlFlowKind::ForEach {
item_name: item_name.to_string(),
key_path: key_path.map(|s| s.to_string()),
},
original_node.clone(),
);
ctx.dependencies.add_dependency(node_id, source);
if let Some(parent) = parent_id {
ctx.tree.add_child(parent, node_id, None);
}
let render_parent = parent_id;
if let serde_json::Value::Array(items) = &array {
for (index, item) in items.iter().enumerate() {
let item_key = generate_item_key(item, key_path, item_name, index);
for child_template in template {
let child_with_item = replace_ir_node_item_bindings(
child_template,
item,
index,
item_name,
&item_key,
);
create_ir_node_tree_with_render_parent(
ctx,
&child_with_item,
Some(node_id), render_parent, is_root && render_parent.is_none(),
);
}
}
}
node_id
}
fn create_conditional_tree(
ctx: &mut ReconcileCtx,
value: &Value,
branches: &[ConditionalBranch],
fallback: Option<&[IRNode]>,
original_node: &IRNode,
parent_id: Option<NodeId>,
is_root: bool,
) -> NodeId {
let evaluated_value = evaluate_value(value, ctx.state, ctx.data_sources);
let mut raw_props = Props::new();
raw_props.insert("__condition".to_string(), value.clone());
let node_id = ctx.tree.create_control_flow_node(
"__Conditional",
IndexMap::new(),
raw_props,
ControlFlowKind::Conditional,
original_node.clone(),
);
if let Value::Binding(binding) = value {
ctx.dependencies.add_dependency(node_id, binding);
} else if let Value::TemplateString { bindings, .. } = value {
for binding in bindings {
ctx.dependencies.add_dependency(node_id, binding);
}
}
if let Some(parent) = parent_id {
ctx.tree.add_child(parent, node_id, None);
}
let matched_children =
find_matching_branch(&evaluated_value, branches, fallback, ctx.state, ctx.data_sources);
let render_parent = parent_id;
if let Some(children) = matched_children {
for child in children {
create_ir_node_tree_with_render_parent(
ctx,
child,
Some(node_id), render_parent, is_root && render_parent.is_none(),
);
}
}
node_id
}
pub fn reconcile_ir_node(
tree: &mut InstanceTree,
node_id: NodeId,
node: &IRNode,
state: &serde_json::Value,
patches: &mut Vec<Patch>,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) {
let existing_node = tree.get(node_id).cloned();
if existing_node.is_none() {
return;
}
let existing = existing_node.unwrap();
match node {
IRNode::Element(element) => {
if element.ir_children.is_empty() {
reconcile_node(tree, node_id, element, state, patches, dependencies, data_sources);
} else {
reconcile_element_with_ir_children(
tree, node_id, element, state, patches, dependencies, data_sources,
);
}
}
IRNode::ForEach {
source,
item_name,
key_path,
template,
props: _,
} => {
if !existing.is_foreach() {
let parent_id = existing.parent;
remove_subtree(tree, node_id, patches, dependencies);
create_ir_node_tree(
tree,
node,
parent_id,
state,
patches,
parent_id.is_none(),
dependencies,
data_sources,
);
return;
}
dependencies.add_dependency(node_id, source);
let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
if let serde_json::Value::Array(items) = &array {
let old_children = existing.children.clone();
let expected_children_count = items.len() * template.len();
let render_parent = existing.parent.unwrap_or(node_id);
if old_children.len() != expected_children_count {
for &old_child_id in &old_children {
patches.push(Patch::remove(old_child_id));
}
if let Some(node) = tree.get_mut(node_id) {
node.children.clear();
}
for (index, item) in items.iter().enumerate() {
let item_key =
generate_item_key(item, key_path.as_deref(), item_name, index);
for child_template in template {
let child_with_item = replace_ir_node_item_bindings(
child_template,
item,
index,
item_name,
&item_key,
);
create_ir_node_tree(
tree,
&child_with_item,
Some(render_parent),
state,
patches,
false,
dependencies,
data_sources,
);
}
}
} else {
let mut child_index = 0;
for (item_index, item) in items.iter().enumerate() {
let item_key =
generate_item_key(item, key_path.as_deref(), item_name, item_index);
for child_template in template {
if let Some(&old_child_id) = old_children.get(child_index) {
let child_with_item = replace_ir_node_item_bindings(
child_template,
item,
item_index,
item_name,
&item_key,
);
reconcile_ir_node(
tree,
old_child_id,
&child_with_item,
state,
patches,
dependencies,
data_sources,
);
}
child_index += 1;
}
}
}
}
}
IRNode::Conditional {
value,
branches,
fallback,
} => {
if !existing.is_conditional() {
let parent_id = existing.parent;
remove_subtree(tree, node_id, patches, dependencies);
create_ir_node_tree(
tree,
node,
parent_id,
state,
patches,
parent_id.is_none(),
dependencies,
data_sources,
);
return;
}
if let Value::Binding(binding) = value {
dependencies.add_dependency(node_id, binding);
} else if let Value::TemplateString { bindings, .. } = value {
for binding in bindings {
dependencies.add_dependency(node_id, binding);
}
}
let evaluated_value = evaluate_value(value, state, data_sources);
let matched_children =
find_matching_branch(&evaluated_value, branches, fallback.as_deref(), state, data_sources);
let old_children = existing.children.clone();
let old_len = old_children.len();
let render_parent = existing.parent;
if let Some(children) = matched_children {
let new_len = children.len();
let common = old_len.min(new_len);
for (i, child) in children.iter().enumerate().take(common) {
if let Some(&old_child_id) = old_children.get(i) {
reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies, data_sources);
}
}
for i in common..old_len {
if let Some(&old_child_id) = old_children.get(i) {
remove_subtree(tree, old_child_id, patches, dependencies);
if let Some(cond_node) = tree.get_mut(node_id) {
cond_node.children = cond_node
.children
.iter()
.filter(|&&id| id != old_child_id)
.copied()
.collect();
}
}
}
let mut ctx = ReconcileCtx {
tree,
state,
patches,
dependencies,
data_sources,
};
for child in &children[common..] {
create_ir_node_tree_with_render_parent(
&mut ctx,
child,
Some(node_id), render_parent, false,
);
}
} else {
for &old_child_id in &old_children {
remove_subtree(tree, old_child_id, patches, dependencies);
}
if let Some(cond_node) = tree.get_mut(node_id) {
cond_node.children.clear();
}
}
}
}
}
fn remove_subtree(
tree: &mut InstanceTree,
node_id: NodeId,
patches: &mut Vec<Patch>,
dependencies: &mut DependencyGraph,
) {
let ids = collect_subtree_ids(tree, node_id);
for &id in &ids {
patches.push(Patch::remove(id));
dependencies.remove_node(id);
}
tree.remove(node_id);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::Value;
use serde_json::json;
#[test]
fn test_create_simple_tree() {
use crate::reactive::DependencyGraph;
let mut tree = InstanceTree::new();
let mut patches = Vec::new();
let mut dependencies = DependencyGraph::new();
let element = Element::new("Column")
.with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
let state = json!({});
create_tree(
&mut tree,
&element,
None,
&state,
&mut patches,
true,
&mut dependencies,
None,
);
assert_eq!(patches.len(), 4);
let root_insert = patches
.iter()
.find(|p| matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root"));
assert!(root_insert.is_some(), "Root insert patch should exist");
}
#[test]
fn test_diff_props() {
let node_id = NodeId::default();
let old = indexmap::indexmap! {
"color".to_string() => json!("red"),
"size".to_string() => json!(16),
};
let new = indexmap::indexmap! {
"color".to_string() => json!("blue"),
"size".to_string() => json!(16),
};
let patches = diff_props(node_id, &old, &new);
assert_eq!(patches.len(), 1);
}
}