class KeyboardHandler { constructor() { this.queue = []; this.shortcuts = new Map(); this.triggers = new Set(); } on(combination, callback) { const keys = combination.split(" "); this.shortcuts.set(combination, { keys, callback }); this.triggers.add(keys[0]); } listen() { document.onkeydown = (event) => { const key = KeyboardHandler.getKey(event); if (this.isEventIgnored(event, key) || KeyboardHandler.isModifierKeyDown(event)) { return; } if (key !== "Enter") { event.preventDefault(); } this.queue.push(key); for (const [combination, { keys, callback }] of this.shortcuts.entries()) { if (keys.every((value, index) => value === this.queue[index])) { this.queue = []; callback(event); return; } if (keys.length === 1 && key === keys[0]) { this.queue = []; callback(event); return; } } if (this.queue.length >= 2) { this.queue = []; } }; } isEventIgnored(event, key) { return event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA" || (this.queue.length < 1 && !this.triggers.has(key)); } static isModifierKeyDown(event) { return event.getModifierState("Control") || event.getModifierState("Alt") || event.getModifierState("Meta"); } static getKey(event) { switch (event.key) { case 'Esc': return 'Escape'; case 'Up': return 'ArrowUp'; case 'Down': return 'ArrowDown'; case 'Left': return 'ArrowLeft'; case 'Right': return 'ArrowRight'; default: return event.key; } } }