From 527d25104b4a482f0af1336abd42734d88a060e9 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Wed, 11 Dec 2024 14:44:21 +0100 Subject: [PATCH 1/3] fix(useHotKey): make sure `shift` modifier is defined - adjust documentation and code comments Signed-off-by: Maksim Sukharev --- docs/composables/useHotKey.md | 12 ++++++------ src/composables/useHotKey/index.js | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/composables/useHotKey.md b/docs/composables/useHotKey.md index de0891094d..ff65e7ad64 100644 --- a/docs/composables/useHotKey.md +++ b/docs/composables/useHotKey.md @@ -18,12 +18,12 @@ where: 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) - `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..245ea5547a 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 From c67b40cbfeeca7d06346eb311fe97252893d114f Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Wed, 11 Dec 2024 14:49:01 +0100 Subject: [PATCH 2/3] docs(useHotKey): describe support of keyFilter function - it is allowed by composable and supported by upstream library, but not defined in vue-lib documentation Signed-off-by: Maksim Sukharev --- docs/composables/useHotKey.md | 8 ++++++-- src/composables/useHotKey/index.js | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/composables/useHotKey.md b/docs/composables/useHotKey.md index ff65e7ad64..cb750c4d8b 100644 --- a/docs/composables/useHotKey.md +++ b/docs/composables/useHotKey.md @@ -13,9 +13,13 @@ 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 (default: `false`) diff --git a/src/composables/useHotKey/index.js b/src/composables/useHotKey/index.js index 245ea5547a..263f646797 100644 --- a/src/composables/useHotKey/index.js +++ b/src/composables/useHotKey/index.js @@ -60,25 +60,25 @@ 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), { + const stopKeyDown = onKeyStroke(keysOrFilter, eventHandler(callback, options), { eventName: 'keydown', dedupe: true, passive: !options.prevent, }) const stopKeyUp = options.push - ? onKeyStroke(key, eventHandler(callback, options), { + ? onKeyStroke(keysOrFilter, eventHandler(callback, options), { eventName: 'keyup', passive: !options.prevent, }) From 85cb2753c128e74558a87cfdeba93dbecb6e61f6 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Wed, 11 Dec 2024 14:56:20 +0100 Subject: [PATCH 3/3] fix(useHotKey): validate event keys as case-insensitive by default Signed-off-by: Maksim Sukharev --- docs/composables/useHotKey.md | 1 + src/composables/useHotKey/index.js | 38 ++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/composables/useHotKey.md b/docs/composables/useHotKey.md index cb750c4d8b..619ef93e78 100644 --- a/docs/composables/useHotKey.md +++ b/docs/composables/useHotKey.md @@ -28,6 +28,7 @@ where: - `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 263f646797..75b3d44dee 100644 --- a/src/composables/useHotKey/index.js +++ b/src/composables/useHotKey/index.js @@ -71,14 +71,48 @@ export function useHotKey(keysOrFilter, callback = () => {}, options = {}) { return () => {} } - const stopKeyDown = onKeyStroke(keysOrFilter, 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(keysOrFilter, eventHandler(callback, options), { + ? onKeyStroke(keyFilter, eventHandler(callback, options), { eventName: 'keyup', passive: !options.prevent, })