← back to Open Seo

src/client/components/table/tableSelection.ts

72 lines

import type { MouseEvent, MutableRefObject } from "react";
import type { Row, RowSelectionState, Table } from "@tanstack/react-table";

export type SelectionAnchor = {
  id: string;
  selected: boolean;
};

type SelectionRow<T> = Pick<Row<T>, "id" | "getIsSelected">;

type SelectionTable<T> = Pick<Table<T>, "setRowSelection"> & {
  getRowModel: () => {
    rows: SelectionRow<T>[];
  };
};

export function applyShiftRangeSelection<T>(
  event: Pick<MouseEvent<HTMLElement>, "shiftKey" | "preventDefault">,
  row: SelectionRow<T>,
  table: SelectionTable<T>,
  anchorRef: MutableRefObject<SelectionAnchor | null>,
): boolean {
  if (!event.shiftKey || !anchorRef.current) {
    anchorRef.current = {
      id: row.id,
      selected: !row.getIsSelected(),
    };
    return false;
  }

  const rows = table.getRowModel().rows;
  const anchorIndex = rows.findIndex((candidate) => {
    return candidate.id === anchorRef.current?.id;
  });
  const currentIndex = rows.findIndex((candidate) => candidate.id === row.id);

  if (anchorIndex === -1 || currentIndex === -1) {
    anchorRef.current = {
      id: row.id,
      selected: !row.getIsSelected(),
    };
    return false;
  }

  event.preventDefault();

  const [from, to] =
    anchorIndex < currentIndex
      ? [anchorIndex, currentIndex]
      : [currentIndex, anchorIndex];

  const selected = anchorRef.current.selected;
  table.setRowSelection((currentSelection: RowSelectionState) => {
    const nextSelection = { ...currentSelection };

    for (let index = from; index <= to; index++) {
      const rangeRow = rows[index];
      if (!rangeRow) continue;

      if (selected) {
        nextSelection[rangeRow.id] = true;
      } else {
        delete nextSelection[rangeRow.id];
      }
    }

    return nextSelection;
  });
  anchorRef.current = { id: row.id, selected };
  return true;
}