/** * Keyboard utility functions for handling keyboard events */ export type OSType = "macos" | "windows" | "linux" | "unknown"; /** * Extract a consistent key name from a KeyboardEvent * This function provides cross-platform keyboard event handling * and returns key names appropriate for the target operating system */ export const getKeyName = ( e: KeyboardEvent, osType: OSType = "unknown", ): string => { // Handle special cases first if (e.code) { const code = e.code; // Handle function keys (F1-F24) if (code.match(/^F\d+$/)) { return code.toLowerCase(); // F1, F2, ..., F14, F15, etc. } // Handle regular letter keys (KeyA -> a) if (code.match(/^Key[A-Z]$/)) { return code.replace("Key", "").toLowerCase(); } // Handle digit keys (Digit0 -> 0) if (code.match(/^Digit\d$/)) { return code.replace("Digit", ""); } // Handle numpad digit keys (Numpad0 -> numpad 0) if (code.match(/^Numpad\d$/)) { return code.replace("Numpad", "numpad ").toLowerCase(); } // Handle modifier keys - OS-specific naming const getModifierName = (baseModifier: string): string => { switch (baseModifier) { case "shift": return "shift"; case "ctrl": return osType === "macos" ? "ctrl" : "ctrl"; case "alt": return osType === "macos" ? "option" : "alt"; case "meta": // Windows key on Windows/Linux, Command key on Mac if (osType === "macos") return "command"; if (osType === "windows") return "win"; return "super"; // Linux convention default: return baseModifier; } }; const modifierMap: Record = { ShiftLeft: getModifierName("shift"), ShiftRight: getModifierName("shift"), ControlLeft: getModifierName("ctrl"), ControlRight: getModifierName("ctrl"), AltLeft: getModifierName("alt"), AltRight: getModifierName("alt"), MetaLeft: getModifierName("meta"), MetaRight: getModifierName("meta"), OSLeft: getModifierName("meta"), OSRight: getModifierName("meta"), CapsLock: "caps lock", Tab: "tab", Enter: "enter", Space: "space", Backspace: "backspace", Delete: "delete", Escape: "esc", ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", Home: "home", End: "end", PageUp: "page up", PageDown: "page down", Insert: "insert", PrintScreen: "print screen", ScrollLock: "scroll lock", Pause: "pause", ContextMenu: "menu", NumpadMultiply: "numpad *", NumpadAdd: "numpad +", NumpadSubtract: "numpad -", NumpadDecimal: "numpad .", NumpadDivide: "numpad /", NumLock: "num lock", }; if (modifierMap[code]) { return modifierMap[code]; } // Handle punctuation and special characters const punctuationMap: Record = { Semicolon: ";", Equal: "=", Comma: ",", Minus: "-", Period: ".", Slash: "/", Backquote: "`", BracketLeft: "[", Backslash: "\\", BracketRight: "]", Quote: "'", }; if (punctuationMap[code]) { return punctuationMap[code]; } // For any other codes, try to convert to a reasonable format return code.toLowerCase().replace(/([a-z])([A-Z])/g, "$1 $2"); } // Fallback to e.key if e.code is not available if (e.key) { const key = e.key; // Handle special key names with OS-specific formatting const keyMap: Record = { Control: osType === "macos" ? "ctrl" : "ctrl", Alt: osType === "macos" ? "option" : "alt", Shift: "shift", Meta: osType === "macos" ? "command" : osType === "windows" ? "win" : "super", OS: osType === "macos" ? "command" : osType === "windows" ? "win" : "super", CapsLock: "caps lock", ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", Escape: "esc", " ": "space", }; if (keyMap[key]) { return keyMap[key]; } return key.toLowerCase(); } // Last resort fallback return `unknown-${e.keyCode || e.which || 0}`; }; /** * Get display-friendly key combination string for the current OS * Returns basic plus-separated format with correct platform key names */ export const formatKeyCombination = ( combination: string, osType: OSType, ): string => { // Simply return the combination as-is since getKeyName already provides // the correct platform-specific key names return combination; }; /** * Normalize modifier keys to handle left/right variants */ export const normalizeKey = (key: string): string => { // Handle left/right variants of modifier keys if (key.startsWith("left ") || key.startsWith("right ")) { const parts = key.split(" "); if (parts.length === 2) { // Return just the modifier name without left/right prefix return parts[1]; } } return key; };