@tanstack/hotkeys 0.15.0 → 0.16.0
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 +1 -1
- package/dist/_keyboard-event.js +19 -3
- package/dist/_manager-ref.js +10 -0
- package/dist/_match.js +9 -1
- package/dist/_recorder-chord.js +10 -6
- package/dist/_registration.js +8 -5
- package/dist/adapter.d.ts +53 -6
- package/dist/adapter.js +45 -3
- package/dist/app-hotkeys.d.ts +11 -8
- package/dist/app-hotkeys.js +13 -7
- package/dist/conflicts.js +9 -8
- package/dist/constants.d.ts +1 -24
- package/dist/constants.js +21 -83
- package/dist/format.d.ts +13 -9
- package/dist/format.js +12 -28
- package/dist/hint.js +2 -2
- package/dist/hotkey-manager.d.ts +94 -11
- package/dist/hotkey-manager.js +231 -78
- package/dist/hotkey-recorder.d.ts +44 -4
- package/dist/hotkey-recorder.js +9 -5
- package/dist/hotkey.types.d.ts +72 -46
- package/dist/index.d.ts +6 -7
- package/dist/index.js +3 -3
- package/dist/key-state-tracker.js +36 -31
- package/dist/match.d.ts +19 -18
- package/dist/match.js +26 -40
- package/dist/parse.d.ts +36 -73
- package/dist/parse.js +58 -151
- package/dist/validate.js +8 -24
- package/package.json +1 -1
- package/dist/recorder-options.d.ts +0 -34
package/dist/format.d.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ParsedHotkeyChord, RegisterableHotkeyChord } from "./hotkey.types.js";
|
|
2
2
|
//#region src/format.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Converts a
|
|
4
|
+
* Converts a parsed hotkey back to a string, with each platform modifier
|
|
5
|
+
* spelled out, such as `Control+Shift+S`. A parsed chord returns a string, and
|
|
6
|
+
* a parsed sequence returns one string per step. To get `Mod` back where the
|
|
7
|
+
* platform allows it, use `normalizeHotkey` instead.
|
|
5
8
|
*
|
|
6
|
-
* @param parsed -
|
|
7
|
-
* @returns A hotkey string in canonical form
|
|
9
|
+
* @param parsed - A parsed chord, or the parsed steps of a sequence
|
|
8
10
|
*
|
|
9
11
|
* @example
|
|
10
12
|
* ```ts
|
|
11
|
-
* formatHotkey(
|
|
12
|
-
* //
|
|
13
|
+
* formatHotkey(parseHotkey('Mod+Shift+S', 'windows')) // 'Control+Shift+S'
|
|
14
|
+
* formatHotkey(parseHotkey(['G', 'Mod+K'], 'mac')) // ['G', 'Meta+K']
|
|
13
15
|
* ```
|
|
14
16
|
*/
|
|
15
|
-
export declare function formatHotkey(parsed:
|
|
17
|
+
export declare function formatHotkey(parsed: ParsedHotkeyChord): string;
|
|
18
|
+
export declare function formatHotkey(parsed: ReadonlyArray<ParsedHotkeyChord>): Array<string>;
|
|
19
|
+
export declare function formatHotkey(parsed: ParsedHotkeyChord | ReadonlyArray<ParsedHotkeyChord>): string | Array<string>;
|
|
16
20
|
/** A single chord to display: a string, raw object, or parsed hotkey. */
|
|
17
|
-
export type DisplayHotkeyChord = RegisterableHotkeyChord |
|
|
21
|
+
export type DisplayHotkeyChord = RegisterableHotkeyChord | ParsedHotkeyChord | (string & {});
|
|
18
22
|
/** A hotkey to display: a chord or a sequence of chords. */
|
|
19
23
|
export type DisplayHotkey = DisplayHotkeyChord | ReadonlyArray<DisplayHotkeyChord>;
|
|
20
24
|
/**
|
|
@@ -31,7 +35,7 @@ export type DisplayHotkey = DisplayHotkeyChord | ReadonlyArray<DisplayHotkeyChor
|
|
|
31
35
|
* Sequence steps are formatted individually and joined with `sequenceSeparator`
|
|
32
36
|
* (a space by default).
|
|
33
37
|
*
|
|
34
|
-
* @param hotkey - A logical or physical string, raw object,
|
|
38
|
+
* @param hotkey - A logical or physical string, raw object, ParsedHotkeyChord, or sequence
|
|
35
39
|
* @param options - Formatting options
|
|
36
40
|
* @returns Display text, or individual key labels when parts is true (one array per step for sequences)
|
|
37
41
|
*
|
package/dist/format.js
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MODIFIER_ORDER, normalizeKeyName } from "./constants.js";
|
|
2
2
|
import { KEY_DISPLAY_SYMBOLS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS } from "./display-labels.js";
|
|
3
3
|
import { detectPlatform } from "./platform.js";
|
|
4
|
-
import { assertLogicalKey,
|
|
5
|
-
import {
|
|
4
|
+
import { assertLogicalKey, codeToLogicalKey, modifierAlias, physicalCodeOf, splitChordParts } from "./_keyboard-event.js";
|
|
5
|
+
import { normalizeHotkey } from "./parse.js";
|
|
6
6
|
|
|
7
7
|
//#region src/format.ts
|
|
8
|
-
/**
|
|
9
|
-
* Converts a ParsedHotkey back to a hotkey string.
|
|
10
|
-
*
|
|
11
|
-
* @param parsed - The parsed hotkey object
|
|
12
|
-
* @returns A hotkey string in canonical form
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* formatHotkey({ key: 'S', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] })
|
|
17
|
-
* // Returns: 'Control+Shift+S'
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
8
|
function formatHotkey(parsed) {
|
|
9
|
+
return Array.isArray(parsed) ? parsed.map((step) => formatParsedChord(step)) : formatParsedChord(parsed);
|
|
10
|
+
}
|
|
11
|
+
function formatParsedChord(parsed) {
|
|
21
12
|
if (parsed.code === void 0) assertLogicalKey(parsed.key);
|
|
22
13
|
const parts = [];
|
|
23
14
|
for (const modifier of MODIFIER_ORDER) if (parsed.modifiers.includes(modifier)) parts.push(modifier);
|
|
@@ -45,8 +36,8 @@ function formatChordParts(hotkey, options) {
|
|
|
45
36
|
const symbols = options.useSymbols ?? true;
|
|
46
37
|
const modifierSymbols = modifierSymbolsEnabled(options);
|
|
47
38
|
const keySymbols = typeof symbols === "boolean" ? symbols : symbols.keys ?? true;
|
|
48
|
-
const normalized =
|
|
49
|
-
const segments =
|
|
39
|
+
const normalized = normalizeHotkey(hotkey, platform);
|
|
40
|
+
const segments = splitChordParts(normalized);
|
|
50
41
|
if (platform === "mac") {
|
|
51
42
|
const modifierIndex = (segment) => {
|
|
52
43
|
const index = MODIFIER_ORDER.indexOf(segment === "Mod" ? "Meta" : segment);
|
|
@@ -55,23 +46,16 @@ function formatChordParts(hotkey, options) {
|
|
|
55
46
|
segments.sort((a, b) => modifierIndex(a) - modifierIndex(b));
|
|
56
47
|
}
|
|
57
48
|
return segments.map((segment) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
const code = /^\[([A-Za-z][A-Za-z0-9]*)\]$/.exec(segment)?.[1];
|
|
49
|
+
const modifier = modifierAlias(segment);
|
|
50
|
+
if (modifier) return platform === "mac" ? modifierSymbols ? MAC_MODIFIER_SYMBOLS[modifier] : MAC_MODIFIER_LABELS[modifier] : platform === "windows" ? WINDOWS_MODIFIER_LABELS[modifier] : LINUX_MODIFIER_LABELS[modifier];
|
|
51
|
+
const code = physicalCodeOf(segment);
|
|
63
52
|
const label = options.keyLabels?.[code ?? segment];
|
|
64
53
|
if (label !== void 0) return label;
|
|
65
54
|
const numpad = code?.startsWith("Numpad") ?? false;
|
|
66
55
|
let displayKey = code ?? segment;
|
|
67
56
|
const mappedKey = code ? options.layoutMap?.get(code) : void 0;
|
|
68
57
|
if (mappedKey) displayKey = normalizeKeyName(mappedKey);
|
|
69
|
-
else if (code)
|
|
70
|
-
if (/^Key[A-Z]$/.test(code)) displayKey = code.slice(3);
|
|
71
|
-
else if (/^Digit[0-9]$/.test(code)) displayKey = code.slice(5);
|
|
72
|
-
else if (numpad) displayKey = code.slice(6);
|
|
73
|
-
else displayKey = PUNCTUATION_CODE_MAP[code] ?? code;
|
|
74
|
-
}
|
|
58
|
+
else if (code) displayKey = numpad ? code.slice(6) : codeToLogicalKey(code) ?? code;
|
|
75
59
|
const display = (keySymbols ? KEY_DISPLAY_SYMBOLS : PUNCTUATION_KEY_DISPLAY_LABELS)[displayKey] ?? displayKey;
|
|
76
60
|
return numpad ? `Numpad ${display.replace(/([a-z])([A-Z])/g, "$1 $2")}` : display;
|
|
77
61
|
});
|
package/dist/hint.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MODIFIER_KEYS, normalizeKeyName } from "./constants.js";
|
|
2
|
-
import { isHotkeySequence,
|
|
2
|
+
import { isHotkeySequence, parseHotkey } from "./parse.js";
|
|
3
3
|
|
|
4
4
|
//#region src/hint.ts
|
|
5
5
|
/**
|
|
@@ -18,7 +18,7 @@ function matchesHeldModifiers(hotkey, heldKeys, options = {}) {
|
|
|
18
18
|
const held = [...new Set(heldKeys.map(normalizeKeyName).filter((key) => MODIFIER_KEYS.has(key)))];
|
|
19
19
|
const first = isHotkeySequence(hotkey) ? hotkey[0] : hotkey;
|
|
20
20
|
if (first === void 0) return false;
|
|
21
|
-
const { modifiers } =
|
|
21
|
+
const { modifiers } = parseHotkey(first, options.platform);
|
|
22
22
|
return held.length > 0 && held.every((key) => modifiers.includes(key)) && (!options.exact || held.length === modifiers.length);
|
|
23
23
|
}
|
|
24
24
|
|
package/dist/hotkey-manager.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConflictBehavior, HotkeyCallback, HotkeyChord, HotkeyMeta, HotkeySequence,
|
|
1
|
+
import { ConflictBehavior, HotkeyCallback, HotkeyCallbackContextFor, HotkeyChord, HotkeyMeta, HotkeySequence, ParsedHotkeyChord, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
|
|
2
2
|
import { AppHotkeysStore } from "./app-hotkeys.js";
|
|
3
3
|
import { Store } from "@tanstack/store";
|
|
4
4
|
//#region src/hotkey-manager.d.ts
|
|
@@ -39,14 +39,30 @@ export interface HotkeyOptions {
|
|
|
39
39
|
ignoreInputs?: boolean | 'auto';
|
|
40
40
|
/** The target platform for resolving 'Mod' */
|
|
41
41
|
platform?: 'mac' | 'windows' | 'linux';
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Prevent the default browser action when the hotkey matches. For a
|
|
44
|
+
* sequence, every step that advances it does. Defaults to true
|
|
45
|
+
*/
|
|
43
46
|
preventDefault?: boolean;
|
|
44
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Stop event propagation when the hotkey matches. For a sequence, only the
|
|
49
|
+
* last step does, so earlier steps reach other listeners. Defaults to true
|
|
50
|
+
*/
|
|
45
51
|
stopPropagation?: boolean;
|
|
46
52
|
/** The DOM element to attach the event listener to. Defaults to document. */
|
|
47
53
|
target?: Target | null;
|
|
48
54
|
/** Optional metadata (name, description, custom fields via declaration merging) */
|
|
49
55
|
meta?: HotkeyMeta;
|
|
56
|
+
/**
|
|
57
|
+
* Scope this hotkey belongs to. Defaults to `'global'`, which is always
|
|
58
|
+
* active. Other scopes are active while activated with
|
|
59
|
+
* `HotkeyManager.activateScope`, which framework providers do while
|
|
60
|
+
* mounted. While an exclusive scope is active, only its hotkeys, those of
|
|
61
|
+
* scopes nested in it, and those of scopes it allows fire.
|
|
62
|
+
* When active scopes share a hotkey on the same target, the most recently
|
|
63
|
+
* activated scope handles it.
|
|
64
|
+
*/
|
|
65
|
+
scope?: string;
|
|
50
66
|
/** Id of the app hotkey this registration implements. Adapters set this when registering an app hotkey from `defineAppHotkeys`. */
|
|
51
67
|
appHotkeyId?: string;
|
|
52
68
|
}
|
|
@@ -64,7 +80,7 @@ export interface HotkeySequenceOptions extends HotkeyOptions {
|
|
|
64
80
|
* The options type for a hotkey: chord options for chords (including one-step
|
|
65
81
|
* tuples), sequence options for sequences.
|
|
66
82
|
*/
|
|
67
|
-
export type HotkeyOptionsFor<T extends RegisterableHotkey> = [T] extends [never] ? HotkeyChordOptions & HotkeySequenceOptions : T extends readonly [RegisterableHotkeyChord] ? HotkeyChordOptions : T extends
|
|
83
|
+
export type HotkeyOptionsFor<T extends RegisterableHotkey> = [T] extends [never] ? HotkeyChordOptions & HotkeySequenceOptions : T extends readonly [RegisterableHotkeyChord] ? HotkeyChordOptions : T extends RegisterableHotkeySequence ? HotkeySequenceOptions : HotkeyChordOptions;
|
|
68
84
|
interface RegistrationBase<TOptions extends HotkeyOptions> {
|
|
69
85
|
/** Unique identifier for this registration */
|
|
70
86
|
id: string;
|
|
@@ -83,7 +99,7 @@ interface RegistrationBase<TOptions extends HotkeyOptions> {
|
|
|
83
99
|
export interface HotkeyChordRegistration extends RegistrationBase<HotkeyChordOptions> {
|
|
84
100
|
kind: 'chord';
|
|
85
101
|
hotkey: HotkeyChord;
|
|
86
|
-
|
|
102
|
+
parsedChord: ParsedHotkeyChord;
|
|
87
103
|
/** The concrete key/code that activated a requireReset registration. */
|
|
88
104
|
activeMatch?: {
|
|
89
105
|
key: string;
|
|
@@ -94,7 +110,7 @@ export interface HotkeyChordRegistration extends RegistrationBase<HotkeyChordOpt
|
|
|
94
110
|
export interface HotkeySequenceRegistration extends RegistrationBase<HotkeySequenceOptions> {
|
|
95
111
|
kind: 'sequence';
|
|
96
112
|
hotkey: HotkeySequence;
|
|
97
|
-
parsedSequence: Array<
|
|
113
|
+
parsedSequence: Array<ParsedHotkeyChord>;
|
|
98
114
|
/** Steps matched in the current attempt */
|
|
99
115
|
currentIndex: number;
|
|
100
116
|
/** `Date.now()` when the last step in the current attempt matched */
|
|
@@ -112,14 +128,14 @@ interface RegistrationViewBase {
|
|
|
112
128
|
export interface HotkeyChordRegistrationView extends RegistrationViewBase {
|
|
113
129
|
kind: 'chord';
|
|
114
130
|
hotkey: HotkeyChord;
|
|
115
|
-
|
|
131
|
+
parsedChord: ParsedHotkeyChord;
|
|
116
132
|
options: HotkeyChordOptions;
|
|
117
133
|
}
|
|
118
134
|
/** Public view of a sequence registration, without its callback. */
|
|
119
135
|
export interface HotkeySequenceRegistrationView extends RegistrationViewBase {
|
|
120
136
|
kind: 'sequence';
|
|
121
137
|
hotkey: HotkeySequence;
|
|
122
|
-
parsedSequence: Array<
|
|
138
|
+
parsedSequence: Array<ParsedHotkeyChord>;
|
|
123
139
|
options: HotkeySequenceOptions;
|
|
124
140
|
/** Steps matched in the current attempt (0 when idle or just completed). */
|
|
125
141
|
matchedStepCount: number;
|
|
@@ -172,6 +188,44 @@ export interface HotkeyRegistrationHandle<TOptions extends HotkeyOptions = Hotke
|
|
|
172
188
|
/** Unregister this hotkey */
|
|
173
189
|
unregister: () => void;
|
|
174
190
|
}
|
|
191
|
+
/** Active hotkey scopes, published on `HotkeyManager.scopes`. */
|
|
192
|
+
export interface HotkeyScopesState {
|
|
193
|
+
/**
|
|
194
|
+
* Activated scopes, lowest precedence first. `'global'` is always active
|
|
195
|
+
* and not listed.
|
|
196
|
+
*/
|
|
197
|
+
active: ReadonlyArray<string>;
|
|
198
|
+
/** The exclusive scope that owns the keyboard, or `null` when none is active. */
|
|
199
|
+
exclusive: string | null;
|
|
200
|
+
/**
|
|
201
|
+
* Scopes the exclusive scope allows that are active, possibly including
|
|
202
|
+
* `'global'`. Empty when no scope is exclusive.
|
|
203
|
+
*/
|
|
204
|
+
allowed: ReadonlyArray<string>;
|
|
205
|
+
/**
|
|
206
|
+
* Scopes that are active but cannot fire because another scope is
|
|
207
|
+
* exclusive, possibly including `'global'`.
|
|
208
|
+
*/
|
|
209
|
+
suspended: ReadonlyArray<string>;
|
|
210
|
+
}
|
|
211
|
+
/** Options for `HotkeyManager.activateScope`. */
|
|
212
|
+
export interface ActivateHotkeyScopeOptions {
|
|
213
|
+
/**
|
|
214
|
+
* Suspend every other scope, including `'global'`, until this activation is
|
|
215
|
+
* released. Scopes nested in this one stay active. Pass `{ allow }` to keep
|
|
216
|
+
* other scopes active too, such as `{ allow: ['global'] }`. An allowed scope
|
|
217
|
+
* still has to be activated, except `'global'`.
|
|
218
|
+
*/
|
|
219
|
+
exclusive?: boolean | {
|
|
220
|
+
allow: ReadonlyArray<string>;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* The scope this one is nested in. A nested scope takes precedence over its
|
|
224
|
+
* ancestors even when it was activated first, as nested providers are when
|
|
225
|
+
* their effects run child-first. Defaults to `'global'`.
|
|
226
|
+
*/
|
|
227
|
+
parent?: string;
|
|
228
|
+
}
|
|
175
229
|
/**
|
|
176
230
|
* Singleton manager for hotkey registrations, including sequences.
|
|
177
231
|
*
|
|
@@ -225,6 +279,8 @@ export declare class HotkeyManager {
|
|
|
225
279
|
* devtools can list declared hotkeys that are not registered.
|
|
226
280
|
*/
|
|
227
281
|
readonly appHotkeys: Store<AppHotkeysStore | null>;
|
|
282
|
+
/** Active scopes. Subscribe to show which hotkeys are suspended. */
|
|
283
|
+
readonly scopes: Store<HotkeyScopesState>;
|
|
228
284
|
private constructor();
|
|
229
285
|
/**
|
|
230
286
|
* Gets the singleton instance of HotkeyManager.
|
|
@@ -234,17 +290,44 @@ export declare class HotkeyManager {
|
|
|
234
290
|
* Resets the singleton instance. Useful for testing.
|
|
235
291
|
*/
|
|
236
292
|
static resetInstance(): void;
|
|
293
|
+
/**
|
|
294
|
+
* Activates a scope until the returned function is called. Activations are
|
|
295
|
+
* counted, so a scope stays active until every activation is released.
|
|
296
|
+
* Framework providers call this while mounted.
|
|
297
|
+
*
|
|
298
|
+
* When active scopes share a hotkey, a nested scope takes precedence over
|
|
299
|
+
* its ancestors, and otherwise the most recently activated scope wins. The
|
|
300
|
+
* same order picks the exclusive scope when several are active, and only
|
|
301
|
+
* that scope's `allow` list applies.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* const release = manager.activateScope('dialog', { exclusive: true })
|
|
306
|
+
* // Only hotkeys with scope 'dialog', or scopes nested in it, fire now.
|
|
307
|
+
* release()
|
|
308
|
+
*
|
|
309
|
+
* manager.activateScope('dialog', { exclusive: { allow: ['global'] } })
|
|
310
|
+
* // Hotkeys with scope 'global' fire too.
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
activateScope(scope: string, options?: ActivateHotkeyScopeOptions): () => void;
|
|
314
|
+
/**
|
|
315
|
+
* Whether hotkeys in a scope can fire: the scope is `'global'` or
|
|
316
|
+
* activated, and either no scope is exclusive or this scope is the
|
|
317
|
+
* exclusive one, nested in it, or allowed by it.
|
|
318
|
+
*/
|
|
319
|
+
isScopeActive(scope?: string): boolean;
|
|
237
320
|
/**
|
|
238
321
|
* Registers a hotkey handler and returns a handle for updating the registration.
|
|
239
322
|
*
|
|
240
|
-
* Pass a chord (`'Mod+S'` or a
|
|
323
|
+
* Pass a chord (`'Mod+S'` or a RawHotkeyChord object) or a sequence (`['G', 'G']`).
|
|
241
324
|
* A one-step sequence registers as its chord. Chords accept `requireReset`;
|
|
242
325
|
* sequences accept `timeout`.
|
|
243
326
|
*
|
|
244
327
|
* The returned handle allows updating the callback and options without
|
|
245
328
|
* re-registering, which is useful for avoiding stale closures in React.
|
|
246
329
|
*
|
|
247
|
-
* @param hotkey - A chord string,
|
|
330
|
+
* @param hotkey - A chord string, RawHotkeyChord object, or sequence
|
|
248
331
|
* @param callback - The function to call when the hotkey is pressed
|
|
249
332
|
* @param options - Options for the hotkey behavior
|
|
250
333
|
* @returns A handle for managing the registration
|
|
@@ -263,7 +346,7 @@ export declare class HotkeyManager {
|
|
|
263
346
|
* handle.unregister()
|
|
264
347
|
* ```
|
|
265
348
|
*/
|
|
266
|
-
register<T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback
|
|
349
|
+
register<const T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: HotkeyOptionsFor<T>): HotkeyRegistrationHandle<HotkeyOptionsFor<T>>;
|
|
267
350
|
/**
|
|
268
351
|
* Resets the progress of every in-progress sequence.
|
|
269
352
|
*/
|