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]);
}
|