insert method

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

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