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 | 8x 8x 5x 3x 7x 5x 8x 3x 5x 5x 5x 1x 1x 1x 4x 4x 8x 8x 8x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 5x 5x 5x | import { useEffect } from "react";
import type { UseUndoRedoResult } from "@/hooks/useUndoRedo";
type KeyboardShortcutHandler = UseUndoRedoResult<unknown>["undo"];
type KeyboardShortcutAvailability = UseUndoRedoResult<unknown>["canUndo"];
type KeyboardEventTarget = Pick<Window, "addEventListener" | "removeEventListener">;
export interface UseKeyboardShortcutsOptions {
onUndo: KeyboardShortcutHandler;
onRedo: KeyboardShortcutHandler;
onDelete?: KeyboardShortcutHandler;
onSave?: KeyboardShortcutHandler;
canUndo?: KeyboardShortcutAvailability;
canRedo?: KeyboardShortcutAvailability;
canDelete?: KeyboardShortcutAvailability;
canSave?: KeyboardShortcutAvailability;
target?: KeyboardEventTarget;
shouldIgnoreEvent?: (event: KeyboardEvent) => boolean;
}
function isEditableTarget(event: KeyboardEvent): boolean {
const target = event.target;
if (!(target instanceof Element)) {
return false;
}
return (
(target instanceof HTMLElement && target.isContentEditable) ||
target.closest("input, textarea, [contenteditable='true'], [contenteditable='']") !== null
);
}
export function useKeyboardShortcuts({
onUndo,
onRedo,
onDelete,
onSave,
canUndo = true,
canRedo = true,
canDelete = false,
canSave = true,
target = window,
shouldIgnoreEvent = isEditableTarget,
}: UseKeyboardShortcutsOptions): void {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (shouldIgnoreEvent(event)) {
return;
}
const key = event.key.toLowerCase();
const isDeleteShortcut = key === "delete";
if (isDeleteShortcut && canDelete && onDelete) {
event.preventDefault();
onDelete();
return;
}
Iif (!event.ctrlKey && !event.metaKey) {
return;
}
const isSaveShortcut = key === "s" && !event.shiftKey;
const isUndoShortcut = key === "z" && !event.shiftKey;
const isRedoShortcut = key === "y" || (key === "z" && event.shiftKey);
if (isSaveShortcut && canSave && onSave) {
event.preventDefault();
onSave();
return;
}
if (isUndoShortcut && canUndo) {
event.preventDefault();
onUndo();
return;
}
Eif (isRedoShortcut && canRedo) {
event.preventDefault();
onRedo();
}
};
target.addEventListener("keydown", handleKeyDown);
return () => {
target.removeEventListener("keydown", handleKeyDown);
};
}, [
canDelete,
canRedo,
canSave,
canUndo,
onDelete,
onRedo,
onSave,
onUndo,
shouldIgnoreEvent,
target,
]);
}
|