All files / webview/csv useSelection.ts

100% Statements 40/40
100% Branches 26/26
100% Functions 8/8
100% Lines 37/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                    16x 16x   16x 4x 1x         3x 3x             16x 3x 3x 3x           16x 3x 1x           16x 1x     16x         9x 8x 9x     9x   2x 2x   3x 3x   1x 1x   2x 2x     8x 1x   7x       16x       16x                 11x 10x 10x    
import { useCallback, useRef } from 'react';
import type { CellPosition, Selection, CsvAction } from './types';
 
interface UseSelectionProps {
  dispatch: (action: CsvAction) => void;
  totalRows: number;
  totalCols: number;
}
 
export function useSelection({ dispatch, totalRows, totalCols }: UseSelectionProps) {
  const isDragging = useRef(false);
  const dragStart = useRef<CellPosition | null>(null);
 
  const selectCell = useCallback((pos: CellPosition, extend = false) => {
    if (extend && dragStart.current) {
      dispatch({
        type: 'SET_SELECTION',
        selection: { start: dragStart.current, end: pos },
      });
    } else {
      dragStart.current = pos;
      dispatch({
        type: 'SET_SELECTION',
        selection: { start: pos, end: pos },
      });
    }
  }, [dispatch]);
 
  const startDrag = useCallback((pos: CellPosition) => {
    isDragging.current = true;
    dragStart.current = pos;
    dispatch({
      type: 'SET_SELECTION',
      selection: { start: pos, end: pos },
    });
  }, [dispatch]);
 
  const updateDrag = useCallback((pos: CellPosition) => {
    if (!isDragging.current || !dragStart.current) return;
    dispatch({
      type: 'SET_SELECTION',
      selection: { start: dragStart.current, end: pos },
    });
  }, [dispatch]);
 
  const endDrag = useCallback(() => {
    isDragging.current = false;
  }, []);
 
  const moveSelection = useCallback((
    current: Selection | null,
    direction: 'up' | 'down' | 'left' | 'right',
    extend = false
  ) => {
    if (!current) return;
    const anchor = extend ? current.start : current.end;
    const moving = current.end;
    let newPos: CellPosition;
 
    switch (direction) {
      case 'up':
        newPos = { row: Math.max(0, moving.row - 1), col: moving.col };
        break;
      case 'down':
        newPos = { row: Math.min(totalRows - 1, moving.row + 1), col: moving.col };
        break;
      case 'left':
        newPos = { row: moving.row, col: Math.max(0, moving.col - 1) };
        break;
      case 'right':
        newPos = { row: moving.row, col: Math.min(totalCols - 1, moving.col + 1) };
        break;
    }
 
    if (extend) {
      dispatch({ type: 'SET_SELECTION', selection: { start: anchor, end: newPos } });
    } else {
      dispatch({ type: 'SET_SELECTION', selection: { start: newPos, end: newPos } });
    }
  }, [dispatch, totalRows, totalCols]);
 
  return { selectCell, startDrag, updateDrag, endDrag, moveSelection };
}
 
export function getSelectionRange(selection: Selection): { minRow: number; maxRow: number; minCol: number; maxCol: number } {
  return {
    minRow: Math.min(selection.start.row, selection.end.row),
    maxRow: Math.max(selection.start.row, selection.end.row),
    minCol: Math.min(selection.start.col, selection.end.col),
    maxCol: Math.max(selection.start.col, selection.end.col),
  };
}
 
export function isCellInSelection(row: number, col: number, selection: Selection | null): boolean {
  if (!selection) return false;
  const { minRow, maxRow, minCol, maxCol } = getSelectionRange(selection);
  return row >= minRow && row <= maxRow && col >= minCol && col <= maxCol;
}