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 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(count + 1), [count])
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 not registered if a focus focuses on an input field. This will prevent accidental triggering of hotkeys when the user is typing. If you want to enable hotkeys, use this option. Setting it to true will enable on all form tags, otherwise you can give an array of form tags to enable the hotkey on (possible options are: `['input', 'textarea', 'select']`) |
154
- | `enableOnContentEditable` | `boolean` | `false` | Set this option to enable hotkeys on tags that have set the `contentEditable` prop to `true` |
155
- | `combinationKey` | `string` | `+` | Character to indicate keystrokes like `shift+c`. You might want to change this if you want to listen to the `+` character like `ctrl-+`. |
156
- | `splitKey` | `string` | `,` | Character to separate different keystrokes like `ctrl+a, ctrl+b`. |
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` | Determines whether to listen to the browsers `keyup` event for triggering the callback. |
159
- | `keydown` | `boolean` | `true` | Determines whether to listen to the browsers `keydown` event for triggering the callback. If you set both `keyup`and `keydown` to true, the callback will trigger on both events. |
160
- | `preventDefault` | `boolean` or `(keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean` | `false` | Set this to a `true` if you want the hook to prevent the browsers default behavior on certain keystrokes like `meta+s` to save a page. NOTE: Certain keystrokes are not preventable, like `meta+w` to close a tab in chrome. |
161
- | `description` | `string` | `undefined` | Use this option to describe what the hotkey does. this is helpful if you want to display a list of active hotkeys to the user. |
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 the [Github Discussions]([Support](https://github.com/JohannesKlauss/react-hotkeys-hook/discussions))
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 { default as useHotkeys } from './useHotkeys';
2
- import { Options, Keys, HotkeyCallback } from './types';
3
- import { HotkeysProvider, useHotkeysContext } from './HotkeysProvider';
4
- import { isHotkeyPressed } from './isHotkeyPressed';
5
- import { default as useRecordHotkeys } from './useRecordHotkeys';
6
- export { useHotkeys, useRecordHotkeys, useHotkeysContext, isHotkeyPressed, HotkeysProvider, type Options, type Keys, type HotkeyCallback, };
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 T, useContext as M, useState as A, useCallback as w, useRef as S, useLayoutEffect as z, useEffect as J } from "react";
2
- import { jsx as b } from "react/jsx-runtime";
3
- const j = ["shift", "alt", "meta", "mod", "ctrl", "control"], Q = {
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 K(e) {
22
- return (Q[e.trim()] || e.trim()).toLowerCase().replace(/key|digit|numpad/, "");
25
+ function L(e) {
26
+ return (ee[e.trim()] || e.trim()).toLowerCase().replace(/key|digit|numpad/, "");
23
27
  }
24
- function D(e) {
25
- return j.includes(e);
28
+ function $(e) {
29
+ return F.includes(e);
26
30
  }
27
- function H(e, r = ",") {
28
- return e.toLowerCase().split(r);
31
+ function R(e, t = ",") {
32
+ return e.toLowerCase().split(t);
29
33
  }
30
- function P(e, r = "+", o = ">", i = !1, c, a) {
31
- let n = [], y = !1;
32
- e = e.trim(), e.includes(o) ? (y = !0, n = e.toLocaleLowerCase().split(o).map((f) => K(f))) : n = e.toLocaleLowerCase().split(r).map((f) => K(f));
33
- const u = {
34
- alt: n.includes("alt"),
35
- ctrl: n.includes("ctrl") || n.includes("control"),
36
- shift: n.includes("shift"),
37
- meta: n.includes("meta"),
38
- mod: n.includes("mod"),
39
- useKey: i
40
- }, l = n.filter((f) => !j.includes(f));
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
- ...u,
43
- keys: l,
44
- description: c,
45
- isSequence: y,
46
+ ...o,
47
+ keys: p,
48
+ description: s,
49
+ isSequence: l,
46
50
  hotkey: e,
47
- metadata: a
51
+ metadata: f
48
52
  };
49
53
  }
50
54
  typeof document < "u" && (document.addEventListener("keydown", (e) => {
51
- e.code !== void 0 && _([K(e.code)]);
55
+ e.code !== void 0 && z([L(e.code)]);
52
56
  }), document.addEventListener("keyup", (e) => {
53
- e.code !== void 0 && I([K(e.code)]);
57
+ e.code !== void 0 && G([L(e.code)]);
54
58
  })), typeof window < "u" && (window.addEventListener("blur", () => {
55
- L.clear();
59
+ g.clear();
60
+ }), window.addEventListener("focus", () => {
61
+ g.clear();
56
62
  }), window.addEventListener("contextmenu", () => {
57
63
  setTimeout(() => {
58
- L.clear();
64
+ g.clear();
59
65
  }, 0);
60
- }));
61
- const L = /* @__PURE__ */ new Set();
62
- function R(e) {
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 U(e, r = ",") {
66
- return (R(e) ? e : e.split(r)).every((i) => L.has(i.trim().toLowerCase()));
73
+ function te(e, t = ",") {
74
+ return (D(e) ? e : e.split(t)).every((u) => g.has(u.trim().toLowerCase()));
67
75
  }
68
- function _(e) {
69
- const r = Array.isArray(e) ? e : [e];
70
- L.has("meta") && L.forEach((o) => {
71
- D(o) || L.delete(o.toLowerCase());
72
- }), r.forEach((o) => {
73
- L.add(o.toLowerCase());
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 I(e) {
77
- const r = Array.isArray(e) ? e : [e];
78
- e === "meta" ? L.clear() : r.forEach((o) => {
79
- L.delete(o.toLowerCase());
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 V(e, r, o) {
83
- (typeof o == "function" && o(e, r) || o === !0) && e.preventDefault();
90
+ function re(e, t, r) {
91
+ (typeof r == "function" && r(e, t) || r === !0) && e.preventDefault();
84
92
  }
85
- function X(e, r, o) {
86
- return typeof o == "function" ? o(e, r) : o === !0 || o === void 0;
93
+ function ne(e, t, r) {
94
+ return typeof r == "function" ? r(e, t) : r === !0 || r === void 0;
87
95
  }
88
- const Y = [
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 Z(e) {
103
- return F(e, Y);
110
+ function ie(e) {
111
+ return J(e, oe);
104
112
  }
105
- function F(e, r = !1) {
106
- const { target: o, composed: i } = e;
107
- let c, a;
108
- return ee(o) && i ? (c = e.composedPath()[0] && e.composedPath()[0].tagName, a = e.composedPath()[0] && e.composedPath()[0].role) : (c = o && o.tagName, a = o && o.role), R(r) ? !!(c && r && r.some((n) => n.toLowerCase() === c.toLowerCase() || n === a)) : !!(c && r && r);
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 ee(e) {
118
+ function se(e) {
111
119
  return !!e.tagName && !e.tagName.startsWith("-") && e.tagName.includes("-");
112
120
  }
113
- function te(e, r) {
114
- return e.length === 0 && r ? !1 : r ? e.some((o) => r.includes(o)) || e.includes("*") : !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 re = (e, r, o = !1) => {
117
- const { alt: i, meta: c, mod: a, shift: n, ctrl: y, keys: u, useKey: l } = r, { code: f, key: t, ctrlKey: d, metaKey: m, shiftKey: g, altKey: k } = e, p = K(f);
118
- if (l && u?.length === 1 && u.includes(t.toLowerCase()))
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 (!u?.includes(p) && !["ctrl", "control", "unknown", "meta", "alt", "shift", "os"].includes(p))
128
+ if (!o?.includes(m) && !["ctrl", "control", "unknown", "meta", "alt", "shift", "os"].includes(m))
121
129
  return !1;
122
- if (!o) {
123
- if (i !== k && p !== "alt" || n !== g && p !== "shift")
130
+ if (!r) {
131
+ if (u !== C && m !== "alt" || d !== E && m !== "shift")
124
132
  return !1;
125
- if (a) {
126
- if (!m && !d)
133
+ if (f) {
134
+ if (Z() ? !n : !c)
127
135
  return !1;
128
- } else if (c !== m && p !== "meta" && p !== "os" || y !== d && p !== "ctrl" && p !== "control")
136
+ } else if (s !== n && m !== "meta" && m !== "os" || l !== c && m !== "ctrl" && m !== "control")
129
137
  return !1;
130
138
  }
131
- return u && u.length === 1 && u.includes(p) ? !0 : u && u.length > 0 ? u.includes(p) ? U(u) : !1 : !u || u.length === 0;
132
- }, $ = T(void 0), oe = () => M($);
133
- function ne({ addHotkey: e, removeHotkey: r, children: o }) {
134
- return /* @__PURE__ */ b($.Provider, { value: { addHotkey: e, removeHotkey: r }, children: o });
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 x(e, r) {
137
- return e && r && typeof e == "object" && typeof r == "object" ? Object.keys(e).length === Object.keys(r).length && // @ts-expect-error TS7053
138
- Object.keys(e).reduce((o, i) => o && x(e[i], r[i]), !0) : e === r;
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 W = T({
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
- }), se = () => M(W), de = ({ initiallyActiveScopes: e = ["*"], children: r }) => {
151
- const [o, i] = A(e), [c, a] = A([]), n = w((t) => {
152
- i((d) => d.includes("*") ? [t] : Array.from(/* @__PURE__ */ new Set([...d, t])));
153
- }, []), y = w((t) => {
154
- i((d) => d.filter((m) => m !== t));
155
- }, []), u = w((t) => {
156
- i((d) => d.includes(t) ? d.filter((m) => m !== t) : d.includes("*") ? [t] : Array.from(/* @__PURE__ */ new Set([...d, t])));
157
- }, []), l = w((t) => {
158
- a((d) => [...d, t]);
159
- }, []), f = w((t) => {
160
- a((d) => d.filter((m) => !x(m, t)));
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__ */ b(
163
- W.Provider,
170
+ return /* @__PURE__ */ x(
171
+ U.Provider,
164
172
  {
165
- value: { activeScopes: o, hotkeys: c, enableScope: n, disableScope: y, toggleScope: u },
166
- children: /* @__PURE__ */ b(ne, { addHotkey: l, removeHotkey: f, children: r })
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 ie(e) {
171
- const r = S(void 0);
172
- return x(r.current, e) || (r.current = e), r.current;
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 N = (e) => {
182
+ const ye = (e) => {
175
183
  e.stopPropagation(), e.preventDefault(), e.stopImmediatePropagation();
176
- }, ce = typeof window < "u" ? z : J;
177
- function fe(e, r, o, i) {
178
- const c = S(null), a = S(!1), n = Array.isArray(o) ? Array.isArray(i) ? void 0 : i : o, y = R(e) ? e.join(n?.delimiter) : e, u = Array.isArray(o) ? o : Array.isArray(i) ? i : void 0, l = w(r, u ?? []), f = S(l);
179
- u ? f.current = l : f.current = r;
180
- const t = ie(n), { activeScopes: d } = se(), m = oe();
181
- return ce(() => {
182
- if (t?.enabled === !1 || !te(d, t?.scopes))
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 g = [], k;
185
- const p = (s, B = !1) => {
186
- if (!(Z(s) && !F(s, t?.enableOnFormTags))) {
187
- if (c.current !== null) {
188
- const v = c.current.getRootNode();
189
- if ((v instanceof Document || v instanceof ShadowRoot) && v.activeElement !== c.current && !c.current.contains(v.activeElement)) {
190
- N(s);
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
- s.target?.isContentEditable && !t?.enableOnContentEditable || H(y, t?.delimiter).forEach((v) => {
195
- if (v.includes(t?.splitKey ?? "+") && v.includes(t?.sequenceSplitKey ?? ">")) {
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 ${v} contains both ${t?.splitKey ?? "+"} and ${t?.sequenceSplitKey ?? ">"} which is not supported.`
216
+ `Hotkey ${k} contains both ${n?.splitKey ?? "+"} and ${n?.sequenceSplitKey ?? ">"} which is not supported.`
198
217
  );
