Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(useHotKey): composable fixups #6301

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions docs/composables/useHotKey.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>`: 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
Expand Down
58 changes: 50 additions & 8 deletions src/composables/useHotKey/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
})
Expand Down
Loading