export class KeyboardManager { constructor() { this.observers = []; KeyboardManager.KeyTab = "Tab"; KeyboardManager.KeyBackspace = "Backspace"; KeyboardManager.KeyUp = "ArrowUp"; KeyboardManager.KeyDown = "ArrowDown"; KeyboardManager.KeyModifierShift = "Shift"; KeyboardManager.KeyModifierCtrl = "Control"; // ⌘ key on Mac, ⊞ key on Windows KeyboardManager.KeyModifierMeta = "Meta"; KeyboardManager.KeyModifierAlt = "Alt"; KeyboardManager.KeyEventDown = "KeyEventDown"; KeyboardManager.KeyEventUp = "KeyEventUp"; KeyboardManager.AllModifiers = [ KeyboardManager.KeyModifierShift, KeyboardManager.KeyModifierCtrl, KeyboardManager.KeyModifierMeta, KeyboardManager.KeyModifierAlt ]; window.addEventListener('keydown', this.handleKeyDown.bind(this)); window.addEventListener('keyup', this.handleKeyUp.bind(this)); } modifiersForEvent(event) { const eventModifiers = KeyboardManager.AllModifiers.filter((modifier) => { // For a modifier like ctrlKey, must check both event.ctrlKey and event.key. // That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true. const matches = ( ((event.ctrlKey || event.key == KeyboardManager.KeyModifierCtrl) && modifier === KeyboardManager.KeyModifierCtrl) || ((event.metaKey || event.key == KeyboardManager.KeyModifierMeta) && modifier === KeyboardManager.KeyModifierMeta) || ((event.altKey || event.key == KeyboardManager.KeyModifierAlt) && modifier === KeyboardManager.KeyModifierAlt) || ((event.shiftKey || event.key == KeyboardManager.KeyModifierShift) && modifier === KeyboardManager.KeyModifierShift) ); return matches; }); return eventModifiers; } eventMatchesKeyAndModifiers(event, key, modifiers = []) { const eventModifiers = this.modifiersForEvent(event); if(eventModifiers.length != modifiers.length) { return false; } for(const modifier of modifiers) { if(!eventModifiers.includes(modifier)) { return false; } } // Modifers match, check key if(!key) { return true; } // In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F' // In our case we don't differentiate between the two. return key.toLowerCase() == event.key.toLowerCase(); } notifyObserver(event, keyEventType) { for(const observer of this.observers) { if(observer.element && event.target != observer.element) { continue; } if(observer.elements && !observer.elements.includes(event.target)) { continue; } if(observer.notElement && observer.notElement == event.target) { continue; } if(observer.notElementIds && observer.notElementIds.includes(event.target.id)) { continue; } if(this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) { const callback = keyEventType == KeyboardManager.KeyEventDown ? observer.onKeyDown : observer.onKeyUp; if(callback) { callback(event); } } } } handleKeyDown(event) { this.notifyObserver(event, KeyboardManager.KeyEventDown); } handleKeyUp(event) { this.notifyObserver(event, KeyboardManager.KeyEventUp); } addKeyObserver({key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds}) { const observer = {key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds}; this.observers.push(observer); return observer; } removeKeyObserver(observer) { this.observers.splice(this.observers.indexOf(observer), 1); } }