199
218
  return;
200
219
  }
201
- const h = P(
202
- v,
203
- t?.splitKey,
204
- t?.sequenceSplitKey,
205
- t?.useKey,
206
- t?.description,
207
- t?.metadata
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 (h.isSequence) {
210
- k = setTimeout(() => {
211
- g = [];
212
- }, t?.sequenceTimeoutMs ?? 1e3);
213
- const C = h.useKey ? s.key : K(s.code);
214
- if (D(C.toLowerCase()))
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
- g.push(C);
217
- const G = h.keys?.[g.length - 1];
218
- if (C !== G) {
219
- g = [], k && clearTimeout(k);
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
- g.length === h.keys?.length && (f.current(s, h), k && clearTimeout(k), g = []);
223
- } else if (re(s, h, t?.ignoreModifiers) || h.keys?.includes("*")) {
224
- if (t?.ignoreEventWhen?.(s) || B && a.current)
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
- if (V(s, h, t?.preventDefault), !X(s, h, t?.enabled)) {
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
- }, O = (s) => {
235
- s.code !== void 0 && (_(K(s.code)), (t?.keydown === void 0 && t?.keyup !== !0 || t?.keydown) && p(s));
236
- }, q = (s) => {
237
- s.code !== void 0 && (I(K(s.code)), a.current = !1, t?.keyup && p(s, !0));
238
- }, E = c.current || n?.document || document;
239
- return E.addEventListener("keyup", q, n?.eventListenerOptions), E.addEventListener("keydown", O, n?.eventListenerOptions), m && H(y, t?.delimiter).forEach((s) => {
240
- m.addHotkey(
241
- P(
242
- s,
243
- t?.splitKey,
244
- t?.sequenceSplitKey,
245
- t?.useKey,
246
- t?.description,
247
- t?.metadata
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
- E.removeEventListener("keyup", q, n?.eventListenerOptions), E.removeEventListener("keydown", O, n?.eventListenerOptions), m && H(y, t?.delimiter).forEach((s) => {
252
- m.removeHotkey(
253
- P(
254
- s,
255
- t?.splitKey,
256
- t?.sequenceSplitKey,
257
- t?.useKey,
258
- t?.description,
259
- t?.metadata
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
- }), g = [], k && clearTimeout(k);
277
+ }), w = [], S && clearTimeout(S);
263
278
  };
264
- }, [y, t, d]), c;
279
+ }, [s, n, T, p]), d;
265
280
  }
266
- function le(e = !1) {
267
- const [r, o] = A(/* @__PURE__ */ new Set()), [i, c] = A(!1), a = w(
268
- (l) => {
269
- l.code !== void 0 && (l.preventDefault(), l.stopPropagation(), o((f) => {
270
- const t = new Set(f);
271
- return t.add(K(e ? l.key : l.code)), t;
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
- ), n = w(() => {
276
- typeof document < "u" && (document.removeEventListener("keydown", a), c(!1));
277
- }, [a]), y = w(() => {
278
- o(/* @__PURE__ */ new Set()), typeof document < "u" && (n(), document.addEventListener("keydown", a), c(!0));
279
- }, [a, n]), u = w(() => {
280
- o(/* @__PURE__ */ new Set());
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 [r, { start: y, stop: n, resetKeys: u, isRecording: i }];
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
- de as HotkeysProvider,
286
- U as isHotkeyPressed,
287
- fe as useHotkeys,
288
- se as useHotkeysContext,
289
- le as useRecordHotkeys
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.2.4",
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": "9.39.2",
41
- "@types/react": "19.2.8",
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.1.2",
49
+ "@vitejs/plugin-react": "5.2.0",
44
50
  "@testing-library/jest-dom": "6.9.1",
45
- "@testing-library/react": "16.3.1",
51
+ "@testing-library/react": "16.3.2",
46
52
  "@testing-library/user-event": "14.6.1",
47
- "@types/node": "24.10.8",
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.1",
52
- "vitest": "4.0.17",
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 {};
@@ -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;
@@ -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;
@@ -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>;
@@ -1,6 +0,0 @@
1
- export default function useRecordHotkeys(useKey?: boolean): readonly [Set<string>, {
2
- readonly start: () => void;
3
- readonly stop: () => void;
4
- readonly resetKeys: () => void;
5
- readonly isRecording: boolean;
6
- }];
@@ -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;