UNPKG

@limetech/lime-elements

Version:
214 lines (213 loc) 5.76 kB
/** * These helpers provide a consistent way to: * - Normalize user-defined hotkey strings (e.g. "cmd+k", "ctrl+shift+p") * - Normalize `KeyboardEvent`s into the same canonical hotkey format * * **Canonical format** * - Modifiers are ordered: `meta`, `ctrl`, `alt`, `shift` * - Tokens are joined with `+` * - The final token is the non-modifier key * * Examples: * - `"cmd+k"` → `"meta+k"` * - `"return"` → `"enter"` * - `"backspace"` → `"backspace"` * - `"alt+shift+l"` → `"alt+shift+l"` * * Note: `keycodes.ts` defines single-key constants (e.g. `ENTER`, `ESCAPE`) * used for simple `event.key` comparisons in components. This module handles * multi-key hotkey combinations with modifier normalization and alias * resolution — a different concern. */ const NORMALIZED_HOTKEY_SEPARATOR = '+'; const KEY_ALIASES = { cmd: 'meta', command: 'meta', win: 'meta', windows: 'meta', control: 'ctrl', option: 'alt', esc: 'escape', return: 'enter', del: 'delete', up: 'arrowup', down: 'arrowdown', left: 'arrowleft', right: 'arrowright', spacebar: 'space', }; export const tokenizeHotkeyString = (hotkey) => { var _a; const raw = (hotkey !== null && hotkey !== void 0 ? hotkey : '').trim(); if (!raw) { return []; } // Allow `+` as a hotkey. if (raw === '+') { return ['+']; } // Split on `+`, but treat `++` as the `+` key. const tokens = []; let current = ''; let index = 0; while (index < raw.length) { const char = raw[index]; if (char !== '+') { current += char; index++; continue; } let nextIndex = index + 1; while (((_a = raw[nextIndex]) === null || _a === void 0 ? void 0 : _a.trim()) === '') { nextIndex++; } const nextChar = raw[nextIndex]; if (nextChar === '+') { const token = current.trim(); if (token) { tokens.push(token); } tokens.push('+'); current = ''; index = nextIndex + 1; continue; } const token = current.trim(); if (token) { tokens.push(token); } current = ''; index++; } const tail = current.trim(); if (tail) { tokens.push(tail); } return tokens; }; const normalizeKey = (key) => { const normalized = key.trim().toLowerCase(); if (!normalized) { return null; } if (Object.hasOwn(KEY_ALIASES, normalized)) { return KEY_ALIASES[normalized]; } return normalized; }; const normalizeModifiersAndKey = (input) => { const normalizedKey = normalizeKey(input.key); if (!normalizedKey) { return null; } // Ignore pure modifier presses if (['shift', 'alt', 'ctrl', 'meta'].includes(normalizedKey)) { return null; } const parts = []; if (input.meta) { parts.push('meta'); } if (input.ctrl) { parts.push('ctrl'); } if (input.alt) { parts.push('alt'); } if (input.shift && normalizedKey !== '+') { parts.push('shift'); } parts.push(normalizedKey); return parts.join(NORMALIZED_HOTKEY_SEPARATOR); }; /** * Normalize a user-defined hotkey string to the canonical format. * * Returns `null` for empty/invalid inputs or if the string only contains * modifiers (e.g. `"ctrl+shift"`). * * If multiple non-modifier keys are present, only the last one is used * (e.g. `"ctrl+a+b"` becomes `"ctrl+b"`). * * @param hotkey - User-provided hotkey string. */ export const normalizeHotkeyString = (hotkey) => { if (!hotkey) { return null; } const tokens = tokenizeHotkeyString(hotkey); if (tokens.length === 0) { return null; } let alt = false; let ctrl = false; let meta = false; let shift = false; let key = null; for (const token of tokens) { const normalized = normalizeKey(token); if (!normalized) { continue; } if (normalized === 'alt') { alt = true; continue; } if (normalized === 'ctrl') { ctrl = true; continue; } if (normalized === 'meta') { meta = true; continue; } if (normalized === 'shift') { shift = true; continue; } // Last non-modifier wins key = normalized; } if (!key) { return null; } return normalizeModifiersAndKey({ key, alt, ctrl, meta, shift }); }; const normalizeEventKey = (event) => { const code = (event.code || '').trim(); if (/^Key[A-Z]$/.test(code)) { return code.slice(3).toLowerCase(); } if (/^Digit\d$/.test(code)) { return code.slice(5); } const key = event.key; if (key === ' ') { return 'space'; } return normalizeKey(key); }; /** * Convert a `KeyboardEvent` into a canonical hotkey string. * * Uses `event.code` when possible for letter/digit keys to avoid * layout-dependent results. * * @param event - Keyboard event to normalize. */ export const hotkeyFromKeyboardEvent = (event) => { const key = normalizeEventKey(event); if (!key) { return null; } // `+` typically requires Shift on many keyboard layouts, but users expect to // write hotkeys like `meta++` (⌘+) without explicitly adding `shift`. const shift = key === '+' ? false : event.shiftKey; return normalizeModifiersAndKey({ key, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey, shift, }); };