react-hotkeys-hook 3.4.7 → 4.0.0-2
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 +113 -40
- package/dist/BoundHotkeysProxyProvider.d.ts +14 -0
- package/dist/HotkeysProvider.d.ts +16 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.js +2 -8
- package/dist/index.js.map +7 -0
- package/dist/parseHotkeys.d.ts +3 -0
- package/dist/types.d.ts +32 -0
- package/dist/useHotkeys.d.ts +2 -18
- package/dist/validators.d.ts +7 -0
- package/package.json +15 -24
- package/src/BoundHotkeysProxyProvider.tsx +23 -0
- package/src/HotkeysProvider.tsx +78 -0
- package/src/index.ts +9 -6
- package/src/isHotkeyPressed.ts +51 -0
- package/src/parseHotkeys.ts +33 -0
- package/src/types.ts +41 -0
- package/src/useHotkeys.ts +99 -77
- package/src/validators.ts +92 -0
- package/dist/react-hotkeys-hook.cjs.development.js +0 -93
- package/dist/react-hotkeys-hook.cjs.development.js.map +0 -1
- package/dist/react-hotkeys-hook.cjs.production.min.js +0 -2
- package/dist/react-hotkeys-hook.cjs.production.min.js.map +0 -1
- package/dist/react-hotkeys-hook.esm.js +0 -87
- package/dist/react-hotkeys-hook.esm.js.map +0 -1
- package/dist/useIsHotkeyPressed.d.ts +0 -7
- package/src/index.test.tsx +0 -230
- package/src/useIsHotkeyPressed.ts +0 -8
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<hr>
|
|
2
2
|
<div align="center">
|
|
3
3
|
<h1 align="center">
|
|
4
|
-
useHotkeys(
|
|
4
|
+
useHotkeys(keys, callback)
|
|
5
5
|
</h1>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
@@ -29,13 +29,15 @@
|
|
|
29
29
|
<pre align="center">npm i react-hotkeys-hook</pre>
|
|
30
30
|
|
|
31
31
|
<p align="center">
|
|
32
|
-
A React hook for using keyboard shortcuts in components.
|
|
32
|
+
A React hook for using keyboard shortcuts in components in a declarative way.
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
<hr>
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
+
The easiest way to use the hook.
|
|
40
|
+
|
|
39
41
|
```jsx harmony
|
|
40
42
|
import { useHotkeys } from 'react-hotkeys-hook'
|
|
41
43
|
|
|
@@ -51,9 +53,72 @@ export const ExampleComponent = () => {
|
|
|
51
53
|
}
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
### Scopes
|
|
57
|
+
|
|
58
|
+
Scopes allow you to group hotkeys together. You can use scopes to prevent hotkeys from colliding with each other.
|
|
59
|
+
|
|
60
|
+
```jsx harmony
|
|
61
|
+
const App = () => {
|
|
62
|
+
return (
|
|
63
|
+
<HotkeysProvider initiallyActiveScopes={['settings']}>
|
|
64
|
+
<ExampleComponent />
|
|
65
|
+
</HotkeysProvider>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const ExampleComponent = () => {
|
|
70
|
+
const [count, setCount] = useState(0)
|
|
71
|
+
useHotkeys('ctrl+k', () => setCount(prevCount => prevCount + 1), { scopes: ['settings'] })
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<p>
|
|
75
|
+
Pressed {count} times.
|
|
76
|
+
</p>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Changing a scope's active state
|
|
82
|
+
|
|
83
|
+
You can change the active state of a scope using the `deactivateScope`, `activateScope` and `toggleScope` functions
|
|
84
|
+
returned by the `useHotkeysContext()` hook. Note that you have to have your app wrapped in a `<HotkeysProvider>` component.
|
|
85
|
+
|
|
86
|
+
```jsx harmony
|
|
87
|
+
const App = () => {
|
|
88
|
+
return (
|
|
89
|
+
<HotkeysProvider initiallyActiveScopes={['settings']}>
|
|
90
|
+
<ExampleComponent />
|
|
91
|
+
</HotkeysProvider>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const ExampleComponent = () => {
|
|
96
|
+
const { toggleScope } = useHotkeysContext()
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<button onClick={() => toggleScope('settings')}>
|
|
100
|
+
Change scope active state
|
|
101
|
+
</button>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Focus trap
|
|
107
|
+
|
|
108
|
+
This will only trigger the hotkey if the component is focused.
|
|
109
|
+
|
|
110
|
+
```tsx harmony
|
|
111
|
+
export const ExampleComponent = () => {
|
|
112
|
+
const [count, setCount] = useState(0)
|
|
113
|
+
const ref = useHotkeys<HTMLParagraphElement>('ctrl+k', () => setCount(prevCount => prevCount + 1))
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<p tabIndex={-1} ref={ref}>
|
|
117
|
+
Pressed {count} times.
|
|
118
|
+
</p>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
57
122
|
|
|
58
123
|
## Documentation & Live Examples
|
|
59
124
|
|
|
@@ -61,52 +126,60 @@ listened to. When the component unmounts it will stop listening.
|
|
|
61
126
|
* [Documentation](https://react-hotkeys-hook.vercel.app/docs/documentation/installation)
|
|
62
127
|
* [API](https://react-hotkeys-hook.vercel.app/docs/api/use-hotkeys)
|
|
63
128
|
|
|
64
|
-
## [Join the discussion for version 4!](https://github.com/JohannesKlauss/react-hotkeys-hook/issues/574)
|
|
65
|
-
|
|
66
|
-
If you use this package please share your thoughts on how we can improve this hook with version 4.
|
|
67
|
-
Please engage at the corresponding [Github issue](https://github.com/JohannesKlauss/react-hotkeys-hook/issues/574).
|
|
68
|
-
|
|
69
129
|
## API
|
|
70
130
|
|
|
71
|
-
### useHotkeys()
|
|
131
|
+
### useHotkeys(keys, callback)
|
|
72
132
|
|
|
73
133
|
```typescript
|
|
74
|
-
useHotkeys(keys: string, callback: (event: KeyboardEvent, handler: HotkeysEvent) => void, options: Options = {}, deps:
|
|
134
|
+
useHotkeys(keys: string | string[], callback: (event: KeyboardEvent, handler: HotkeysEvent) => void, options: Options = {}, deps: DependencyList = [])
|
|
75
135
|
```
|
|
76
136
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
modifier
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
| Parameter | Type | Required? | Default value | Description |
|
|
138
|
+
|---------------|---------------------------------------------------------|-----------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
139
|
+
| `keys` | `string` or `string[]` | required | - | set the hotkeys you want the hook to listen to. You can use single or multiple keys, modifier combinations, etc. This will either be a string or an array of strings. To separate multiple keys, use a colon. This split key value can be overridden with the `splitKey` option. |
|
|
140
|
+
| `callback` | `(event: KeyboardEvent, handler: HotkeysEvent) => void` | required | - | This is the callback function that will be called when the hotkey is pressed. The callback will receive the browsers native `KeyboardEvent` and the libraries `HotkeysEvent`. |
|
|
141
|
+
| `options` | `Options` | optional | `{}` | Object to modify the behavior of the hook. Default options are given below. |
|
|
142
|
+
| `dependencies` | `DependencyList` | optional | `[]` | The given callback will always be memoised inside the hook. So if you reference any outside variables, you need to set them here for the callback to get updated (Much like `useCallback` works in React). |
|
|
143
|
+
|
|
144
|
+
### Options
|
|
145
|
+
|
|
146
|
+
All options are optional and have a default value which you can override to change the behavior of the hook.
|
|
147
|
+
|
|
148
|
+
| Option | Type | Default value | Description |
|
|
149
|
+
|--------------------------|--------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
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. |
|
|
151
|
+
| `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']`) |
|
|
152
|
+
| `enableOnContentEditable` | `boolean` | `false` | Set this option to enable hotkeys on tags that have set the `contentEditable` prop to `true` |
|
|
153
|
+
| `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-+`. |
|
|
154
|
+
| `splitKey` | `string` | `,` | Character to separate different keystrokes like `ctrl+a, ctrl+b`. |
|
|
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. |
|
|
156
|
+
| `keyup` | `boolean` | `false` | Determines whether to listen to the browsers `keyup` event for triggering the callback. |
|
|
157
|
+
| `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. |
|
|
158
|
+
| `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. |
|
|
159
|
+
| `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. |
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
#### Overloads
|
|
163
|
+
|
|
164
|
+
The hooks call signature is very flexible. For example if you don't need to set any special options you can use the dependency
|
|
165
|
+
array as your third parameter:
|
|
166
|
+
|
|
167
|
+
`useHotkeys('ctrl+k', () => console.log(counter + 1), [counter])`
|
|
168
|
+
|
|
169
|
+
### `isHotkeyPressed(keys: string | string[], splitKey?: string = ',')`
|
|
103
170
|
|
|
104
171
|
This function allows us to check if the user is currently pressing down a key.
|
|
105
172
|
|
|
106
173
|
```ts
|
|
107
174
|
import { isHotkeyPressed } from 'react-hotkeys-hook'
|
|
108
175
|
|
|
109
|
-
isHotkeyPressed('
|
|
176
|
+
isHotkeyPressed('esc') // Returns true if Escape key is pressed down.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
You can also check for multiple keys at the same time:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
isHotkeyPressed(['esc', 'ctrl+s']) // Returns true if Escape or Ctrl+S are pressed down.
|
|
110
183
|
```
|
|
111
184
|
|
|
112
185
|
## Support
|
|
@@ -124,7 +197,7 @@ or [pull request](https://github.com/JohannesKlauss/react-hotkeys-hook/compare)
|
|
|
124
197
|
Checkout this repo, run `yarn` or `npm i` and then run the `test` script to test the behavior of the hook.
|
|
125
198
|
|
|
126
199
|
## Contributing
|
|
127
|
-
Contributions are what make the open source community such an amazing place to
|
|
200
|
+
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
128
201
|
|
|
129
202
|
1. Fork the Project
|
|
130
203
|
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { Hotkey } from './types';
|
|
3
|
+
declare 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): JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Hotkey } from './types';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
declare type HotkeysContextType = {
|
|
4
|
+
hotkeys: ReadonlyArray<Hotkey>;
|
|
5
|
+
activeScopes: string[];
|
|
6
|
+
toggleScope: (scope: string) => void;
|
|
7
|
+
activateScope: (scope: string) => void;
|
|
8
|
+
deactivateScope: (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) => JSX.Element;
|
|
16
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
(keyCode: string): boolean;
|
|
6
|
-
};
|
|
7
|
-
export { useHotkeys, useIsHotkeyPressed, isHotkeyPressed, Options };
|
|
1
|
+
import useHotkeys from './useHotkeys';
|
|
2
|
+
import type { Options } from './types';
|
|
3
|
+
import { HotkeysProvider, useHotkeysContext } from './HotkeysProvider';
|
|
4
|
+
export { useHotkeys, useHotkeysContext, HotkeysProvider, Options, };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (process.env.NODE_ENV === 'production') {
|
|
5
|
-
module.exports = require('./react-hotkeys-hook.cjs.production.min.js')
|
|
6
|
-
} else {
|
|
7
|
-
module.exports = require('./react-hotkeys-hook.cjs.development.js')
|
|
8
|
-
}
|
|
1
|
+
import{useCallback as j,useLayoutEffect as q,useRef as B}from"react";var O=["ctrl","shift","alt","meta","mod"];function v(t,o=","){return typeof t=="string"?t.split(o):t}function H(t,o="+"){let e=t.toLocaleLowerCase().split(o).map(c=>c.trim()),a={alt:e.includes("alt"),ctrl:e.includes("ctrl"),shift:e.includes("shift"),meta:e.includes("meta"),mod:e.includes("mod")},i=e.filter(c=>!O.includes(c));return{...a,keys:i}}function b(t,o,e){(typeof e=="function"&&e(t,o)||e===!0)&&t.preventDefault()}function P(t,o,e){return typeof e=="function"?e(t,o):e===!0||e===void 0}function h(t){return g(t,["input","textarea","select"])}function g({target:t},o=!1){let e=t&&t.tagName;return o instanceof Array?Boolean(e&&o&&o.some(a=>a.toLowerCase()===e.toLowerCase())):Boolean(e&&o&&o===!0)}function E(t,o){return t.length===0&&o?(console.warn('A hotkey has the "scopes" option set, however no active scopes were found. If you want to use the global scopes feature, you need to wrap your app in a <HotkeysProvider>'),!0):o?t.some(e=>o.includes(e))||t.includes("*"):!0}var C=(t,o,e)=>{let{alt:a,ctrl:i,meta:c,mod:n,shift:l,keys:y}=o,{altKey:k,ctrlKey:d,metaKey:p,shiftKey:s,key:u,code:r}=t,m=r.toLowerCase().replace("key",""),f=u.toLowerCase();if(k!==a&&f!=="alt"||s!==l&&f!=="shift")return!1;if(n){if(!p&&!d)return!1}else if(p!==c&&m!=="meta"||d!==i&&m!=="ctrl")return!1;return y&&y.length===1&&(y.includes(f)||y.includes(m))?!0:y?y.every(D=>e.has(D)):!y};import{createContext as N,useMemo as F,useState as T,useContext as U}from"react";import{createContext as R,useContext as I}from"react";var S=R(void 0),w=()=>I(S);function x({addHotkey:t,removeHotkey:o,children:e}){return React.createElement(S.Provider,{value:{addHotkey:t,removeHotkey:o}},e)}var A=N({hotkeys:[],activeScopes:[],toggleScope:()=>{},activateScope:()=>{},deactivateScope:()=>{}}),K=()=>U(A),_=({initiallyActiveScopes:t=["*"],children:o})=>{let[e,a]=T(t?.length>0?t:["*"]),[i,c]=T([]),n=F(()=>e.includes("*"),[e]),l=s=>{a(n?[s]:Array.from(new Set([...e,s])))},y=s=>{let u=e.filter(r=>r!==s);u.length===0?a(["*"]):a(u)},k=s=>{e.includes(s)?y(s):l(s)},d=s=>{c([...i,s])},p=s=>{c(i.filter(u=>u.keys!==s.keys))};return React.createElement(A.Provider,{value:{activeScopes:e,hotkeys:i,activateScope:l,deactivateScope:y,toggleScope:k}},React.createElement(x,{addHotkey:d,removeHotkey:p},o))};var L=t=>{t.stopPropagation(),t.preventDefault(),t.stopImmediatePropagation()};function M(t,o,e,a){let i=B(null),{current:c}=B(new Set),n=e instanceof Array?a instanceof Array?void 0:a:e,l=e instanceof Array?e:a instanceof Array?a:[],y=j(o,[...l]),k=K(),d=w();return q(()=>{if(n?.enabled===!1||!E(k.activeScopes,n?.scopes))return;let p=r=>{if(!(h(r)&&!g(r,n?.enableOnFormTags))){if(i.current!==null&&document.activeElement!==i.current&&!i.current.contains(document.activeElement)){L(r);return}r.target?.isContentEditable&&!n?.enableOnContentEditable||v(t,n?.splitKey).forEach(m=>{let f=H(m,n?.combinationKey);if(C(r,f,c)||f.keys?.includes("*")){if(b(r,f,n?.preventDefault),!P(r,f,n?.enabled)){L(r);return}y(r,f)}})}},s=r=>{c.add(r.key.toLowerCase()),(n?.keydown===void 0&&n?.keyup!==!0||n?.keydown)&&p(r)},u=r=>{c.delete(r.key.toLowerCase()),n?.keyup&&p(r)};return(i.current||document).addEventListener("keyup",u),(i.current||document).addEventListener("keydown",s),d&&v(t,n?.splitKey).forEach(r=>d.addHotkey(H(r,n?.combinationKey))),()=>{(i.current||document).removeEventListener("keyup",u),(i.current||document).removeEventListener("keydown",s),d&&v(t,n?.splitKey).forEach(r=>d.removeHotkey(H(r,n?.combinationKey)))}},[t,y,n]),i}export{_ as HotkeysProvider,M as useHotkeys,K as useHotkeysContext};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/useHotkeys.ts", "../src/parseHotkeys.ts", "../src/validators.ts", "../src/HotkeysProvider.tsx", "../src/BoundHotkeysProxyProvider.tsx"],
|
|
4
|
+
"sourcesContent": ["import { HotkeyCallback, Keys, OptionsOrDependencyArray, RefType } from './types'\nimport { useCallback, useLayoutEffect, useRef } from 'react'\nimport { parseHotkey, parseKeysHookInput } from './parseHotkeys'\nimport {\n isHotkeyEnabled,\n isHotkeyEnabledOnTag,\n isHotkeyMatchingKeyboardEvent,\n isKeyboardEventTriggeredByInput,\n isScopeActive,\n maybePreventDefault,\n} from './validators'\nimport { useHotkeysContext } from './HotkeysProvider'\nimport { useBoundHotkeysProxy } from './BoundHotkeysProxyProvider'\n\nconst stopPropagation = (e: KeyboardEvent): void => {\n e.stopPropagation()\n e.preventDefault()\n e.stopImmediatePropagation()\n}\n\nexport default function useHotkeys<T extends HTMLElement>(\n keys: Keys,\n callback: HotkeyCallback,\n options?: OptionsOrDependencyArray,\n dependencies?: OptionsOrDependencyArray,\n) {\n const ref = useRef<RefType<T>>(null)\n const { current: pressedDownKeys } = useRef<Set<string>>(new Set())\n\n const _options = !(options instanceof Array) ? options : !(dependencies instanceof Array) ? dependencies : undefined\n const _deps = options instanceof Array ? options : dependencies instanceof Array ? dependencies : []\n\n const cb = useCallback(callback, [..._deps])\n const ctx = useHotkeysContext()\n\n const proxy = useBoundHotkeysProxy()\n\n useLayoutEffect(() => {\n if (_options?.enabled === false || !isScopeActive(ctx.activeScopes, _options?.scopes)) {\n return\n }\n\n const listener = (e: KeyboardEvent) => {\n if (isKeyboardEventTriggeredByInput(e) && !isHotkeyEnabledOnTag(e, _options?.enableOnFormTags)) {\n return\n }\n\n if (ref.current !== null && document.activeElement !== ref.current && !ref.current.contains(document.activeElement)) {\n stopPropagation(e)\n\n return\n }\n\n if (((e.target as HTMLElement)?.isContentEditable && !_options?.enableOnContentEditable)) {\n return\n }\n\n parseKeysHookInput(keys, _options?.splitKey).forEach((key) => {\n const hotkey = parseHotkey(key, _options?.combinationKey)\n\n if (isHotkeyMatchingKeyboardEvent(e, hotkey, pressedDownKeys) || hotkey.keys?.includes('*')) {\n maybePreventDefault(e, hotkey, _options?.preventDefault)\n\n if (!isHotkeyEnabled(e, hotkey, _options?.enabled)) {\n stopPropagation(e)\n\n return\n }\n\n cb(e, hotkey)\n }\n })\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n pressedDownKeys.add(event.key.toLowerCase())\n\n if ((_options?.keydown === undefined && _options?.keyup !== true) || _options?.keydown) {\n listener(event)\n }\n }\n\n const handleKeyUp = (event: KeyboardEvent) => {\n pressedDownKeys.delete(event.key.toLowerCase())\n\n if (_options?.keyup) {\n listener(event)\n }\n }\n\n // @ts-ignore\n (ref.current || document).addEventListener('keyup', handleKeyUp);\n // @ts-ignore\n (ref.current || document).addEventListener('keydown', handleKeyDown)\n\n if (proxy) {\n parseKeysHookInput(keys, _options?.splitKey).forEach((key) => proxy.addHotkey(parseHotkey(key, _options?.combinationKey)))\n }\n\n return () => {\n // @ts-ignore\n (ref.current || document).removeEventListener('keyup', handleKeyUp);\n // @ts-ignore\n (ref.current || document).removeEventListener('keydown', handleKeyDown)\n\n if (proxy) {\n parseKeysHookInput(keys, _options?.splitKey).forEach((key) => proxy.removeHotkey(parseHotkey(key, _options?.combinationKey)))\n }\n }\n }, [keys, cb, _options])\n\n return ref\n}\n", "import { Hotkey, KeyboardModifiers, Keys } from './types'\n\nconst reservedModifierKeywords = ['ctrl', 'shift', 'alt', 'meta', 'mod']\n\nexport function parseKeysHookInput(keys: Keys, splitKey: string = ','): string[] {\n if (typeof keys === 'string') {\n return keys.split(splitKey)\n }\n\n return keys\n}\n\nexport function parseHotkey(hotkey: string, combinationKey: string = '+'): Hotkey {\n const keys = hotkey\n .toLocaleLowerCase()\n .split(combinationKey)\n .map((k) => k.trim())\n\n const modifiers: KeyboardModifiers = {\n alt: keys.includes('alt'),\n ctrl: keys.includes('ctrl'),\n shift: keys.includes('shift'),\n meta: keys.includes('meta'),\n mod: keys.includes('mod'),\n }\n\n const singleCharKeys = keys.filter((k) => !reservedModifierKeywords.includes(k))\n\n return {\n ...modifiers,\n keys: singleCharKeys,\n }\n}\n", "import { FormTags, Hotkey, Scopes, Trigger } from './types'\n\nexport function maybePreventDefault(e: KeyboardEvent, hotkey: Hotkey, preventDefault?: Trigger): void {\n if ((typeof preventDefault === 'function' && preventDefault(e, hotkey)) || preventDefault === true) {\n e.preventDefault()\n }\n}\n\nexport function isHotkeyEnabled(e: KeyboardEvent, hotkey: Hotkey, enabled?: Trigger): boolean {\n if (typeof enabled === 'function') {\n return enabled(e, hotkey)\n }\n\n return enabled === true || enabled === undefined\n}\n\nexport function isKeyboardEventTriggeredByInput(ev: KeyboardEvent): boolean {\n return isHotkeyEnabledOnTag(ev, ['input', 'textarea', 'select'])\n}\n\nexport function isHotkeyEnabledOnTag({ target }: KeyboardEvent, enabledOnTags: FormTags[] | boolean = false): boolean {\n const targetTagName = target && (target as HTMLElement).tagName\n\n if (enabledOnTags instanceof Array) {\n return Boolean(targetTagName && enabledOnTags && enabledOnTags.some(tag => tag.toLowerCase() === targetTagName.toLowerCase()))\n }\n\n return Boolean(targetTagName && enabledOnTags && enabledOnTags === true)\n}\n\nexport function isScopeActive(activeScopes: string[], scopes?: Scopes): boolean {\n if (activeScopes.length === 0 && scopes) {\n console.warn(\n 'A hotkey has the \"scopes\" option set, however no active scopes were found. If you want to use the global scopes feature, you need to wrap your app in a <HotkeysProvider>'\n )\n\n return true\n }\n\n if (!scopes) {\n return true\n }\n\n return activeScopes.some(scope => scopes.includes(scope)) || activeScopes.includes('*')\n}\n\nexport const isHotkeyMatchingKeyboardEvent = (e: KeyboardEvent, hotkey: Hotkey, pressedDownKeys: Set<string>): boolean => {\n const { alt, ctrl, meta, mod, shift, keys } = hotkey\n const { altKey, ctrlKey, metaKey, shiftKey, key: pressedKeyUppercase, code } = e\n\n const keyCode = code.toLowerCase().replace('key', '')\n const pressedKey = pressedKeyUppercase.toLowerCase()\n\n if (altKey !== alt && pressedKey !== 'alt') {\n return false\n }\n\n if (shiftKey !== shift && pressedKey !== 'shift') {\n return false\n }\n\n // Mod is a special key name that is checking for meta on macOS and ctrl on other platforms\n if (mod) {\n if (!metaKey && !ctrlKey) {\n return false\n }\n } else {\n if (metaKey !== meta && keyCode !== 'meta') {\n return false\n }\n\n if (ctrlKey !== ctrl && keyCode !== 'ctrl') {\n return false\n }\n }\n\n // All modifiers are correct, now check the key\n // If the key is set we check for the key\n if (keys && keys.length === 1 && (keys.includes(pressedKey) || keys.includes(keyCode))) {\n return true\n } else if (keys) {\n // Check if all keys are present in pressedDownKeys set\n return keys.every(key => pressedDownKeys.has(key))\n }\n else if (!keys) {\n // If the key is not set, we only listen for modifiers, that check went alright, so we return true\n return true\n }\n\n // There is nothing that matches.\n return false\n}\n", "import { Hotkey } from './types'\nimport { createContext, ReactNode, useMemo, useState, useContext } from 'react'\nimport BoundHotkeysProxyProviderProvider from './BoundHotkeysProxyProvider'\n\ntype HotkeysContextType = {\n hotkeys: ReadonlyArray<Hotkey>\n activeScopes: string[]\n toggleScope: (scope: string) => void\n activateScope: (scope: string) => void\n deactivateScope: (scope: string) => void\n}\n\n// The context is only needed for special features like global scoping, so we use a graceful default fallback\nconst HotkeysContext = createContext<HotkeysContextType>({\n hotkeys: [],\n activeScopes: [], // This array has to be empty instead of containing '*' as default, to check if the provider is set or not\n toggleScope: () => {},\n activateScope: () => {},\n deactivateScope: () => {},\n})\n\nexport const useHotkeysContext = () => {\n return useContext(HotkeysContext)\n}\n\ninterface Props {\n initiallyActiveScopes?: string[]\n children: ReactNode\n}\n\nexport const HotkeysProvider = ({initiallyActiveScopes = ['*'], children}: Props) => {\n const [internalActiveScopes, setInternalActiveScopes] = useState(initiallyActiveScopes?.length > 0 ? initiallyActiveScopes : ['*'])\n const [boundHotkeys, setBoundHotkeys] = useState<Hotkey[]>([]);\n\n const isAllActive = useMemo(() => internalActiveScopes.includes('*'), [internalActiveScopes])\n\n const activateScope = (scope: string) => {\n if (isAllActive) {\n setInternalActiveScopes([scope])\n } else {\n setInternalActiveScopes(Array.from(new Set([...internalActiveScopes, scope])))\n }\n }\n\n const deactivateScope = (scope: string) => {\n const scopes = internalActiveScopes.filter(s => s !== scope)\n\n if (scopes.length === 0) {\n setInternalActiveScopes(['*'])\n } else {\n setInternalActiveScopes(scopes)\n }\n }\n\n const toggleScope = (scope: string) => {\n if (internalActiveScopes.includes(scope)) {\n deactivateScope(scope)\n } else {\n activateScope(scope)\n }\n }\n\n const addBoundHotkey = (hotkey: Hotkey) => {\n setBoundHotkeys([...boundHotkeys, hotkey])\n }\n\n const removeBoundHotkey = (hotkey: Hotkey) => {\n setBoundHotkeys(boundHotkeys.filter(h => h.keys !== hotkey.keys))\n }\n\n return (\n <HotkeysContext.Provider value={{activeScopes: internalActiveScopes, hotkeys: boundHotkeys, activateScope, deactivateScope, toggleScope}}>\n <BoundHotkeysProxyProviderProvider addHotkey={addBoundHotkey} removeHotkey={removeBoundHotkey}>\n {children}\n </BoundHotkeysProxyProviderProvider>\n </HotkeysContext.Provider>\n )\n}\n", "import { createContext, ReactNode, useContext } from 'react'\nimport { Hotkey } from './types'\n\ntype BoundHotkeysProxyProviderType = {\n addHotkey: (hotkey: Hotkey) => void,\n removeHotkey: (hotkey: Hotkey) => void,\n}\n\nconst BoundHotkeysProxyProvider = createContext<BoundHotkeysProxyProviderType | undefined>(undefined)\n\nexport const useBoundHotkeysProxy = () => {\n return useContext(BoundHotkeysProxyProvider)\n}\n\ninterface Props {\n children: ReactNode\n addHotkey: (hotkey: Hotkey) => void\n removeHotkey: (hotkey: Hotkey) => void\n}\n\nexport default function BoundHotkeysProxyProviderProvider({ addHotkey, removeHotkey, children }: Props) {\n return <BoundHotkeysProxyProvider.Provider value={{addHotkey, removeHotkey}}>{children}</BoundHotkeysProxyProvider.Provider>\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DependencyList } from 'react';
|
|
2
|
+
export declare type FormTags = 'input' | 'textarea' | 'select' | 'INPUT' | 'TEXTAREA' | 'SELECT';
|
|
3
|
+
export declare type Keys = string | string[];
|
|
4
|
+
export declare type Scopes = string | string[];
|
|
5
|
+
export declare type RefType<T> = T | null;
|
|
6
|
+
export declare type KeyboardModifiers = {
|
|
7
|
+
alt?: boolean;
|
|
8
|
+
ctrl?: boolean;
|
|
9
|
+
meta?: boolean;
|
|
10
|
+
shift?: boolean;
|
|
11
|
+
mod?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare type Hotkey = KeyboardModifiers & {
|
|
14
|
+
keys?: string[];
|
|
15
|
+
scopes?: Scopes;
|
|
16
|
+
};
|
|
17
|
+
export declare type HotkeysEvent = Hotkey & {};
|
|
18
|
+
export declare type HotkeyCallback = (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => void;
|
|
19
|
+
export declare type Trigger = boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean);
|
|
20
|
+
export declare type Options = {
|
|
21
|
+
enabled?: Trigger;
|
|
22
|
+
enableOnFormTags?: FormTags[] | boolean;
|
|
23
|
+
enableOnContentEditable?: boolean;
|
|
24
|
+
combinationKey?: string;
|
|
25
|
+
splitKey?: string;
|
|
26
|
+
scopes?: Scopes;
|
|
27
|
+
keyup?: boolean;
|
|
28
|
+
keydown?: boolean;
|
|
29
|
+
preventDefault?: Trigger;
|
|
30
|
+
description?: string;
|
|
31
|
+
};
|
|
32
|
+
export declare type OptionsOrDependencyArray = Options | DependencyList;
|
package/dist/useHotkeys.d.ts
CHANGED
|
@@ -1,18 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
declare type AvailableTags = 'INPUT' | 'TEXTAREA' | 'SELECT';
|
|
4
|
-
export declare type Options = {
|
|
5
|
-
enabled?: boolean;
|
|
6
|
-
filter?: typeof hotkeys.filter;
|
|
7
|
-
filterPreventDefault?: boolean;
|
|
8
|
-
enableOnTags?: AvailableTags[];
|
|
9
|
-
enableOnContentEditable?: boolean;
|
|
10
|
-
splitKey?: string;
|
|
11
|
-
scope?: string;
|
|
12
|
-
keyup?: boolean;
|
|
13
|
-
keydown?: boolean;
|
|
14
|
-
};
|
|
15
|
-
export declare function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, options?: Options): React.MutableRefObject<T | null>;
|
|
16
|
-
export declare function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, deps?: any[]): React.MutableRefObject<T | null>;
|
|
17
|
-
export declare function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, options?: Options, deps?: any[]): React.MutableRefObject<T | null>;
|
|
18
|
-
export {};
|
|
1
|
+
import { HotkeyCallback, Keys, OptionsOrDependencyArray, RefType } from './types';
|
|
2
|
+
export default function useHotkeys<T extends HTMLElement>(keys: Keys, callback: HotkeyCallback, options?: OptionsOrDependencyArray, dependencies?: OptionsOrDependencyArray): import("react").MutableRefObject<RefType<T>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
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({ target }: KeyboardEvent, enabledOnTags?: FormTags[] | boolean): boolean;
|
|
6
|
+
export declare function isScopeActive(activeScopes: string[], scopes?: Scopes): boolean;
|
|
7
|
+
export declare const isHotkeyMatchingKeyboardEvent: (e: KeyboardEvent, hotkey: Hotkey, pressedDownKeys: Set<string>) => boolean;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-hotkeys-hook",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-2",
|
|
4
4
|
"repository": "https://JohannesKlauss@github.com/JohannesKlauss/react-keymap-hook.git",
|
|
5
5
|
"homepage": "https://johannesklauss.github.io/react-hotkeys-hook/",
|
|
6
6
|
"author": "Johannes Klauss",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
|
-
"
|
|
9
|
-
"module": "dist/
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"module": "dist/index.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"src"
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"scripts": {
|
|
28
|
-
"
|
|
29
|
-
"build": "tsdx build",
|
|
28
|
+
"build": "node ./esbuild.js && tsc --project tsconfig.build.json",
|
|
30
29
|
"test": "jest",
|
|
31
30
|
"publish": "np"
|
|
32
31
|
},
|
|
@@ -37,48 +36,40 @@
|
|
|
37
36
|
"@babel/preset-react"
|
|
38
37
|
]
|
|
39
38
|
},
|
|
40
|
-
"jest": {
|
|
41
|
-
"setupFilesAfterEnv": [
|
|
42
|
-
"./setupTests.js"
|
|
43
|
-
],
|
|
44
|
-
"testPathIgnorePatterns": [
|
|
45
|
-
"pkg",
|
|
46
|
-
".docz",
|
|
47
|
-
"docs"
|
|
48
|
-
]
|
|
49
|
-
},
|
|
50
39
|
"prettier": {
|
|
51
|
-
"printWidth":
|
|
52
|
-
"semi":
|
|
40
|
+
"printWidth": 120,
|
|
41
|
+
"semi": false,
|
|
53
42
|
"singleQuote": true,
|
|
54
43
|
"trailingComma": "es5"
|
|
55
44
|
},
|
|
56
|
-
"dependencies": {
|
|
57
|
-
"hotkeys-js": "3.9.4"
|
|
58
|
-
},
|
|
59
45
|
"devDependencies": {
|
|
60
46
|
"@babel/core": "7.18.9",
|
|
61
47
|
"@babel/plugin-proposal-class-properties": "7.18.6",
|
|
48
|
+
"@babel/plugin-transform-react-jsx": "7.18.6",
|
|
62
49
|
"@babel/preset-env": "7.18.9",
|
|
63
50
|
"@babel/preset-react": "7.18.6",
|
|
64
51
|
"@babel/preset-typescript": "7.18.6",
|
|
52
|
+
"@testing-library/jest-dom": "5.16.4",
|
|
65
53
|
"@testing-library/react": "13.3.0",
|
|
66
54
|
"@testing-library/react-hooks": "8.0.1",
|
|
67
|
-
"@testing-library/user-event": "
|
|
68
|
-
"@types/jest": "
|
|
55
|
+
"@testing-library/user-event": "14.3.0",
|
|
56
|
+
"@types/jest": "28.1.6",
|
|
57
|
+
"@types/lodash": "^4.14.182",
|
|
69
58
|
"@types/react": "18.0.15",
|
|
70
59
|
"@types/react-dom": "18.0.6",
|
|
60
|
+
"esbuild": "0.14.49",
|
|
71
61
|
"eslint-plugin-prettier": "4.2.1",
|
|
72
|
-
"jest": "
|
|
62
|
+
"jest": "28.1.3",
|
|
63
|
+
"jest-environment-jsdom": "28.1.3",
|
|
73
64
|
"prettier": "2.7.1",
|
|
74
65
|
"react": "18.2.0",
|
|
75
66
|
"react-dom": "18.2.0",
|
|
76
67
|
"react-test-renderer": "18.2.0",
|
|
77
|
-
"tsdx": "0.14.1",
|
|
78
68
|
"tslib": "2.4.0",
|
|
79
69
|
"typescript": "4.7.4"
|
|
80
70
|
},
|
|
81
71
|
"peerDependencies": {
|
|
72
|
+
"lodash": ">=4.17.21",
|
|
82
73
|
"react": ">=16.8.1",
|
|
83
74
|
"react-dom": ">=16.8.1"
|
|
84
75
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext, ReactNode, useContext } from 'react'
|
|
2
|
+
import { Hotkey } from './types'
|
|
3
|
+
|
|
4
|
+
type BoundHotkeysProxyProviderType = {
|
|
5
|
+
addHotkey: (hotkey: Hotkey) => void,
|
|
6
|
+
removeHotkey: (hotkey: Hotkey) => void,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const BoundHotkeysProxyProvider = createContext<BoundHotkeysProxyProviderType | undefined>(undefined)
|
|
10
|
+
|
|
11
|
+
export const useBoundHotkeysProxy = () => {
|
|
12
|
+
return useContext(BoundHotkeysProxyProvider)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
children: ReactNode
|
|
17
|
+
addHotkey: (hotkey: Hotkey) => void
|
|
18
|
+
removeHotkey: (hotkey: Hotkey) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function BoundHotkeysProxyProviderProvider({ addHotkey, removeHotkey, children }: Props) {
|
|
22
|
+
return <BoundHotkeysProxyProvider.Provider value={{addHotkey, removeHotkey}}>{children}</BoundHotkeysProxyProvider.Provider>
|
|
23
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Hotkey } from './types'
|
|
2
|
+
import { createContext, ReactNode, useMemo, useState, useContext } from 'react'
|
|
3
|
+
import BoundHotkeysProxyProviderProvider from './BoundHotkeysProxyProvider'
|
|
4
|
+
|
|
5
|
+
type HotkeysContextType = {
|
|
6
|
+
hotkeys: ReadonlyArray<Hotkey>
|
|
7
|
+
activeScopes: string[]
|
|
8
|
+
toggleScope: (scope: string) => void
|
|
9
|
+
activateScope: (scope: string) => void
|
|
10
|
+
deactivateScope: (scope: string) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// The context is only needed for special features like global scoping, so we use a graceful default fallback
|
|
14
|
+
const HotkeysContext = createContext<HotkeysContextType>({
|
|
15
|
+
hotkeys: [],
|
|
16
|
+
activeScopes: [], // This array has to be empty instead of containing '*' as default, to check if the provider is set or not
|
|
17
|
+
toggleScope: () => {},
|
|
18
|
+
activateScope: () => {},
|
|
19
|
+
deactivateScope: () => {},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const useHotkeysContext = () => {
|
|
23
|
+
return useContext(HotkeysContext)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Props {
|
|
27
|
+
initiallyActiveScopes?: string[]
|
|
28
|
+
children: ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const HotkeysProvider = ({initiallyActiveScopes = ['*'], children}: Props) => {
|
|
32
|
+
const [internalActiveScopes, setInternalActiveScopes] = useState(initiallyActiveScopes?.length > 0 ? initiallyActiveScopes : ['*'])
|
|
33
|
+
const [boundHotkeys, setBoundHotkeys] = useState<Hotkey[]>([]);
|
|
34
|
+
|
|
35
|
+
const isAllActive = useMemo(() => internalActiveScopes.includes('*'), [internalActiveScopes])
|
|
36
|
+
|
|
37
|
+
const activateScope = (scope: string) => {
|
|
38
|
+
if (isAllActive) {
|
|
39
|
+
setInternalActiveScopes([scope])
|
|
40
|
+
} else {
|
|
41
|
+
setInternalActiveScopes(Array.from(new Set([...internalActiveScopes, scope])))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const deactivateScope = (scope: string) => {
|
|
46
|
+
const scopes = internalActiveScopes.filter(s => s !== scope)
|
|
47
|
+
|
|
48
|
+
if (scopes.length === 0) {
|
|
49
|
+
setInternalActiveScopes(['*'])
|
|
50
|
+
} else {
|
|
51
|
+
setInternalActiveScopes(scopes)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const toggleScope = (scope: string) => {
|
|
56
|
+
if (internalActiveScopes.includes(scope)) {
|
|
57
|
+
deactivateScope(scope)
|
|
58
|
+
} else {
|
|
59
|
+
activateScope(scope)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const addBoundHotkey = (hotkey: Hotkey) => {
|
|
64
|
+
setBoundHotkeys([...boundHotkeys, hotkey])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const removeBoundHotkey = (hotkey: Hotkey) => {
|
|
68
|
+
setBoundHotkeys(boundHotkeys.filter(h => h.keys !== hotkey.keys))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<HotkeysContext.Provider value={{activeScopes: internalActiveScopes, hotkeys: boundHotkeys, activateScope, deactivateScope, toggleScope}}>
|
|
73
|
+
<BoundHotkeysProxyProviderProvider addHotkey={addBoundHotkey} removeHotkey={removeBoundHotkey}>
|
|
74
|
+
{children}
|
|
75
|
+
</BoundHotkeysProxyProviderProvider>
|
|
76
|
+
</HotkeysContext.Provider>
|
|
77
|
+
)
|
|
78
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import useHotkeys from './useHotkeys'
|
|
2
|
+
import type { Options } from './types'
|
|
3
|
+
import { HotkeysProvider, useHotkeysContext } from './HotkeysProvider'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export {
|
|
6
|
+
useHotkeys,
|
|
7
|
+
useHotkeysContext,
|
|
8
|
+
HotkeysProvider,
|
|
9
|
+
Options,
|
|
10
|
+
}
|