apply<T extends DataGridRow> method

  1. @override
DataGridState<T>? apply<T extends DataGridRow>(
  1. EventContext<T> context
)
override

Apply this event's transformation to the state. Returns the new state, or null if no state change should occur. Can return a Future for async operations.

Implementation

@override
DataGridState<T>? apply<T extends DataGridRow>(EventContext<T> context) {
  final state = context.state;
  final activeCellId = state.selection.activeCellId;

  // ── No active cell: fall back to row navigation (up/down only) ──────────
  if (activeCellId == null) {
    return _rowNavFallback(state);
  }

  // ── Resolve current position ─────────────────────────────────────────────
  final (activeRowId, activeColId) = parseCellId(activeCellId);

  final visibleColumns =
      state.effectiveColumns.where((c) => c.visible).toList();

  // For Shift+Arrow we need the anchor's row/col indices as well as the
  // active cell's. Parse the anchor up front so both can be located in a
  // single scan of displayOrder below — avoiding a second pass or a HashMap.
  final existingPath = state.selection.focusedCells;
  final anchorCellId = extend && existingPath.isNotEmpty
      ? existingPath.first
      : null;
  final anchorParsed =
      anchorCellId != null ? parseCellId(anchorCellId) : null;
  final anchorRowId = anchorParsed?.$1;
  final anchorColId = anchorParsed?.$2;

  // ── Row index resolution ─────────────────────────────────────────────────
  // displayOrder can contain hundreds of thousands of entries.
  // List.indexOf is O(n) and building a Map<double,int> allocates on the
  // heap. Instead we do one forward scan that locates both the active row
  // and (when extending) the anchor row simultaneously, breaking as soon
  // as both are found. In the common case — both cells visible on screen —
  // this exits within the first few dozen iterations.
  int rowIndex = -1;
  int anchorRowIdx = -1;
  for (int i = 0; i < state.displayOrder.length; i++) {
    final id = state.displayOrder[i];
    if (rowIndex == -1 && id == activeRowId) rowIndex = i;
    if (anchorRowId != null && anchorRowIdx == -1 && id == anchorRowId) {
      anchorRowIdx = i;
    }
    if (rowIndex != -1 && (anchorRowId == null || anchorRowIdx != -1)) break;
  }

  // Column count is always small (tens), so a straightforward scan is fine.
  // The same two-target pattern is kept for consistency.
  int colIndex = -1;
  int anchorColIdx = -1;
  for (int i = 0; i < visibleColumns.length; i++) {
    final id = visibleColumns[i].id;
    if (colIndex == -1 && id == activeColId) colIndex = i;
    if (anchorColId != null && anchorColIdx == -1 && id == anchorColId) {
      anchorColIdx = i;
    }
    if (colIndex != -1 && (anchorColId == null || anchorColIdx != -1)) break;
  }

  if (rowIndex == -1 || colIndex == -1) return null;

  // ── Compute target position ──────────────────────────────────────────────
  int newRowIndex = rowIndex;
  int newColIndex = colIndex;

  switch (direction) {
    case CellNavDirection.up:
      newRowIndex = rowIndex - 1;
    case CellNavDirection.down:
      newRowIndex = rowIndex + 1;
    case CellNavDirection.left:
      newColIndex = colIndex - 1;
    case CellNavDirection.right:
      newColIndex = colIndex + 1;
  }

  // Clamp to bounds
  if (newRowIndex < 0 ||
      newRowIndex >= state.displayOrder.length ||
      newColIndex < 0 ||
      newColIndex >= visibleColumns.length) {
    return null;
  }

  final newCellId =
      '${state.displayOrder[newRowIndex]}_${visibleColumns[newColIndex].id}';

  // ── Update focused-cells path ────────────────────────────────────────────
  if (!extend) {
    return state.copyWith(
      selection: state.selection.copyWith(focusedCells: [newCellId]),
    );
  }

  // Shift+Arrow: anchor stays fixed, cursor moves to newCell.
  // Rebuild rectangle so selection can shrink/grow in any direction.
  if (anchorRowIdx == -1 || anchorColIdx == -1) {
    return state.copyWith(
      selection: state.selection.copyWith(focusedCells: [newCellId]),
    );
  }

  // Traverse anchor→cursor so focusedCells.first == anchor, .last == cursor.
  final rStep = newRowIndex >= anchorRowIdx ? 1 : -1;
  final cStep = newColIndex >= anchorColIdx ? 1 : -1;
  final cells = <String>[];
  for (int r = anchorRowIdx; r != newRowIndex + rStep; r += rStep) {
    for (int c = anchorColIdx; c != newColIndex + cStep; c += cStep) {
      cells.add('${state.displayOrder[r]}_${visibleColumns[c].id}');
    }
  }

  return state.copyWith(
    selection: state.selection.copyWith(focusedCells: cells),
  );
}