@remcostoeten/use-shortcut 2.0.0 → 2.0.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/CHANGELOG.md +10 -1
- package/README.md +38 -0
- package/dist/index.d.mts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +148 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
- package/src/__tests__/features.test.ts +0 -164
- package/src/builder.ts +0 -179
- package/src/constants.ts +0 -160
- package/src/formatter.ts +0 -97
- package/src/hook.ts +0 -332
- package/src/index.ts +0 -48
- package/src/parser.ts +0 -122
- package/src/runtime/binding.ts +0 -136
- package/src/runtime/conflicts.ts +0 -43
- package/src/runtime/debug.ts +0 -6
- package/src/runtime/guards.ts +0 -82
- package/src/runtime/keys.ts +0 -63
- package/src/runtime/listener.ts +0 -142
- package/src/runtime/recording.ts +0 -39
- package/src/runtime/types.ts +0 -48
- package/src/types.ts +0 -267
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [2.0.1] - 2026-03-11
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Re-exported `useShortcutMap`, `registerShortcutMap`, `createShortcutGroup`, and `useShortcutGroup` from the public package entrypoint.
|
|
13
|
+
- Re-exported shortcut map and shortcut group types from the public package entrypoint.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Updated the README to document the public shortcut map API.
|
|
9
18
|
|
|
10
19
|
## [2.0.0] - 2026-03-04
|
|
11
20
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ WIP keyboard shortcut library for React with a chainable API.
|
|
|
10
10
|
## Implemented Features
|
|
11
11
|
|
|
12
12
|
- Chainable shortcut builder: `$.mod.key("k").on(handler)`
|
|
13
|
+
- Bulk shortcut maps: `useShortcutMap()` and `registerShortcutMap()`
|
|
13
14
|
- Modifier support: `ctrl`, `shift`, `alt`, `cmd`, `mod`
|
|
14
15
|
- Sequence support: `$.key("g").then("d")`
|
|
15
16
|
- Scope-aware shortcuts:
|
|
@@ -22,14 +23,51 @@ WIP keyboard shortcut library for React with a chainable API.
|
|
|
22
23
|
- Global guard/filter support via `eventFilter`
|
|
23
24
|
- React entry point:
|
|
24
25
|
- `useShortcut`
|
|
26
|
+
- `useShortcutMap`
|
|
27
|
+
- `useShortcutGroup`
|
|
25
28
|
|
|
26
29
|
## API Intention (Consumer-Facing)
|
|
27
30
|
|
|
28
31
|
- `useShortcut(options?)`
|
|
29
32
|
- Main React hook. Use this for the chainable API (`$.mod.key("s").on(...)`).
|
|
33
|
+
- `useShortcutMap(shortcutMap, options?)`
|
|
34
|
+
- React-safe bulk registration for render paths where a declarative object is cleaner than multiple `.on()` calls.
|
|
35
|
+
- `registerShortcutMap(builder, shortcutMap)`
|
|
36
|
+
- Imperative bulk registration helper when you already have a `useShortcut()` builder.
|
|
30
37
|
|
|
31
38
|
Internal helpers follow underscore naming (for example `_createShortcutBuilder`, `_canonicalizeParsed`) and are not re-exported from `src/index.ts`.
|
|
32
39
|
|
|
40
|
+
## Shortcut Map Example
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { useShortcutMap } from "@remcostoeten/use-shortcut"
|
|
44
|
+
|
|
45
|
+
function App() {
|
|
46
|
+
useShortcutMap(
|
|
47
|
+
{
|
|
48
|
+
openPalette: {
|
|
49
|
+
keys: "mod+k",
|
|
50
|
+
handler: () => openPalette(),
|
|
51
|
+
options: { preventDefault: true },
|
|
52
|
+
},
|
|
53
|
+
closePalette: {
|
|
54
|
+
keys: "escape",
|
|
55
|
+
handler: () => closePalette(),
|
|
56
|
+
},
|
|
57
|
+
toggleSidebar: {
|
|
58
|
+
keys: "g then s",
|
|
59
|
+
handler: () => toggleSidebar(),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{ ignoreInputs: false },
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return <div>Shortcuts ready</div>
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If you already have a builder from `useShortcut()`, you can bulk register with `registerShortcutMap($, shortcutMap)` and unbind the returned handles on cleanup.
|
|
70
|
+
|
|
33
71
|
## Architecture Notes
|
|
34
72
|
|
|
35
73
|
- Core runtime lives in `src/builder.ts`
|
package/dist/index.d.mts
CHANGED
|
@@ -264,6 +264,26 @@ type UseShortcutOptions = {
|
|
|
264
264
|
/** Global event filter; return false to skip all shortcuts for the event */
|
|
265
265
|
eventFilter?: (event: KeyboardEvent) => boolean;
|
|
266
266
|
};
|
|
267
|
+
/** Single shortcut-map entry used by `registerShortcutMap` and `useShortcutMap`. */
|
|
268
|
+
type ShortcutMapEntry = {
|
|
269
|
+
keys: string | string[];
|
|
270
|
+
handler: ShortcutHandler;
|
|
271
|
+
options?: HandlerOptions;
|
|
272
|
+
};
|
|
273
|
+
/** Bulk registration shape mapping action ids to key+handler definitions. */
|
|
274
|
+
type ShortcutMap = Record<string, ShortcutMapEntry>;
|
|
275
|
+
/** Return type for map registrations, keyed by the same ids as the source map. */
|
|
276
|
+
type ShortcutMapResult<T extends ShortcutMap = ShortcutMap> = {
|
|
277
|
+
[K in keyof T]: ShortcutResult;
|
|
278
|
+
};
|
|
279
|
+
/** Imperative grouping controller for binding/unbinding many shortcut registrations together. */
|
|
280
|
+
type ShortcutGroup = {
|
|
281
|
+
add: (...results: ShortcutResult[]) => void;
|
|
282
|
+
addMany: (results: ShortcutResult[] | Record<string, ShortcutResult>) => void;
|
|
283
|
+
unbindAll: () => void;
|
|
284
|
+
clear: () => void;
|
|
285
|
+
getResults: () => ShortcutResult[];
|
|
286
|
+
};
|
|
267
287
|
|
|
268
288
|
/**
|
|
269
289
|
* Parse a shortcut string into its components
|
|
@@ -317,6 +337,23 @@ declare function matchesAnyShortcut(event: KeyboardEvent, parsedShortcuts: Parse
|
|
|
317
337
|
*/
|
|
318
338
|
declare function formatShortcut(shortcut: string, platform?: PlatformType): string;
|
|
319
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Registers an object-based shortcut map in one call and returns per-action handles.
|
|
342
|
+
*
|
|
343
|
+
* @param builder - Builder returned by `useShortcut()`
|
|
344
|
+
* @param shortcutMap - Record of action ids to key bindings, handlers, and options
|
|
345
|
+
* @returns A result map with one `ShortcutResult` per shortcut id
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* const $ = useShortcut()
|
|
350
|
+
* const results = registerShortcutMap($, {
|
|
351
|
+
* save: { keys: "mod+s", handler: onSave },
|
|
352
|
+
* nav: { keys: ["g", "d"], handler: onGoDashboard },
|
|
353
|
+
* })
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
declare function registerShortcutMap<T extends ShortcutMap>(builder: ShortcutBuilder, shortcutMap: T): ShortcutMapResult<T>;
|
|
320
357
|
/**
|
|
321
358
|
* React hook for registering chainable keyboard shortcuts
|
|
322
359
|
*
|
|
@@ -333,5 +370,46 @@ declare function formatShortcut(shortcut: string, platform?: PlatformType): stri
|
|
|
333
370
|
* ```
|
|
334
371
|
*/
|
|
335
372
|
declare function useShortcut(options?: UseShortcutOptions): ShortcutBuilder;
|
|
373
|
+
/**
|
|
374
|
+
* React hook that registers a shortcut map and automatically unbinds on cleanup.
|
|
375
|
+
*
|
|
376
|
+
* @param shortcutMap - Record of action ids to key bindings, handlers, and options
|
|
377
|
+
* @param options - Same options as `useShortcut()`
|
|
378
|
+
* @returns A map of `ShortcutResult` keyed by your shortcut ids
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* const mapResults = useShortcutMap({
|
|
383
|
+
* save: { keys: "mod+s", handler: onSave },
|
|
384
|
+
* close: { keys: "escape", handler: onClose },
|
|
385
|
+
* })
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
declare function useShortcutMap<T extends ShortcutMap>(shortcutMap: T, options?: UseShortcutOptions): ShortcutMapResult<T>;
|
|
389
|
+
/**
|
|
390
|
+
* Creates an imperative group controller for many shortcut registrations.
|
|
391
|
+
*
|
|
392
|
+
* @returns A `ShortcutGroup` that can add and unbind multiple shortcuts together
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const group = createShortcutGroup()
|
|
397
|
+
* group.add($.mod.key("s").on(onSave))
|
|
398
|
+
* group.add($.key("escape").on(onClose))
|
|
399
|
+
* group.unbindAll()
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
declare function createShortcutGroup(): ShortcutGroup;
|
|
403
|
+
/**
|
|
404
|
+
* React hook that returns a stable `ShortcutGroup` instance.
|
|
405
|
+
*
|
|
406
|
+
* @returns A memoized `ShortcutGroup` tied to the component lifecycle
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* const group = useShortcutGroup()
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
declare function useShortcutGroup(): ShortcutGroup;
|
|
336
414
|
|
|
337
|
-
export { type ActionKey, type AlphaKey, type ExceptPredicate, type ExceptPreset, type FunctionKey, type HandlerOptions, type KeyChain, ModifierAliases, type ModifierChain, ModifierDisplayOrder, ModifierDisplaySymbols, type ModifierFlags, ModifierKey, type ModifierName, type ModifierState, type NavigationKey, type NumericKey, type ParsedShortcut, Platform, type ShortcutBuilder, type ShortcutConflict, type ShortcutHandler, type ShortcutRecordingOptions, type ShortcutResult, type ShortcutScope, type SpecialKey, SpecialKeyMap, type SymbolKey, type UseShortcutOptions, detectPlatform, formatShortcut, matchesAnyShortcut, matchesShortcut, parseShortcut, parseShortcuts, useShortcut };
|
|
415
|
+
export { type ActionKey, type AlphaKey, type ExceptPredicate, type ExceptPreset, type FunctionKey, type HandlerOptions, type KeyChain, ModifierAliases, type ModifierChain, ModifierDisplayOrder, ModifierDisplaySymbols, type ModifierFlags, ModifierKey, type ModifierName, type ModifierState, type NavigationKey, type NumericKey, type ParsedShortcut, Platform, type ShortcutBuilder, type ShortcutConflict, type ShortcutGroup, type ShortcutHandler, type ShortcutMap, type ShortcutMapEntry, type ShortcutMapResult, type ShortcutRecordingOptions, type ShortcutResult, type ShortcutScope, type SpecialKey, SpecialKeyMap, type SymbolKey, type UseShortcutOptions, createShortcutGroup, detectPlatform, formatShortcut, matchesAnyShortcut, matchesShortcut, parseShortcut, parseShortcuts, registerShortcutMap, useShortcut, useShortcutGroup, useShortcutMap };
|
package/dist/index.d.ts
CHANGED
|
@@ -264,6 +264,26 @@ type UseShortcutOptions = {
|
|
|
264
264
|
/** Global event filter; return false to skip all shortcuts for the event */
|
|
265
265
|
eventFilter?: (event: KeyboardEvent) => boolean;
|
|
266
266
|
};
|
|
267
|
+
/** Single shortcut-map entry used by `registerShortcutMap` and `useShortcutMap`. */
|
|
268
|
+
type ShortcutMapEntry = {
|
|
269
|
+
keys: string | string[];
|
|
270
|
+
handler: ShortcutHandler;
|
|
271
|
+
options?: HandlerOptions;
|
|
272
|
+
};
|
|
273
|
+
/** Bulk registration shape mapping action ids to key+handler definitions. */
|
|
274
|
+
type ShortcutMap = Record<string, ShortcutMapEntry>;
|
|
275
|
+
/** Return type for map registrations, keyed by the same ids as the source map. */
|
|
276
|
+
type ShortcutMapResult<T extends ShortcutMap = ShortcutMap> = {
|
|
277
|
+
[K in keyof T]: ShortcutResult;
|
|
278
|
+
};
|
|
279
|
+
/** Imperative grouping controller for binding/unbinding many shortcut registrations together. */
|
|
280
|
+
type ShortcutGroup = {
|
|
281
|
+
add: (...results: ShortcutResult[]) => void;
|
|
282
|
+
addMany: (results: ShortcutResult[] | Record<string, ShortcutResult>) => void;
|
|
283
|
+
unbindAll: () => void;
|
|
284
|
+
clear: () => void;
|
|
285
|
+
getResults: () => ShortcutResult[];
|
|
286
|
+
};
|
|
267
287
|
|
|
268
288
|
/**
|
|
269
289
|
* Parse a shortcut string into its components
|
|
@@ -317,6 +337,23 @@ declare function matchesAnyShortcut(event: KeyboardEvent, parsedShortcuts: Parse
|
|
|
317
337
|
*/
|
|
318
338
|
declare function formatShortcut(shortcut: string, platform?: PlatformType): string;
|
|
319
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Registers an object-based shortcut map in one call and returns per-action handles.
|
|
342
|
+
*
|
|
343
|
+
* @param builder - Builder returned by `useShortcut()`
|
|
344
|
+
* @param shortcutMap - Record of action ids to key bindings, handlers, and options
|
|
345
|
+
* @returns A result map with one `ShortcutResult` per shortcut id
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* const $ = useShortcut()
|
|
350
|
+
* const results = registerShortcutMap($, {
|
|
351
|
+
* save: { keys: "mod+s", handler: onSave },
|
|
352
|
+
* nav: { keys: ["g", "d"], handler: onGoDashboard },
|
|
353
|
+
* })
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
declare function registerShortcutMap<T extends ShortcutMap>(builder: ShortcutBuilder, shortcutMap: T): ShortcutMapResult<T>;
|
|
320
357
|
/**
|
|
321
358
|
* React hook for registering chainable keyboard shortcuts
|
|
322
359
|
*
|
|
@@ -333,5 +370,46 @@ declare function formatShortcut(shortcut: string, platform?: PlatformType): stri
|
|
|
333
370
|
* ```
|
|
334
371
|
*/
|
|
335
372
|
declare function useShortcut(options?: UseShortcutOptions): ShortcutBuilder;
|
|
373
|
+
/**
|
|
374
|
+
* React hook that registers a shortcut map and automatically unbinds on cleanup.
|
|
375
|
+
*
|
|
376
|
+
* @param shortcutMap - Record of action ids to key bindings, handlers, and options
|
|
377
|
+
* @param options - Same options as `useShortcut()`
|
|
378
|
+
* @returns A map of `ShortcutResult` keyed by your shortcut ids
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* const mapResults = useShortcutMap({
|
|
383
|
+
* save: { keys: "mod+s", handler: onSave },
|
|
384
|
+
* close: { keys: "escape", handler: onClose },
|
|
385
|
+
* })
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
declare function useShortcutMap<T extends ShortcutMap>(shortcutMap: T, options?: UseShortcutOptions): ShortcutMapResult<T>;
|
|
389
|
+
/**
|
|
390
|
+
* Creates an imperative group controller for many shortcut registrations.
|
|
391
|
+
*
|
|
392
|
+
* @returns A `ShortcutGroup` that can add and unbind multiple shortcuts together
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const group = createShortcutGroup()
|
|
397
|
+
* group.add($.mod.key("s").on(onSave))
|
|
398
|
+
* group.add($.key("escape").on(onClose))
|
|
399
|
+
* group.unbindAll()
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
declare function createShortcutGroup(): ShortcutGroup;
|
|
403
|
+
/**
|
|
404
|
+
* React hook that returns a stable `ShortcutGroup` instance.
|
|
405
|
+
*
|
|
406
|
+
* @returns A memoized `ShortcutGroup` tied to the component lifecycle
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* const group = useShortcutGroup()
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
declare function useShortcutGroup(): ShortcutGroup;
|
|
336
414
|
|
|
337
|
-
export { type ActionKey, type AlphaKey, type ExceptPredicate, type ExceptPreset, type FunctionKey, type HandlerOptions, type KeyChain, ModifierAliases, type ModifierChain, ModifierDisplayOrder, ModifierDisplaySymbols, type ModifierFlags, ModifierKey, type ModifierName, type ModifierState, type NavigationKey, type NumericKey, type ParsedShortcut, Platform, type ShortcutBuilder, type ShortcutConflict, type ShortcutHandler, type ShortcutRecordingOptions, type ShortcutResult, type ShortcutScope, type SpecialKey, SpecialKeyMap, type SymbolKey, type UseShortcutOptions, detectPlatform, formatShortcut, matchesAnyShortcut, matchesShortcut, parseShortcut, parseShortcuts, useShortcut };
|
|
415
|
+
export { type ActionKey, type AlphaKey, type ExceptPredicate, type ExceptPreset, type FunctionKey, type HandlerOptions, type KeyChain, ModifierAliases, type ModifierChain, ModifierDisplayOrder, ModifierDisplaySymbols, type ModifierFlags, ModifierKey, type ModifierName, type ModifierState, type NavigationKey, type NumericKey, type ParsedShortcut, Platform, type ShortcutBuilder, type ShortcutConflict, type ShortcutGroup, type ShortcutHandler, type ShortcutMap, type ShortcutMapEntry, type ShortcutMapResult, type ShortcutRecordingOptions, type ShortcutResult, type ShortcutScope, type SpecialKey, SpecialKeyMap, type SymbolKey, type UseShortcutOptions, createShortcutGroup, detectPlatform, formatShortcut, matchesAnyShortcut, matchesShortcut, parseShortcut, parseShortcuts, registerShortcutMap, useShortcut, useShortcutGroup, useShortcutMap };
|
package/dist/index.js
CHANGED
|
@@ -750,6 +750,95 @@ function _createShortcutBuilder(options = {}) {
|
|
|
750
750
|
}
|
|
751
751
|
|
|
752
752
|
// src/hook.ts
|
|
753
|
+
function _areShortcutMapKeysEqual(a, b) {
|
|
754
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
755
|
+
if (a.length !== b.length) return false;
|
|
756
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
757
|
+
if (a[i] !== b[i]) return false;
|
|
758
|
+
}
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
if (!Array.isArray(a) && !Array.isArray(b)) {
|
|
762
|
+
return a === b;
|
|
763
|
+
}
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
function _areShortcutMapsEquivalent(a, b) {
|
|
767
|
+
const aKeys = Object.keys(a);
|
|
768
|
+
const bKeys = Object.keys(b);
|
|
769
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
770
|
+
for (const key of aKeys) {
|
|
771
|
+
const aEntry = a[key];
|
|
772
|
+
const bEntry = b[key];
|
|
773
|
+
if (!bEntry) return false;
|
|
774
|
+
if (!_areShortcutMapKeysEqual(aEntry.keys, bEntry.keys)) return false;
|
|
775
|
+
if (aEntry.handler !== bEntry.handler) return false;
|
|
776
|
+
if (aEntry.options !== bEntry.options) return false;
|
|
777
|
+
}
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
function _normalizeShortcutMapKeys(keys) {
|
|
781
|
+
if (Array.isArray(keys)) {
|
|
782
|
+
return keys.map((key) => key.trim()).filter(Boolean);
|
|
783
|
+
}
|
|
784
|
+
const trimmed = keys.trim();
|
|
785
|
+
if (!trimmed) return [];
|
|
786
|
+
if (trimmed.includes(" then ")) {
|
|
787
|
+
return trimmed.split(/\s+then\s+/i).map((key) => key.trim()).filter(Boolean);
|
|
788
|
+
}
|
|
789
|
+
if (trimmed.includes(" ") && !trimmed.includes("+")) {
|
|
790
|
+
return trimmed.split(/\s+/).map((key) => key.trim()).filter(Boolean);
|
|
791
|
+
}
|
|
792
|
+
return [trimmed];
|
|
793
|
+
}
|
|
794
|
+
function _applyStep(builder, step) {
|
|
795
|
+
const tokens = step.toLowerCase().split("+").map((token) => token.trim()).filter(Boolean);
|
|
796
|
+
if (tokens.length === 0) {
|
|
797
|
+
throw new Error("[useShortcutMap] Invalid step: empty shortcut step");
|
|
798
|
+
}
|
|
799
|
+
const key = tokens.pop();
|
|
800
|
+
let chain = builder;
|
|
801
|
+
for (const token of tokens) {
|
|
802
|
+
if (token === "ctrl" || token === "control") {
|
|
803
|
+
chain = chain.ctrl;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (token === "shift") {
|
|
807
|
+
chain = chain.shift;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (token === "alt" || token === "option") {
|
|
811
|
+
chain = chain.alt;
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
if (token === "cmd" || token === "command" || token === "meta") {
|
|
815
|
+
chain = chain.cmd;
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
if (token === "mod") {
|
|
819
|
+
chain = chain.mod;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
throw new Error(`[useShortcutMap] Unsupported modifier token "${token}" in step "${step}"`);
|
|
823
|
+
}
|
|
824
|
+
return chain.key(key);
|
|
825
|
+
}
|
|
826
|
+
function registerShortcutMap(builder, shortcutMap) {
|
|
827
|
+
const results = {};
|
|
828
|
+
for (const id of Object.keys(shortcutMap)) {
|
|
829
|
+
const entry = shortcutMap[id];
|
|
830
|
+
const steps = _normalizeShortcutMapKeys(entry.keys);
|
|
831
|
+
if (steps.length === 0) {
|
|
832
|
+
throw new Error(`[useShortcutMap] Shortcut "${String(id)}" has no key steps`);
|
|
833
|
+
}
|
|
834
|
+
let chain = _applyStep(builder, steps[0]);
|
|
835
|
+
for (const step of steps.slice(1)) {
|
|
836
|
+
chain = chain.then(step);
|
|
837
|
+
}
|
|
838
|
+
results[id] = chain.on(entry.handler, entry.options);
|
|
839
|
+
}
|
|
840
|
+
return results;
|
|
841
|
+
}
|
|
753
842
|
function useShortcut(options = {}) {
|
|
754
843
|
const optionsRef = react.useRef(options);
|
|
755
844
|
optionsRef.current = options;
|
|
@@ -777,6 +866,64 @@ function useShortcut(options = {}) {
|
|
|
777
866
|
}, [registry]);
|
|
778
867
|
return builder;
|
|
779
868
|
}
|
|
869
|
+
function useShortcutMap(shortcutMap, options = {}) {
|
|
870
|
+
const $ = useShortcut(options);
|
|
871
|
+
const stableShortcutMapRef = react.useRef(shortcutMap);
|
|
872
|
+
if (!_areShortcutMapsEquivalent(stableShortcutMapRef.current, shortcutMap)) {
|
|
873
|
+
stableShortcutMapRef.current = shortcutMap;
|
|
874
|
+
}
|
|
875
|
+
const stableShortcutMap = stableShortcutMapRef.current;
|
|
876
|
+
const resultsRef = react.useRef({});
|
|
877
|
+
react.useEffect(() => {
|
|
878
|
+
const registrations = registerShortcutMap($, stableShortcutMap);
|
|
879
|
+
const results = resultsRef.current;
|
|
880
|
+
for (const key of Object.keys(results)) {
|
|
881
|
+
delete results[key];
|
|
882
|
+
}
|
|
883
|
+
Object.assign(results, registrations);
|
|
884
|
+
return () => {
|
|
885
|
+
for (const result of Object.values(registrations)) {
|
|
886
|
+
result.unbind();
|
|
887
|
+
}
|
|
888
|
+
for (const key of Object.keys(results)) {
|
|
889
|
+
delete results[key];
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
}, [$, stableShortcutMap]);
|
|
893
|
+
return resultsRef.current;
|
|
894
|
+
}
|
|
895
|
+
function createShortcutGroup() {
|
|
896
|
+
const results = [];
|
|
897
|
+
return {
|
|
898
|
+
add: (...entries) => {
|
|
899
|
+
results.push(...entries);
|
|
900
|
+
},
|
|
901
|
+
addMany: (entries) => {
|
|
902
|
+
if (Array.isArray(entries)) {
|
|
903
|
+
results.push(...entries);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
results.push(...Object.values(entries));
|
|
907
|
+
},
|
|
908
|
+
unbindAll: () => {
|
|
909
|
+
for (const entry of results) {
|
|
910
|
+
entry.unbind();
|
|
911
|
+
}
|
|
912
|
+
results.length = 0;
|
|
913
|
+
},
|
|
914
|
+
clear: () => {
|
|
915
|
+
results.length = 0;
|
|
916
|
+
},
|
|
917
|
+
getResults: () => [...results]
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function useShortcutGroup() {
|
|
921
|
+
const groupRef = react.useRef(null);
|
|
922
|
+
if (!groupRef.current) {
|
|
923
|
+
groupRef.current = createShortcutGroup();
|
|
924
|
+
}
|
|
925
|
+
return groupRef.current;
|
|
926
|
+
}
|
|
780
927
|
|
|
781
928
|
exports.ModifierAliases = ModifierAliases;
|
|
782
929
|
exports.ModifierDisplayOrder = ModifierDisplayOrder;
|
|
@@ -784,12 +931,16 @@ exports.ModifierDisplaySymbols = ModifierDisplaySymbols;
|
|
|
784
931
|
exports.ModifierKey = ModifierKey;
|
|
785
932
|
exports.Platform = Platform;
|
|
786
933
|
exports.SpecialKeyMap = SpecialKeyMap;
|
|
934
|
+
exports.createShortcutGroup = createShortcutGroup;
|
|
787
935
|
exports.detectPlatform = detectPlatform;
|
|
788
936
|
exports.formatShortcut = formatShortcut;
|
|
789
937
|
exports.matchesAnyShortcut = matchesAnyShortcut;
|
|
790
938
|
exports.matchesShortcut = matchesShortcut;
|
|
791
939
|
exports.parseShortcut = parseShortcut;
|
|
792
940
|
exports.parseShortcuts = parseShortcuts;
|
|
941
|
+
exports.registerShortcutMap = registerShortcutMap;
|
|
793
942
|
exports.useShortcut = useShortcut;
|
|
943
|
+
exports.useShortcutGroup = useShortcutGroup;
|
|
944
|
+
exports.useShortcutMap = useShortcutMap;
|
|
794
945
|
//# sourceMappingURL=index.js.map
|
|
795
946
|
//# sourceMappingURL=index.js.map
|