react-hotkeys-hook 5.3.0 → 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?
@@ -31,7 +31,7 @@ function $(e) {
31
31
  function R(e, t = ",") {
32
32
  return e.toLowerCase().split(t);
33
33
  }
34
- function O(e, t = "+", r = ">", i = !1, u, f) {
34
+ function O(e, t = "+", r = ">", u = !1, s, f) {
35
35
  let d = [], l = !1;
36
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
37
  const o = {
@@ -40,12 +40,12 @@ function O(e, t = "+", r = ">", i = !1, u, f) {
40
40
  shift: d.includes("shift"),
41
41
  meta: d.includes("meta"),
42
42
  mod: d.includes("mod"),
43
- useKey: i
43
+ useKey: u
44
44
  }, p = d.filter((y) => !F.includes(y));
45
45
  return {
46
46
  ...o,
47
47
  keys: p,
48
- description: u,
48
+ description: s,
49
49
  isSequence: l,
50
50
  hotkey: e,
51
51
  metadata: f
@@ -71,7 +71,7 @@ function D(e) {
71
71
  return Array.isArray(e);
72
72
  }
73
73
  function te(e, t = ",") {
74
- return (D(e) ? e : e.split(t)).every((i) => g.has(i.trim().toLowerCase()));
74
+ return (D(e) ? e : e.split(t)).every((u) => g.has(u.trim().toLowerCase()));
75
75
  }
76
76
  function z(e) {
77
77
  const t = Array.isArray(e) ? e : [e];
@@ -107,33 +107,33 @@ const oe = [
107
107
  "radio",
108
108
  "textbox"
109
109
  ];
110
- function se(e) {
110
+ function ie(e) {
111
111
  return J(e, oe);
112
112
  }
113
113
  function J(e, t = !1) {
114
- const { target: r, composed: i } = e;
115
- let u, f;
116
- return ie(r) && i ? (u = e.composedPath()[0] && e.composedPath()[0].tagName, f = e.composedPath()[0] && e.composedPath()[0].role) : (u = r && r.tagName, f = r && r.role), D(t) ? !!(u && t?.some((d) => d.toLowerCase() === u.toLowerCase() || d === f)) : !!(u && t && t);
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);
117
117
  }
118
- function ie(e) {
118
+ function se(e) {
119
119
  return !!e.tagName && !e.tagName.startsWith("-") && e.tagName.includes("-");
120
120
  }
121
121
  function ue(e, t) {
122
122
  return e.length === 0 && t ? !1 : t ? e.some((r) => t.includes(r)) || e.includes("*") : !0;
123
123
  }
124
124
  const ce = (e, t, r = !1) => {
125
- const { alt: i, meta: u, mod: f, shift: d, ctrl: l, keys: o, useKey: p } = t, { code: y, key: s, ctrlKey: c, metaKey: n, shiftKey: E, altKey: C } = e, m = L(y);
126
- if (p && o?.length === 1 && o.includes(s.toLowerCase()))
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()))
127
127
  return !0;
128
128
  if (!o?.includes(m) && !["ctrl", "control", "unknown", "meta", "alt", "shift", "os"].includes(m))
129
129
  return !1;
130
130
  if (!r) {
131
- if (i !== C && m !== "alt" || d !== E && m !== "shift")
131
+ if (u !== C && m !== "alt" || d !== E && m !== "shift")
132
132
  return !1;
133
133
  if (f) {
134
134
  if (Z() ? !n : !c)
135
135
  return !1;
136
- } else if (u !== n && m !== "meta" && m !== "os" || l !== c && m !== "ctrl" && m !== "control")
136
+ } else if (s !== n && m !== "meta" && m !== "os" || l !== c && m !== "ctrl" && m !== "control")
137
137
  return !1;
138
138
  }
139
139
  return o && o.length === 1 && o.includes(m) ? !0 : o && o.length > 0 ? o.includes(m) ? te(o) : !1 : !o || o.length === 0;
@@ -143,7 +143,7 @@ function de({ addHotkey: e, removeHotkey: t, children: r }) {
143
143
  }
144
144
  function M(e, t) {
145
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, i) => r && M(e[i], t[i]), !0) : e === t;
146
+ Object.keys(e).reduce((r, u) => r && M(e[u], t[u]), !0) : e === t;
147
147
  }
148
148
  const U = j({
149
149
  hotkeys: [],
@@ -156,21 +156,21 @@ const U = j({
156
156
  disableScope: () => {
157
157
  }
158
158
  }), fe = () => W(U), ve = ({ initiallyActiveScopes: e = ["*"], children: t }) => {
159
- const [r, i] = A(e), [u, f] = A([]), d = h((s) => {
160
- i((c) => c.includes("*") ? [s] : Array.from(/* @__PURE__ */ new Set([...c, s])));
161
- }, []), l = h((s) => {
162
- i((c) => c.filter((n) => n !== s));
163
- }, []), o = h((s) => {
164
- i((c) => c.includes(s) ? c.filter((n) => n !== s) : c.includes("*") ? [s] : Array.from(/* @__PURE__ */ new Set([...c, s])));
165
- }, []), p = h((s) => {
166
- f((c) => [...c, s]);
167
- }, []), y = h((s) => {
168
- f((c) => c.filter((n) => !M(n, s)));
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)));
169
169
  }, []);
170
170
  return /* @__PURE__ */ x(
171
171
  U.Provider,
172
172
  {
173
- value: { activeScopes: r, hotkeys: u, enableScope: d, disableScope: l, toggleScope: o },
173
+ value: { activeScopes: r, hotkeys: s, enableScope: d, disableScope: l, toggleScope: o },
174
174
  children: /* @__PURE__ */ x(de, { addHotkey: p, removeHotkey: y, children: t })
175
175
  }
176
176
  );
@@ -184,12 +184,12 @@ const ye = (e) => {
184
184
  }, me = typeof window < "u" ? X : I;
185
185
  function pe(e) {
186
186
  if (!e) return;
187
- const { enabled: t, preventDefault: r, ignoreEventWhen: i, ...u } = e;
188
- return u;
187
+ const { enabled: t, preventDefault: r, ignoreEventWhen: u, ...s } = e;
188
+ return typeof t == "function" ? s : { ...s, enabled: t };
189
189
  }
190
- function we(e, t, r, i) {
191
- const [u, f] = A(null), d = h((w) => (f(w), () => f(null)), []), l = K(!1), o = Array.isArray(r) ? Array.isArray(i) ? void 0 : i : r, p = D(e) ? e.join(o?.delimiter) : e, y = Array.isArray(r) ? r : Array.isArray(i) ? i : void 0, s = h(t, y ?? []), c = K(s);
192
- y ? c.current = s : c.current = t;
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
193
  const n = le(pe(o)), E = K(o?.enabled);
194
194
  E.current = o?.enabled;
195
195
  const C = K(o?.preventDefault);
@@ -202,10 +202,10 @@ function we(e, t, r, i) {
202
202
  return;
203
203
  let w = [], S;
204
204
  const q = (a, _ = !1) => {
205
- if (!(se(a) && !J(a, n?.enableOnFormTags))) {
206
- if (u !== null) {
207
- const k = u.getRootNode();
208
- if ((k instanceof Document || k instanceof ShadowRoot) && k.activeElement !== u && !u.contains(k.activeElement)) {
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
209
  ye(a);
210
210
  return;
211
211
  }
@@ -250,7 +250,7 @@ function we(e, t, r, i) {
250
250
  a.code !== void 0 && (z(L(a.code)), (n?.keydown === void 0 && n?.keyup !== !0 || n?.keydown) && q(a));
251
251
  }, N = (a) => {
252
252
  a.code !== void 0 && (G(L(a.code)), l.current = !1, n?.keyup && q(a, !0));
253
- }, H = u || o?.document || document;
253
+ }, H = s || o?.document || document;
254
254
  return H.addEventListener("keyup", N, o?.eventListenerOptions), H.addEventListener("keydown", B, o?.eventListenerOptions), b && R(p, n?.delimiter).forEach((a) => {
255
255
  b.addHotkey(
256
256
  O(
@@ -276,15 +276,15 @@ function we(e, t, r, i) {
276
276
  );
277
277
  }), w = [], S && clearTimeout(S);
278
278
  };
279
- }, [u, n, T, p]), d;
279
+ }, [s, n, T, p]), d;
280
280
  }
281
281
  function ke(e = !1, t = []) {
282
- const [r, i] = A(/* @__PURE__ */ new Set()), [u, f] = A(!1), d = Y(() => new Set(t.map((s) => s.toLowerCase())), [t]), l = h(
283
- (s) => {
284
- if (s.code === void 0)
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
285
  return;
286
- const c = L(e ? s.key : s.code).toLowerCase();
287
- d.has(c) || (s.preventDefault(), s.stopPropagation(), i((n) => {
286
+ const c = L(e ? i.key : i.code).toLowerCase();
287
+ d.has(c) || (i.preventDefault(), i.stopPropagation(), u((n) => {
288
288
  const E = new Set(n);
289
289
  return E.add(c), E;
290
290
  }));
@@ -293,16 +293,16 @@ function ke(e = !1, t = []) {
293
293
  ), o = h(() => {
294
294
  f(!1);
295
295
  }, []), p = h(() => {
296
- i(/* @__PURE__ */ new Set()), f(!0);
296
+ u(/* @__PURE__ */ new Set()), f(!0);
297
297
  }, []), y = h(() => {
298
- i(/* @__PURE__ */ new Set());
298
+ u(/* @__PURE__ */ new Set());
299
299
  }, []);
300
300
  return I(() => {
301
- if (typeof document < "u" && u)
301
+ if (typeof document < "u" && s)
302
302
  return document.addEventListener("keydown", l), () => {
303
303
  document.removeEventListener("keydown", l);
304
304
  };
305
- }, [u, l]), [r, { start: p, stop: o, resetKeys: y, isRecording: u }];
305
+ }, [s, l]), [r, { start: p, stop: o, resetKeys: y, isRecording: s }];
306
306
  }
307
307
  export {
308
308
  ve as HotkeysProvider,
package/package.json CHANGED
@@ -1,19 +1,29 @@
1
1
  {
2
2
  "name": "react-hotkeys-hook",
3
- "description": "React hook for handling keyboard shortcuts",
4
- "version": "5.3.0",
5
- "sideEffects": false,
3
+ "version": "5.3.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "tsc && vite build",
8
+ "test": "vitest",
9
+ "prepublishOnly": "vitest run && npm run build"
10
+ },
6
11
  "repository": {
7
12
  "type": "git",
8
13
  "url": "https://github.com/JohannesKlauss/react-keymap-hook.git"
9
14
  },
10
15
  "homepage": "https://react-hotkeys-hook.vercel.app/",
11
16
  "author": "Johannes Klauss",
12
- "type": "module",
13
- "main": "packages/react-hotkeys-hook/dist/index.js",
14
- "types": "packages/react-hotkeys-hook/dist/index.d.ts",
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
15
25
  "files": [
16
- "packages/react-hotkeys-hook/dist"
26
+ "dist"
17
27
  ],
18
28
  "keywords": [
19
29
  "react",
@@ -28,22 +38,24 @@
28
38
  "hotkeys"
29
39
  ],
30
40
  "license": "MIT",
31
- "scripts": {
32
- "build": "npm run -w packages/react-hotkeys-hook build",
33
- "build:documentation": "npm run -w packages/documentation build",
34
- "test": "npm run -w packages/react-hotkeys-hook test",
35
- "prepublishOnly": "npm run test && npm run lint && npm run build",
36
- "format": "npx @biomejs/biome format --write packages",
37
- "lint": "npx @biomejs/biome lint --write packages"
38
- },
39
- "workspaces": [
40
- "packages/*"
41
- ],
42
41
  "peerDependencies": {
43
42
  "react": ">=16.8.0",
44
43
  "react-dom": ">=16.8.0"
45
44
  },
46
45
  "devDependencies": {
47
- "@biomejs/biome": "2.4.13"
46
+ "@eslint/js": "10.0.1",
47
+ "@types/react": "19.2.14",
48
+ "@types/react-dom": "19.2.3",
49
+ "@vitejs/plugin-react": "5.2.0",
50
+ "@testing-library/jest-dom": "6.9.1",
51
+ "@testing-library/react": "16.3.2",
52
+ "@testing-library/user-event": "14.6.1",
53
+ "@types/node": "24.12.2",
54
+ "globals": "16.5.0",
55
+ "jsdom": "27.4.0",
56
+ "typescript": "5.9.3",
57
+ "vite": "7.3.2",
58
+ "vitest": "4.1.5",
59
+ "vite-plugin-dts": "4.5.4"
48
60
  }
49
61
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2018 Johannes Klauss
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.