endDrag method

Future<void> endDrag()

Commits the drop: mutates treeController (via TreeController.moveNode, TreeController.reorderChildren, or TreeController.reorderRoots) and starts the FLIP slide animation to interpolate old → new positions.

If no valid target is currently resolved, behaves like cancelDrag.

The slide is installed IN-FRAME by the sliver render object: this method asks the render object to capture a baseline of current painted offsets BEFORE mutating the controller; the next performLayout (triggered by that mutation) snapshots the post-mutation offsets and installs a FLIP slide from baseline → current. The paint pass of the same frame then renders rows at their prior painted position and slides them toward their new structural position smoothly — no one-frame "jump to new position, then slide back" flicker.

Implementation

Future<void> endDrag() async {
  final session = _session;
  if (session == null) return;
  final target = session.currentTarget;
  if (target == null) {
    cancelDrag();
    return;
  }

  _autoScrollTicker.stop();
  _lastAutoScrollTick = null;

  // Stage the FLIP baseline BEFORE mutating. The render object's next
  // performLayout will consume it and install the slide in-frame,
  // avoiding the post-frame gap that used to produce a single-frame
  // flicker of each moved row at its destination.
  session.renderObject.beginSlideBaseline(
    duration: slideDuration,
    curve: slideCurve,
  );

  final currentParent = treeController.getParent(session.draggedKey);
  final sameParent = currentParent == target.parentKey;

  if (sameParent) {
    // Build the live final sibling list — reorderChildren/reorderRoots
    // reject lists containing pending-deletion entries and re-append them
    // internally after validating the live ordering.
    final liveSiblings = target.parentKey == null
        ? treeController.liveRootKeys
        : treeController.getLiveChildren(target.parentKey as TKey);
    liveSiblings.remove(session.draggedKey);
    final insertAt = target.indexInFinalList.clamp(0, liveSiblings.length);
    liveSiblings.insert(insertAt, session.draggedKey);

    if (target.parentKey == null) {
      treeController.reorderRoots(liveSiblings);
    } else {
      treeController.reorderChildren(
        target.parentKey as TKey,
        liveSiblings,
      );
    }
  } else {
    // Cross-parent: moveNode's `index` is the position in the new parent's
    // final child list — exactly indexInFinalList.
    treeController.moveNode(
      session.draggedKey,
      target.parentKey,
      index: target.indexInFinalList,
    );
  }

  _session = null;
  notifyListeners();
}