diff --git a/docs/composables/useHotKey.md b/docs/composables/useHotKey.md index de0891094d..619ef93e78 100644 --- a/docs/composables/useHotKey.md +++ b/docs/composables/useHotKey.md @@ -13,17 +13,22 @@ import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey/index.js' const stopCallback = useHotKey(key, callback, options) ``` where: -- `key`: string representing the keyboard key to listen to +- `keysOrFilter`: one of following: + - `string`: representing the keyboard key to listen to + - `Array`: representing any of keys to listen to + - `Function`: custom filter function to validate event key + - `true`: to listen for all events - See [KeyboardEvent.key Value column](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) for possible values + See [KeyboardEvent.key Value column](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) for possible values - `callback`: a function to be called when the key is pressed. Before called, it will be checked whether keyboard shortcuts are disabled, or interactive element is currently focused, or whether options should be applied - `options`: options to be applied to the shortcut: - - `push`: whether the event should be triggered on both keydown and keyup - - `prevent`: prevents the default action of the event - - `stop`: prevents propagation of the event in the capturing and bubbling phases - - `ctrl`: whether the Ctrl key should be pressed (Cmd key on MacOS) - - `alt`: whether the Alt key should be pressed - - `shift`: whether the Shift key should be pressed + - `push`: whether the event should be triggered on both keydown and keyup (default: `false`) + - `prevent`: prevents the default action of the event (default: `false`) + - `stop`: prevents propagation of the event in the capturing and bubbling phases (default: `false`) + - `ctrl`: whether the Ctrl key (Cmd key on MacOS) should be pressed (default: `false`) + - `alt`: whether the Alt key should be pressed (default: `false`) + - `shift`: whether the Shift key should be pressed (should be explicitly defined as `true`|`false` if needed) + - `caseSensitive`: whether specific case should be listened, e.g. only 'd' and not 'D' (default: `false`) - `stopCallback`: a callback to stop listening to the event ### Playground diff --git a/src/composables/useHotKey/index.js b/src/composables/useHotKey/index.js index b9a413ab33..75b3d44dee 100644 --- a/src/composables/useHotKey/index.js +++ b/src/composables/useHotKey/index.js @@ -29,13 +29,21 @@ function shouldIgnoreEvent(event) { const eventHandler = (callback, options) => (event) => { const ctrlKeyPressed = isMac ? event.metaKey : event.ctrlKey if (ctrlKeyPressed !== Boolean(options.ctrl)) { - // Ctrl is required and not pressed, or the opposite + /** + * Ctrl is required and not pressed, or the opposite + * As on macOS 'cmd' key is used instead of 'ctrl' key for most key combinations, + * 'event.metaKey' should be checked + */ return - } else if (!!options.alt !== event.altKey) { + } else if (event.altKey !== Boolean(options.alt)) { // Alt is required and not pressed, or the opposite return - } else if (!!options.shift !== event.shiftKey) { - // Shift is required and not pressed, or the opposite + } else if (options.shift !== undefined && event.shiftKey !== Boolean(options.shift)) { + /** + * Shift is required and not pressed, or the opposite + * As shift key is used to type capital letters and alternate characters, + * option should be explicitly defined + */ return } else if (shouldIgnoreEvent(event)) { // Keyboard shortcuts are disabled, because active element assumes input @@ -52,25 +60,59 @@ const eventHandler = (callback, options) => (event) => { } /** - * @param {string} key - keyboard key or keys to listen to + * @param {true|string|string[]|Function} keysOrFilter - keyboard key or keys to listen to, or filter function * @param {Function} callback - callback function * @param {object} options - composable options * @see docs/composables/usekeystroke.md */ -export function useHotKey(key, callback = () => {}, options = {}) { +export function useHotKey(keysOrFilter, callback = () => {}, options = {}) { if (disableKeyboardShortcuts) { // Keyboard shortcuts are disabled return () => {} } - const stopKeyDown = onKeyStroke(key, eventHandler(callback, options), { + /** + * Validates event key to expected key + * FIXME should support any languages / key codes + * + * @param {KeyboardEvent} event keyboard event + * @param {string} key expected key + * @return {boolean} whether it satisfies expected value or not + */ + const validateKeyEvent = (event, key) => { + if (options.caseSensitive) { + return event.key === key + } + return event.key.toLowerCase() === key.toLowerCase() + } + + /** + * Filter function for the listener + * see https://github.com/vueuse/vueuse/blob/v11.3.0/packages/core/onKeyStroke/index.ts#L21-L32 + * + * @param {KeyboardEvent} event keyboard event + * @return {boolean} whether it satisfies expected value or not + */ + const keyFilter = (event) => { + if (typeof keysOrFilter === 'function') { + return keysOrFilter(event) + } else if (typeof keysOrFilter === 'string') { + return validateKeyEvent(event, keysOrFilter) + } else if (Array.isArray(keysOrFilter)) { + return keysOrFilter.some(key => validateKeyEvent(event, key)) + } else { + return true + } + } + + const stopKeyDown = onKeyStroke(keyFilter, eventHandler(callback, options), { eventName: 'keydown', dedupe: true, passive: !options.prevent, }) const stopKeyUp = options.push - ? onKeyStroke(key, eventHandler(callback, options), { + ? onKeyStroke(keyFilter, eventHandler(callback, options), { eventName: 'keyup', passive: !options.prevent, })