import * as dom from "../shared/utils/dom"; import Key from "../shared/settings/Key"; const cancelKey = (e: KeyboardEvent): boolean => { if (e.key === "Escape") { return true; } if (e.key === "[" && e.ctrlKey) { return true; } return false; }; const modifiedKeyName = (name: string): string => { if (name === " ") { return "Space"; } if (name.length === 1) { return name; } else if (name === "Escape") { return "Esc"; } return name; }; // visible for testing export const keyFromKeyboardEvent = (e: KeyboardEvent): Key => { const key = modifiedKeyName(e.key); let shift = e.shiftKey; if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { // make shift false for symbols to enable key bindings by symbold keys. // But this limits key bindings by symbol keys with Shift // (such as Shift+$>. shift = false; } return new Key({ key: modifiedKeyName(e.key), shift: shift, ctrl: e.ctrlKey, alt: e.altKey, meta: e.metaKey, }); }; export default class InputDriver { private pressed: { [key: string]: string } = {}; private onKeyListeners: ((key: Key) => boolean)[] = []; constructor(target: HTMLElement) { this.pressed = {}; this.onKeyListeners = []; target.addEventListener("keypress", this.onKeyPress.bind(this)); target.addEventListener("keydown", this.onKeyDown.bind(this)); target.addEventListener("keyup", this.onKeyUp.bind(this)); } onKey(cb: (key: Key) => boolean) { this.onKeyListeners.push(cb); } private onKeyPress(e: KeyboardEvent) { if (this.pressed[e.key] && this.pressed[e.key] !== "keypress") { return; } this.pressed[e.key] = "keypress"; this.capture(e); } private onKeyDown(e: KeyboardEvent) { if (this.pressed[e.key] && this.pressed[e.key] !== "keydown") { return; } this.pressed[e.key] = "keydown"; this.capture(e); } private onKeyUp(e: KeyboardEvent) { delete this.pressed[e.key]; } // eslint-disable-next-line max-statements private capture(e: KeyboardEvent) { const target = e.target; if (!(target instanceof HTMLElement)) { return; } if (this.fromInput(target)) { if (cancelKey(e) && target.blur) { target.blur(); } return; } if (["Shift", "Control", "Alt", "OS"].includes(e.key)) { // pressing only meta key is ignored return; } const key = keyFromKeyboardEvent(e); for (const listener of this.onKeyListeners) { const stop = listener(key); if (stop) { e.preventDefault(); e.stopPropagation(); break; } } } private fromInput(e: Element) { return ( e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement || dom.isContentEditable(e) ); } }