diff options
Diffstat (limited to 'ext/js/input/hotkey-handler.js')
-rw-r--r-- | ext/js/input/hotkey-handler.js | 102 |
1 files changed, 73 insertions, 29 deletions
diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js index 2fd35a5c..f1512b8f 100644 --- a/ext/js/input/hotkey-handler.js +++ b/ext/js/input/hotkey-handler.js @@ -22,28 +22,25 @@ import {yomitan} from '../yomitan.js'; /** * Class which handles hotkey events and actions. + * @augments EventDispatcher<import('hotkey-handler').EventType> */ export class HotkeyHandler extends EventDispatcher { /** - * Information describing a hotkey. - * @typedef {object} HotkeyDefinition - * @property {string} action A string indicating which action to perform. - * @property {string} key A keyboard key code indicating which key needs to be pressed. - * @property {string[]} modifiers An array of keyboard modifiers which also need to be pressed. Supports: `'alt', 'ctrl', 'shift', 'meta'`. - * @property {string[]} scopes An array of scopes for which the hotkey is valid. If this array does not contain `this.scope`, the hotkey will not be registered. - * @property {boolean} enabled A boolean indicating whether the hotkey is currently enabled. - */ - - /** * Creates a new instance of the class. */ constructor() { super(); + /** @type {Map<string, (argument: unknown) => (boolean|void)>} */ this._actions = new Map(); + /** @type {Map<string, import('hotkey-handler').HotkeyHandlers>} */ this._hotkeys = new Map(); + /** @type {Map<import('settings').InputsHotkeyScope, import('settings').InputsHotkeyOptions[]>} */ this._hotkeyRegistrations = new Map(); + /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); + /** @type {boolean} */ this._isPrepared = false; + /** @type {boolean} */ this._hasEventListeners = false; } @@ -60,7 +57,7 @@ export class HotkeyHandler extends EventDispatcher { /** * Registers a set of actions that this hotkey handler supports. - * @param {*[][]} actions An array of `[name, handler]` entries, where `name` is a string and `handler` is a function. + * @param {[name: string, handler: (argument: unknown) => (boolean|void)][]} actions An array of `[name, handler]` entries, where `name` is a string and `handler` is a function. */ registerActions(actions) { for (const [name, handler] of actions) { @@ -70,8 +67,8 @@ export class HotkeyHandler extends EventDispatcher { /** * Registers a set of hotkeys for a given scope. - * @param {string} scope The scope that the hotkey definitions must be for in order to be activated. - * @param {HotkeyDefinition[]} hotkeys An array of hotkey definitions. + * @param {import('settings').InputsHotkeyScope} scope The scope that the hotkey definitions must be for in order to be activated. + * @param {import('settings').InputsHotkeyOptions[]} hotkeys An array of hotkey definitions. */ registerHotkeys(scope, hotkeys) { let registrations = this._hotkeyRegistrations.get(scope); @@ -85,7 +82,7 @@ export class HotkeyHandler extends EventDispatcher { /** * Removes all registered hotkeys for a given scope. - * @param {string} scope The scope that the hotkey definitions were registered in. + * @param {import('settings').InputsHotkeyScope} scope The scope that the hotkey definitions were registered in. */ clearHotkeys(scope) { const registrations = this._hotkeyRegistrations.get(scope); @@ -98,8 +95,8 @@ export class HotkeyHandler extends EventDispatcher { /** * Assigns a set of hotkeys for a given scope. This is an optimized shorthand for calling * `clearHotkeys`, then calling `registerHotkeys`. - * @param {string} scope The scope that the hotkey definitions must be for in order to be activated. - * @param {HotkeyDefinition[]} hotkeys An array of hotkey definitions. + * @param {import('settings').InputsHotkeyScope} scope The scope that the hotkey definitions must be for in order to be activated. + * @param {import('settings').InputsHotkeyOptions[]} hotkeys An array of hotkey definitions. */ setHotkeys(scope, hotkeys) { let registrations = this._hotkeyRegistrations.get(scope); @@ -109,14 +106,24 @@ export class HotkeyHandler extends EventDispatcher { } else { registrations.length = 0; } - registrations.push(...hotkeys); + for (const {action, argument, key, modifiers, scopes, enabled} of hotkeys) { + registrations.push({ + action, + argument, + key, + modifiers: [...modifiers], + scopes: [...scopes], + enabled + }); + } this._updateHotkeyRegistrations(); } /** * Adds a single event listener to a specific event. - * @param {string} eventName The string representing the event's name. - * @param {Function} callback The event listener callback to add. + * @template [TEventDetails=unknown] + * @param {import('hotkey-handler').EventType} eventName The string representing the event's name. + * @param {(details: TEventDetails) => void} callback The event listener callback to add. * @returns {void} */ on(eventName, callback) { @@ -128,8 +135,9 @@ export class HotkeyHandler extends EventDispatcher { /** * Removes a single event listener from a specific event. - * @param {string} eventName The string representing the event's name. - * @param {Function} callback The event listener callback to add. + * @template [TEventDetails=unknown] + * @param {import('hotkey-handler').EventType} eventName The string representing the event's name. + * @param {(details: TEventDetails) => void} callback The event listener callback to add. * @returns {boolean} `true` if the callback was removed, `false` otherwise. */ off(eventName, callback) { @@ -142,37 +150,50 @@ export class HotkeyHandler extends EventDispatcher { /** * Attempts to simulate an action for a given combination of key and modifiers. * @param {string} key A keyboard key code indicating which key needs to be pressed. - * @param {string[]} modifiers An array of keyboard modifiers which also need to be pressed. Supports: `'alt', 'ctrl', 'shift', 'meta'`. + * @param {import('input').ModifierKey[]} modifiers An array of keyboard modifiers which also need to be pressed. Supports: `'alt', 'ctrl', 'shift', 'meta'`. * @returns {boolean} `true` if an action was performed, `false` otherwise. */ simulate(key, modifiers) { const hotkeyInfo = this._hotkeys.get(key); return ( typeof hotkeyInfo !== 'undefined' && - this._invokeHandlers(modifiers, hotkeyInfo) + this._invokeHandlers(modifiers, hotkeyInfo, key) ); } // Message handlers + /** + * @param {{key: string, modifiers: import('input').ModifierKey[]}} details + * @returns {boolean} + */ _onMessageForwardHotkey({key, modifiers}) { return this.simulate(key, modifiers); } // Private - _onKeyDown(e) { - const hotkeyInfo = this._hotkeys.get(e.code); + /** + * @param {KeyboardEvent} event + */ + _onKeyDown(event) { + const hotkeyInfo = this._hotkeys.get(event.code); if (typeof hotkeyInfo !== 'undefined') { - const eventModifiers = DocumentUtil.getActiveModifiers(e); - if (this._invokeHandlers(eventModifiers, hotkeyInfo, e.key)) { - e.preventDefault(); + const eventModifiers = DocumentUtil.getActiveModifiers(event); + if (this._invokeHandlers(eventModifiers, hotkeyInfo, event.key)) { + event.preventDefault(); return; } } - this.trigger('keydownNonHotkey', e); + this.trigger('keydownNonHotkey', event); } + /** + * @param {import('input').ModifierKey[]} modifiers + * @param {import('hotkey-handler').HotkeyHandlers} hotkeyInfo + * @param {string} key + * @returns {boolean} + */ _invokeHandlers(modifiers, hotkeyInfo, key) { for (const {modifiers: handlerModifiers, action, argument} of hotkeyInfo.handlers) { if (!this._areSame(handlerModifiers, modifiers) || !this._isHotkeyPermitted(modifiers, key)) { continue; } @@ -189,6 +210,11 @@ export class HotkeyHandler extends EventDispatcher { return false; } + /** + * @param {Set<unknown>} set + * @param {unknown[]} array + * @returns {boolean} + */ _areSame(set, array) { if (set.size !== array.length) { return false; } for (const value of array) { @@ -199,6 +225,9 @@ export class HotkeyHandler extends EventDispatcher { return true; } + /** + * @returns {void} + */ _updateHotkeyRegistrations() { if (this._hotkeys.size === 0 && this._hotkeyRegistrations.size === 0) { return; } @@ -219,10 +248,16 @@ export class HotkeyHandler extends EventDispatcher { this._updateEventHandlers(); } + /** + * @returns {void} + */ _updateHasEventListeners() { this._hasEventListeners = this.hasListeners('keydownNonHotkey'); } + /** + * @returns {void} + */ _updateEventHandlers() { if (this._isPrepared && (this._hotkeys.size > 0 || this._hasEventListeners)) { if (this._eventListeners.size !== 0) { return; } @@ -232,6 +267,11 @@ export class HotkeyHandler extends EventDispatcher { } } + /** + * @param {import('input').ModifierKey[]} modifiers + * @param {string} key + * @returns {boolean} + */ _isHotkeyPermitted(modifiers, key) { return !( (modifiers.length === 0 || (modifiers.length === 1 && modifiers[0] === 'shift')) && @@ -240,6 +280,10 @@ export class HotkeyHandler extends EventDispatcher { ); } + /** + * @param {string} key + * @returns {boolean} + */ _isKeyCharacterInput(key) { return key.length === 1; } |