insert method
void
insert({})
Inserts a new node as a child of the given parent.
If animate is true, the node will animate in.
Implementation
void insert({
required TKey parentKey,
required TreeNode<TKey, TData> node,
int? index,
bool animate = true,
bool preservePendingSubtreeState = false,
}) {
if (animationDuration == Duration.zero) animate = false;
assert(_hasKey(parentKey), "Parent node $parentKey not found");
assert(
!_isPendingDeletion(parentKey),
"Cannot insert under $parentKey while it is animating out "
"(pending deletion). The parent will be purged when its exit animation "
"completes, leaving the new child orphaned.",
);
// If the node is pending deletion, cancel the deletion
if (_isPendingDeletion(node.key)) {
// If the pending-deletion node lives under a different parent (or is
// a root), move it to [parentKey] before cancelling the deletion.
// Without this relocation, cancelDeletion would resurrect the node
// under its old parent, silently ignoring the parentKey/index args.
final oldParent = _parentKeyOfKey(node.key);
if (oldParent != parentKey) {
if (oldParent != null) {
_childListOf(oldParent)?.remove(node.key);
} else {
_roots.remove(node.key);
}
_setParentKey(node.key, parentKey);
final siblings = _childListOrCreate(parentKey);
final effectiveIndex =
index ?? (comparator != null ? _sortedIndex(siblings, node) : null);
if (effectiveIndex != null && effectiveIndex < siblings.length) {
siblings.insert(effectiveIndex, node.key);
} else {
siblings.add(node.key);
}
final parentDepth = _depthOfKey(parentKey);
_refreshSubtreeDepths(node.key, parentDepth + 1);
} else if (index != null) {
// Same parent — honor an explicitly requested index by relocating
// within the sibling list.
final siblings = _childListOrCreate(parentKey);
final current = siblings.indexOf(node.key);
if (current != -1) {
siblings.removeAt(current);
final clamped = index.clamp(0, siblings.length);
siblings.insert(clamped, node.key);
}
}
_cancelDeletion(
node.key,
animate: animate,
preserveSubtreeState: preservePendingSubtreeState,
);
_adoptKey(node.key);
_store.setData(node.key, node);
if (preservePendingSubtreeState) {
_markVisibleOrderDirty();
// See insertRoot's matching branch: cancelling a pending deletion
// may restore a subtree and flip ancestor hasChildren state — fall
// back to a full refresh.
_notifyStructural();
return;
}
// Reset expansion state so a subsequent expand() works cleanly.
// Descendants that were mid-exit are left alone by _cancelDeletion
// and continue animating out under the restored parent via
// _rebuildVisibleOrder's "collapsed with active animations" branch.
// Yanking them here would visually jump following rows upward by
// the descendant's current extent in a single frame.
_setExpandedKey(node.key, false);
_markVisibleOrderDirty();
_notifyStructural();
return;
}
// Node is already present (e.g. restored by an ancestor's
// _cancelDeletion, or a live re-insert). Update the data and — if the
// caller requested a different location — relocate it to honor the
// insert(parentKey:, index:) contract instead of silently dropping it.
if (_hasKey(node.key)) {
_adoptKey(node.key);
_store.setData(node.key, node);
final currentParent = _parentKeyOfKey(node.key);
if (currentParent != parentKey) {
// Different parent — delegate to moveNode.
moveNode(node.key, parentKey, index: index);
return;
}
final siblings = _childListOrCreate(parentKey);
final currentIndex = siblings.indexOf(node.key);
final desiredIndex =
index ?? (comparator != null ? _sortedIndex(siblings, node) : null);
final wantsRelocate =
desiredIndex != null &&
desiredIndex != currentIndex &&
!(currentIndex == siblings.length - 1 &&
desiredIndex >= siblings.length);
if (wantsRelocate) {
siblings.removeAt(currentIndex);
final clamped = desiredIndex.clamp(0, siblings.length);
siblings.insert(clamped, node.key);
_markVisibleOrderDirty();
}
// Data payload for node.key was just overwritten — rebuild its row.
_notifyStructural(affectedKeys: <TKey>{node.key});
return;
}
final parentDepth = _depthOfKey(parentKey);
// Add to data structures
_adoptKey(node.key);
_store.setData(node.key, node);
_setParentKey(node.key, parentKey);
_setChildList(node.key, []);
_setDepthKey(node.key, parentDepth + 1);
_setExpandedKey(node.key, false);
// Add to parent's children
final siblings = _childListOrCreate(parentKey);
final parentHadChildren = siblings.isNotEmpty;
final effectiveIndex =
index ?? (comparator != null ? _sortedIndex(siblings, node) : null);
if (effectiveIndex != null && effectiveIndex < siblings.length) {
siblings.insert(effectiveIndex, node.key);
} else {
siblings.add(node.key);
}
// If parent is expanded, add to visible order
if (_isExpandedKey(parentKey)) {
final parentVisibleIndex = _order.indexOf(parentKey);
if (parentVisibleIndex != VisibleOrderBuffer.kNotVisible) {
// Fast path: the visible insertion index equals the parent's
// visible index plus every prior sibling's visible-subtree
// contribution. Cache lookups are O(1) per sibling, so the
// whole computation is O(prior-sibling-count) — one array
// read per sibling, no nested descendant walks.
int insertIndex;
if (effectiveIndex != null) {
insertIndex = parentVisibleIndex + 1;
final limit = effectiveIndex < siblings.length - 1
? effectiveIndex
: siblings.length - 1;
for (int i = 0; i < limit; i++) {
final siblingNid = _nids[siblings[i]];
if (siblingNid != null) {
insertIndex += _visibleSubtreeSizeByNid[siblingNid];
}
}
} else {
// Append after last visible descendant of parent. Parent is
// visible (checked above) so its cached subtree size counts
// itself + all currently-visible descendants; subtracting
// 1 for the parent itself and adding 1 for "position after"
// yields parentVisibleIndex + subtreeSize directly.
final parentNid = _nids[parentKey]!;
insertIndex =
parentVisibleIndex + _visibleSubtreeSizeByNid[parentNid];
}
_order.insertKey(insertIndex, node.key);
_updateIndicesFrom(insertIndex);
_structureGeneration++;
if (animate) {
_startStandaloneEnterAnimation(node.key);
}
}
}
// The new key enters via createChild. The only retained row whose
// builder output can change is the parent — and only when its
// hasChildren state just flipped from false → true (the chevron
// appears for the first time).
final affected = <TKey>{};
if (!parentHadChildren) {
affected.add(parentKey);
}
_notifyStructural(affectedKeys: affected);
}