expandAll method

void expandAll({
  1. bool animate = true,
  2. int? maxDepth,
})

Expands all nodes in the tree.

Uses batch operations for better performance with large trees.

Implementation

void expandAll({bool animate = true, int? maxDepth}) {
  if (animationDuration == Duration.zero) animate = false;
  // Collect all nodes to expand, nodes to show, and nodes currently exiting
  final nodesToExpand = <TKey>[];
  final nodesToShow = <TKey>[];
  final nodesToReverseExit = <TKey>[];

  // Iterative DFS. Depth is recomputed per-visit via [_depthOfKey]
  // (matching the original recursive implementation) so we do not
  // need to carry depth along in a parallel stack.
  final stack = <TKey>[];
  for (final rootId in _roots) {
    stack.add(rootId);
  }
  while (stack.isNotEmpty) {
    final key = stack.removeLast();
    if (_isPendingDeletion(key)) {
      continue;
    }
    final children = _childListOf(key);
    if (children == null || children.isEmpty) {
      continue;
    }

    final depth = _depthOfKey(key);
    final withinDepthLimit = maxDepth == null || depth < maxDepth;

    if (withinDepthLimit && !_isExpandedKey(key)) {
      nodesToExpand.add(key);
      for (final childId in children) {
        if (!_order.contains(childId)) {
          nodesToShow.add(childId);
        }
      }
    }

    // Still check children for exiting animations regardless of depth.
    for (final childId in children) {
      // Check standalone exiting
      final animation = _standaloneAt(childId);
      if (animation != null && animation.type == AnimationType.exiting) {
        if (!_isPendingDeletion(childId)) {
          nodesToReverseExit.add(childId);
        }
      }
      // Check operation group exiting (pendingRemoval)
      final opGroupKey = _operationGroupOf(childId);
      if (opGroupKey != null) {
        final opGroup = _opGroupAt(opGroupKey);
        if (opGroup != null && opGroup.pendingRemoval.contains(childId)) {
          if (!_isPendingDeletion(childId)) {
            nodesToReverseExit.add(childId);
          }
        }
      }
    }

    // Only descend into children if within depth limit.
    if (withinDepthLimit) {
      for (final childId in children) {
        stack.add(childId);
      }
    }
  }
  if (nodesToExpand.isEmpty && nodesToReverseExit.isEmpty) {
    return;
  }
  // Batch update expansion states. Skip per-call ancestors-expanded
  // propagation — we rebuild it wholesale below in O(N).
  for (final key in nodesToExpand) {
    _setExpandedKey(key, true, propagate: false);
  }
  _rebuildAllAncestorsExpanded();
  // Rebuild visible order from scratch (more efficient for bulk operations)
  _markVisibleOrderDirty();
  // Flush the deferred rebuild NOW. The bulk-member registration loops
  // below gate on `_order.contains(key)` to skip children that aren't
  // structurally visible; inside a [runBatch], `_markVisibleOrderDirty`
  // sets only a dirty flag and `_order` remains pre-mutation. Without
  // this flush, every newly-visible child fails the `contains` check
  // and never gets added to the bulk group, so the bulk animation runs
  // empty and the children appear at full extent in one frame.
  // Outside a batch this is a no-op (the mark already triggered the
  // rebuild synchronously); inside a batch it forces consumption now,
  // and any subsequent in-batch mutation can re-mark dirty as needed.
  _ensureVisibleOrder();
  // Start animations for newly visible nodes and reverse exiting animations
  if (animate) {
    // Reverse collapsing operation groups
    bool opGroupReversed = false;
    for (final entry in _opGroupEntries) {
      final group = entry.value;
      if (group.pendingRemoval.isNotEmpty) {
        group.pendingRemoval.clear();
        opGroupReversed = true;
        // Restore each member's targetExtent to full so the reversal
        // terminates at the correct natural size instead of at a
        // captured mid-flight value.
        for (final member in group.members.entries) {
          member.value.targetExtent =
              _fullExtentOf(member.key) ?? _unknownExtent;
        }
        group.controller.forward();
      }
    }
    if (opGroupReversed) _bumpAnimGen();

    // Check if there's a collapsing bulk animation we can reverse
    if (_activeBulkGroup != null &&
        _activeBulkGroup!.pendingRemoval.isNotEmpty) {
      // Reverse the animation - nodes being removed will now expand
      // Clear pending removal since we're expanding now
      _clearBulkPending();

      // Reverse standalone exit animations smoothly
      for (final key in nodesToReverseExit) {
        if (!_hasOperationGroup(key)) {
          _startStandaloneEnterAnimation(key);
        }
      }

      // Add any new nodes to the group (skip if already in an operation group)
      for (final key in nodesToShow) {
        if (_order.contains(key) && !_hasOperationGroup(key)) {
          _addBulkMember(key);
        }
      }

      // Reverse the controller direction
      _activeBulkGroup!.controller.forward();
      _bumpBulkGen();
    } else {
      // Create fresh group via the BulkAnimator (auto-disposes any
      // prior). All listener wiring happens inside createGroup.
      _anim.bulk.createGroup(animationDuration, animationCurve);
      _bumpBulkGen();   // matches today's `_createBulkAnimationGroup` body

      // Reverse standalone exit animations smoothly
      for (final key in nodesToReverseExit) {
        if (!_hasOperationGroup(key)) {
          _startStandaloneEnterAnimation(key);
        }
      }

      // Add new nodes to the bulk group (skip if already in an operation group)
      for (final key in nodesToShow) {
        if (_order.contains(key) && !_hasOperationGroup(key)) {
          _addBulkMember(key);
        }
      }

      // Start expanding (value 0 -> 1)
      _activeBulkGroup!.controller.forward();
      _bumpBulkGen();
    }
  } else {
    // Remove animations if not animating
    for (final key in nodesToReverseExit) {
      _removeAnimation(key);
    }
  }
  // Bulk expansion touches many ancestors' expansion state + every
  // previously-collapsed node now flips its chevron. Enumerating the
  // affected set precisely is error-prone; fall back to a full refresh.
  _notifyStructural();
}