@rocketc/react-use-shortcuts 0.0.1-alpha.10

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 heychenfq
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.
package/README.md ADDED
@@ -0,0 +1,596 @@
1
+ # @rocketc/react-use-shortcuts
2
+
3
+ ---
4
+
5
+ Full React shortcut solution built on top of [`@rocketc/shortcuts`](../shortcuts/README.md).
6
+
7
+ ## Features
8
+
9
+ - **React Hooks**: Use `useShortcut` hook to access shortcut APIs
10
+ - **Context Provider**: `ReactShortcutProvider` for React context integration
11
+ - **Strict/Loose mode**: Support both strict and loose matching modes
12
+ - **Page scoped register**: Register shortcuts scoped to specific elements
13
+ - **Dynamic register**: Register and unregister shortcuts at runtime
14
+ - **Dynamic enable/disable**: Enable or disable registered shortcuts dynamically
15
+ - **Flexible key combinations**: Support complex modifier and normal key combinations
16
+ - **Use modern browser API**: Uses modern browser APIs (`KeyboardEvent.code`)
17
+ - **Full TypeScript support**: Complete type definitions included
18
+ - **Shortcut validation**: Built-in validation for accelerator strings
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # npm
24
+ npm install @rocketc/react-use-shortcuts
25
+
26
+ # yarn
27
+ yarn add @rocketc/react-use-shortcuts
28
+
29
+ # pnpm
30
+ pnpm add @rocketc/react-use-shortcuts
31
+ ```
32
+
33
+ **Note**: This package depends on [`@rocketc/shortcuts`](../shortcuts/README.md) for core functionality. It will be installed automatically.
34
+
35
+ ## Supported Keys
36
+
37
+ See the [Supported Keys section](../shortcuts/README.md#supported-keys) in `@rocketc/shortcuts` documentation.
38
+
39
+ ## Quick Start
40
+
41
+ ```tsx
42
+ import React, { useEffect } from 'react';
43
+ import {
44
+ ReactShortcutProvider,
45
+ useShortcut,
46
+ } from '@rocketc/react-use-shortcuts';
47
+
48
+ function App() {
49
+ return (
50
+ <ReactShortcutProvider>
51
+ <Main />
52
+ </ReactShortcutProvider>
53
+ );
54
+ }
55
+
56
+ function Main() {
57
+ const { registerShortcut, unregisterShortcut } = useShortcut();
58
+
59
+ useEffect(() => {
60
+ registerShortcut('Ctrl+a', (event) => {
61
+ console.log('Ctrl+A pressed!');
62
+ event.preventDefault();
63
+ });
64
+
65
+ return () => {
66
+ unregisterShortcut('Ctrl+a');
67
+ };
68
+ }, []);
69
+
70
+ return <h1>Hello World!</h1>;
71
+ }
72
+ ```
73
+
74
+ ## Examples
75
+
76
+ ### 1. Register Single Key Shortcut
77
+
78
+ ```tsx
79
+ import React, { useEffect } from 'react';
80
+ import {
81
+ ReactShortcutProvider,
82
+ useShortcut,
83
+ } from '@rocketc/react-use-shortcuts';
84
+
85
+ function App() {
86
+ return (
87
+ <ReactShortcutProvider>
88
+ <Main />
89
+ </ReactShortcutProvider>
90
+ );
91
+ }
92
+
93
+ function Main() {
94
+ const { registerShortcut, unregisterShortcut } = useShortcut();
95
+
96
+ useEffect(() => {
97
+ registerShortcut('a', (event) => {
98
+ event.preventDefault();
99
+ console.log('You pressed A');
100
+ });
101
+ return () => {
102
+ unregisterShortcut('a');
103
+ };
104
+ }, []);
105
+
106
+ return <h1>Hello World!</h1>;
107
+ }
108
+ ```
109
+
110
+ ### 2. Register Shortcut with Modifiers
111
+
112
+ ```tsx
113
+ import React, { useEffect } from 'react';
114
+ import {
115
+ ReactShortcutProvider,
116
+ useShortcut,
117
+ } from '@rocketc/react-use-shortcuts';
118
+
119
+ function App() {
120
+ return (
121
+ <ReactShortcutProvider>
122
+ <Main />
123
+ </ReactShortcutProvider>
124
+ );
125
+ }
126
+
127
+ function Main() {
128
+ const { registerShortcut, unregisterShortcut } = useShortcut();
129
+
130
+ useEffect(() => {
131
+ registerShortcut('Ctrl+a', (event) => {
132
+ console.log('You pressed Ctrl and A');
133
+ });
134
+ return () => {
135
+ unregisterShortcut('Ctrl+a');
136
+ };
137
+ }, []);
138
+
139
+ return <h1>Hello World!</h1>;
140
+ }
141
+ ```
142
+
143
+ ### 3. Register Scoped Shortcut
144
+
145
+ ```tsx
146
+ import React, { useEffect, useRef } from 'react';
147
+ import {
148
+ ReactShortcutProvider,
149
+ useShortcut,
150
+ } from '@rocketc/react-use-shortcuts';
151
+
152
+ function App() {
153
+ return (
154
+ <div id="root">
155
+ <ReactShortcutProvider options={{ auto: false }}>
156
+ <Main />
157
+ </ReactShortcutProvider>
158
+ <ReactShortcutProvider options={{ auto: false }}>
159
+ <Main />
160
+ <Main />
161
+ </ReactShortcutProvider>
162
+ </div>
163
+ );
164
+ }
165
+
166
+ function Main() {
167
+ const { registerShortcut, unregisterShortcut, attachElement } = useShortcut();
168
+
169
+ const root = useRef<HTMLDivElement>(null);
170
+
171
+ useEffect(() => {
172
+ if (root.current) {
173
+ return attachElement(root.current);
174
+ }
175
+ }, []);
176
+
177
+ useEffect(() => {
178
+ registerShortcut('Ctrl+a', (event) => {
179
+ console.log('You pressed Ctrl and A');
180
+ });
181
+ return () => {
182
+ unregisterShortcut('Ctrl+a');
183
+ };
184
+ }, []);
185
+
186
+ return (
187
+ <h1 ref={root} tabIndex={-1}>
188
+ Hello World!
189
+ </h1>
190
+ );
191
+ }
192
+ ```
193
+
194
+ ‼️ **Important**: Set element `tabIndex` property to `-1` to make the element focusable. Scoped shortcuts will not work without this.
195
+
196
+ ### 4. Loose Mode
197
+
198
+ `@rocketc/react-use-shortcuts` works in strict mode by default (`strict: true`). Set `strict: false` to enable loose mode. This only affects the `getCurrentKeyPressed` API.
199
+
200
+ ```tsx
201
+ import React, { useEffect } from 'react';
202
+ import {
203
+ ReactShortcutProvider,
204
+ useShortcut,
205
+ } from '@rocketc/react-use-shortcuts';
206
+
207
+ function App() {
208
+ return (
209
+ <ReactShortcutProvider options={{ strict: false }}>
210
+ <Main />
211
+ </ReactShortcutProvider>
212
+ );
213
+ }
214
+
215
+ function Main() {
216
+ const { onKeyPressedChanged, getCurrentKeyPressed } = useShortcut();
217
+
218
+ useEffect(() => {
219
+ return onKeyPressedChanged((event) => {
220
+ // If you pressed ControlLeft and A:
221
+ // - strict mode: prints 'ControlLeft+A'
222
+ // - loose mode: prints 'Ctrl+A'
223
+ console.log(getCurrentKeyPressed());
224
+ // event.detail indicates the event type: 'keydown' or 'keyup'
225
+ console.log('Event type:', event.detail);
226
+ });
227
+ }, []);
228
+
229
+ return <h1>Hello World!</h1>;
230
+ }
231
+ ```
232
+
233
+ ### 5. Dynamic Enable/Disable Shortcut
234
+
235
+ ```tsx
236
+ import React, { useEffect, useCallback, useState } from 'react';
237
+ import {
238
+ ReactShortcutProvider,
239
+ useShortcut,
240
+ } from '@rocketc/react-use-shortcuts';
241
+
242
+ function App() {
243
+ return (
244
+ <ReactShortcutProvider>
245
+ <Main />
246
+ </ReactShortcutProvider>
247
+ );
248
+ }
249
+
250
+ function Main() {
251
+ const {
252
+ registerShortcut,
253
+ unregisterShortcut,
254
+ enableShortcut,
255
+ disableShortcut,
256
+ } = useShortcut();
257
+ const [enable, setEnable] = useState<boolean>(true);
258
+
259
+ const handleClick = useCallback(() => {
260
+ setEnable((prev) => {
261
+ if (prev) {
262
+ disableShortcut('Ctrl+a');
263
+ } else {
264
+ enableShortcut('Ctrl+a');
265
+ }
266
+ return !prev;
267
+ });
268
+ }, []);
269
+
270
+ useEffect(() => {
271
+ registerShortcut('Ctrl+a', (event) => {
272
+ console.log('You pressed Control and A');
273
+ });
274
+ return () => {
275
+ unregisterShortcut('Ctrl+a');
276
+ };
277
+ }, []);
278
+
279
+ return <button onClick={handleClick}>{enable ? 'disable' : 'enable'}</button>;
280
+ }
281
+ ```
282
+
283
+ ### 6. Multiple Callbacks for the Same Shortcut
284
+
285
+ You can register multiple callbacks for the same accelerator and manage them individually:
286
+
287
+ ```tsx
288
+ import React, { useEffect, useCallback } from 'react';
289
+ import {
290
+ ReactShortcutProvider,
291
+ useShortcut,
292
+ } from '@rocketc/react-use-shortcuts';
293
+
294
+ function App() {
295
+ return (
296
+ <ReactShortcutProvider>
297
+ <Main />
298
+ </ReactShortcutProvider>
299
+ );
300
+ }
301
+
302
+ function Main() {
303
+ const {
304
+ registerShortcut,
305
+ unregisterShortcut,
306
+ enableShortcut,
307
+ disableShortcut,
308
+ } = useShortcut();
309
+
310
+ useEffect(() => {
311
+ const handler1 = () => console.log('Handler 1');
312
+ const handler2 = () => console.log('Handler 2');
313
+
314
+ // Register multiple handlers for the same shortcut
315
+ registerShortcut('Ctrl+a', handler1);
316
+ registerShortcut('Ctrl+a', handler2);
317
+
318
+ // Disable only handler1
319
+ disableShortcut('Ctrl+a', handler1);
320
+
321
+ // Enable handler1 again
322
+ enableShortcut('Ctrl+a', handler1);
323
+
324
+ // Unregister only handler1
325
+ unregisterShortcut('Ctrl+a', handler1);
326
+
327
+ return () => {
328
+ // Unregister all handlers for Ctrl+a
329
+ unregisterShortcut('Ctrl+a');
330
+ };
331
+ }, []);
332
+
333
+ return <h1>Hello World!</h1>;
334
+ }
335
+ ```
336
+
337
+ ### 7. Custom Event Filter
338
+
339
+ The default filter automatically filters out:
340
+
341
+ - `event.repeat` (key repeat events)
342
+ - `event.isComposing` (IME composition events, e.g., when typing Chinese/Japanese/Korean)
343
+ - Events from `INPUT`, `TEXTAREA`, `SELECT` elements
344
+ - Events from `contentEditable` elements
345
+
346
+ You can provide a custom filter to override this behavior:
347
+
348
+ ```tsx
349
+ import React, { useEffect } from 'react';
350
+ import {
351
+ ReactShortcutProvider,
352
+ useShortcut,
353
+ } from '@rocketc/react-use-shortcuts';
354
+
355
+ function App() {
356
+ return (
357
+ <ReactShortcutProvider
358
+ options={{
359
+ filter: (event) => (event.target as HTMLElement)?.tagName !== 'INPUT',
360
+ }}
361
+ >
362
+ <Main />
363
+ </ReactShortcutProvider>
364
+ );
365
+ }
366
+
367
+ function Main() {
368
+ const { registerShortcut, unregisterShortcut } = useShortcut();
369
+
370
+ useEffect(() => {
371
+ registerShortcut('Ctrl+a', (event) => {
372
+ console.log('You pressed Control and A');
373
+ });
374
+ return () => {
375
+ unregisterShortcut('Ctrl+a');
376
+ };
377
+ }, []);
378
+
379
+ return (
380
+ <div>
381
+ <input />
382
+ </div>
383
+ );
384
+ }
385
+ ```
386
+
387
+ ### 8. Custom Key Aliases
388
+
389
+ You can define custom key name aliases to use in your shortcuts:
390
+
391
+ ```tsx
392
+ import React, { useEffect } from 'react';
393
+ import {
394
+ ReactShortcutProvider,
395
+ useShortcut,
396
+ } from '@rocketc/react-use-shortcuts';
397
+
398
+ function App() {
399
+ return (
400
+ <ReactShortcutProvider
401
+ options={{
402
+ alias: {
403
+ Save: 'Ctrl',
404
+ I: 'i',
405
+ },
406
+ }}
407
+ >
408
+ <Main />
409
+ </ReactShortcutProvider>
410
+ );
411
+ }
412
+
413
+ function Main() {
414
+ const { registerShortcut, unregisterShortcut } = useShortcut();
415
+
416
+ useEffect(() => {
417
+ // Use the custom alias 'Save' instead of 'Ctrl'
418
+ registerShortcut('Save+s', () => {
419
+ console.log('Save shortcut triggered');
420
+ });
421
+
422
+ return () => {
423
+ unregisterShortcut('Save+s');
424
+ };
425
+ }, []);
426
+
427
+ return <h1>Hello World!</h1>;
428
+ }
429
+ ```
430
+
431
+ ### 9. Custom Debug Function
432
+
433
+ You can provide a custom debug function instead of using the default debug logger:
434
+
435
+ ```tsx
436
+ import React, { useEffect } from 'react';
437
+ import {
438
+ ReactShortcutProvider,
439
+ useShortcut,
440
+ } from '@rocketc/react-use-shortcuts';
441
+
442
+ function App() {
443
+ return (
444
+ <ReactShortcutProvider
445
+ options={{
446
+ debug: (...args) => {
447
+ console.log('[Shortcut Debug]', ...args);
448
+ },
449
+ }}
450
+ >
451
+ <Main />
452
+ </ReactShortcutProvider>
453
+ );
454
+ }
455
+
456
+ function Main() {
457
+ const { registerShortcut, unregisterShortcut } = useShortcut();
458
+
459
+ useEffect(() => {
460
+ registerShortcut('Ctrl+a', () => {
461
+ console.log('You pressed Control and A');
462
+ });
463
+ return () => {
464
+ unregisterShortcut('Ctrl+a');
465
+ };
466
+ }, []);
467
+
468
+ return <h1>Hello World!</h1>;
469
+ }
470
+ ```
471
+
472
+ ## API Reference
473
+
474
+ ### `ReactShortcutProvider`
475
+
476
+ React Context Provider component. Wrap your app or component tree with this provider.
477
+
478
+ **Props:**
479
+
480
+ ```typescript
481
+ interface ReactShortcutProviderProps {
482
+ options?: ReactShortcutOptions;
483
+ children?: ReactNode;
484
+ }
485
+
486
+ interface ReactShortcutOptions {
487
+ strict?: boolean; // Default: false (strict mode by default)
488
+ debug?: boolean | ((...args: any[]) => void); // Default: false
489
+ auto?: boolean; // Default: true
490
+ filter?: Filter;
491
+ alias?: Record<string, string>;
492
+ separator?: string; // Default: '+'
493
+ }
494
+ ```
495
+
496
+ ### `useShortcut`
497
+
498
+ React Hook to access shortcut APIs.
499
+
500
+ **Returns:**
501
+
502
+ ```typescript
503
+ interface ReactShortcutContextValue {
504
+ registerShortcut(
505
+ accelerator: Accelerator,
506
+ callback: KeyboardEventListener,
507
+ ): boolean;
508
+ unregisterShortcut(
509
+ accelerator: Accelerator,
510
+ cb?: KeyboardEventListener,
511
+ ): boolean;
512
+ enableShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean;
513
+ disableShortcut(
514
+ accelerator: Accelerator,
515
+ cb?: KeyboardEventListener,
516
+ ): boolean;
517
+ isShortcutRegistered(accelerator: Accelerator): boolean;
518
+ getCurrentKeyPressed(): Accelerator;
519
+ onKeyPressedChanged(listener: KeyPressedChangedEventListener): Dispose;
520
+ attachElement(ele: Window | HTMLElement): Dispose;
521
+ getOptions(): ReactShortcutOptions;
522
+ getShortcutRegisters(accelerator?: Accelerator): Array<ShortcutRegister>;
523
+ }
524
+ ```
525
+
526
+ ### `acceleratorParser`
527
+
528
+ Utility object for parsing and validating accelerator strings. Re-exported from `@rocketc/shortcuts`.
529
+
530
+ ```tsx
531
+ import { acceleratorParser } from '@rocketc/react-use-shortcuts';
532
+
533
+ // Validate accelerator
534
+ const isValid = acceleratorParser.validate('Ctrl+a');
535
+
536
+ // Convert to loose mode
537
+ const loose = acceleratorParser.convertAcceleratorToLooseMode('ControlLeft+a');
538
+
539
+ // Parse accelerator
540
+ const parsed = acceleratorParser.parse('Ctrl+Shift+a');
541
+ console.log(parsed); // ['Ctrl', 'Shift', 'a']
542
+
543
+ // Check if key code name is supported
544
+ const isSupported = acceleratorParser.isKeyCodeNameSupported('Ctrl');
545
+ console.log(isSupported); // true
546
+
547
+ // Check if accelerators match
548
+ const isMatched = acceleratorParser.isAcceleratorMatched(
549
+ 'Ctrl+a',
550
+ 'ControlLeft+KeyA',
551
+ );
552
+ console.log(isMatched); // true
553
+
554
+ // Get default separator
555
+ const separator = acceleratorParser.defaultSeparator;
556
+ console.log(separator); // '+'
557
+ ```
558
+
559
+ For complete API documentation, see [`@rocketc/shortcuts`](../shortcuts/README.md#api-reference).
560
+
561
+ ## Shortcut Match Rules
562
+
563
+ See the [Shortcut Match Rules section](../shortcuts/README.md#shortcut-match-rules) in `@rocketc/shortcuts` documentation.
564
+
565
+ ## Browser Compatibility
566
+
567
+ - Chrome ≥ 48
568
+ - Firefox ≥ 38
569
+ - Safari ≥ 10.1
570
+ - Edge ≥ 79
571
+
572
+ ## Core Library
573
+
574
+ This package is built on top of [`@rocketc/shortcuts`](../shortcuts/README.md), which provides the core shortcut functionality without React dependencies. If you need to use shortcuts in a non-React environment, use `@rocketc/shortcuts` directly.
575
+
576
+ ## Alternatives
577
+
578
+ - [react-hotkeys-hook](https://www.npmjs.com/package/react-hotkeys-hook)
579
+ - [react-hot-keys](https://www.npmjs.com/package/react-hot-keys)
580
+
581
+ ## Comparisons
582
+
583
+ | **Features** | **@rocketc/react-use-shortcuts** | **react-hotkeys-hook** | **react-hot-keys** |
584
+ | ------------------------------------------ | -------------------------------- | ---------------------- | ------------------ |
585
+ | Dynamic register | ✅ | ❌ | ❌ |
586
+ | Page scoped register | ✅ | ✅ | ❌ |
587
+ | Strict/Loose mode | ✅ | ❌ | ❌ |
588
+ | Dynamic enable/disable shortcut registered | ✅ | ✅ | ❌ |
589
+ | Normal key combinations | ❌ | ✅ | ✅ |
590
+ | Namespace | ❌ | ❌ | ✅ |
591
+ | Shortcuts validation | ✅ | ❌ | ❌ |
592
+ | Used React ≤ 18.0.0 | ❌ | ❌ | ✅ |
593
+
594
+ ## License
595
+
596
+ Distributed under the MIT License. See `LICENSE` for more information.
@@ -0,0 +1,46 @@
1
+ import { Accelerator } from '@rocketc/shortcuts';
2
+ import { default as default_2 } from 'react';
3
+ import { Dispose } from '@rocketc/shortcuts';
4
+ import { Filter } from '@rocketc/shortcuts';
5
+ import { KeyboardEventListener } from '@rocketc/shortcuts';
6
+ import { KeyPressedChangedEventListener } from '@rocketc/shortcuts';
7
+ import { PropsWithChildren } from 'react';
8
+ import { ShortcutRegister } from '@rocketc/shortcuts';
9
+
10
+ declare interface ReactShortcutContextValue {
11
+ registerShortcut(accelerator: Accelerator, callback: KeyboardEventListener): boolean;
12
+ unregisterShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean;
13
+ enableShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean;
14
+ disableShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean;
15
+ isShortcutRegistered(accelerator: Accelerator): boolean;
16
+ getCurrentKeyPressed(): Accelerator;
17
+ onKeyPressedChanged(listener: KeyPressedChangedEventListener): Dispose;
18
+ attachElement(ele: Window | HTMLElement): Dispose;
19
+ getOptions(): ReactShortcutOptions;
20
+ getShortcutRegisters(accelerator?: Accelerator): Array<ShortcutRegister>;
21
+ }
22
+
23
+ export declare interface ReactShortcutOptions {
24
+ separator?: string;
25
+ strict?: boolean;
26
+ debug?: boolean | ((...args: any[]) => void);
27
+ auto?: boolean;
28
+ filter?: Filter;
29
+ alias?: Record<string, string>;
30
+ }
31
+
32
+ export declare const ReactShortcutProvider: {
33
+ (props: ReactShortcutProviderProps): default_2.JSX.Element;
34
+ displayName: string;
35
+ };
36
+
37
+ export declare type ReactShortcutProviderProps = PropsWithChildren<{
38
+ options?: ReactShortcutOptions;
39
+ }>;
40
+
41
+ export declare const useShortcut: () => ReactShortcutContextValue;
42
+
43
+
44
+ export * from "@rocketc/shortcuts";
45
+
46
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,145 @@
1
+ import React, { createContext, useRef, useMemo, useCallback, useEffect, useContext } from "react";
2
+ import { ShortcutRegistry } from "@rocketc/shortcuts";
3
+ export * from "@rocketc/shortcuts";
4
+ const defaultShortcutRegistry = new ShortcutRegistry({
5
+ strict: false,
6
+ // Default to loose mode
7
+ debug: false
8
+ });
9
+ let autoWindowDispose = null;
10
+ if (typeof window !== "undefined") {
11
+ autoWindowDispose = defaultShortcutRegistry.attachElement(window);
12
+ }
13
+ const createDefaultContextValue = () => {
14
+ return {
15
+ registerShortcut: defaultShortcutRegistry.registerShortcut.bind(
16
+ defaultShortcutRegistry
17
+ ),
18
+ unregisterShortcut: defaultShortcutRegistry.unregisterShortcut.bind(
19
+ defaultShortcutRegistry
20
+ ),
21
+ enableShortcut: defaultShortcutRegistry.enableShortcut.bind(
22
+ defaultShortcutRegistry
23
+ ),
24
+ disableShortcut: defaultShortcutRegistry.disableShortcut.bind(
25
+ defaultShortcutRegistry
26
+ ),
27
+ isShortcutRegistered: defaultShortcutRegistry.isShortcutRegistered.bind(
28
+ defaultShortcutRegistry
29
+ ),
30
+ getCurrentKeyPressed: defaultShortcutRegistry.getCurrentKeyPressed.bind(
31
+ defaultShortcutRegistry
32
+ ),
33
+ onKeyPressedChanged: defaultShortcutRegistry.onKeyPressedChanged.bind(
34
+ defaultShortcutRegistry
35
+ ),
36
+ attachElement: (ele) => {
37
+ return defaultShortcutRegistry.attachElement(ele);
38
+ },
39
+ getOptions: () => {
40
+ const options = defaultShortcutRegistry.getOptions();
41
+ return {
42
+ strict: options.strict ?? false,
43
+ debug: options.debug ?? false,
44
+ auto: autoWindowDispose !== null,
45
+ filter: options.filter,
46
+ alias: options.alias,
47
+ separator: options.separator
48
+ };
49
+ },
50
+ getShortcutRegisters: defaultShortcutRegistry.getShortcutRegisters.bind(
51
+ defaultShortcutRegistry
52
+ )
53
+ };
54
+ };
55
+ const ReactShortcutContext = createContext(
56
+ createDefaultContextValue()
57
+ );
58
+ const ReactShortcutProvider = function ReactShortcutProvider2(props) {
59
+ const {
60
+ children,
61
+ options: {
62
+ strict = false,
63
+ debug = false,
64
+ auto = true,
65
+ filter,
66
+ separator,
67
+ alias
68
+ } = {}
69
+ } = props;
70
+ const autoRef = useRef(auto);
71
+ autoRef.current = auto;
72
+ const shortcutRegistry = useRef(
73
+ new ShortcutRegistry({ strict, debug, filter, alias })
74
+ );
75
+ const staticsContextValue = useMemo(() => {
76
+ return {
77
+ getOptions: shortcutRegistry.current.getOptions.bind(
78
+ shortcutRegistry.current
79
+ ),
80
+ getShortcutRegisters: shortcutRegistry.current.getShortcutRegisters.bind(
81
+ shortcutRegistry.current
82
+ ),
83
+ registerShortcut: shortcutRegistry.current.registerShortcut.bind(
84
+ shortcutRegistry.current
85
+ ),
86
+ unregisterShortcut: shortcutRegistry.current.unregisterShortcut.bind(
87
+ shortcutRegistry.current
88
+ ),
89
+ enableShortcut: shortcutRegistry.current.enableShortcut.bind(
90
+ shortcutRegistry.current
91
+ ),
92
+ disableShortcut: shortcutRegistry.current.disableShortcut.bind(
93
+ shortcutRegistry.current
94
+ ),
95
+ isShortcutRegistered: shortcutRegistry.current.isShortcutRegistered.bind(
96
+ shortcutRegistry.current
97
+ ),
98
+ getCurrentKeyPressed: shortcutRegistry.current.getCurrentKeyPressed.bind(
99
+ shortcutRegistry.current
100
+ ),
101
+ onKeyPressedChanged: shortcutRegistry.current.onKeyPressedChanged.bind(
102
+ shortcutRegistry.current
103
+ )
104
+ };
105
+ }, []);
106
+ const attachElement = useCallback((ele) => {
107
+ if (autoRef.current) {
108
+ throw new Error("attachElement is not supported when auto is true");
109
+ }
110
+ return shortcutRegistry.current.attachElement(ele);
111
+ }, []);
112
+ useEffect(() => {
113
+ if (auto) {
114
+ return shortcutRegistry.current.attachElement(window);
115
+ }
116
+ }, [auto]);
117
+ useEffect(() => {
118
+ shortcutRegistry.current.setOptions({
119
+ strict,
120
+ debug,
121
+ filter,
122
+ separator,
123
+ alias
124
+ });
125
+ }, [strict, debug, filter, separator, alias]);
126
+ return /* @__PURE__ */ React.createElement(
127
+ ReactShortcutContext.Provider,
128
+ {
129
+ value: {
130
+ ...staticsContextValue,
131
+ attachElement
132
+ }
133
+ },
134
+ children
135
+ );
136
+ };
137
+ ReactShortcutProvider.displayName = "ReactShortcutProvider";
138
+ const useShortcut = () => {
139
+ return useContext(ReactShortcutContext);
140
+ };
141
+ export {
142
+ ReactShortcutProvider,
143
+ useShortcut
144
+ };
145
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/shortcut-context.ts","../src/shortcut-provider.tsx","../src/use-shortcut.ts"],"sourcesContent":["import { createContext } from 'react';\nimport { ShortcutRegistry } from '@rocketc/shortcuts';\nimport type { ReactShortcutOptions } from './shortcut-provider';\nimport type {\n Accelerator,\n ShortcutRegister,\n KeyboardEventListener,\n Dispose,\n KeyPressedChangedEventListener,\n} from '@rocketc/shortcuts';\n\nexport interface ReactShortcutContextValue {\n registerShortcut(\n accelerator: Accelerator,\n callback: KeyboardEventListener,\n ): boolean;\n unregisterShortcut(\n accelerator: Accelerator,\n cb?: KeyboardEventListener,\n ): boolean;\n enableShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean;\n disableShortcut(\n accelerator: Accelerator,\n cb?: KeyboardEventListener,\n ): boolean;\n isShortcutRegistered(accelerator: Accelerator): boolean;\n getCurrentKeyPressed(): Accelerator;\n onKeyPressedChanged(listener: KeyPressedChangedEventListener): Dispose;\n attachElement(ele: Window | HTMLElement): Dispose;\n getOptions(): ReactShortcutOptions;\n getShortcutRegisters(accelerator?: Accelerator): Array<ShortcutRegister>;\n}\n\n// Create a default ShortcutRegistry instance for use without Provider\nconst defaultShortcutRegistry = new ShortcutRegistry({\n strict: false, // Default to loose mode\n debug: false,\n});\n\n// Track the dispose function for auto-attached window\n// Since attachElement returns different dispose functions each time,\n// we only track the one created by auto option\nlet autoWindowDispose: Dispose | null = null;\n\n// Auto-attach to window by default\nif (typeof window !== 'undefined') {\n autoWindowDispose = defaultShortcutRegistry.attachElement(window);\n}\n\n// Create default context value\nconst createDefaultContextValue = (): ReactShortcutContextValue => {\n return {\n registerShortcut: defaultShortcutRegistry.registerShortcut.bind(\n defaultShortcutRegistry,\n ),\n unregisterShortcut: defaultShortcutRegistry.unregisterShortcut.bind(\n defaultShortcutRegistry,\n ),\n enableShortcut: defaultShortcutRegistry.enableShortcut.bind(\n defaultShortcutRegistry,\n ),\n disableShortcut: defaultShortcutRegistry.disableShortcut.bind(\n defaultShortcutRegistry,\n ),\n isShortcutRegistered: defaultShortcutRegistry.isShortcutRegistered.bind(\n defaultShortcutRegistry,\n ),\n getCurrentKeyPressed: defaultShortcutRegistry.getCurrentKeyPressed.bind(\n defaultShortcutRegistry,\n ),\n onKeyPressedChanged: defaultShortcutRegistry.onKeyPressedChanged.bind(\n defaultShortcutRegistry,\n ),\n attachElement: (ele: Window | HTMLElement) => {\n // Simply delegate to the registry\n // The registry handles reference counting internally\n return defaultShortcutRegistry.attachElement(ele);\n },\n getOptions: () => {\n const options = defaultShortcutRegistry.getOptions();\n return {\n strict: options.strict ?? false,\n debug: options.debug ?? false,\n auto: autoWindowDispose !== null,\n filter: options.filter,\n alias: options.alias,\n separator: options.separator,\n };\n },\n getShortcutRegisters: defaultShortcutRegistry.getShortcutRegisters.bind(\n defaultShortcutRegistry,\n ),\n };\n};\n\nexport const ReactShortcutContext = createContext<ReactShortcutContextValue>(\n createDefaultContextValue(),\n);\n","/* eslint-disable react-hooks/refs */\nimport React, {\n useEffect,\n useMemo,\n useRef,\n useCallback,\n type PropsWithChildren,\n} from 'react';\nimport {\n ReactShortcutContext,\n type ReactShortcutContextValue,\n} from './shortcut-context';\nimport { ShortcutRegistry, type Filter } from '@rocketc/shortcuts';\n\nexport interface ReactShortcutOptions {\n separator?: string;\n strict?: boolean;\n debug?: boolean | ((...args: any[]) => void);\n auto?: boolean;\n filter?: Filter;\n alias?: Record<string, string>;\n}\n\nexport type ReactShortcutProviderProps = PropsWithChildren<{\n options?: ReactShortcutOptions;\n}>;\n\nconst ReactShortcutProvider = function ReactShortcutProvider(\n props: ReactShortcutProviderProps,\n) {\n const {\n children,\n options: {\n strict = false,\n debug = false,\n auto = true,\n filter,\n separator,\n alias,\n } = {},\n } = props;\n\n const autoRef = useRef(auto);\n autoRef.current = auto;\n\n const shortcutRegistry = useRef<ShortcutRegistry>(\n new ShortcutRegistry({ strict, debug, filter, alias }),\n );\n\n const staticsContextValue = useMemo<\n Omit<ReactShortcutContextValue, 'attachElement'>\n >(() => {\n return {\n getOptions: shortcutRegistry.current.getOptions.bind(\n shortcutRegistry.current,\n ),\n getShortcutRegisters: shortcutRegistry.current.getShortcutRegisters.bind(\n shortcutRegistry.current,\n ),\n registerShortcut: shortcutRegistry.current.registerShortcut.bind(\n shortcutRegistry.current,\n ),\n unregisterShortcut: shortcutRegistry.current.unregisterShortcut.bind(\n shortcutRegistry.current,\n ),\n enableShortcut: shortcutRegistry.current.enableShortcut.bind(\n shortcutRegistry.current,\n ),\n disableShortcut: shortcutRegistry.current.disableShortcut.bind(\n shortcutRegistry.current,\n ),\n isShortcutRegistered: shortcutRegistry.current.isShortcutRegistered.bind(\n shortcutRegistry.current,\n ),\n getCurrentKeyPressed: shortcutRegistry.current.getCurrentKeyPressed.bind(\n shortcutRegistry.current,\n ),\n onKeyPressedChanged: shortcutRegistry.current.onKeyPressedChanged.bind(\n shortcutRegistry.current,\n ),\n };\n }, []);\n\n const attachElement = useCallback((ele: Window | HTMLElement) => {\n if (autoRef.current) {\n throw new Error('attachElement is not supported when auto is true');\n }\n return shortcutRegistry.current.attachElement(ele);\n }, []);\n\n useEffect(() => {\n if (auto) {\n return shortcutRegistry.current.attachElement(window);\n }\n }, [auto]);\n\n useEffect(() => {\n shortcutRegistry.current.setOptions({\n strict,\n debug,\n filter,\n separator,\n alias,\n });\n }, [strict, debug, filter, separator, alias]);\n\n return (\n <ReactShortcutContext.Provider\n value={{\n ...staticsContextValue,\n attachElement,\n }}\n >\n {children}\n </ReactShortcutContext.Provider>\n );\n};\n\nReactShortcutProvider.displayName = 'ReactShortcutProvider';\n\nexport default ReactShortcutProvider;\n","import { useContext } from 'react';\nimport {\n ReactShortcutContext,\n type ReactShortcutContextValue,\n} from './shortcut-context';\n\nconst useShortcut = () => {\n return useContext<ReactShortcutContextValue>(ReactShortcutContext);\n};\n\nexport default useShortcut;\n"],"names":["ReactShortcutProvider"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@rocketc/react-use-shortcuts",
3
+ "version": "0.0.1-alpha.10",
4
+ "type": "module",
5
+ "description": "a full react shortcut solution",
6
+ "files": [
7
+ "LICENSE",
8
+ "README.md",
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/chenfq95/rocketc.git",
21
+ "directory": "packages/libs/react-use-shortcuts"
22
+ },
23
+ "sideEffects": false,
24
+ "keywords": [
25
+ "react",
26
+ "shortcut"
27
+ ],
28
+ "author": "chenfq95 <chenfq95@foxmail.com>",
29
+ "license": "MIT",
30
+ "peerDependencies": {
31
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
32
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@rocketc/shortcuts": "0.0.1-alpha.4"
36
+ }
37
+ }