react-hotkeys-hook 5.0.0-0 → 5.0.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/isHotkeyPressed.d.ts +1 -1
- package/dist/parseHotkeys.d.ts +2 -2
- package/dist/react-hotkeys-hook.cjs.development.js +54 -43
- package/dist/react-hotkeys-hook.cjs.development.js.map +1 -1
- package/dist/react-hotkeys-hook.cjs.production.min.js +1 -1
- package/dist/react-hotkeys-hook.cjs.production.min.js.map +1 -1
- package/dist/react-hotkeys-hook.esm.js +54 -43
- package/dist/react-hotkeys-hook.esm.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/useRecordHotkeys.d.ts +1 -0
- package/package.json +2 -2
- package/src/isHotkeyPressed.ts +6 -8
- package/src/parseHotkeys.ts +6 -6
- package/src/types.ts +29 -13
- package/src/useHotkeys.ts +15 -11
- package/src/useRecordHotkeys.ts +7 -3
- package/src/validators.ts +16 -11
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-hotkeys-hook",
|
|
3
3
|
"description": "React hook for handling keyboard shortcuts",
|
|
4
|
-
"version": "5.0.0-
|
|
4
|
+
"version": "5.0.0-1",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/JohannesKlauss/react-keymap-hook.git"
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"@testing-library/react": "14.1.2",
|
|
89
89
|
"@testing-library/user-event": "14.5.2",
|
|
90
90
|
"@types/jest": "29.5.11",
|
|
91
|
-
"@types/react": "18.2.
|
|
91
|
+
"@types/react": "18.2.47",
|
|
92
92
|
"@types/react-dom": "18.2.18",
|
|
93
93
|
"@typescript-eslint/eslint-plugin": "5.60.0",
|
|
94
94
|
"@typescript-eslint/parser": "5.60.0",
|
package/src/isHotkeyPressed.ts
CHANGED
|
@@ -2,23 +2,21 @@ import { isHotkeyModifier, mapKey } from './parseHotkeys'
|
|
|
2
2
|
;(() => {
|
|
3
3
|
if (typeof document !== 'undefined') {
|
|
4
4
|
document.addEventListener('keydown', (e) => {
|
|
5
|
-
if (e.
|
|
5
|
+
if (e.code === undefined) {
|
|
6
6
|
// Synthetic event (e.g., Chrome autofill). Ignore.
|
|
7
7
|
return
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
pushToCurrentlyPressedKeys([mapKey(e.key)])
|
|
10
|
+
pushToCurrentlyPressedKeys([mapKey(e.code)])
|
|
13
11
|
})
|
|
14
12
|
|
|
15
13
|
document.addEventListener('keyup', (e) => {
|
|
16
|
-
if (e.
|
|
14
|
+
if (e.code === undefined) {
|
|
17
15
|
// Synthetic event (e.g., Chrome autofill). Ignore.
|
|
18
16
|
return
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
removeFromCurrentlyPressedKeys([mapKey(e.
|
|
19
|
+
removeFromCurrentlyPressedKeys([mapKey(e.code)])
|
|
22
20
|
})
|
|
23
21
|
}
|
|
24
22
|
|
|
@@ -36,8 +34,8 @@ export function isReadonlyArray(value: unknown): value is readonly unknown[] {
|
|
|
36
34
|
return Array.isArray(value)
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
export function isHotkeyPressed(key: string | readonly string[],
|
|
40
|
-
const hotkeyArray = isReadonlyArray(key) ? key : key.split(
|
|
37
|
+
export function isHotkeyPressed(key: string | readonly string[], delimiter = ','): boolean {
|
|
38
|
+
const hotkeyArray = isReadonlyArray(key) ? key : key.split(delimiter)
|
|
41
39
|
|
|
42
40
|
return hotkeyArray.every((hotkey) => currentlyPressedKeys.has(hotkey.trim().toLowerCase()))
|
|
43
41
|
}
|
package/src/parseHotkeys.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hotkey, KeyboardModifiers } from './types'
|
|
2
2
|
|
|
3
|
-
const reservedModifierKeywords = ['shift', 'alt', 'meta', 'mod', 'ctrl']
|
|
3
|
+
const reservedModifierKeywords = ['shift', 'alt', 'meta', 'mod', 'ctrl', 'control']
|
|
4
4
|
|
|
5
5
|
const mappedKeys: Record<string, string> = {
|
|
6
6
|
esc: 'escape',
|
|
@@ -9,7 +9,6 @@ const mappedKeys: Record<string, string> = {
|
|
|
9
9
|
right: 'arrowright',
|
|
10
10
|
up: 'arrowup',
|
|
11
11
|
down: 'arrowdown',
|
|
12
|
-
space: ' ',
|
|
13
12
|
ShiftLeft: 'shift',
|
|
14
13
|
ShiftRight: 'shift',
|
|
15
14
|
AltLeft: 'alt',
|
|
@@ -30,14 +29,14 @@ export function isHotkeyModifier(key: string) {
|
|
|
30
29
|
return reservedModifierKeywords.includes(key)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
export function parseKeysHookInput(keys: string,
|
|
34
|
-
return keys.split(
|
|
32
|
+
export function parseKeysHookInput(keys: string, delimiter = ','): string[] {
|
|
33
|
+
return keys.toLowerCase().split(delimiter)
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
export function parseHotkey(hotkey: string,
|
|
36
|
+
export function parseHotkey(hotkey: string, splitKey = '+', useKey = false, description?: string): Hotkey {
|
|
38
37
|
const keys = hotkey
|
|
39
38
|
.toLocaleLowerCase()
|
|
40
|
-
.split(
|
|
39
|
+
.split(splitKey)
|
|
41
40
|
.map((k) => mapKey(k))
|
|
42
41
|
|
|
43
42
|
const modifiers: KeyboardModifiers = {
|
|
@@ -46,6 +45,7 @@ export function parseHotkey(hotkey: string, combinationKey = '+', description?:
|
|
|
46
45
|
shift: keys.includes('shift'),
|
|
47
46
|
meta: keys.includes('meta'),
|
|
48
47
|
mod: keys.includes('mod'),
|
|
48
|
+
useKey,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const singleCharKeys = keys.filter((k) => !reservedModifierKeywords.includes(k))
|
package/src/types.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type KeyboardModifiers = {
|
|
|
12
12
|
meta?: boolean
|
|
13
13
|
shift?: boolean
|
|
14
14
|
mod?: boolean
|
|
15
|
+
useKey?: boolean // Custom modifier to listen to the produced key instead of the code
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export type Hotkey = KeyboardModifiers & {
|
|
@@ -27,19 +28,34 @@ export type HotkeyCallback = (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey
|
|
|
27
28
|
export type Trigger = boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean)
|
|
28
29
|
|
|
29
30
|
export type Options = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
// Main setting that determines if the hotkey is enabled or not. (Default: true)
|
|
32
|
+
enabled?: Trigger
|
|
33
|
+
// Enable hotkeys on a list of tags. (Default: false)
|
|
34
|
+
enableOnFormTags?: readonly FormTags[] | boolean
|
|
35
|
+
// Enable hotkeys on tags with contentEditable props. (Default: false)
|
|
36
|
+
enableOnContentEditable?: boolean
|
|
37
|
+
// Ignore evenets based on a condition (Default: undefined)
|
|
38
|
+
ignoreEventWhen?: (e: KeyboardEvent) => boolean
|
|
39
|
+
// Character to split keys in hotkeys combinations. (Default: +)
|
|
40
|
+
splitKey?: string
|
|
41
|
+
// Character to separate different hotkeys. (Default: ,)
|
|
42
|
+
delimiter?: string
|
|
43
|
+
// Scope of the hotkey. (Default: undefined)
|
|
44
|
+
scopes?: Scopes
|
|
45
|
+
// Trigger on keyup event? (Default: undefined)
|
|
46
|
+
keyup?: boolean
|
|
47
|
+
// Trigger on keydown event? (Default: true)
|
|
48
|
+
keydown?: boolean
|
|
49
|
+
// Prevent default browser behavior? (Default: false)
|
|
50
|
+
preventDefault?: Trigger
|
|
51
|
+
// Use this option to describe what the hotkey does. (Default: undefined)
|
|
52
|
+
description?: string
|
|
53
|
+
// Listen to events on the document instead of the window. (Default: false)
|
|
54
|
+
document?: Document
|
|
55
|
+
// Ignore modifiers when matching hotkeys. (Default: false)
|
|
56
|
+
ignoreModifiers?: boolean
|
|
57
|
+
// Listen to the produced key instead of the code. (Default: false)
|
|
58
|
+
useKey?: boolean
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
export type OptionsOrDependencyArray = Options | DependencyList
|
package/src/useHotkeys.ts
CHANGED
|
@@ -36,7 +36,7 @@ export default function useHotkeys<T extends HTMLElement>(
|
|
|
36
36
|
: !(dependencies instanceof Array)
|
|
37
37
|
? (dependencies as Options)
|
|
38
38
|
: undefined
|
|
39
|
-
const _keys: string = isReadonlyArray(keys) ? keys.join(_options?.
|
|
39
|
+
const _keys: string = isReadonlyArray(keys) ? keys.join(_options?.delimiter) : keys
|
|
40
40
|
const _deps: DependencyList | undefined =
|
|
41
41
|
options instanceof Array ? options : dependencies instanceof Array ? dependencies : undefined
|
|
42
42
|
|
|
@@ -83,8 +83,8 @@ export default function useHotkeys<T extends HTMLElement>(
|
|
|
83
83
|
return
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
parseKeysHookInput(_keys, memoisedOptions?.
|
|
87
|
-
const hotkey = parseHotkey(key, memoisedOptions?.
|
|
86
|
+
parseKeysHookInput(_keys, memoisedOptions?.delimiter).forEach((key) => {
|
|
87
|
+
const hotkey = parseHotkey(key, memoisedOptions?.splitKey, memoisedOptions?.useKey)
|
|
88
88
|
|
|
89
89
|
if (isHotkeyMatchingKeyboardEvent(e, hotkey, memoisedOptions?.ignoreModifiers) || hotkey.keys?.includes('*')) {
|
|
90
90
|
if (memoisedOptions?.ignoreEventWhen?.(e)) {
|
|
@@ -114,12 +114,12 @@ export default function useHotkeys<T extends HTMLElement>(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
117
|
-
if (event.
|
|
117
|
+
if (event.code === undefined) {
|
|
118
118
|
// Synthetic event (e.g., Chrome autofill). Ignore.
|
|
119
119
|
return
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
pushToCurrentlyPressedKeys(mapKey(event.
|
|
122
|
+
pushToCurrentlyPressedKeys(mapKey(event.code))
|
|
123
123
|
|
|
124
124
|
if ((memoisedOptions?.keydown === undefined && memoisedOptions?.keyup !== true) || memoisedOptions?.keydown) {
|
|
125
125
|
listener(event)
|
|
@@ -127,12 +127,12 @@ export default function useHotkeys<T extends HTMLElement>(
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
const handleKeyUp = (event: KeyboardEvent) => {
|
|
130
|
-
if (event.
|
|
130
|
+
if (event.code === undefined) {
|
|
131
131
|
// Synthetic event (e.g., Chrome autofill). Ignore.
|
|
132
132
|
return
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
removeFromCurrentlyPressedKeys(mapKey(event.
|
|
135
|
+
removeFromCurrentlyPressedKeys(mapKey(event.code))
|
|
136
136
|
|
|
137
137
|
hasTriggeredRef.current = false
|
|
138
138
|
|
|
@@ -149,8 +149,10 @@ export default function useHotkeys<T extends HTMLElement>(
|
|
|
149
149
|
domNode.addEventListener('keydown', handleKeyDown)
|
|
150
150
|
|
|
151
151
|
if (proxy) {
|
|
152
|
-
parseKeysHookInput(_keys, memoisedOptions?.
|
|
153
|
-
proxy.addHotkey(
|
|
152
|
+
parseKeysHookInput(_keys, memoisedOptions?.delimiter).forEach((key) =>
|
|
153
|
+
proxy.addHotkey(
|
|
154
|
+
parseHotkey(key, memoisedOptions?.splitKey, memoisedOptions?.useKey, memoisedOptions?.description)
|
|
155
|
+
)
|
|
154
156
|
)
|
|
155
157
|
}
|
|
156
158
|
|
|
@@ -161,8 +163,10 @@ export default function useHotkeys<T extends HTMLElement>(
|
|
|
161
163
|
domNode.removeEventListener('keydown', handleKeyDown)
|
|
162
164
|
|
|
163
165
|
if (proxy) {
|
|
164
|
-
parseKeysHookInput(_keys, memoisedOptions?.
|
|
165
|
-
proxy.removeHotkey(
|
|
166
|
+
parseKeysHookInput(_keys, memoisedOptions?.delimiter).forEach((key) =>
|
|
167
|
+
proxy.removeHotkey(
|
|
168
|
+
parseHotkey(key, memoisedOptions?.splitKey, memoisedOptions?.useKey, memoisedOptions?.description)
|
|
169
|
+
)
|
|
166
170
|
)
|
|
167
171
|
}
|
|
168
172
|
}
|
package/src/useRecordHotkeys.ts
CHANGED
|
@@ -6,7 +6,7 @@ export default function useRecordHotkeys() {
|
|
|
6
6
|
const [isRecording, setIsRecording] = useState(false)
|
|
7
7
|
|
|
8
8
|
const handler = useCallback((event: KeyboardEvent) => {
|
|
9
|
-
if (event.
|
|
9
|
+
if (event.code === undefined) {
|
|
10
10
|
// Synthetic event (e.g., Chrome autofill). Ignore.
|
|
11
11
|
return
|
|
12
12
|
}
|
|
@@ -17,7 +17,7 @@ export default function useRecordHotkeys() {
|
|
|
17
17
|
setKeys((prev) => {
|
|
18
18
|
const newKeys = new Set(prev)
|
|
19
19
|
|
|
20
|
-
newKeys.add(mapKey(event.
|
|
20
|
+
newKeys.add(mapKey(event.code))
|
|
21
21
|
|
|
22
22
|
return newKeys
|
|
23
23
|
})
|
|
@@ -43,5 +43,9 @@ export default function useRecordHotkeys() {
|
|
|
43
43
|
}
|
|
44
44
|
}, [handler, stop])
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
const resetKeys = useCallback(() => {
|
|
47
|
+
setKeys(new Set<string>())
|
|
48
|
+
}, [])
|
|
49
|
+
|
|
50
|
+
return [keys, { start, stop, resetKeys, isRecording }] as const
|
|
47
51
|
}
|
package/src/validators.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FormTags, Hotkey, Scopes, Trigger } from './types'
|
|
2
2
|
import { isHotkeyPressed, isReadonlyArray } from './isHotkeyPressed'
|
|
3
|
+
import { mapKey } from './parseHotkeys'
|
|
3
4
|
|
|
4
5
|
export function maybePreventDefault(e: KeyboardEvent, hotkey: Hotkey, preventDefault?: Trigger): void {
|
|
5
6
|
if ((typeof preventDefault === 'function' && preventDefault(e, hotkey)) || preventDefault === true) {
|
|
@@ -31,7 +32,7 @@ export function isHotkeyEnabledOnTag(
|
|
|
31
32
|
)
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
return Boolean(targetTagName && enabledOnTags && enabledOnTags
|
|
35
|
+
return Boolean(targetTagName && enabledOnTags && enabledOnTags)
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export function isScopeActive(activeScopes: string[], scopes?: Scopes): boolean {
|
|
@@ -51,25 +52,29 @@ export function isScopeActive(activeScopes: string[], scopes?: Scopes): boolean
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export const isHotkeyMatchingKeyboardEvent = (e: KeyboardEvent, hotkey: Hotkey, ignoreModifiers = false): boolean => {
|
|
54
|
-
const { alt, meta, mod, shift, ctrl, keys } = hotkey
|
|
55
|
-
const { key:
|
|
55
|
+
const { alt, meta, mod, shift, ctrl, keys, useKey } = hotkey
|
|
56
|
+
const { code, key: producedKey, ctrlKey, metaKey, shiftKey, altKey } = e
|
|
56
57
|
|
|
57
|
-
const
|
|
58
|
+
const mappedCode = mapKey(code)
|
|
59
|
+
|
|
60
|
+
if (useKey && keys?.length === 1 && keys.includes(producedKey)) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
58
63
|
|
|
59
64
|
if (
|
|
60
|
-
!keys?.includes(
|
|
61
|
-
!['ctrl', 'control', 'unknown', 'meta', 'alt', 'shift', 'os'].includes(
|
|
65
|
+
!keys?.includes(mappedCode) &&
|
|
66
|
+
!['ctrl', 'control', 'unknown', 'meta', 'alt', 'shift', 'os'].includes(mappedCode)
|
|
62
67
|
) {
|
|
63
68
|
return false
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
if (!ignoreModifiers) {
|
|
67
72
|
// We check the pressed keys for compatibility with the keyup event. In keyup events the modifier flags are not set.
|
|
68
|
-
if (alt
|
|
73
|
+
if (alt !== altKey && mappedCode !== 'alt') {
|
|
69
74
|
return false
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
if (shift
|
|
77
|
+
if (shift !== shiftKey && mappedCode !== 'shift') {
|
|
73
78
|
return false
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -79,11 +84,11 @@ export const isHotkeyMatchingKeyboardEvent = (e: KeyboardEvent, hotkey: Hotkey,
|
|
|
79
84
|
return false
|
|
80
85
|
}
|
|
81
86
|
} else {
|
|
82
|
-
if (meta
|
|
87
|
+
if (meta !== metaKey && mappedCode !== 'meta' && mappedCode !== 'os') {
|
|
83
88
|
return false
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
if (ctrl
|
|
91
|
+
if (ctrl !== ctrlKey && mappedCode !== 'ctrl' && mappedCode !== 'control') {
|
|
87
92
|
return false
|
|
88
93
|
}
|
|
89
94
|
}
|
|
@@ -91,7 +96,7 @@ export const isHotkeyMatchingKeyboardEvent = (e: KeyboardEvent, hotkey: Hotkey,
|
|
|
91
96
|
|
|
92
97
|
// All modifiers are correct, now check the key
|
|
93
98
|
// If the key is set, we check for the key
|
|
94
|
-
if (keys && keys.length === 1 && keys.includes(
|
|
99
|
+
if (keys && keys.length === 1 && keys.includes(mappedCode)) {
|
|
95
100
|
return true
|
|
96
101
|
} else if (keys) {
|
|
97
102
|
// Check if all keys are present in pressedDownKeys set
|