insertRoot method

void insertRoot(
  1. TreeNode<TKey, TData> node, {
  2. int? index,
  3. bool animate = true,
  4. bool preservePendingSubtreeState = false,
})

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 {});
}