insertRoot method
void
insertRoot(})
Adds a new root node to the tree.
If animate is true, the node will animate in.
If the node is currently pending deletion (animating out from a previous
remove), the deletion is cancelled and the node animates back in.
Implementation
void insertRoot(
TreeNode<TKey, TData> node, {
int? index,
bool animate = true,
bool preservePendingSubtreeState = false,
}) {
if (animationDuration == Duration.zero) animate = false;
// If the node is pending deletion, cancel the deletion
if (_isPendingDeletion(node.key)) {
// If the node was pending deletion under a non-null parent, detach
// it and re-attach as a root. Without this relocation, cancelling
// the deletion would resurrect it under its old parent, silently
// ignoring the insertRoot() contract.
final oldParent = _parentKeyOfKey(node.key);
if (oldParent != null) {
_childListOf(oldParent)?.remove(node.key);
_setParentKey(node.key, null);
final effectiveIndex =
index ?? (comparator != null ? _sortedIndex(_roots, node) : null);
if (effectiveIndex != null && effectiveIndex < _roots.length) {
_roots.insert(effectiveIndex, node.key);
} else {
_roots.add(node.key);
}
_refreshSubtreeDepths(node.key, 0);
} else if (index != null) {
// Already a root — honor an explicitly requested index by
// relocating within _roots.
final current = _roots.indexOf(node.key);
if (current != -1) {
_roots.removeAt(current);
final clamped = index.clamp(0, _roots.length);
_roots.insert(clamped, node.key);
}
}
_cancelDeletion(
node.key,
animate: animate,
preserveSubtreeState: preservePendingSubtreeState,
);
_adoptKey(node.key);
_store.setData(node.key, node);
if (preservePendingSubtreeState) {
_markVisibleOrderDirty();
// Cancelling a pending deletion restores the node (and possibly
// descendants) to the tree. Downstream builder-output effects span
// the restored subtree plus any ancestor whose hasChildren state
// flips; enumerating all of that precisely is complex, so 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
// insertRoot(index:) contract instead of silently dropping the index.
if (_hasKey(node.key)) {
_adoptKey(node.key);
_store.setData(node.key, node);
final currentParent = _parentKeyOfKey(node.key);
if (currentParent != null) {
// Different parent — delegate to moveNode.
moveNode(node.key, null, index: index);
return;
}
final currentRootIndex = _roots.indexOf(node.key);
final desiredIndex =
index ?? (comparator != null ? _sortedIndex(_roots, node) : null);
final wantsRelocate =
desiredIndex != null &&
desiredIndex != currentRootIndex &&
// Appending is a no-op if already at the end.
!(currentRootIndex == _roots.length - 1 &&
desiredIndex >= _roots.length);
if (wantsRelocate) {
_roots.removeAt(currentRootIndex);
final clamped = desiredIndex.clamp(0, _roots.length);
_roots.insert(clamped, node.key);
_markVisibleOrderDirty();
}
// Data payload for node.key was just overwritten — rebuild its row.
_notifyStructural(affectedKeys: <TKey>{node.key});
return;
}
// Add to data structures
_adoptKey(node.key);
_store.setData(node.key, node);
_setParentKey(node.key, null);
_setChildList(node.key, []);
_setDepthKey(node.key, 0);
_setExpandedKey(node.key, false);
// Add to roots list
final effectiveIndex =
index ?? (comparator != null ? _sortedIndex(_roots, node) : null);
// Compute visible insert position BEFORE modifying _roots, since
// _calculateRootInsertIndex reads _roots[effectiveIndex].
final visibleInsertIndex =
effectiveIndex != null && effectiveIndex < _roots.length
? _calculateRootInsertIndex(effectiveIndex)
: _order.length;
if (effectiveIndex != null && effectiveIndex < _roots.length) {
_roots.insert(effectiveIndex, node.key);
} else {
_roots.add(node.key);
}
// Add to visible order (root nodes are always visible)
final insertIndex = visibleInsertIndex;
_order.insertKey(insertIndex, node.key);
_updateIndicesFrom(insertIndex);
_structureGeneration++;
if (animate) {
_startStandaloneEnterAnimation(node.key);
}
// Fresh root: the new key enters visible order via createChild, not a
// refresh. Roots have no parent whose hasChildren could flip, and no
// sibling's builder output depends on the new key. Empty set.
_notifyStructural(affectedKeys: const {});
}