import { useEffect } from "react"; import { editingCellContext } from "../context/EditorContext"; import { usePropsRef } from "../context/PropsContext"; import { selectionContext } from "../context/SelectionContext"; import { useSelectionUtils } from "./useSelectionUtils"; const functionKeys = [ "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Control", "Shift", "Enter", " ", "Tab", "Backspace", "Delete", ]; export const useEditorKeyHandlers = () => { const editingCellRef = editingCellContext.useRef(); const setEditingCell = editingCellContext.useSetter(); const selectionRef = selectionContext.useRef(); const setSelection = selectionContext.useSetter(); const propsRef = usePropsRef(); const { offsetSelection } = useSelectionUtils(); useEffect(() => { const listener = (e: KeyboardEvent) => { if (e.defaultPrevented) { return; } // Enter triggers editing cell to end editing and move down, or a selected // cell to start editing. if (e.key === "Enter") { if (editingCellRef.current) { setEditingCell(null); setSelection( (selection) => selection && offsetSelection(selection, "down") ); e.preventDefault(); } else if (selectionRef.current) { const isReadonly = propsRef.current.columnProps[ selectionRef.current.primary.x ].isReadonly?.( propsRef.current.rows[selectionRef.current.primary.y] ) ?? false; if (!isReadonly) { setEditingCell(selectionRef.current.primary); } e.preventDefault(); } } // Tab triggers a editing cell to end editing and moves the selection to // the right if (e.key === "Tab") { setEditingCell(null); setSelection( (selection) => selection && offsetSelection(selection, "right") ); e.preventDefault(); } // If there is no editing cell, delete and backspace call the onClear // callback on the selected cell if (e.key === "Delete" || e.key === "Backspace") { if (!editingCellRef.current && selectionRef.current) { const isReadonly = propsRef.current.columnProps[ selectionRef.current.primary.x ].isReadonly?.( propsRef.current.rows[selectionRef.current.primary.y] ) ?? false; if (!isReadonly) { propsRef.current.onChange([ propsRef.current.columnProps[ selectionRef.current.primary.x ].onClear(propsRef.current.rows[selectionRef.current.primary.y]), ]); } e.preventDefault(); } } // Space causes the selected cell to start editing if (e.key === " ") { if (!editingCellRef.current && selectionRef.current) { const isReadonly = propsRef.current.columnProps[ selectionRef.current.primary.x ].isReadonly?.( propsRef.current.rows[selectionRef.current.primary.y] ) ?? false; if (!isReadonly) { setEditingCell(selectionRef.current.primary); } e.preventDefault(); } } // All "normal" keys cause us to start editing. This allows us to press a // letter key on a non-editing cell and expect that letter to appear in // the editor if ( selectionRef.current && !editingCellRef.current && !functionKeys.includes(e.key) && !e.ctrlKey && !e.metaKey ) { const isReadonly = propsRef.current.columnProps[ selectionRef.current.primary.x ].isReadonly?.( propsRef.current.rows[selectionRef.current.primary.y] ) ?? false; if (!isReadonly) { propsRef.current.onChange([ propsRef.current.columnProps[ selectionRef.current.primary.x ].onClear(propsRef.current.rows[selectionRef.current.primary.y]), ]); setEditingCell(selectionRef.current.primary); } } }; document.addEventListener("keydown", listener); return () => document.removeEventListener("keydown", listener); }, []); };