import { closeModal, modals } from 'svelte-modals'; import { get } from 'svelte/store'; type LowercaseLetter = | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'; type UppercaseLetter = Uppercase; type Letter = LowercaseLetter | UppercaseLetter; type Special = '?' | 'Enter' | 'Escape' | 'Delete'; const modeSwitches = ['n', 'g', 'i'] as const; type ModeSwitch = (typeof modeSwitches)[number]; function isModeSwitch(s: string): s is ModeSwitch { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument return modeSwitches.indexOf(s as any) !== -1; } type Key = Letter | Special; type KeyCombo = `${ModeSwitch}${Letter}`; export type Shortcut = Key | KeyCombo; type EventAction = (event: KeyboardEvent) => void; type FocusAction = HTMLInputElement; type ClickAction = HTMLElement; type Action = EventAction | FocusAction | ClickAction; const handlers = new Map(); let mode: ModeSwitch | undefined; export function handleShortcuts(event: KeyboardEvent) { if (isInputElement(event.target)) { if (event.key === 'Escape') { event.target.blur(); event.preventDefault(); event.stopImmediatePropagation(); } return; } if (event.ctrlKey) { return; } if (event.key === 'Escape') { if (get(modals).length > 0) { closeModal(); event.preventDefault(); event.stopImmediatePropagation(); return; } } if (isModeSwitch(event.key) && mode === undefined) { mode = event.key; event.preventDefault(); return; } const handler = handlers.get(mode === undefined ? event.key : `${mode}${event.key}`); if (!handler || get(modals).length > 0) { mode = undefined; return; } if (handler instanceof HTMLInputElement) { handler.focus(); } else if (handler instanceof HTMLElement) { handler.click(); } else { handler(event); } mode = undefined; event.preventDefault(); } export function accelerator(node: HTMLElement | HTMLInputElement, sc: Shortcut) { handlers.set(sc, node); return { destroy() { handlers.delete(sc); } }; } export function binds(node: Document, scs: [string, EventAction][]) { const handlers = new Map(); for (const [k, a] of scs) { handlers.set(k, a); } function keydown(event: KeyboardEvent) { if (isInputElement(event.target)) return; const handler = handlers.get(event.key); if (!handler) return; handler(event); event.preventDefault(); } node.addEventListener('keydown', keydown); return { destroy() { node.removeEventListener('keydown', keydown); } }; } export function addShortcut(sc: Shortcut, action: EventAction) { handlers.set(sc, action); } function isInputElement(target: EventTarget | null): target is HTMLElement { return ( target instanceof HTMLElement && (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement || target.isContentEditable) ); }