import { isDarwin } from "./constants"; import type { ValueOf } from "./utility-types"; export const CODES = { EQUAL: "Equal", MINUS: "Minus", NUM_ADD: "NumpadAdd", NUM_SUBTRACT: "NumpadSubtract", NUM_ZERO: "Numpad0", BRACKET_RIGHT: "BracketRight", BRACKET_LEFT: "BracketLeft", ONE: "Digit1", TWO: "Digit2", THREE: "Digit3", NINE: "Digit9", QUOTE: "Quote", ZERO: "Digit0", SLASH: "Slash", C: "KeyC", D: "KeyD", H: "KeyH", V: "KeyV", Z: "KeyZ", Y: "KeyY", R: "KeyR", S: "KeyS", } as const; export const KEYS = { ARROW_DOWN: "ArrowDown", ARROW_LEFT: "ArrowLeft", ARROW_RIGHT: "ArrowRight", ARROW_UP: "ArrowUp", PAGE_UP: "PageUp", PAGE_DOWN: "PageDown", BACKSPACE: "Backspace", ALT: "Alt", CTRL_OR_CMD: isDarwin ? "metaKey" : "ctrlKey", DELETE: "Delete", ENTER: "Enter", ESCAPE: "Escape", QUESTION_MARK: "?", SPACE: " ", TAB: "Tab", CHEVRON_LEFT: "<", CHEVRON_RIGHT: ">", PERIOD: ".", COMMA: ",", SUBTRACT: "-", SLASH: "/", A: "a", C: "c", D: "d", E: "e", F: "f", G: "g", H: "h", I: "i", L: "l", O: "o", P: "p", Q: "q", R: "r", S: "s", T: "t", V: "v", X: "x", Y: "y", Z: "z", K: "k", W: "w", 0: "0", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", } as const; export type Key = keyof typeof KEYS; // defines key code mapping for matching codes as fallback to respective keys on non-latin keyboard layouts export const KeyCodeMap = new Map, ValueOf>([ [KEYS.Z, CODES.Z], [KEYS.Y, CODES.Y], ]); export const isLatinChar = (key: string) => /^[a-z]$/.test(key.toLowerCase()); /** * Used to match key events for any keyboard layout, especially on Windows and Linux, * where non-latin character with modified (CMD) is not substituted with latin-based alternative. * * Uses `event.key` when it's latin, otherwise fallbacks to `event.code` (if mapping exists). * * Example of pressing "z" on different layouts, with the chosen key or code highlighted in []: * * Layout | Code | Key | Comment * --------------------- | ----- | --- | ------- * U.S. | KeyZ | [z] | * Czech | KeyY | [z] | * Turkish | KeyN | [z] | * French | KeyW | [z] | * Macedonian | [KeyZ] | з | z with cmd; з is Cyrillic equivalent of z * Russian | [KeyZ] | я | z with cmd * Serbian | [KeyZ] | ѕ | z with cmd * Greek | [KeyZ] | ζ | z with cmd; also ζ is Greek equivalent of z * Hebrew | [KeyZ] | ז | z with cmd; also ז is Hebrew equivalent of z * Pinyin - Simplified | KeyZ | [z] | due to IME * Cangije - Traditional | [KeyZ] | 重 | z with cmd * Japanese | [KeyZ] | つ | z with cmd * 2-Set Korean | [KeyZ] | ㅋ | z with cmd * * More details in https://github.com/excalidraw/excalidraw/pull/5944 */ export const matchKey = ( event: KeyboardEvent | React.KeyboardEvent, key: ValueOf, ): boolean => { // for latin layouts use key if (key === event.key.toLowerCase()) { return true; } // non-latin layouts fallback to code const code = KeyCodeMap.get(key); return Boolean(code && !isLatinChar(event.key) && event.code === code); }; export const isArrowKey = (key: string) => key === KEYS.ARROW_LEFT || key === KEYS.ARROW_RIGHT || key === KEYS.ARROW_DOWN || key === KEYS.ARROW_UP; export const shouldResizeFromCenter = (event: MouseEvent | KeyboardEvent) => event.altKey; export const shouldMaintainAspectRatio = (event: MouseEvent | KeyboardEvent) => event.shiftKey; export const shouldRotateWithDiscreteAngle = ( event: MouseEvent | KeyboardEvent | React.PointerEvent, ) => event.shiftKey;