All files / webview/csv useKeyboard.ts

89.85% Statements 62/69
80.39% Branches 41/51
100% Functions 4/4
90.62% Lines 58/64

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 100 101 102 103 104 105 106 107 108 109 110                        14x 14x 14x   14x 1x 1x 1x     13x   2x 2x 2x     11x   10x 1x 1x   1x     9x   9x           1x 1x 1x           1x 1x 1x   2x 2x 2x   1x 1x 1x   1x   1x 1x 1x   1x     1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 4x       1x   1x 1x 1x   1x 1x   1x       14x 14x 14x 14x 14x      
import { useEffect, useCallback } from 'react';
import type { CsvState, CsvAction, Selection } from './types';
 
interface UseKeyboardProps {
  state: CsvState;
  sortedToSourceMap: number[];
  dispatch: (action: CsvAction) => void;
  moveSelection: (current: Selection | null, dir: 'up' | 'down' | 'left' | 'right', extend?: boolean) => void;
  containerRef: React.RefObject<HTMLDivElement | null>;
}
 
export function useKeyboard({ state, sortedToSourceMap, dispatch, moveSelection, containerRef }: UseKeyboardProps) {
  const handleKeyDown = useCallback((e: KeyboardEvent) => {
    const target = e.target as HTMLElement;
    const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';
 
    if (e.key === 'z' && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
      e.preventDefault();
      dispatch({ type: 'UNDO' });
      return;
    }
 
    if ((e.key === 'z' && (e.ctrlKey || e.metaKey) && e.shiftKey) ||
        (e.key === 'y' && (e.ctrlKey || e.metaKey))) {
      e.preventDefault();
      dispatch({ type: 'REDO' });
      return;
    }
 
    if (isInput) return;
 
    if (state.editingCell) {
      Eif (e.key === 'Escape') {
        dispatch({ type: 'SET_EDITING_CELL', cell: null });
      }
      return;
    }
 
    const extend = e.shiftKey;
 
    switch (e.key) {
      case 'ArrowUp':
        e.preventDefault();
        moveSelection(state.selection, 'up', extend);
        break;
      case 'ArrowDown':
        e.preventDefault();
        moveSelection(state.selection, 'down', extend);
        break;
      case 'ArrowLeft':
        e.preventDefault();
        moveSelection(state.selection, 'left', extend);
        break;
      case 'ArrowRight':
        e.preventDefault();
        moveSelection(state.selection, 'right', extend);
        break;
      case 'Tab':
        e.preventDefault();
        moveSelection(state.selection, e.shiftKey ? 'left' : 'right', false);
        break;
      case 'Enter':
        e.preventDefault();
        Eif (state.selection) {
          dispatch({ type: 'SET_EDITING_CELL', cell: state.selection.end });
        }
        break;
      case 'F2':
        e.preventDefault();
        Eif (state.selection) {
          dispatch({ type: 'SET_EDITING_CELL', cell: state.selection.end });
        }
        break;
      case 'Delete':
      case 'Backspace':
        e.preventDefault();
        Eif (state.selection) {
          const { start, end: selEnd } = state.selection;
          const minR = Math.min(start.row, selEnd.row);
          const maxR = Math.max(start.row, selEnd.row);
          const minC = Math.min(start.col, selEnd.col);
          const maxC = Math.max(start.col, selEnd.col);
          for (let r = minR; r <= maxR; r++) {
            const sourceRow = sortedToSourceMap[r];
            for (let c = minC; c <= maxC; c++) {
              dispatch({ type: 'SET_CELL', row: sourceRow, col: c, value: '' });
            }
          }
        }
        break;
      case 'Escape':
        dispatch({ type: 'SET_SELECTION', selection: null });
        dispatch({ type: 'SET_CONTEXT_MENU', menu: { visible: false, x: 0, y: 0, targetCell: null } });
        break;
      default:
        Eif (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && state.selection) {
          dispatch({ type: 'SET_EDITING_CELL', cell: state.selection.end });
        }
        break;
    }
  }, [state, dispatch, moveSelection]);
 
  useEffect(() => {
    const el = containerRef.current;
    Iif (!el) return;
    el.addEventListener('keydown', handleKeyDown);
    return () => el.removeEventListener('keydown', handleKeyDown);
  }, [handleKeyDown, containerRef]);
}