replaceById function
Replace node id with updated anywhere in the tree.
If id does not exist, returns doc unchanged.
A replacement must preserve the target node ID. When the replacement changes the target's descendants, its subtree IDs must also remain nonblank, internally unique, and collision-free with nodes outside the subtree being replaced.
Implementation
CanvasSceneDocument replaceById(
CanvasSceneDocument doc,
ElementId id,
Node updated,
) {
final current = findById(doc, id);
// Preserve the existing missing-target no-op contract.
if (current == null) return doc;
if (updated.id != id) {
throw ArgumentError.value(
updated.id,
'updated.id',
'Replacement node ID must match target ID "$id".',
);
}
if (_replacementChangesDescendants(current, updated)) {
// IDs belonging to the old subtree are intentionally excluded from the
// collision set. The replacement is allowed to retain or rearrange those
// IDs because the old subtree disappears atomically when replacement
// succeeds.
final replacedIds = collectAllNodeIds(root: current);
final outsideIds = collectAllNodeIds(doc: doc)..removeAll(replacedIds);
_validateReplacementSubtreeIds(updated, outsideIds);
}
return rewriteSceneDocument(
doc,
(n) => rewritePostOrder(
n,
(m) => (m.id == id) ? updated : m,
prune: (m) => m.id == id,
),
);
}