react-hotkeys-hook 5.2.4 → 5.3.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/README.md +13 -12
- package/dist/index.d.ts +94 -6
- package/dist/index.js +203 -180
- package/package.json +14 -8
- package/dist/BoundHotkeysProxyProvider.d.ts +0 -14
- package/dist/HotkeysProvider.d.ts +0 -16
- package/dist/deepEqual.d.ts +0 -1
- package/dist/isHotkeyPressed.d.ts +0 -4
- package/dist/parseHotkeys.d.ts +0 -5
- package/dist/types.d.ts +0 -50
- package/dist/useDeepEqualMemo.d.ts +0 -1
- package/dist/useHotkeys.d.ts +0 -2
- package/dist/useRecordHotkeys.d.ts +0 -6
- package/dist/validators.d.ts +0 -8
package/README.md
CHANGED
|
@@ -38,14 +38,12 @@ A React hook for using keyboard shortcuts in components in a declarative way.
|
|
|
38
38
|
|
|
39
39
|
## Quick Start
|
|
40
40
|
|
|
41
|
-
The easiest way to use the hook.
|
|
42
|
-
|
|
43
41
|
```jsx harmony
|
|
44
42
|
import { useHotkeys } from 'react-hotkeys-hook'
|
|
45
43
|
|
|
46
44
|
export const ExampleComponent = () => {
|
|
47
45
|
const [count, setCount] = useState(0)
|
|
48
|
-
useHotkeys('ctrl+k', () => setCount(
|
|
46
|
+
useHotkeys('ctrl+k', () => setCount(prevCount => prevCount + 1))
|
|
49
47
|
|
|
50
48
|
return (
|
|
51
49
|
<p>
|
|
@@ -150,15 +148,18 @@ All options are optional and have a default value which you can override to chan
|
|
|
150
148
|
| Option | Type | Default value | Description |
|
|
151
149
|
|--------------------------|--------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
152
150
|
| `enabled` | `boolean` or `(keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean` | `true` | This option determines whether the hotkey is active or not. It can take a boolean (for example a flag from a state outside) or a function which gets executed once the hotkey is pressed. If the function returns `false` the hotkey won't get executed and all browser events are prevented. |
|
|
153
|
-
| `enableOnFormTags` | `boolean` or `FormTags[]` | `false` | By default hotkeys are
|
|
154
|
-
| `enableOnContentEditable` | `boolean` | `false` |
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
151
|
+
| `enableOnFormTags` | `boolean` or `FormTags[]` | `false` | By default, hotkeys are disabled when a form element is focused. This prevents accidental triggering while the user is typing. Set to `true` to enable on all form tags, or pass an array of specific tags (e.g. `['input', 'textarea', 'select']`) |
|
|
152
|
+
| `enableOnContentEditable` | `boolean` | `false` | Enable hotkeys on elements with the `contentEditable` attribute set to `true` |
|
|
153
|
+
| `splitKey` | `string` | `+` | Character that joins keys within a combination (e.g. `shift+a`). Change this if you need to listen for the `+` key itself |
|
|
154
|
+
| `delimiter` | `string` | `,` | Character that separates different hotkey combinations mapped to the same callback (e.g. `ctrl+a, shift+b`) |
|
|
157
155
|
| `scopes` | `string` or `string[]` | `*` | With scopes you can group hotkeys together. The default scope is the wildcard `*` which matches all hotkeys. Use the `<HotkeysProvider>` component to change active scopes. |
|
|
158
|
-
| `keyup` | `boolean` | `false` |
|
|
159
|
-
| `keydown` | `boolean` | `true` |
|
|
160
|
-
| `preventDefault` | `boolean` or `(keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean` | `false` |
|
|
161
|
-
| `description`
|
|
156
|
+
| `keyup` | `boolean` | `false` | Trigger the callback on the browser's `keyup` event |
|
|
157
|
+
| `keydown` | `boolean` | `true` | Trigger the callback on the browser's `keydown` event. Set both `keyup` and `keydown` to `true` to trigger on both events |
|
|
158
|
+
| `preventDefault` | `boolean` or `(keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean` | `false` | Prevent the browser's default behavior for the matched keystroke. Useful for overriding shortcuts like `meta+s`. Note: some browser shortcuts (e.g. `meta+w`) cannot be overridden |
|
|
159
|
+
| `description` | `string` | `undefined` | Human-readable description of what the hotkey does. Useful for building help panels |
|
|
160
|
+
| `useKey` | `boolean` | `false` | Listen to the produced character instead of the physical key code |
|
|
161
|
+
| `ignoreModifiers` | `boolean` | `false` | Ignore modifier keys when matching hotkeys |
|
|
162
|
+
| `sequenceTimeoutMs` | `number` | `1000` | Time window in milliseconds for sequential hotkeys |
|
|
162
163
|
|
|
163
164
|
|
|
164
165
|
#### Overloads
|
|
@@ -186,7 +187,7 @@ isHotkeyPressed(['esc', 'ctrl+s']) // Returns true if Escape or Ctrl+S are press
|
|
|
186
187
|
|
|
187
188
|
## Support
|
|
188
189
|
|
|
189
|
-
* Ask your question in
|
|
190
|
+
* Ask your question in [GitHub Discussions](https://github.com/JohannesKlauss/react-hotkeys-hook/discussions)
|
|
190
191
|
* Ask your question on [StackOverflow](https://stackoverflow.com/search?page=1&tab=Relevance&q=react-hotkeys-hook)
|
|
191
192
|
|
|
192
193
|
## Found an issue or have a feature request?
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { DependencyList } from 'react';
|
|
2
|
+
import { JSX } from 'react/jsx-runtime';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { RefCallback } from 'react';
|
|
5
|
+
|
|
6
|
+
declare type EventListenerOptions_2 = {
|
|
7
|
+
capture?: boolean;
|
|
8
|
+
once?: boolean;
|
|
9
|
+
passive?: boolean;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
} | boolean;
|
|
12
|
+
|
|
13
|
+
declare type FormTags = 'input' | 'textarea' | 'select' | 'INPUT' | 'TEXTAREA' | 'SELECT' | 'searchbox' | 'slider' | 'spinbutton' | 'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'option' | 'radio' | 'textbox';
|
|
14
|
+
|
|
15
|
+
declare type Hotkey = KeyboardModifiers & {
|
|
16
|
+
keys?: readonly string[];
|
|
17
|
+
scopes?: Scopes;
|
|
18
|
+
description?: string;
|
|
19
|
+
isSequence?: boolean;
|
|
20
|
+
hotkey: string;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export declare type HotkeyCallback = (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => void;
|
|
25
|
+
|
|
26
|
+
declare type HotkeysContextType = {
|
|
27
|
+
hotkeys: ReadonlyArray<Hotkey>;
|
|
28
|
+
activeScopes: string[];
|
|
29
|
+
toggleScope: (scope: string) => void;
|
|
30
|
+
enableScope: (scope: string) => void;
|
|
31
|
+
disableScope: (scope: string) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
declare type HotkeysEvent = Hotkey;
|
|
35
|
+
|
|
36
|
+
export declare const HotkeysProvider: ({ initiallyActiveScopes, children }: Props) => JSX.Element;
|
|
37
|
+
|
|
38
|
+
export declare function isHotkeyPressed(key: string | readonly string[], delimiter?: string): boolean;
|
|
39
|
+
|
|
40
|
+
declare type KeyboardModifiers = {
|
|
41
|
+
alt?: boolean;
|
|
42
|
+
ctrl?: boolean;
|
|
43
|
+
meta?: boolean;
|
|
44
|
+
shift?: boolean;
|
|
45
|
+
mod?: boolean;
|
|
46
|
+
useKey?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export declare type Keys = string | readonly string[];
|
|
50
|
+
|
|
51
|
+
export declare type Options = {
|
|
52
|
+
enabled?: Trigger;
|
|
53
|
+
enableOnFormTags?: readonly FormTags[] | boolean;
|
|
54
|
+
enableOnContentEditable?: boolean;
|
|
55
|
+
ignoreEventWhen?: (e: KeyboardEvent) => boolean;
|
|
56
|
+
splitKey?: string;
|
|
57
|
+
delimiter?: string;
|
|
58
|
+
scopes?: Scopes;
|
|
59
|
+
keyup?: boolean;
|
|
60
|
+
keydown?: boolean;
|
|
61
|
+
preventDefault?: Trigger;
|
|
62
|
+
description?: string;
|
|
63
|
+
document?: Document;
|
|
64
|
+
ignoreModifiers?: boolean;
|
|
65
|
+
eventListenerOptions?: EventListenerOptions_2;
|
|
66
|
+
useKey?: boolean;
|
|
67
|
+
sequenceTimeoutMs?: number;
|
|
68
|
+
sequenceSplitKey?: string;
|
|
69
|
+
metadata?: Record<string, unknown>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
declare type OptionsOrDependencyArray = Options | DependencyList;
|
|
73
|
+
|
|
74
|
+
declare interface Props {
|
|
75
|
+
initiallyActiveScopes?: string[];
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare type Scopes = string | readonly string[];
|
|
80
|
+
|
|
81
|
+
declare type Trigger = boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean);
|
|
82
|
+
|
|
83
|
+
export declare function useHotkeys<T extends HTMLElement>(keys: Keys, callback: HotkeyCallback, options?: OptionsOrDependencyArray, dependencies?: OptionsOrDependencyArray): RefCallback<T | null>;
|
|
84
|
+
|
|
85
|
+
export declare const useHotkeysContext: () => HotkeysContextType;
|
|
86
|
+
|
|
87
|
+
export declare function useRecordHotkeys(useKey?: boolean, blacklist?: string[]): readonly [Set<string>, {
|
|
88
|
+
readonly start: () => void;
|
|
89
|
+
readonly stop: () => void;
|
|
90
|
+
readonly resetKeys: () => void;
|
|
91
|
+
readonly isRecording: boolean;
|
|
92
|
+
}];
|
|
93
|
+
|
|
94
|
+
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { createContext as
|
|
2
|
-
import { jsx as
|
|
3
|
-
const
|
|
1
|
+
import { createContext as j, useContext as W, useState as A, useCallback as h, useRef as K, useLayoutEffect as X, useEffect as I, useMemo as Y } from "react";
|
|
2
|
+
import { jsx as x } from "react/jsx-runtime";
|
|
3
|
+
const F = ["shift", "alt", "meta", "mod", "ctrl", "control"];
|
|
4
|
+
function Z() {
|
|
5
|
+
return typeof navigator > "u" ? !1 : /mac/i.test(navigator.userAgent) && !/iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
6
|
+
}
|
|
7
|
+
const ee = {
|
|
4
8
|
esc: "escape",
|
|
5
9
|
return: "enter",
|
|
6
10
|
left: "arrowleft",
|
|
@@ -18,74 +22,78 @@ const j = ["shift", "alt", "meta", "mod", "ctrl", "control"], Q = {
|
|
|
18
22
|
ControlLeft: "ctrl",
|
|
19
23
|
ControlRight: "ctrl"
|
|
20
24
|
};
|
|
21
|
-
function
|
|
22
|
-
return (
|
|
25
|
+
function L(e) {
|
|
26
|
+
return (ee[e.trim()] || e.trim()).toLowerCase().replace(/key|digit|numpad/, "");
|
|
23
27
|
}
|
|
24
|
-
function
|
|
25
|
-
return
|
|
28
|
+
function $(e) {
|
|
29
|
+
return F.includes(e);
|
|
26
30
|
}
|
|
27
|
-
function
|
|
28
|
-
return e.toLowerCase().split(
|
|
31
|
+
function R(e, t = ",") {
|
|
32
|
+
return e.toLowerCase().split(t);
|
|
29
33
|
}
|
|
30
|
-
function
|
|
31
|
-
let
|
|
32
|
-
e = e.trim(), e.includes(
|
|
33
|
-
const
|
|
34
|
-
alt:
|
|
35
|
-
ctrl:
|
|
36
|
-
shift:
|
|
37
|
-
meta:
|
|
38
|
-
mod:
|
|
39
|
-
useKey:
|
|
40
|
-
},
|
|
34
|
+
function O(e, t = "+", r = ">", u = !1, s, f) {
|
|
35
|
+
let d = [], l = !1;
|
|
36
|
+
e = e.trim(), e.includes(r) ? (l = !0, d = e.toLocaleLowerCase().split(r).map((y) => L(y))) : d = e.toLocaleLowerCase().split(t).map((y) => L(y));
|
|
37
|
+
const o = {
|
|
38
|
+
alt: d.includes("alt"),
|
|
39
|
+
ctrl: d.includes("ctrl") || d.includes("control"),
|
|
40
|
+
shift: d.includes("shift"),
|
|
41
|
+
meta: d.includes("meta"),
|
|
42
|
+
mod: d.includes("mod"),
|
|
43
|
+
useKey: u
|
|
44
|
+
}, p = d.filter((y) => !F.includes(y));
|
|
41
45
|
return {
|
|
42
|
-
...
|
|
43
|
-
keys:
|
|
44
|
-
description:
|
|
45
|
-
isSequence:
|
|
46
|
+
...o,
|
|
47
|
+
keys: p,
|
|
48
|
+
description: s,
|
|
49
|
+
isSequence: l,
|
|
46
50
|
hotkey: e,
|
|
47
|
-
metadata:
|
|
51
|
+
metadata: f
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
typeof document < "u" && (document.addEventListener("keydown", (e) => {
|
|
51
|
-
e.code !== void 0 &&
|
|
55
|
+
e.code !== void 0 && z([L(e.code)]);
|
|
52
56
|
}), document.addEventListener("keyup", (e) => {
|
|
53
|
-
e.code !== void 0 &&
|
|
57
|
+
e.code !== void 0 && G([L(e.code)]);
|
|
54
58
|
})), typeof window < "u" && (window.addEventListener("blur", () => {
|
|
55
|
-
|
|
59
|
+
g.clear();
|
|
60
|
+
}), window.addEventListener("focus", () => {
|
|
61
|
+
g.clear();
|
|
56
62
|
}), window.addEventListener("contextmenu", () => {
|
|
57
63
|
setTimeout(() => {
|
|
58
|
-
|
|
64
|
+
g.clear();
|
|
59
65
|
}, 0);
|
|
60
|
-
}))
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
})), typeof document < "u" && document.addEventListener("visibilitychange", () => {
|
|
67
|
+
g.clear();
|
|
68
|
+
});
|
|
69
|
+
const g = /* @__PURE__ */ new Set();
|
|
70
|
+
function D(e) {
|
|
63
71
|
return Array.isArray(e);
|
|
64
72
|
}
|
|
65
|
-
function
|
|
66
|
-
return (
|
|
73
|
+
function te(e, t = ",") {
|
|
74
|
+
return (D(e) ? e : e.split(t)).every((u) => g.has(u.trim().toLowerCase()));
|
|
67
75
|
}
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}),
|
|
73
|
-
|
|
76
|
+
function z(e) {
|
|
77
|
+
const t = Array.isArray(e) ? e : [e];
|
|
78
|
+
g.has("meta") && g.forEach((r) => {
|
|
79
|
+
$(r) || g.delete(r.toLowerCase());
|
|
80
|
+
}), t.forEach((r) => {
|
|
81
|
+
g.add(r.toLowerCase());
|
|
74
82
|
});
|
|
75
83
|
}
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
e === "meta" ?
|
|
79
|
-
|
|
84
|
+
function G(e) {
|
|
85
|
+
const t = Array.isArray(e) ? e : [e];
|
|
86
|
+
e === "meta" ? g.clear() : t.forEach((r) => {
|
|
87
|
+
g.delete(r.toLowerCase());
|
|
80
88
|
});
|
|
81
89
|
}
|
|
82
|
-
function
|
|
83
|
-
(typeof
|
|
90
|
+
function re(e, t, r) {
|
|
91
|
+
(typeof r == "function" && r(e, t) || r === !0) && e.preventDefault();
|
|
84
92
|
}
|
|
85
|
-
function
|
|
86
|
-
return typeof
|
|
93
|
+
function ne(e, t, r) {
|
|
94
|
+
return typeof r == "function" ? r(e, t) : r === !0 || r === void 0;
|
|
87
95
|
}
|
|
88
|
-
const
|
|
96
|
+
const oe = [
|
|
89
97
|
"input",
|
|
90
98
|
"textarea",
|
|
91
99
|
"select",
|
|
@@ -99,45 +107,45 @@ const Y = [
|
|
|
99
107
|
"radio",
|
|
100
108
|
"textbox"
|
|
101
109
|
];
|
|
102
|
-
function
|
|
103
|
-
return
|
|
110
|
+
function ie(e) {
|
|
111
|
+
return J(e, oe);
|
|
104
112
|
}
|
|
105
|
-
function
|
|
106
|
-
const { target:
|
|
107
|
-
let
|
|
108
|
-
return
|
|
113
|
+
function J(e, t = !1) {
|
|
114
|
+
const { target: r, composed: u } = e;
|
|
115
|
+
let s, f;
|
|
116
|
+
return se(r) && u ? (s = e.composedPath()[0] && e.composedPath()[0].tagName, f = e.composedPath()[0] && e.composedPath()[0].role) : (s = r && r.tagName, f = r && r.role), D(t) ? !!(s && t?.some((d) => d.toLowerCase() === s.toLowerCase() || d === f)) : !!(s && t && t);
|
|
109
117
|
}
|
|
110
|
-
function
|
|
118
|
+
function se(e) {
|
|
111
119
|
return !!e.tagName && !e.tagName.startsWith("-") && e.tagName.includes("-");
|
|
112
120
|
}
|
|
113
|
-
function
|
|
114
|
-
return e.length === 0 &&
|
|
121
|
+
function ue(e, t) {
|
|
122
|
+
return e.length === 0 && t ? !1 : t ? e.some((r) => t.includes(r)) || e.includes("*") : !0;
|
|
115
123
|
}
|
|
116
|
-
const
|
|
117
|
-
const { alt:
|
|
118
|
-
if (
|
|
124
|
+
const ce = (e, t, r = !1) => {
|
|
125
|
+
const { alt: u, meta: s, mod: f, shift: d, ctrl: l, keys: o, useKey: p } = t, { code: y, key: i, ctrlKey: c, metaKey: n, shiftKey: E, altKey: C } = e, m = L(y);
|
|
126
|
+
if (p && o?.length === 1 && o.includes(i.toLowerCase()))
|
|
119
127
|
return !0;
|
|
120
|
-
if (!
|
|
128
|
+
if (!o?.includes(m) && !["ctrl", "control", "unknown", "meta", "alt", "shift", "os"].includes(m))
|
|
121
129
|
return !1;
|
|
122
|
-
if (!
|
|
123
|
-
if (
|
|
130
|
+
if (!r) {
|
|
131
|
+
if (u !== C && m !== "alt" || d !== E && m !== "shift")
|
|
124
132
|
return !1;
|
|
125
|
-
if (
|
|
126
|
-
if (!
|
|
133
|
+
if (f) {
|
|
134
|
+
if (Z() ? !n : !c)
|
|
127
135
|
return !1;
|
|
128
|
-
} else if (
|
|
136
|
+
} else if (s !== n && m !== "meta" && m !== "os" || l !== c && m !== "ctrl" && m !== "control")
|
|
129
137
|
return !1;
|
|
130
138
|
}
|
|
131
|
-
return
|
|
132
|
-
},
|
|
133
|
-
function
|
|
134
|
-
return /* @__PURE__ */
|
|
139
|
+
return o && o.length === 1 && o.includes(m) ? !0 : o && o.length > 0 ? o.includes(m) ? te(o) : !1 : !o || o.length === 0;
|
|
140
|
+
}, Q = j(void 0), ae = () => W(Q);
|
|
141
|
+
function de({ addHotkey: e, removeHotkey: t, children: r }) {
|
|
142
|
+
return /* @__PURE__ */ x(Q.Provider, { value: { addHotkey: e, removeHotkey: t }, children: r });
|
|
135
143
|
}
|
|
136
|
-
function
|
|
137
|
-
return e &&
|
|
138
|
-
Object.keys(e).reduce((
|
|
144
|
+
function M(e, t) {
|
|
145
|
+
return e && t && typeof e == "object" && typeof t == "object" ? Object.keys(e).length === Object.keys(t).length && // @ts-expect-error TS7053
|
|
146
|
+
Object.keys(e).reduce((r, u) => r && M(e[u], t[u]), !0) : e === t;
|
|
139
147
|
}
|
|
140
|
-
const
|
|
148
|
+
const U = j({
|
|
141
149
|
hotkeys: [],
|
|
142
150
|
activeScopes: [],
|
|
143
151
|
// This array has to be empty instead of containing '*' as default, to check if the provider is set or not
|
|
@@ -147,144 +155,159 @@ const W = T({
|
|
|
147
155
|
},
|
|
148
156
|
disableScope: () => {
|
|
149
157
|
}
|
|
150
|
-
}),
|
|
151
|
-
const [
|
|
152
|
-
|
|
153
|
-
}, []),
|
|
154
|
-
|
|
155
|
-
}, []),
|
|
156
|
-
|
|
157
|
-
}, []),
|
|
158
|
-
|
|
159
|
-
}, []),
|
|
160
|
-
|
|
158
|
+
}), fe = () => W(U), ve = ({ initiallyActiveScopes: e = ["*"], children: t }) => {
|
|
159
|
+
const [r, u] = A(e), [s, f] = A([]), d = h((i) => {
|
|
160
|
+
u((c) => c.includes("*") ? [i] : Array.from(/* @__PURE__ */ new Set([...c, i])));
|
|
161
|
+
}, []), l = h((i) => {
|
|
162
|
+
u((c) => c.filter((n) => n !== i));
|
|
163
|
+
}, []), o = h((i) => {
|
|
164
|
+
u((c) => c.includes(i) ? c.filter((n) => n !== i) : c.includes("*") ? [i] : Array.from(/* @__PURE__ */ new Set([...c, i])));
|
|
165
|
+
}, []), p = h((i) => {
|
|
166
|
+
f((c) => [...c, i]);
|
|
167
|
+
}, []), y = h((i) => {
|
|
168
|
+
f((c) => c.filter((n) => !M(n, i)));
|
|
161
169
|
}, []);
|
|
162
|
-
return /* @__PURE__ */
|
|
163
|
-
|
|
170
|
+
return /* @__PURE__ */ x(
|
|
171
|
+
U.Provider,
|
|
164
172
|
{
|
|
165
|
-
value: { activeScopes:
|
|
166
|
-
children: /* @__PURE__ */
|
|
173
|
+
value: { activeScopes: r, hotkeys: s, enableScope: d, disableScope: l, toggleScope: o },
|
|
174
|
+
children: /* @__PURE__ */ x(de, { addHotkey: p, removeHotkey: y, children: t })
|
|
167
175
|
}
|
|
168
176
|
);
|
|
169
177
|
};
|
|
170
|
-
function
|
|
171
|
-
const
|
|
172
|
-
return
|
|
178
|
+
function le(e) {
|
|
179
|
+
const t = K(void 0);
|
|
180
|
+
return M(t.current, e) || (t.current = e), t.current;
|
|
173
181
|
}
|
|
174
|
-
const
|
|
182
|
+
const ye = (e) => {
|
|
175
183
|
e.stopPropagation(), e.preventDefault(), e.stopImmediatePropagation();
|
|
176
|
-
},
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
}, me = typeof window < "u" ? X : I;
|
|
185
|
+
function pe(e) {
|
|
186
|
+
if (!e) return;
|
|
187
|
+
const { enabled: t, preventDefault: r, ignoreEventWhen: u, ...s } = e;
|
|
188
|
+
return typeof t == "function" ? s : { ...s, enabled: t };
|
|
189
|
+
}
|
|
190
|
+
function we(e, t, r, u) {
|
|
191
|
+
const [s, f] = A(null), d = h((w) => (f(w), () => f(null)), []), l = K(!1), o = Array.isArray(r) ? Array.isArray(u) ? void 0 : u : r, p = D(e) ? e.join(o?.delimiter) : e, y = Array.isArray(r) ? r : Array.isArray(u) ? u : void 0, i = h(t, y ?? []), c = K(i);
|
|
192
|
+
y ? c.current = i : c.current = t;
|
|
193
|
+
const n = le(pe(o)), E = K(o?.enabled);
|
|
194
|
+
E.current = o?.enabled;
|
|
195
|
+
const C = K(o?.preventDefault);
|
|
196
|
+
C.current = o?.preventDefault;
|
|
197
|
+
const m = K(o?.ignoreEventWhen);
|
|
198
|
+
m.current = o?.ignoreEventWhen;
|
|
199
|
+
const { activeScopes: T } = fe(), b = ae();
|
|
200
|
+
return me(() => {
|
|
201
|
+
if (E.current === !1 || !ue(T, n?.scopes))
|
|
183
202
|
return;
|
|
184
|
-
let
|
|
185
|
-
const
|
|
186
|
-
if (!(
|
|
187
|
-
if (
|
|
188
|
-
const
|
|
189
|
-
if ((
|
|
190
|
-
|
|
203
|
+
let w = [], S;
|
|
204
|
+
const q = (a, _ = !1) => {
|
|
205
|
+
if (!(ie(a) && !J(a, n?.enableOnFormTags))) {
|
|
206
|
+
if (s !== null) {
|
|
207
|
+
const k = s.getRootNode();
|
|
208
|
+
if ((k instanceof Document || k instanceof ShadowRoot) && k.activeElement !== s && !s.contains(k.activeElement)) {
|
|
209
|
+
ye(a);
|
|
191
210
|
return;
|
|
192
211
|
}
|
|
193
212
|
}
|
|
194
|
-
|
|
195
|
-
if (
|
|
213
|
+
a.target?.isContentEditable && !n?.enableOnContentEditable || R(p, n?.delimiter).forEach((k) => {
|
|
214
|
+
if (k.includes(n?.splitKey ?? "+") && k.includes(n?.sequenceSplitKey ?? ">")) {
|
|
196
215
|
console.warn(
|
|
197
|
-
`Hotkey ${
|
|
216
|
+
`Hotkey ${k} contains both ${n?.splitKey ?? "+"} and ${n?.sequenceSplitKey ?? ">"} which is not supported.`
|
|
198
217
|
);
|
|
199
218
|
return;
|
|
200
219
|
}
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
220
|
+
const v = O(
|
|
221
|
+
k,
|
|
222
|
+
n?.splitKey,
|
|
223
|
+
n?.sequenceSplitKey,
|
|
224
|
+
n?.useKey,
|
|
225
|
+
n?.description,
|
|
226
|
+
n?.metadata
|
|
208
227
|
);
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
},
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
228
|
+
if (v.isSequence) {
|
|
229
|
+
S = setTimeout(() => {
|
|
230
|
+
w = [];
|
|
231
|
+
}, n?.sequenceTimeoutMs ?? 1e3);
|
|
232
|
+
const P = v.useKey ? a.key : L(a.code);
|
|
233
|
+
if ($(P.toLowerCase()))
|
|
215
234
|
return;
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
if (
|
|
219
|
-
|
|
235
|
+
w.push(P);
|
|
236
|
+
const V = v.keys?.[w.length - 1];
|
|
237
|
+
if (P !== V) {
|
|
238
|
+
w = [], S && clearTimeout(S);
|
|
220
239
|
return;
|
|
221
240
|
}
|
|
222
|
-
|
|
223
|
-
} else if (
|
|
224
|
-
if (
|
|
241
|
+
w.length === v.keys?.length && (c.current(a, v), S && clearTimeout(S), w = []);
|
|
242
|
+
} else if (ce(a, v, n?.ignoreModifiers) || v.keys?.includes("*")) {
|
|
243
|
+
if (m.current?.(a) || _ && l.current || (re(a, v, C.current), !ne(a, v, E.current)))
|
|
225
244
|
return;
|
|
226
|
-
|
|
227
|
-
N(s);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
f.current(s, h), B || (a.current = !0);
|
|
245
|
+
c.current(a, v), _ || (l.current = !0);
|
|
231
246
|
}
|
|
232
247
|
});
|
|
233
248
|
}
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
},
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
}, B = (a) => {
|
|
250
|
+
a.code !== void 0 && (z(L(a.code)), (n?.keydown === void 0 && n?.keyup !== !0 || n?.keydown) && q(a));
|
|
251
|
+
}, N = (a) => {
|
|
252
|
+
a.code !== void 0 && (G(L(a.code)), l.current = !1, n?.keyup && q(a, !0));
|
|
253
|
+
}, H = s || o?.document || document;
|
|
254
|
+
return H.addEventListener("keyup", N, o?.eventListenerOptions), H.addEventListener("keydown", B, o?.eventListenerOptions), b && R(p, n?.delimiter).forEach((a) => {
|
|
255
|
+
b.addHotkey(
|
|
256
|
+
O(
|
|
257
|
+
a,
|
|
258
|
+
n?.splitKey,
|
|
259
|
+
n?.sequenceSplitKey,
|
|
260
|
+
n?.useKey,
|
|
261
|
+
n?.description,
|
|
262
|
+
n?.metadata
|
|
248
263
|
)
|
|
249
264
|
);
|
|
250
265
|
}), () => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
H.removeEventListener("keyup", N, o?.eventListenerOptions), H.removeEventListener("keydown", B, o?.eventListenerOptions), b && R(p, n?.delimiter).forEach((a) => {
|
|
267
|
+
b.removeHotkey(
|
|
268
|
+
O(
|
|
269
|
+
a,
|
|
270
|
+
n?.splitKey,
|
|
271
|
+
n?.sequenceSplitKey,
|
|
272
|
+
n?.useKey,
|
|
273
|
+
n?.description,
|
|
274
|
+
n?.metadata
|
|
260
275
|
)
|
|
261
276
|
);
|
|
262
|
-
}),
|
|
277
|
+
}), w = [], S && clearTimeout(S);
|
|
263
278
|
};
|
|
264
|
-
}, [
|
|
279
|
+
}, [s, n, T, p]), d;
|
|
265
280
|
}
|
|
266
|
-
function
|
|
267
|
-
const [r,
|
|
268
|
-
(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
281
|
+
function ke(e = !1, t = []) {
|
|
282
|
+
const [r, u] = A(/* @__PURE__ */ new Set()), [s, f] = A(!1), d = Y(() => new Set(t.map((i) => i.toLowerCase())), [t]), l = h(
|
|
283
|
+
(i) => {
|
|
284
|
+
if (i.code === void 0)
|
|
285
|
+
return;
|
|
286
|
+
const c = L(e ? i.key : i.code).toLowerCase();
|
|
287
|
+
d.has(c) || (i.preventDefault(), i.stopPropagation(), u((n) => {
|
|
288
|
+
const E = new Set(n);
|
|
289
|
+
return E.add(c), E;
|
|
272
290
|
}));
|
|
273
291
|
},
|
|
274
|
-
[e]
|
|
275
|
-
),
|
|
276
|
-
|
|
277
|
-
}, [
|
|
278
|
-
|
|
279
|
-
}, [
|
|
280
|
-
|
|
292
|
+
[e, d]
|
|
293
|
+
), o = h(() => {
|
|
294
|
+
f(!1);
|
|
295
|
+
}, []), p = h(() => {
|
|
296
|
+
u(/* @__PURE__ */ new Set()), f(!0);
|
|
297
|
+
}, []), y = h(() => {
|
|
298
|
+
u(/* @__PURE__ */ new Set());
|
|
281
299
|
}, []);
|
|
282
|
-
return
|
|
300
|
+
return I(() => {
|
|
301
|
+
if (typeof document < "u" && s)
|
|
302
|
+
return document.addEventListener("keydown", l), () => {
|
|
303
|
+
document.removeEventListener("keydown", l);
|
|
304
|
+
};
|
|
305
|
+
}, [s, l]), [r, { start: p, stop: o, resetKeys: y, isRecording: s }];
|
|
283
306
|
}
|
|
284
307
|
export {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
ve as HotkeysProvider,
|
|
309
|
+
te as isHotkeyPressed,
|
|
310
|
+
we as useHotkeys,
|
|
311
|
+
fe as useHotkeysContext,
|
|
312
|
+
ke as useRecordHotkeys
|
|
290
313
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-hotkeys-hook",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
"author": "Johannes Klauss",
|
|
17
17
|
"main": "dist/index.js",
|
|
18
18
|
"types": "dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
19
25
|
"files": [
|
|
20
26
|
"dist"
|
|
21
27
|
],
|
|
@@ -37,19 +43,19 @@
|
|
|
37
43
|
"react-dom": ">=16.8.0"
|
|
38
44
|
},
|
|
39
45
|
"devDependencies": {
|
|
40
|
-
"@eslint/js": "
|
|
41
|
-
"@types/react": "19.2.
|
|
46
|
+
"@eslint/js": "10.0.1",
|
|
47
|
+
"@types/react": "19.2.14",
|
|
42
48
|
"@types/react-dom": "19.2.3",
|
|
43
|
-
"@vitejs/plugin-react": "5.
|
|
49
|
+
"@vitejs/plugin-react": "5.2.0",
|
|
44
50
|
"@testing-library/jest-dom": "6.9.1",
|
|
45
|
-
"@testing-library/react": "16.3.
|
|
51
|
+
"@testing-library/react": "16.3.2",
|
|
46
52
|
"@testing-library/user-event": "14.6.1",
|
|
47
|
-
"@types/node": "24.
|
|
53
|
+
"@types/node": "24.12.2",
|
|
48
54
|
"globals": "16.5.0",
|
|
49
55
|
"jsdom": "27.4.0",
|
|
50
56
|
"typescript": "5.9.3",
|
|
51
|
-
"vite": "7.3.
|
|
52
|
-
"vitest": "4.
|
|
57
|
+
"vite": "7.3.2",
|
|
58
|
+
"vitest": "4.1.5",
|
|
53
59
|
"vite-plugin-dts": "4.5.4"
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { Hotkey } from './types';
|
|
3
|
-
type BoundHotkeysProxyProviderType = {
|
|
4
|
-
addHotkey: (hotkey: Hotkey) => void;
|
|
5
|
-
removeHotkey: (hotkey: Hotkey) => void;
|
|
6
|
-
};
|
|
7
|
-
export declare const useBoundHotkeysProxy: () => BoundHotkeysProxyProviderType | undefined;
|
|
8
|
-
interface Props {
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
addHotkey: (hotkey: Hotkey) => void;
|
|
11
|
-
removeHotkey: (hotkey: Hotkey) => void;
|
|
12
|
-
}
|
|
13
|
-
export default function BoundHotkeysProxyProviderProvider({ addHotkey, removeHotkey, children }: Props): import("react/jsx-runtime").JSX.Element;
|
|
14
|
-
export {};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Hotkey } from './types';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
export type HotkeysContextType = {
|
|
4
|
-
hotkeys: ReadonlyArray<Hotkey>;
|
|
5
|
-
activeScopes: string[];
|
|
6
|
-
toggleScope: (scope: string) => void;
|
|
7
|
-
enableScope: (scope: string) => void;
|
|
8
|
-
disableScope: (scope: string) => void;
|
|
9
|
-
};
|
|
10
|
-
export declare const useHotkeysContext: () => HotkeysContextType;
|
|
11
|
-
interface Props {
|
|
12
|
-
initiallyActiveScopes?: string[];
|
|
13
|
-
children: ReactNode;
|
|
14
|
-
}
|
|
15
|
-
export declare const HotkeysProvider: ({ initiallyActiveScopes, children }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
-
export {};
|
package/dist/deepEqual.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function deepEqual(x?: unknown, y?: unknown): boolean;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export declare function isReadonlyArray(value: unknown): value is readonly unknown[];
|
|
2
|
-
export declare function isHotkeyPressed(key: string | readonly string[], delimiter?: string): boolean;
|
|
3
|
-
export declare function pushToCurrentlyPressedKeys(key: string | string[]): void;
|
|
4
|
-
export declare function removeFromCurrentlyPressedKeys(key: string | string[]): void;
|
package/dist/parseHotkeys.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { Hotkey } from './types';
|
|
2
|
-
export declare function mapCode(key: string): string;
|
|
3
|
-
export declare function isHotkeyModifier(key: string): boolean;
|
|
4
|
-
export declare function parseKeysHookInput(keys: string, delimiter?: string): string[];
|
|
5
|
-
export declare function parseHotkey(hotkey: string, splitKey?: string, sequenceSplitKey?: string, useKey?: boolean, description?: string, metadata?: Record<string, unknown>): Hotkey;
|
package/dist/types.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { DependencyList } from 'react';
|
|
2
|
-
export type FormTags = 'input' | 'textarea' | 'select' | 'INPUT' | 'TEXTAREA' | 'SELECT' | 'searchbox' | 'slider' | 'spinbutton' | 'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'option' | 'radio' | 'textbox';
|
|
3
|
-
export type Keys = string | readonly string[];
|
|
4
|
-
export type Scopes = string | readonly string[];
|
|
5
|
-
export type EventListenerOptions = {
|
|
6
|
-
capture?: boolean;
|
|
7
|
-
once?: boolean;
|
|
8
|
-
passive?: boolean;
|
|
9
|
-
signal?: AbortSignal;
|
|
10
|
-
} | boolean;
|
|
11
|
-
export type KeyboardModifiers = {
|
|
12
|
-
alt?: boolean;
|
|
13
|
-
ctrl?: boolean;
|
|
14
|
-
meta?: boolean;
|
|
15
|
-
shift?: boolean;
|
|
16
|
-
mod?: boolean;
|
|
17
|
-
useKey?: boolean;
|
|
18
|
-
};
|
|
19
|
-
export type Hotkey = KeyboardModifiers & {
|
|
20
|
-
keys?: readonly string[];
|
|
21
|
-
scopes?: Scopes;
|
|
22
|
-
description?: string;
|
|
23
|
-
isSequence?: boolean;
|
|
24
|
-
hotkey: string;
|
|
25
|
-
metadata?: Record<string, unknown>;
|
|
26
|
-
};
|
|
27
|
-
export type HotkeysEvent = Hotkey;
|
|
28
|
-
export type HotkeyCallback = (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => void;
|
|
29
|
-
export type Trigger = boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean);
|
|
30
|
-
export type Options = {
|
|
31
|
-
enabled?: Trigger;
|
|
32
|
-
enableOnFormTags?: readonly FormTags[] | boolean;
|
|
33
|
-
enableOnContentEditable?: boolean;
|
|
34
|
-
ignoreEventWhen?: (e: KeyboardEvent) => boolean;
|
|
35
|
-
splitKey?: string;
|
|
36
|
-
delimiter?: string;
|
|
37
|
-
scopes?: Scopes;
|
|
38
|
-
keyup?: boolean;
|
|
39
|
-
keydown?: boolean;
|
|
40
|
-
preventDefault?: Trigger;
|
|
41
|
-
description?: string;
|
|
42
|
-
document?: Document;
|
|
43
|
-
ignoreModifiers?: boolean;
|
|
44
|
-
eventListenerOptions?: EventListenerOptions;
|
|
45
|
-
useKey?: boolean;
|
|
46
|
-
sequenceTimeoutMs?: number;
|
|
47
|
-
sequenceSplitKey?: string;
|
|
48
|
-
metadata?: Record<string, unknown>;
|
|
49
|
-
};
|
|
50
|
-
export type OptionsOrDependencyArray = Options | DependencyList;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function useDeepEqualMemo<T>(value: T): T | undefined;
|
package/dist/useHotkeys.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { HotkeyCallback, Keys, OptionsOrDependencyArray } from './types';
|
|
2
|
-
export default function useHotkeys<T extends HTMLElement>(keys: Keys, callback: HotkeyCallback, options?: OptionsOrDependencyArray, dependencies?: OptionsOrDependencyArray): import('react').RefObject<T | null>;
|
package/dist/validators.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { FormTags, Hotkey, Scopes, Trigger } from './types';
|
|
2
|
-
export declare function maybePreventDefault(e: KeyboardEvent, hotkey: Hotkey, preventDefault?: Trigger): void;
|
|
3
|
-
export declare function isHotkeyEnabled(e: KeyboardEvent, hotkey: Hotkey, enabled?: Trigger): boolean;
|
|
4
|
-
export declare function isKeyboardEventTriggeredByInput(ev: KeyboardEvent): boolean;
|
|
5
|
-
export declare function isHotkeyEnabledOnTag(event: KeyboardEvent, enabledOnTags?: readonly FormTags[] | boolean): boolean;
|
|
6
|
-
export declare function isCustomElement(element: HTMLElement): boolean;
|
|
7
|
-
export declare function isScopeActive(activeScopes: string[], scopes?: Scopes): boolean;
|
|
8
|
-
export declare const isHotkeyMatchingKeyboardEvent: (e: KeyboardEvent, hotkey: Hotkey, ignoreModifiers?: boolean) => boolean;
|