@tanstack/hotkeys 0.11.0 → 0.12.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/dist/_match.js +2 -0
- package/dist/adapter.d.ts +108 -19
- package/dist/adapter.js +90 -65
- package/dist/app-hotkeys.d.ts +239 -0
- package/dist/app-hotkeys.js +295 -0
- package/dist/conflicts.d.ts +5 -9
- package/dist/conflicts.js +6 -11
- package/dist/format.d.ts +19 -24
- package/dist/format.js +19 -30
- package/dist/hint.d.ts +1 -0
- package/dist/hint.js +5 -2
- package/dist/hotkey-manager.d.ts +110 -59
- package/dist/hotkey-manager.js +254 -151
- package/dist/hotkey-recorder.d.ts +66 -42
- package/dist/hotkey-recorder.js +148 -107
- package/dist/hotkey.types.d.ts +26 -12
- package/dist/index.d.ts +10 -11
- package/dist/index.js +5 -6
- package/dist/match.d.ts +38 -5
- package/dist/match.js +72 -4
- package/dist/parse.d.ts +20 -8
- package/dist/parse.js +15 -5
- package/dist/recorder-options.d.ts +6 -3
- package/dist/validate.d.ts +10 -4
- package/dist/validate.js +26 -4
- package/package.json +1 -1
- package/dist/hotkey-sequence-recorder.d.ts +0 -84
- package/dist/hotkey-sequence-recorder.js +0 -259
- package/dist/sequence-manager.d.ts +0 -169
- package/dist/sequence-manager.js +0 -443
package/dist/_match.js
CHANGED
|
@@ -14,6 +14,7 @@ function matchKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
|
14
14
|
if (normalized.isComposing && (parsed.code !== void 0 || isPrintableKey(parsed.key))) return noMatch;
|
|
15
15
|
if (normalized.altGraph && (parsed.ctrl || parsed.alt)) return noMatch;
|
|
16
16
|
if (parsed.code !== void 0) {
|
|
17
|
+
if (normalized.altGraph) return noMatch;
|
|
17
18
|
if (!modifiersMatch(normalized, parsed, false)) return noMatch;
|
|
18
19
|
return normalized.code === parsed.code ? matched("code", 3) : noMatch;
|
|
19
20
|
}
|
|
@@ -21,6 +22,7 @@ function matchKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
|
21
22
|
const implicitShift = direct && isPrintableKey(normalized.key) && !/^\p{Letter}$/u.test(normalized.key) && !parsed.shift;
|
|
22
23
|
if (direct && modifiersMatch(normalized, parsed, implicitShift)) return matched("key", implicitShift ? 2 : 3);
|
|
23
24
|
if (isAsciiLetter(normalized.key) || isLatinLetter(normalized.key) && normalized.code.startsWith("Key") && !normalized.alt) return noMatch;
|
|
25
|
+
if (normalized.altGraph) return noMatch;
|
|
24
26
|
if (!modifiersMatch(normalized, parsed, false)) return noMatch;
|
|
25
27
|
const fallbackKey = codeToLogicalKey(normalized.code);
|
|
26
28
|
return fallbackKey !== void 0 && keysEqual(fallbackKey, parsed.key) ? matched("fallback", 1) : noMatch;
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,27 +1,116 @@
|
|
|
1
|
-
import { HotkeyCallback, RegisterableHotkey } from "./hotkey.types.js";
|
|
2
|
-
import { HotkeyOptions } from "./hotkey-manager.js";
|
|
3
|
-
import {
|
|
1
|
+
import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord } from "./hotkey.types.js";
|
|
2
|
+
import { HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
|
|
3
|
+
import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
|
|
4
4
|
//#region src/adapter.d.ts
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* A hotkey and its callback, with optional per-binding options. Chords accept
|
|
7
|
+
* `requireReset`; sequences accept `timeout`.
|
|
8
|
+
*/
|
|
9
|
+
export type HotkeyDefinition = {
|
|
10
|
+
hotkey: RegisterableHotkeyChord;
|
|
8
11
|
callback: HotkeyCallback;
|
|
9
|
-
options?:
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface HotkeySequenceDefinition {
|
|
13
|
-
sequence: HotkeySequence;
|
|
12
|
+
options?: HotkeyChordOptions;
|
|
13
|
+
} | {
|
|
14
|
+
hotkey: HotkeySequence;
|
|
14
15
|
callback: HotkeyCallback;
|
|
15
|
-
options?:
|
|
16
|
-
}
|
|
16
|
+
options?: HotkeySequenceOptions;
|
|
17
|
+
};
|
|
18
|
+
/** Options shared by every definition. Each option applies only to the kinds that support it. */
|
|
19
|
+
export type HotkeyBindingsCommonOptions = HotkeyChordOptions & HotkeySequenceOptions;
|
|
17
20
|
/** Reconciles adapter registrations while retaining handles for option updates. */
|
|
18
21
|
export declare function createHotkeyBindings(): {
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Registers new definitions, updates existing ones in place, and removes
|
|
24
|
+
* stale ones. Definitions with an empty sequence or a null target are skipped.
|
|
25
|
+
*/
|
|
26
|
+
update(definitions: ReadonlyArray<HotkeyDefinition>, commonOptions?: HotkeyBindingsCommonOptions): void;
|
|
27
|
+
destroy(): void;
|
|
21
28
|
};
|
|
22
|
-
/**
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
/** Options with a framework-specific target, such as a ref. */
|
|
30
|
+
export type WithTarget<TOptions, TTarget> = Omit<TOptions, 'target' | 'appHotkeyId'> & {
|
|
31
|
+
/** The element to listen on. Defaults to document. */
|
|
32
|
+
target?: TTarget;
|
|
26
33
|
};
|
|
34
|
+
/** Options shared by chords and sequences, with a framework target. */
|
|
35
|
+
export type AdapterHotkeyOptions<TTarget> = WithTarget<HotkeyOptions, TTarget>;
|
|
36
|
+
/** Options for a chord such as `'Mod+S'`, with a framework target. */
|
|
37
|
+
export type AdapterChordOptions<TTarget> = WithTarget<HotkeyChordOptions, TTarget>;
|
|
38
|
+
/** Options for a sequence such as `['G', 'G']`, with a framework target. */
|
|
39
|
+
export type AdapterSequenceOptions<TTarget> = WithTarget<HotkeySequenceOptions, TTarget>;
|
|
40
|
+
/** Options applied to every hotkey, such as defaults. Each applies to the kinds that support it. */
|
|
41
|
+
export type AdapterCommonOptions<TTarget> = AdapterChordOptions<TTarget> & AdapterSequenceOptions<TTarget>;
|
|
42
|
+
/** Chord options for chords, sequence options for sequences. */
|
|
43
|
+
export type AdapterOptionsFor<T extends RegisterableHotkey, TTarget> = [T] extends [never] ? AdapterCommonOptions<TTarget> : T extends readonly [unknown] ? AdapterChordOptions<TTarget> : T extends ReadonlyArray<unknown> ? AdapterSequenceOptions<TTarget> : AdapterChordOptions<TTarget>;
|
|
44
|
+
/**
|
|
45
|
+
* Call-site options for an app hotkey. `meta` comes from the declaration.
|
|
46
|
+
* Kind-specific options follow the declared default: `requireReset` for a
|
|
47
|
+
* chord, `timeout` for a sequence, both for an app hotkey without a default.
|
|
48
|
+
* If the user rebinds it to the other kind, options for the old kind no
|
|
49
|
+
* longer apply.
|
|
50
|
+
*/
|
|
51
|
+
export type AdapterAppHotkeyOptions<T extends AppHotkey, TTarget> = Omit<[T['defaultHotkey']] extends [HotkeySequence] ? AdapterSequenceOptions<TTarget> : [T['defaultHotkey']] extends [HotkeyChord] ? AdapterChordOptions<TTarget> : AdapterCommonOptions<TTarget>, 'meta'>;
|
|
52
|
+
/**
|
|
53
|
+
* One hotkey and its callback for adapter APIs such as `useHotkeys`. Chords
|
|
54
|
+
* accept `requireReset`; sequences accept `timeout`; hotkeys of unknown kind
|
|
55
|
+
* accept only shared options.
|
|
56
|
+
*/
|
|
57
|
+
export type AdapterHotkeyDefinition<TTarget> = {
|
|
58
|
+
/** A chord string such as `'Mod+S'`, or a RawHotkey object */
|
|
59
|
+
hotkey: RegisterableHotkeyChord;
|
|
60
|
+
/** The function to call when the hotkey is pressed */
|
|
61
|
+
callback: HotkeyCallback;
|
|
62
|
+
/** Per-hotkey options (merged on top of common options) */
|
|
63
|
+
options?: AdapterChordOptions<TTarget>;
|
|
64
|
+
} | {
|
|
65
|
+
/** A sequence such as `['G', 'G']` */
|
|
66
|
+
hotkey: HotkeySequence;
|
|
67
|
+
callback: HotkeyCallback;
|
|
68
|
+
options?: AdapterSequenceOptions<TTarget>;
|
|
69
|
+
} | {
|
|
70
|
+
/**
|
|
71
|
+
* An app hotkey from `defineAppHotkeys`. Kind-specific options apply
|
|
72
|
+
* only while its effective hotkey has that kind.
|
|
73
|
+
*/
|
|
74
|
+
hotkey: AppHotkey;
|
|
75
|
+
callback: HotkeyCallback;
|
|
76
|
+
options?: Omit<AdapterCommonOptions<TTarget>, 'meta'>;
|
|
77
|
+
} | {
|
|
78
|
+
/**
|
|
79
|
+
* A hotkey whose kind is not known statically, such as user data typed
|
|
80
|
+
* `Hotkey`. Accepts only shared options.
|
|
81
|
+
*/
|
|
82
|
+
hotkey: RegisterableHotkey;
|
|
83
|
+
callback: HotkeyCallback;
|
|
84
|
+
options?: AdapterHotkeyOptions<TTarget>;
|
|
85
|
+
};
|
|
86
|
+
/** Context for `prepareHotkeyDefinitions`. */
|
|
87
|
+
export interface PrepareHotkeyDefinitionsOptions<TTarget> {
|
|
88
|
+
/** The provider's app hotkeys store, so app hotkeys use custom hotkeys. */
|
|
89
|
+
appHotkeysStore?: AppHotkeysStore | null;
|
|
90
|
+
/** Provider defaults, applied first. */
|
|
91
|
+
defaults?: AdapterCommonOptions<TTarget>;
|
|
92
|
+
/** Options for every definition, applied after app hotkey declarations. */
|
|
93
|
+
commonOptions?: AdapterCommonOptions<TTarget>;
|
|
94
|
+
/**
|
|
95
|
+
* Resolves a framework target, such as a ref. Return `undefined` for the
|
|
96
|
+
* document or `null` to skip registration until the target exists.
|
|
97
|
+
* Without it, targets pass through and `null` means document.
|
|
98
|
+
*/
|
|
99
|
+
resolveTarget?: (target: TTarget) => Target | null | undefined;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Converts adapter definitions into `createHotkeyBindings` definitions.
|
|
103
|
+
* Options merge in this order: defaults, app hotkey declaration, common
|
|
104
|
+
* options, per-definition options. App hotkeys resolve their effective hotkey
|
|
105
|
+
* from the store, and an unbound app hotkey keeps its position but registers
|
|
106
|
+
* nothing.
|
|
107
|
+
*/
|
|
108
|
+
export declare function prepareHotkeyDefinitions<TTarget>(definitions: ReadonlyArray<AdapterHotkeyDefinition<TTarget>>, options?: PrepareHotkeyDefinitionsOptions<TTarget>): Array<HotkeyDefinition>;
|
|
109
|
+
/**
|
|
110
|
+
* Calls `onChange` when the store published with `publishAppHotkeysStore` or
|
|
111
|
+
* `provideAppHotkeys` changes, or when its app hotkeys or custom hotkeys
|
|
112
|
+
* change. For adapters that resolve app hotkeys without a provider. Returns an
|
|
113
|
+
* unsubscribe function.
|
|
114
|
+
*/
|
|
115
|
+
export declare function subscribePublishedAppHotkeys(onChange: () => void): () => void;
|
|
27
116
|
//#endregion
|
package/dist/adapter.js
CHANGED
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
import { detectPlatform } from "./platform.js";
|
|
2
|
-
import { normalizeRegisterableHotkey, parseHotkey } from "./parse.js";
|
|
2
|
+
import { isHotkeySequence, normalizeRegisterableHotkey, parseHotkey } from "./parse.js";
|
|
3
3
|
import { defaultHotkeyOptions, getDefaultIgnoreInputs } from "./_registration.js";
|
|
4
4
|
import { getHotkeyManager } from "./hotkey-manager.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getAppHotkeyRegistrationOptions, isAppHotkey, resolveAppHotkey } from "./app-hotkeys.js";
|
|
6
6
|
|
|
7
7
|
//#region src/adapter.ts
|
|
8
|
-
|
|
8
|
+
/** Reconciles adapter registrations while retaining handles for option updates. */
|
|
9
|
+
function createHotkeyBindings() {
|
|
9
10
|
const registrations = /* @__PURE__ */ new Map();
|
|
10
11
|
return {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Registers new definitions, updates existing ones in place, and removes
|
|
14
|
+
* stale ones. Definitions with an empty sequence or a null target are skipped.
|
|
15
|
+
*/
|
|
16
|
+
update(definitions, commonOptions = {}) {
|
|
17
|
+
const prepared = [];
|
|
18
|
+
definitions.forEach((definition, index) => {
|
|
19
|
+
const merged = {
|
|
20
|
+
...commonOptions,
|
|
21
|
+
...definition.options
|
|
22
|
+
};
|
|
23
|
+
const options = {
|
|
24
|
+
...defaultHotkeyOptions,
|
|
25
|
+
...merged,
|
|
26
|
+
ignoreInputs: merged.ignoreInputs,
|
|
27
|
+
target: "target" in merged ? merged.target ?? null : typeof document === "undefined" ? null : document,
|
|
28
|
+
platform: merged.platform ?? detectPlatform()
|
|
29
|
+
};
|
|
30
|
+
if (!options.target || isHotkeySequence(definition.hotkey) && definition.hotkey.length === 0) return;
|
|
31
|
+
const hotkey = normalizeRegisterableHotkey(definition.hotkey, options.platform);
|
|
32
|
+
const first = isHotkeySequence(hotkey) ? hotkey[0] : hotkey;
|
|
33
|
+
options.ignoreInputs ??= getDefaultIgnoreInputs(parseHotkey(first, options.platform));
|
|
34
|
+
if (isHotkeySequence(hotkey)) delete options.requireReset;
|
|
35
|
+
else delete options.timeout;
|
|
36
|
+
prepared.push({
|
|
37
|
+
key: `${index}:${isHotkeySequence(hotkey) ? hotkey.join(" ") : hotkey}`,
|
|
38
|
+
callback: definition.callback,
|
|
39
|
+
options,
|
|
40
|
+
register: () => getHotkeyManager().register(hotkey, definition.callback, options)
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
const keys = new Set(prepared.map((binding) => binding.key));
|
|
13
44
|
for (const [key, registration] of registrations) if (!keys.has(key)) {
|
|
14
45
|
registration.handle.unregister();
|
|
15
46
|
registrations.delete(key);
|
|
16
47
|
}
|
|
17
|
-
for (const binding of
|
|
48
|
+
for (const binding of prepared) {
|
|
18
49
|
const previous = registrations.get(binding.key);
|
|
19
50
|
const { target, platform } = binding.options;
|
|
20
51
|
if (previous?.handle.isActive && previous.target === target && previous.platform === platform) {
|
|
@@ -42,68 +73,62 @@ function createBindings() {
|
|
|
42
73
|
}
|
|
43
74
|
};
|
|
44
75
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Converts adapter definitions into `createHotkeyBindings` definitions.
|
|
78
|
+
* Options merge in this order: defaults, app hotkey declaration, common
|
|
79
|
+
* options, per-definition options. App hotkeys resolve their effective hotkey
|
|
80
|
+
* from the store, and an unbound app hotkey keeps its position but registers
|
|
81
|
+
* nothing.
|
|
82
|
+
*/
|
|
83
|
+
function prepareHotkeyDefinitions(definitions, options = {}) {
|
|
84
|
+
return definitions.map((definition) => {
|
|
85
|
+
let hotkey = definition.hotkey;
|
|
86
|
+
let declared = {};
|
|
87
|
+
if (isAppHotkey(hotkey)) {
|
|
88
|
+
const appHotkey = resolveAppHotkey(hotkey, options.appHotkeysStore);
|
|
89
|
+
declared = getAppHotkeyRegistrationOptions(appHotkey);
|
|
90
|
+
hotkey = appHotkey.hotkey ?? [];
|
|
91
|
+
}
|
|
92
|
+
const { target, ...merged } = {
|
|
93
|
+
...options.defaults,
|
|
94
|
+
...declared,
|
|
95
|
+
...options.commonOptions,
|
|
96
|
+
...definition.options
|
|
97
|
+
};
|
|
98
|
+
const resolved = target === void 0 ? void 0 : options.resolveTarget ? options.resolveTarget(target) : target ?? void 0;
|
|
99
|
+
return {
|
|
100
|
+
hotkey,
|
|
101
|
+
callback: definition.callback,
|
|
102
|
+
options: resolved === void 0 ? merged : {
|
|
103
|
+
...merged,
|
|
104
|
+
target: resolved
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
});
|
|
54
108
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const hotkey = normalizeRegisterableHotkey(definition.hotkey, options.platform);
|
|
68
|
-
options.ignoreInputs ??= getDefaultIgnoreInputs(parseHotkey(hotkey, options.platform));
|
|
69
|
-
prepared.push({
|
|
70
|
-
key: `${index}:${hotkey}`,
|
|
71
|
-
callback: definition.callback,
|
|
72
|
-
options,
|
|
73
|
-
register: () => getHotkeyManager().register(hotkey, definition.callback, options)
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
bindings.update(prepared);
|
|
77
|
-
},
|
|
78
|
-
destroy: bindings.destroy
|
|
109
|
+
/**
|
|
110
|
+
* Calls `onChange` when the store published with `publishAppHotkeysStore` or
|
|
111
|
+
* `provideAppHotkeys` changes, or when its app hotkeys or custom hotkeys
|
|
112
|
+
* change. For adapters that resolve app hotkeys without a provider. Returns an
|
|
113
|
+
* unsubscribe function.
|
|
114
|
+
*/
|
|
115
|
+
function subscribePublishedAppHotkeys(onChange) {
|
|
116
|
+
const slot = getHotkeyManager().appHotkeys;
|
|
117
|
+
let inner;
|
|
118
|
+
const follow = () => {
|
|
119
|
+
inner?.unsubscribe();
|
|
120
|
+
inner = slot.state?.store.subscribe(onChange);
|
|
79
121
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const options = resolveOptions({
|
|
89
|
-
timeout: DEFAULT_SEQUENCE_TIMEOUT,
|
|
90
|
-
...commonOptions,
|
|
91
|
-
...definition.options
|
|
92
|
-
});
|
|
93
|
-
if (!options.target || definition.sequence.length === 0) return;
|
|
94
|
-
options.ignoreInputs ??= getDefaultIgnoreInputs(parseHotkey(definition.sequence[0], options.platform));
|
|
95
|
-
prepared.push({
|
|
96
|
-
key: `${index}:${JSON.stringify(definition.sequence)}`,
|
|
97
|
-
callback: definition.callback,
|
|
98
|
-
options,
|
|
99
|
-
register: () => getSequenceManager().register(definition.sequence, definition.callback, options)
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
bindings.update(prepared);
|
|
103
|
-
},
|
|
104
|
-
destroy: bindings.destroy
|
|
122
|
+
follow();
|
|
123
|
+
const outer = slot.subscribe(() => {
|
|
124
|
+
follow();
|
|
125
|
+
onChange();
|
|
126
|
+
});
|
|
127
|
+
return () => {
|
|
128
|
+
outer.unsubscribe();
|
|
129
|
+
inner?.unsubscribe();
|
|
105
130
|
};
|
|
106
131
|
}
|
|
107
132
|
|
|
108
133
|
//#endregion
|
|
109
|
-
export { createHotkeyBindings,
|
|
134
|
+
export { createHotkeyBindings, prepareHotkeyDefinitions, subscribePublishedAppHotkeys };
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Hotkey, HotkeyChord, HotkeyMeta, HotkeySequence } from "./hotkey.types.js";
|
|
2
|
+
import { HotkeyChordOptions, HotkeyRegistrationView, HotkeySequenceOptions } from "./hotkey-manager.js";
|
|
3
|
+
import { Store } from "@tanstack/store";
|
|
4
|
+
//#region src/app-hotkeys.d.ts
|
|
5
|
+
type Platform = 'mac' | 'windows' | 'linux';
|
|
6
|
+
type DeclaredOptionKeys = 'conflictBehavior' | 'eventType' | 'ignoreInputs' | 'preventDefault' | 'stopPropagation';
|
|
7
|
+
/** Declared options for an app hotkey whose default is a chord. */
|
|
8
|
+
export type AppHotkeyChordOptions = Pick<HotkeyChordOptions, DeclaredOptionKeys | 'requireReset'>;
|
|
9
|
+
/** Declared options for an app hotkey whose default is a sequence. */
|
|
10
|
+
export type AppHotkeySequenceOptions = Pick<HotkeySequenceOptions, DeclaredOptionKeys | 'timeout'>;
|
|
11
|
+
/** Every declared option. Kind-specific options apply only while the effective hotkey has that kind. */
|
|
12
|
+
export type AppHotkeyOptions = AppHotkeyChordOptions & AppHotkeySequenceOptions;
|
|
13
|
+
/** One declaration passed to `defineAppHotkeys`. */
|
|
14
|
+
export interface AppHotkeyInput {
|
|
15
|
+
/** Default hotkey: a chord, a sequence, or `null` for an action without a default. */
|
|
16
|
+
hotkey: Hotkey | null;
|
|
17
|
+
/** Display metadata for cheat sheets and settings screens. */
|
|
18
|
+
meta?: HotkeyMeta;
|
|
19
|
+
/**
|
|
20
|
+
* Label for where this hotkey applies, such as `'editor'`. App hotkeys with
|
|
21
|
+
* different contexts may share a hotkey; one without a context conflicts with
|
|
22
|
+
* any app hotkey that shares its hotkey. It does not affect matching.
|
|
23
|
+
*/
|
|
24
|
+
context?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Options that describe the hotkey itself. Chord defaults accept
|
|
27
|
+
* `requireReset`; sequence defaults accept `timeout`.
|
|
28
|
+
*/
|
|
29
|
+
options?: AppHotkeyOptions;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A declared app hotkey. It never holds a callback: register it with
|
|
33
|
+
* `useHotkey(appHotkey, callback)` or the equivalent adapter API.
|
|
34
|
+
*
|
|
35
|
+
* `TDefault` is the kind of the declared default (`HotkeyChord`,
|
|
36
|
+
* `HotkeySequence`, or `null`). Adapters use it to type kind-specific options.
|
|
37
|
+
*/
|
|
38
|
+
export interface AppHotkey<TId extends string = string, TDefault extends HotkeyChord | HotkeySequence | null = HotkeyChord | HotkeySequence | null> {
|
|
39
|
+
/** Brand that distinguishes app hotkeys from raw hotkey objects. */
|
|
40
|
+
readonly '~appHotkey': true;
|
|
41
|
+
/** Stable id. Saved custom hotkeys are keyed by this value. */
|
|
42
|
+
readonly id: TId;
|
|
43
|
+
/** Effective hotkey: the user's custom hotkey, or the default. `null` means unbound. */
|
|
44
|
+
readonly hotkey: Hotkey | null;
|
|
45
|
+
/** Hotkey declared in `defineAppHotkeys`. */
|
|
46
|
+
readonly defaultHotkey: TDefault;
|
|
47
|
+
readonly meta?: HotkeyMeta;
|
|
48
|
+
readonly context?: string;
|
|
49
|
+
readonly options?: AppHotkeyOptions;
|
|
50
|
+
}
|
|
51
|
+
/** A map of app hotkeys keyed by id. */
|
|
52
|
+
export type AppHotkeys = Readonly<Record<string, AppHotkey>>;
|
|
53
|
+
type AllowedOptions<THotkey> = THotkey extends HotkeySequence ? AppHotkeySequenceOptions : THotkey extends null ? AppHotkeyOptions : AppHotkeyChordOptions;
|
|
54
|
+
type StrictOptions<TOptions, TAllowed> = TOptions extends object ? { [P in Exclude<keyof TOptions, keyof TAllowed>]: never; } & TAllowed : TAllowed;
|
|
55
|
+
/** Rejects options that do not apply to each declaration's default kind. */
|
|
56
|
+
type ValidatedAppHotkeyInputs<T> = { [K in keyof T]: T[K] extends {
|
|
57
|
+
hotkey: infer THotkey;
|
|
58
|
+
} ? Omit<T[K], 'options'> & {
|
|
59
|
+
options?: StrictOptions<T[K] extends {
|
|
60
|
+
options: infer TOptions;
|
|
61
|
+
} ? TOptions : {}, AllowedOptions<THotkey>>;
|
|
62
|
+
} : never; };
|
|
63
|
+
/** The kind of a declared default hotkey; a one-step sequence is a chord. */
|
|
64
|
+
export type AppHotkeyDefaultKind<THotkey> = THotkey extends null ? null : THotkey extends readonly [unknown] ? HotkeyChord : THotkey extends ReadonlyArray<unknown> ? HotkeySequence : HotkeyChord;
|
|
65
|
+
/** The app hotkeys returned by `defineAppHotkeys` for a declaration object. */
|
|
66
|
+
export type DefinedAppHotkeys<T extends Record<string, AppHotkeyInput>> = { readonly [K in keyof T & string]: AppHotkey<K, AppHotkeyDefaultKind<T[K]['hotkey']>>; };
|
|
67
|
+
/**
|
|
68
|
+
* Register your app hotkeys to type `useAppHotkeys()` and `CustomHotkeys`.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* declare module '@tanstack/react-hotkeys' {
|
|
73
|
+
* interface Register {
|
|
74
|
+
* appHotkeys: typeof appHotkeys
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export interface Register {}
|
|
80
|
+
/** The app hotkeys type from `Register`, or a generic map when none is registered. */
|
|
81
|
+
export type RegisteredAppHotkeys = Register extends {
|
|
82
|
+
appHotkeys: infer T extends AppHotkeys;
|
|
83
|
+
} ? T : AppHotkeys;
|
|
84
|
+
/** Ids of the registered app hotkeys. */
|
|
85
|
+
export type AppHotkeyId = keyof RegisteredAppHotkeys & string;
|
|
86
|
+
/**
|
|
87
|
+
* The user's hotkeys that differ from the declared defaults, keyed by app
|
|
88
|
+
* hotkey id. This is the shape to save. `null` means the user unbound it.
|
|
89
|
+
*/
|
|
90
|
+
export type CustomHotkeys<T extends AppHotkeys = RegisteredAppHotkeys> = { -readonly [K in keyof T]?: Hotkey | null; };
|
|
91
|
+
/**
|
|
92
|
+
* Declares every app hotkey up front, without registering any, so cheat
|
|
93
|
+
* sheets and settings screens can list hotkeys whose components have not
|
|
94
|
+
* mounted. Pass the result to `HotkeysProvider` as `appHotkeys`.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* export const appHotkeys = defineAppHotkeys({
|
|
99
|
+
* 'doc.save': { hotkey: 'Mod+S', meta: { name: 'Save', group: 'Document' } },
|
|
100
|
+
* 'nav.home': { hotkey: ['G', 'H'], meta: { name: 'Go home' }, options: { timeout: 800 } },
|
|
101
|
+
* 'palette.open': { hotkey: null, meta: { name: 'Command palette' } },
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare function defineAppHotkeys<const T extends Record<string, AppHotkeyInput>>(definitions: T & ValidatedAppHotkeyInputs<T>): DefinedAppHotkeys<T>;
|
|
106
|
+
/** Whether a value is an app hotkey created by `defineAppHotkeys`. */
|
|
107
|
+
export declare function isAppHotkey(value: unknown): value is AppHotkey;
|
|
108
|
+
/** Whether an app hotkey's effective hotkey differs from its default. */
|
|
109
|
+
export declare function isCustomHotkey(appHotkey: AppHotkey, platform?: Platform): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Status of an app hotkey in the running app.
|
|
112
|
+
* - `'active'`: at least one enabled registration
|
|
113
|
+
* - `'disabled'`: registered, but every registration has `enabled: false`
|
|
114
|
+
* - `'inactive'`: has a hotkey, but nothing has registered it
|
|
115
|
+
* - `'unbound'`: no hotkey assigned
|
|
116
|
+
*/
|
|
117
|
+
export type AppHotkeyStatus = 'active' | 'disabled' | 'inactive' | 'unbound';
|
|
118
|
+
/** One row of an app hotkey cheat sheet. */
|
|
119
|
+
export interface AppHotkeyListItem<T extends AppHotkey = AppHotkey> {
|
|
120
|
+
appHotkey: T;
|
|
121
|
+
status: AppHotkeyStatus;
|
|
122
|
+
/** Whether the effective hotkey differs from the declared default. */
|
|
123
|
+
isCustom: boolean;
|
|
124
|
+
/** Live registrations of this app hotkey (for example, one per list row). */
|
|
125
|
+
registrations: Array<HotkeyRegistrationView>;
|
|
126
|
+
}
|
|
127
|
+
/** State of an `AppHotkeysStore`. */
|
|
128
|
+
export interface AppHotkeysState<T extends AppHotkeys = AppHotkeys> {
|
|
129
|
+
/** Effective app hotkeys: declarations with custom hotkeys applied. */
|
|
130
|
+
appHotkeys: T;
|
|
131
|
+
/** The user's hotkeys that differ from the defaults. */
|
|
132
|
+
customHotkeys: CustomHotkeys<T>;
|
|
133
|
+
}
|
|
134
|
+
/** Options for `AppHotkeysStore`. */
|
|
135
|
+
export interface AppHotkeysStoreOptions<T extends AppHotkeys = AppHotkeys> {
|
|
136
|
+
/** Starting custom hotkeys, such as saved data. Invalid entries are ignored with a warning. */
|
|
137
|
+
customHotkeys?: CustomHotkeys<T> | null;
|
|
138
|
+
/** Called with the custom hotkeys after every change, ready to save. */
|
|
139
|
+
onCustomHotkeysChange?: (customHotkeys: CustomHotkeys<T>) => void;
|
|
140
|
+
/** Platform used to compare hotkeys. Defaults to detection. */
|
|
141
|
+
platform?: Platform;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Holds declared app hotkeys and the user's custom hotkeys. Framework
|
|
145
|
+
* providers create one and expose it through hooks; devtools read it from
|
|
146
|
+
* `HotkeyManager.appHotkeys`.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* const store = new AppHotkeysStore(appHotkeys, {
|
|
151
|
+
* customHotkeys: JSON.parse(localStorage.getItem('hotkeys') ?? '{}'),
|
|
152
|
+
* onCustomHotkeysChange: (custom) =>
|
|
153
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom)),
|
|
154
|
+
* })
|
|
155
|
+
* store.setCustomHotkey('doc.save', 'Mod+Shift+S')
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export declare class AppHotkeysStore<T extends AppHotkeys = AppHotkeys> {
|
|
159
|
+
#private;
|
|
160
|
+
readonly store: Store<AppHotkeysState<T>>;
|
|
161
|
+
constructor(appHotkeys: T, options?: AppHotkeysStoreOptions<T>);
|
|
162
|
+
/** Effective app hotkeys. */
|
|
163
|
+
get appHotkeys(): T;
|
|
164
|
+
/** The user's hotkeys that differ from the defaults. */
|
|
165
|
+
get customHotkeys(): CustomHotkeys<T>;
|
|
166
|
+
/** Updates callbacks and other options without resetting custom hotkeys. */
|
|
167
|
+
setOptions(options: Omit<AppHotkeysStoreOptions<T>, 'customHotkeys'>): void;
|
|
168
|
+
/**
|
|
169
|
+
* Replaces the declarations, for example after hot module replacement.
|
|
170
|
+
* Custom hotkeys for ids that still exist are kept.
|
|
171
|
+
*/
|
|
172
|
+
setAppHotkeys(appHotkeys: T): void;
|
|
173
|
+
/** Effective app hotkey for an id, or `undefined` when it is not declared. */
|
|
174
|
+
get(id: string): AppHotkey | undefined;
|
|
175
|
+
/**
|
|
176
|
+
* Other app hotkeys that would collide if `id` used `hotkey`. App hotkeys
|
|
177
|
+
* collide when their contexts match or either has no context, and their
|
|
178
|
+
* hotkeys are equal or one sequence starts with the other.
|
|
179
|
+
*/
|
|
180
|
+
findConflicts(id: keyof T & string, hotkey: Hotkey): Array<AppHotkey>;
|
|
181
|
+
/**
|
|
182
|
+
* Sets the user's hotkey for an app hotkey. `null` unbinds it. A hotkey
|
|
183
|
+
* equal to the default removes the customization.
|
|
184
|
+
*
|
|
185
|
+
* @returns Conflicting app hotkeys. When any exist, nothing changes.
|
|
186
|
+
*/
|
|
187
|
+
setCustomHotkey(id: keyof T & string, hotkey: Hotkey | null): Array<AppHotkey>;
|
|
188
|
+
/** Restores the default hotkey for an app hotkey. */
|
|
189
|
+
resetCustomHotkey(id: keyof T & string): void;
|
|
190
|
+
/** Restores every default hotkey. */
|
|
191
|
+
resetCustomHotkeys(): void;
|
|
192
|
+
/**
|
|
193
|
+
* Joins app hotkeys with live registrations, in declaration order.
|
|
194
|
+
* Adapters call this with the manager's registration views.
|
|
195
|
+
*/
|
|
196
|
+
getList(registrations: Iterable<HotkeyRegistrationView>): Array<AppHotkeyListItem<T[keyof T]>>;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns the provider's version of an app hotkey, so custom hotkeys apply
|
|
200
|
+
* even when a component imports the declaration directly. Falls back to the
|
|
201
|
+
* given app hotkey when no store declares it.
|
|
202
|
+
*/
|
|
203
|
+
export declare function resolveAppHotkey(appHotkey: AppHotkey, store: AppHotkeysStore | null | undefined): AppHotkey;
|
|
204
|
+
/**
|
|
205
|
+
* Options that adapters pass to the manager when registering an app hotkey:
|
|
206
|
+
* declared options, meta, and the app hotkey id. The manager drops options
|
|
207
|
+
* that do not apply to the effective hotkey's kind.
|
|
208
|
+
*/
|
|
209
|
+
export declare function getAppHotkeyRegistrationOptions(appHotkey: AppHotkey): AppHotkeyOptions & {
|
|
210
|
+
meta?: HotkeyMeta;
|
|
211
|
+
appHotkeyId: string;
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Publishes a provider's store on the HotkeyManager so devtools can list
|
|
215
|
+
* declared hotkeys. Returns a cleanup function that unpublishes it if it is
|
|
216
|
+
* still the published store.
|
|
217
|
+
*/
|
|
218
|
+
export declare function publishAppHotkeysStore(store: AppHotkeysStore): () => void;
|
|
219
|
+
/**
|
|
220
|
+
* Creates an app hotkeys store and publishes it on the HotkeyManager, for
|
|
221
|
+
* adapters without a provider component (such as Lit and Alpine). Hotkey
|
|
222
|
+
* APIs in those adapters resolve app hotkeys and custom hotkeys from the
|
|
223
|
+
* published store. Call it once at startup. Returns the store and a function
|
|
224
|
+
* that unpublishes it.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* provideAppHotkeys(appHotkeys, {
|
|
229
|
+
* customHotkeys: JSON.parse(localStorage.getItem('hotkeys') ?? '{}'),
|
|
230
|
+
* onCustomHotkeysChange: (custom) =>
|
|
231
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom)),
|
|
232
|
+
* })
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
export declare function provideAppHotkeys<T extends AppHotkeys>(appHotkeys: T, options?: AppHotkeysStoreOptions<T>): {
|
|
236
|
+
store: AppHotkeysStore<T>;
|
|
237
|
+
dispose: () => void;
|
|
238
|
+
};
|
|
239
|
+
//#endregion
|