diff --git a/src/components/tables/common.tsx b/src/components/tables/common.tsx index 81e33e6..c74953b 100644 --- a/src/components/tables/common.tsx +++ b/src/components/tables/common.tsx @@ -125,18 +125,23 @@ function useTable( return [table, columnVisibility, setColumnVisibility, columnOrder, setColumnOrder, columnSizing, sorting]; } +interface HIDEvent { + modKey: boolean, + shiftKey: boolean, + isRmb: boolean, +} + function useSelectHandler( table: Table, selectedReducer: TableSelectReducer, getRowId: (row: TData) => string, setCurrent?: (id: string) => void, -): [number, (event: React.MouseEvent, index: number, lastIndex: number) => void] { +): [number, (event: HIDEvent, index: number, lastIndex: number) => void] { const [lastIndex, setLastIndex] = useState(-1); - const onRowClick = useCallback((event: React.MouseEvent, index: number, lastIndex: number) => { + const onRowClick = useCallback((event: HIDEvent, index: number, lastIndex: number) => { const rows = table.getRowModel().rows; - event.preventDefault(); - const modKey = eventHasModKey(event); + if (index < 0 || index >= rows.length) return; function genIds() { const minIndex = Math.min(index, lastIndex); @@ -148,15 +153,15 @@ function useSelectHandler( return ids; } - if (event.shiftKey && modKey && lastIndex !== -1) { + if (event.shiftKey && event.modKey && lastIndex !== -1) { const ids = genIds(); selectedReducer({ verb: "add", ids }); } else if (event.shiftKey && lastIndex !== -1) { const ids = genIds(); selectedReducer({ verb: "set", ids }); - } else if (modKey) { + } else if (event.modKey) { selectedReducer({ verb: "toggle", ids: [getRowId(rows[index].original)] }); - } else if (event.button !== 2 || !rows[index].getIsSelected()) { + } else if (!event.isRmb || !rows[index].getIsSelected()) { selectedReducer({ verb: "set", ids: [getRowId(rows[index].original)] }); } @@ -258,7 +263,7 @@ function TableRow(props: { index: number, start: number, lastIndex: number, - onRowClick: (e: React.MouseEvent, i: number, li: number) => void, + onRowClick: (e: HIDEvent, i: number, li: number) => void, onRowDoubleClick?: (row: TData) => void, height: number, columnSizing: ColumnSizingState, @@ -272,17 +277,55 @@ function TableRow(props: { } }, [propsDblClick, row.original]); + const { onRowClick, index, lastIndex } = props; + + const onMouseEvent = useCallback((e: React.MouseEvent) => { + onRowClick({ + modKey: eventHasModKey(e), + shiftKey: e.shiftKey, + isRmb: e.button === 2, + }, index, lastIndex); + }, [index, lastIndex, onRowClick]); + + const ref = useRef(null); + + const onKeyDown = useCallback((e: React.KeyboardEvent) => { + let newIndex = index; + if (e.key === "ArrowDown") { + newIndex += 1; + } else if (e.key === "ArrowUp") { + newIndex -= 1; + } else if (e.key !== "ContextMenu") { + return; + } + e.preventDefault(); + if (ref.current != null) { + if (e.key === "ArrowDown" && ref.current.nextElementSibling != null) { + const element = ref.current.nextElementSibling as HTMLElement; + element.scrollIntoView({ behavior: "instant", block: "nearest" }); + element.focus(); + } + if (e.key === "ArrowUp" && ref.current.previousElementSibling != null) { + const element = ref.current.previousElementSibling as HTMLElement; + element.scrollIntoView({ behavior: "instant", block: "nearest" }); + element.focus(); + } + } + onRowClick({ + modKey: eventHasModKey(e), + shiftKey: e.shiftKey, + isRmb: e.key === "ContextMenu", + }, newIndex, lastIndex); + }, [index, lastIndex, onRowClick]); + return ( -
{ - props.onRowClick(e, props.index, props.lastIndex); - }} - onContextMenu={(e) => { - props.onRowClick(e, props.index, props.lastIndex); - }} + onClick={onMouseEvent} + onContextMenu={onMouseEvent} onDoubleClick={onRowDoubleClick} + onKeyDown={onKeyDown} tabIndex={-1} > @@ -530,7 +573,9 @@ export function EditableNameField(props: EditableNameFieldProps) { setNewName(props.currentName); }, [props.currentName]); - const onEnter = useCallback((event: React.KeyboardEvent) => { + const ref = useRef(null); + + const onTextKeyDown = useCallback((event: React.KeyboardEvent) => { if (event.key === "Enter") { props.onUpdate?.( newName, @@ -539,6 +584,10 @@ export function EditableNameField(props: EditableNameFieldProps) { }, () => { setRenaming(false); }); } + if (event.key === "Escape") { + setRenaming(false); + ref.current?.focus(); + } }, [newName, props]); useEffect(() => { @@ -548,8 +597,6 @@ export function EditableNameField(props: EditableNameFieldProps) { } }, [isRenaming]); - const ref = useRef(null); - useEffect(() => { if (ref.current != null) { const row = ref.current.parentNode?.parentNode as HTMLDivElement; @@ -580,7 +627,7 @@ export function EditableNameField(props: EditableNameFieldProps) { }} onChange={(e) => { setNewName(e.target.value); }} onBlur={() => { setRenaming(false); }} - onKeyDown={onEnter} + onKeyDown={onTextKeyDown} onClick={(e) => { e.stopPropagation(); }} /> : {props.currentName} diff --git a/src/trutil.ts b/src/trutil.ts index 4397c84..565d161 100644 --- a/src/trutil.ts +++ b/src/trutil.ts @@ -125,7 +125,7 @@ export function pathMapToServer(path: string, config: ServerConfig) { return mappedPath; } -export function eventHasModKey(event: React.MouseEvent) { +export function eventHasModKey(event: React.MouseEvent | React.KeyboardEvent) { return (navigator.platform.startsWith("Mac") && event.metaKey) || (!navigator.platform.startsWith("Mac") && event.ctrlKey); }