@tanstack/hotkeys 0.15.1 → 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 +2 -2
- package/dist/_match.js +2 -2
- package/dist/_recorder-chord.js +3 -3
- 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 +5 -5
- package/dist/format.d.ts +13 -9
- package/dist/format.js +7 -16
- package/dist/hint.js +2 -2
- package/dist/hotkey-manager.d.ts +94 -11
- package/dist/hotkey-manager.js +208 -31
- package/dist/hotkey-recorder.d.ts +20 -10
- package/dist/hotkey-recorder.js +9 -5
- package/dist/hotkey.types.d.ts +59 -32
- package/dist/index.d.ts +6 -6
- package/dist/index.js +3 -3
- package/dist/match.d.ts +19 -18
- package/dist/match.js +24 -21
- package/dist/parse.d.ts +35 -73
- package/dist/parse.js +39 -108
- package/dist/validate.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
# TanStack Hotkeys
|
|
52
52
|
|
|
53
53
|
> [!NOTE]
|
|
54
|
-
> TanStack Hotkeys is
|
|
54
|
+
> TanStack Hotkeys is in beta. We are actively developing the library and are open to feedback and contributions.
|
|
55
55
|
|
|
56
56
|
Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objects, a cross-platform `Mod` key, a singleton Hotkey Manager, and utilities for cheatsheet UIs—built to stay SSR-friendly.
|
|
57
57
|
|
package/dist/_keyboard-event.js
CHANGED
|
@@ -62,7 +62,7 @@ function physicalCodeOf(token) {
|
|
|
62
62
|
return token.startsWith("[") && token.endsWith("]") && isCodeName(token.slice(1, -1)) ? token.slice(1, -1) : void 0;
|
|
63
63
|
}
|
|
64
64
|
/** Splits modifier tokens while retaining a trailing literal plus: `Control++` → `['Control', '+']`. */
|
|
65
|
-
function
|
|
65
|
+
function splitChordParts(hotkey) {
|
|
66
66
|
if (!hotkey.endsWith("+")) return hotkey.split("+");
|
|
67
67
|
const parts = hotkey.slice(0, -1).split("+");
|
|
68
68
|
if (parts.at(-1) === "") parts.pop();
|
|
@@ -88,4 +88,4 @@ function assertLogicalKey(key) {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
//#endregion
|
|
91
|
-
export { assertLogicalKey, codeToLogicalKey, isAsciiLetter, isCodeName, isLatinLetter, isPrintableKey, keysEqual, modifierAlias, normalizeKeyboardEvent, physicalCodeOf,
|
|
91
|
+
export { assertLogicalKey, codeToLogicalKey, isAsciiLetter, isCodeName, isLatinLetter, isPrintableKey, keysEqual, modifierAlias, normalizeKeyboardEvent, physicalCodeOf, splitChordParts };
|
package/dist/_match.js
CHANGED
|
@@ -50,9 +50,9 @@ function modifiersMatch(event, parsed, allowImplicitShift) {
|
|
|
50
50
|
* same physical code or (case-insensitively) the same logical key. A code and
|
|
51
51
|
* a key never match, even when one keyboard event could produce both.
|
|
52
52
|
*/
|
|
53
|
-
function
|
|
53
|
+
function parsedChordsEqual(a, b) {
|
|
54
54
|
return a.ctrl === b.ctrl && a.alt === b.alt && a.shift === b.shift && a.meta === b.meta && (a.code !== void 0 || b.code !== void 0 ? a.code === b.code : keysEqual(a.key, b.key));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
//#endregion
|
|
58
|
-
export { matchKeyboardEvent,
|
|
58
|
+
export { matchKeyboardEvent, parsedChordsEqual };
|
package/dist/_recorder-chord.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeKeyName } from "./constants.js";
|
|
2
2
|
import { isCodeName, normalizeKeyboardEvent } from "./_keyboard-event.js";
|
|
3
|
-
import { isModifierKey,
|
|
3
|
+
import { isModifierKey, normalizeHotkey, parseKeyboardEvent } from "./parse.js";
|
|
4
4
|
|
|
5
5
|
//#region src/_recorder-chord.ts
|
|
6
6
|
/**
|
|
@@ -15,12 +15,12 @@ function hotkeyChordFromKeydown(event, platform, recordBy = "code") {
|
|
|
15
15
|
const parsed = parseKeyboardEvent(event, platform);
|
|
16
16
|
if (recordBy === "code") {
|
|
17
17
|
const { key: _key, code: _code, ...modifiers } = parsed;
|
|
18
|
-
return
|
|
18
|
+
return normalizeHotkey({
|
|
19
19
|
...modifiers,
|
|
20
20
|
code: event.code
|
|
21
21
|
}, platform);
|
|
22
22
|
}
|
|
23
|
-
return parsed.key ?
|
|
23
|
+
return parsed.key ? normalizeHotkey(parsed, platform) : null;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Explains why a physical chord cannot be recorded before syntax/policy checks.
|
package/dist/_registration.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
//#region src/_registration.ts
|
|
2
|
+
/** The scope of hotkeys that do not name one. It is always active unless an exclusive scope is active. */
|
|
3
|
+
const GLOBAL_SCOPE = "global";
|
|
2
4
|
/**
|
|
3
5
|
* Default options for hotkey/sequence registration.
|
|
4
6
|
* Omitted: platform, target (resolved at registration), requireReset (HotkeyManager only).
|
|
@@ -10,15 +12,16 @@ const defaultHotkeyOptions = {
|
|
|
10
12
|
eventType: "keydown",
|
|
11
13
|
enabled: true,
|
|
12
14
|
ignoreInputs: "auto",
|
|
13
|
-
conflictBehavior: "warn"
|
|
15
|
+
conflictBehavior: "warn",
|
|
16
|
+
scope: GLOBAL_SCOPE
|
|
14
17
|
};
|
|
15
18
|
/**
|
|
16
19
|
* Resolves `ignoreInputs: 'auto'` from the hotkey (the first step of a sequence).
|
|
17
20
|
* Ctrl/Meta shortcuts and Escape fire in inputs; single keys and Shift/Alt combos are ignored.
|
|
18
21
|
*/
|
|
19
|
-
function getDefaultIgnoreInputs(
|
|
20
|
-
if (
|
|
21
|
-
if (
|
|
22
|
+
function getDefaultIgnoreInputs(parsedChord) {
|
|
23
|
+
if (parsedChord.ctrl || parsedChord.meta) return false;
|
|
24
|
+
if (parsedChord.key === "Escape" || parsedChord.code === "Escape") return false;
|
|
22
25
|
return true;
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
@@ -47,4 +50,4 @@ function optionsEqual(left, right) {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
//#endregion
|
|
50
|
-
export { defaultHotkeyOptions, getDefaultIgnoreInputs, handleConflict, optionsEqual };
|
|
53
|
+
export { GLOBAL_SCOPE, defaultHotkeyOptions, getDefaultIgnoreInputs, handleConflict, optionsEqual };
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord } from "./hotkey.types.js";
|
|
2
|
-
import { HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
|
|
1
|
+
import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
|
|
2
|
+
import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
|
|
3
3
|
import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
|
|
4
4
|
//#region src/adapter.d.ts
|
|
5
5
|
/**
|
|
@@ -11,7 +11,7 @@ export type HotkeyDefinition = {
|
|
|
11
11
|
callback: HotkeyCallback;
|
|
12
12
|
options?: HotkeyChordOptions;
|
|
13
13
|
} | {
|
|
14
|
-
hotkey:
|
|
14
|
+
hotkey: RegisterableHotkeySequence;
|
|
15
15
|
callback: HotkeyCallback;
|
|
16
16
|
options?: HotkeySequenceOptions;
|
|
17
17
|
};
|
|
@@ -55,15 +55,15 @@ export type AdapterAppHotkeyOptions<T extends AppHotkey, TTarget> = Omit<[T['def
|
|
|
55
55
|
* accept only shared options.
|
|
56
56
|
*/
|
|
57
57
|
export type AdapterHotkeyDefinition<TTarget> = {
|
|
58
|
-
/** A chord string such as `'Mod+S'`, or a
|
|
58
|
+
/** A chord string such as `'Mod+S'`, or a chord object */
|
|
59
59
|
hotkey: RegisterableHotkeyChord;
|
|
60
60
|
/** The function to call when the hotkey is pressed */
|
|
61
61
|
callback: HotkeyCallback;
|
|
62
62
|
/** Per-hotkey options (merged on top of common options) */
|
|
63
63
|
options?: AdapterChordOptions<TTarget>;
|
|
64
64
|
} | {
|
|
65
|
-
/** A sequence such as `['G', 'G']
|
|
66
|
-
hotkey:
|
|
65
|
+
/** A sequence such as `['G', 'G']`, with each step a string or an object */
|
|
66
|
+
hotkey: RegisterableHotkeySequence;
|
|
67
67
|
callback: HotkeyCallback;
|
|
68
68
|
options?: AdapterSequenceOptions<TTarget>;
|
|
69
69
|
} | {
|
|
@@ -113,4 +113,51 @@ export declare function prepareHotkeyDefinitions<TTarget>(definitions: ReadonlyA
|
|
|
113
113
|
* unsubscribe function.
|
|
114
114
|
*/
|
|
115
115
|
export declare function subscribePublishedAppHotkeys(onChange: () => void): () => void;
|
|
116
|
+
/** Scope props shared by framework providers. */
|
|
117
|
+
export interface HotkeysProviderScopeProps {
|
|
118
|
+
/**
|
|
119
|
+
* Scope for hotkeys registered inside this provider, active while it is
|
|
120
|
+
* mounted and enabled. Defaults to the parent provider's scope, or
|
|
121
|
+
* `'global'` at the root. App hotkeys that declare a scope keep it.
|
|
122
|
+
*/
|
|
123
|
+
scope?: string;
|
|
124
|
+
/**
|
|
125
|
+
* While mounted and enabled, suspend hotkeys in every other scope,
|
|
126
|
+
* including `'global'`, except scopes of nested providers. Pass
|
|
127
|
+
* `{ allow: [...] }` to keep other scopes active, such as `'global'`. Use
|
|
128
|
+
* with `scope`, for dialogs and other overlays.
|
|
129
|
+
*/
|
|
130
|
+
exclusive?: ActivateHotkeyScopeOptions['exclusive'];
|
|
131
|
+
/**
|
|
132
|
+
* Set false to deactivate this provider's scope, and the scopes of
|
|
133
|
+
* providers nested in it, without unmounting. Defaults to true.
|
|
134
|
+
*/
|
|
135
|
+
enabled?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Merges a provider's default options over its parent's. A provider with a
|
|
139
|
+
* `scope` sets `hotkey.scope`, so hotkeys registered inside it use that scope.
|
|
140
|
+
*/
|
|
141
|
+
export declare function mergeHotkeysProviderDefaults<T extends {
|
|
142
|
+
hotkey?: object;
|
|
143
|
+
hotkeyRecorder?: object;
|
|
144
|
+
}>(parent: T | undefined, own: T | undefined, scope: string | undefined): T;
|
|
145
|
+
/**
|
|
146
|
+
* A key for a provider's scope activation, or `null` when the scope should not
|
|
147
|
+
* be active. Equal keys mean the same activation, so adapters reactivate only
|
|
148
|
+
* when the key changes, not when an inline `exclusive` object is recreated.
|
|
149
|
+
*/
|
|
150
|
+
export declare function getHotkeysProviderScopeKey(options: {
|
|
151
|
+
scope: string | undefined;
|
|
152
|
+
exclusive: ActivateHotkeyScopeOptions['exclusive'];
|
|
153
|
+
/** Whether this provider and every ancestor are enabled. */
|
|
154
|
+
active: boolean;
|
|
155
|
+
/** The parent provider's scope. */
|
|
156
|
+
parent: string | undefined;
|
|
157
|
+
}): string | null;
|
|
158
|
+
/**
|
|
159
|
+
* Activates the scope described by a key from `getHotkeysProviderScopeKey`.
|
|
160
|
+
* Returns a function that releases the activation.
|
|
161
|
+
*/
|
|
162
|
+
export declare function activateHotkeysProviderScope(key: string): () => void;
|
|
116
163
|
//#endregion
|
package/dist/adapter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { detectPlatform } from "./platform.js";
|
|
2
|
-
import { isHotkeySequence,
|
|
2
|
+
import { isHotkeySequence, normalizeHotkey } from "./parse.js";
|
|
3
3
|
import { defaultHotkeyOptions } from "./_registration.js";
|
|
4
4
|
import { getHotkeyManager } from "./hotkey-manager.js";
|
|
5
5
|
import { getAppHotkeyRegistrationOptions, isAppHotkey, resolveAppHotkey } from "./app-hotkeys.js";
|
|
@@ -28,7 +28,7 @@ function createHotkeyBindings() {
|
|
|
28
28
|
platform: merged.platform ?? detectPlatform()
|
|
29
29
|
};
|
|
30
30
|
if (!options.target || isHotkeySequence(definition.hotkey) && definition.hotkey.length === 0) return;
|
|
31
|
-
const hotkey =
|
|
31
|
+
const hotkey = normalizeHotkey(definition.hotkey, options.platform);
|
|
32
32
|
if (isHotkeySequence(hotkey)) delete options.requireReset;
|
|
33
33
|
else delete options.timeout;
|
|
34
34
|
prepared.push({
|
|
@@ -127,6 +127,48 @@ function subscribePublishedAppHotkeys(onChange) {
|
|
|
127
127
|
inner?.unsubscribe();
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Merges a provider's default options over its parent's. A provider with a
|
|
132
|
+
* `scope` sets `hotkey.scope`, so hotkeys registered inside it use that scope.
|
|
133
|
+
*/
|
|
134
|
+
function mergeHotkeysProviderDefaults(parent, own, scope) {
|
|
135
|
+
return {
|
|
136
|
+
hotkey: {
|
|
137
|
+
...parent?.hotkey,
|
|
138
|
+
...own?.hotkey,
|
|
139
|
+
...scope && { scope }
|
|
140
|
+
},
|
|
141
|
+
hotkeyRecorder: {
|
|
142
|
+
...parent?.hotkeyRecorder,
|
|
143
|
+
...own?.hotkeyRecorder
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* A key for a provider's scope activation, or `null` when the scope should not
|
|
149
|
+
* be active. Equal keys mean the same activation, so adapters reactivate only
|
|
150
|
+
* when the key changes, not when an inline `exclusive` object is recreated.
|
|
151
|
+
*/
|
|
152
|
+
function getHotkeysProviderScopeKey(options) {
|
|
153
|
+
const { scope, exclusive, active, parent } = options;
|
|
154
|
+
if (!scope || !active) return null;
|
|
155
|
+
return JSON.stringify([
|
|
156
|
+
scope,
|
|
157
|
+
typeof exclusive === "object" ? exclusive.allow : !!exclusive,
|
|
158
|
+
parent ?? null
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Activates the scope described by a key from `getHotkeysProviderScopeKey`.
|
|
163
|
+
* Returns a function that releases the activation.
|
|
164
|
+
*/
|
|
165
|
+
function activateHotkeysProviderScope(key) {
|
|
166
|
+
const [scope, exclusive, parent] = JSON.parse(key);
|
|
167
|
+
return getHotkeyManager().activateScope(scope, {
|
|
168
|
+
exclusive: Array.isArray(exclusive) ? { allow: exclusive } : exclusive,
|
|
169
|
+
parent: parent ?? void 0
|
|
170
|
+
});
|
|
171
|
+
}
|
|
130
172
|
|
|
131
173
|
//#endregion
|
|
132
|
-
export { createHotkeyBindings, prepareHotkeyDefinitions, subscribePublishedAppHotkeys };
|
|
174
|
+
export { activateHotkeysProviderScope, createHotkeyBindings, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults, prepareHotkeyDefinitions, subscribePublishedAppHotkeys };
|
package/dist/app-hotkeys.d.ts
CHANGED
|
@@ -17,11 +17,12 @@ export interface AppHotkeyInput {
|
|
|
17
17
|
/** Display metadata for cheat sheets and settings screens. */
|
|
18
18
|
meta?: HotkeyMeta;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Scope this hotkey fires in, such as `'editor'`. Overrides the scope of the
|
|
21
|
+
* provider it registers under. App hotkeys in different scopes may share a
|
|
22
|
+
* hotkey; one in the `'global'` scope conflicts with any app hotkey that
|
|
23
|
+
* shares its hotkey.
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
scope?: string;
|
|
25
26
|
/**
|
|
26
27
|
* Options that describe the hotkey itself. Chord defaults accept
|
|
27
28
|
* `requireReset`; sequence defaults accept `timeout`.
|
|
@@ -45,7 +46,7 @@ export interface AppHotkey<TId extends string = string, TDefault extends HotkeyC
|
|
|
45
46
|
/** Hotkey declared in `defineAppHotkeys`. */
|
|
46
47
|
readonly defaultHotkey: TDefault;
|
|
47
48
|
readonly meta?: HotkeyMeta;
|
|
48
|
-
readonly
|
|
49
|
+
readonly scope?: string;
|
|
49
50
|
readonly options?: AppHotkeyOptions;
|
|
50
51
|
}
|
|
51
52
|
/** A map of app hotkeys keyed by id. */
|
|
@@ -110,11 +111,12 @@ export declare function isCustomHotkey(appHotkey: AppHotkey, platform?: Platform
|
|
|
110
111
|
/**
|
|
111
112
|
* Status of an app hotkey in the running app.
|
|
112
113
|
* - `'active'`: at least one enabled registration
|
|
114
|
+
* - `'suspended'`: has an enabled registration, but none in an active scope
|
|
113
115
|
* - `'disabled'`: registered, but every registration has `enabled: false`
|
|
114
116
|
* - `'inactive'`: has a hotkey, but nothing has registered it
|
|
115
117
|
* - `'unbound'`: no hotkey assigned
|
|
116
118
|
*/
|
|
117
|
-
export type AppHotkeyStatus = 'active' | 'disabled' | 'inactive' | 'unbound';
|
|
119
|
+
export type AppHotkeyStatus = 'active' | 'suspended' | 'disabled' | 'inactive' | 'unbound';
|
|
118
120
|
/** One row of an app hotkey cheat sheet. */
|
|
119
121
|
export interface AppHotkeyListItem<T extends AppHotkey = AppHotkey> {
|
|
120
122
|
appHotkey: T;
|
|
@@ -174,7 +176,7 @@ export declare class AppHotkeysStore<T extends AppHotkeys = AppHotkeys> {
|
|
|
174
176
|
get(id: string): AppHotkey | undefined;
|
|
175
177
|
/**
|
|
176
178
|
* Other app hotkeys that would collide if `id` used `hotkey`. App hotkeys
|
|
177
|
-
* collide when their
|
|
179
|
+
* collide when their scopes match or either is `'global'`, and their
|
|
178
180
|
* hotkeys are equal or one sequence starts with the other.
|
|
179
181
|
*/
|
|
180
182
|
findConflicts(id: keyof T & string, hotkey: Hotkey): Array<AppHotkey>;
|
|
@@ -203,12 +205,13 @@ export declare class AppHotkeysStore<T extends AppHotkeys = AppHotkeys> {
|
|
|
203
205
|
export declare function resolveAppHotkey(appHotkey: AppHotkey, store: AppHotkeysStore | null | undefined): AppHotkey;
|
|
204
206
|
/**
|
|
205
207
|
* 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
|
|
208
|
+
* declared options, scope, meta, and the app hotkey id. The manager drops options
|
|
207
209
|
* that do not apply to the effective hotkey's kind.
|
|
208
210
|
*/
|
|
209
211
|
export declare function getAppHotkeyRegistrationOptions(appHotkey: AppHotkey): AppHotkeyOptions & {
|
|
210
212
|
meta?: HotkeyMeta;
|
|
211
213
|
appHotkeyId: string;
|
|
214
|
+
scope?: string;
|
|
212
215
|
};
|
|
213
216
|
/**
|
|
214
217
|
* Publishes a provider's store on the HotkeyManager so devtools can list
|
package/dist/app-hotkeys.js
CHANGED
|
@@ -2,6 +2,7 @@ import { detectPlatform } from "./platform.js";
|
|
|
2
2
|
import { isHotkeySequence, simplifyHotkey } from "./parse.js";
|
|
3
3
|
import { validateHotkey } from "./validate.js";
|
|
4
4
|
import { areHotkeysEqual } from "./match.js";
|
|
5
|
+
import { GLOBAL_SCOPE } from "./_registration.js";
|
|
5
6
|
import { getHotkeyManager } from "./hotkey-manager.js";
|
|
6
7
|
import { Store } from "@tanstack/store";
|
|
7
8
|
|
|
@@ -30,7 +31,7 @@ function defineAppHotkeys(definitions) {
|
|
|
30
31
|
hotkey,
|
|
31
32
|
defaultHotkey: hotkey,
|
|
32
33
|
meta: input.meta,
|
|
33
|
-
|
|
34
|
+
scope: input.scope,
|
|
34
35
|
options: input.options
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -86,8 +87,10 @@ function sanitizeCustomHotkeys(appHotkeys, saved) {
|
|
|
86
87
|
return result;
|
|
87
88
|
}
|
|
88
89
|
/** Whether two app hotkeys can apply at the same time. */
|
|
89
|
-
function
|
|
90
|
-
|
|
90
|
+
function scopesOverlap(a, b) {
|
|
91
|
+
const left = a.scope ?? "global";
|
|
92
|
+
const right = b.scope ?? "global";
|
|
93
|
+
return left === "global" || right === "global" || left === right;
|
|
91
94
|
}
|
|
92
95
|
/** Whether two hotkeys collide: equal chords, or one sequence starts with the other. */
|
|
93
96
|
function hotkeysOverlap(left, right, platform) {
|
|
@@ -162,14 +165,14 @@ var AppHotkeysStore = class {
|
|
|
162
165
|
}
|
|
163
166
|
/**
|
|
164
167
|
* Other app hotkeys that would collide if `id` used `hotkey`. App hotkeys
|
|
165
|
-
* collide when their
|
|
168
|
+
* collide when their scopes match or either is `'global'`, and their
|
|
166
169
|
* hotkeys are equal or one sequence starts with the other.
|
|
167
170
|
*/
|
|
168
171
|
findConflicts(id, hotkey) {
|
|
169
172
|
const self = this.store.state.appHotkeys[id];
|
|
170
173
|
if (!self) return [];
|
|
171
174
|
const candidate = simplifyHotkey(hotkey);
|
|
172
|
-
return Object.values(this.store.state.appHotkeys).filter((other) => other.id !== id && other.hotkey !== null &&
|
|
175
|
+
return Object.values(this.store.state.appHotkeys).filter((other) => other.id !== id && other.hotkey !== null && scopesOverlap(self, other) && hotkeysOverlap(candidate, other.hotkey, this.#platform));
|
|
173
176
|
}
|
|
174
177
|
/**
|
|
175
178
|
* Sets the user's hotkey for an app hotkey. `null` unbinds it. A hotkey
|
|
@@ -224,11 +227,13 @@ var AppHotkeysStore = class {
|
|
|
224
227
|
if (list) list.push(registration);
|
|
225
228
|
else byId.set(id, [registration]);
|
|
226
229
|
}
|
|
230
|
+
const manager = getHotkeyManager();
|
|
227
231
|
return Object.values(this.store.state.appHotkeys).map((appHotkey) => {
|
|
228
232
|
const registered = byId.get(appHotkey.id) ?? [];
|
|
233
|
+
const enabled = registered.filter((r) => r.options.enabled !== false);
|
|
229
234
|
return {
|
|
230
235
|
appHotkey,
|
|
231
|
-
status: appHotkey.hotkey === null ? "unbound" : registered.length === 0 ? "inactive" :
|
|
236
|
+
status: appHotkey.hotkey === null ? "unbound" : registered.length === 0 ? "inactive" : enabled.length === 0 ? "disabled" : enabled.some((r) => manager.isScopeActive(r.options.scope)) ? "active" : "suspended",
|
|
232
237
|
isCustom: isCustomHotkey(appHotkey, this.#platform),
|
|
233
238
|
registrations: registered
|
|
234
239
|
};
|
|
@@ -245,12 +250,13 @@ function resolveAppHotkey(appHotkey, store) {
|
|
|
245
250
|
}
|
|
246
251
|
/**
|
|
247
252
|
* Options that adapters pass to the manager when registering an app hotkey:
|
|
248
|
-
* declared options, meta, and the app hotkey id. The manager drops options
|
|
253
|
+
* declared options, scope, meta, and the app hotkey id. The manager drops options
|
|
249
254
|
* that do not apply to the effective hotkey's kind.
|
|
250
255
|
*/
|
|
251
256
|
function getAppHotkeyRegistrationOptions(appHotkey) {
|
|
252
257
|
return {
|
|
253
258
|
...appHotkey.options,
|
|
259
|
+
...appHotkey.scope !== void 0 && { scope: appHotkey.scope },
|
|
254
260
|
meta: appHotkey.meta,
|
|
255
261
|
appHotkeyId: appHotkey.id
|
|
256
262
|
};
|
package/dist/conflicts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { detectPlatform } from "./platform.js";
|
|
2
|
-
import { isHotkeySequence,
|
|
3
|
-
import {
|
|
2
|
+
import { isHotkeySequence, parseHotkey } from "./parse.js";
|
|
3
|
+
import { parsedChordsEqual } from "./_match.js";
|
|
4
4
|
import { matchesKeyboardEvent } from "./match.js";
|
|
5
5
|
import { getTargetDocument } from "./_event-target.js";
|
|
6
6
|
import { managerRef } from "./_manager-ref.js";
|
|
@@ -23,15 +23,15 @@ function findHotkeyConflicts(candidate, options = {}) {
|
|
|
23
23
|
const steps = isHotkeySequence(candidate) ? candidate : [candidate];
|
|
24
24
|
if (steps.length === 0) return [];
|
|
25
25
|
const platform = options.platform ?? detectPlatform();
|
|
26
|
-
const parsed = steps.map((step) =>
|
|
26
|
+
const parsed = steps.map((step) => parseHotkey(step, platform));
|
|
27
27
|
const target = options.target ?? (typeof document === "undefined" ? void 0 : document);
|
|
28
28
|
const conflicts = [];
|
|
29
29
|
const matches = (registration, bindings) => {
|
|
30
30
|
if (!options.includeDisabled && registration.options.enabled === false || (registration.options.eventType ?? "keydown") !== (options.eventType ?? "keydown") || options.excludeIds?.includes(registration.id) || options.exclude?.(registration) || options.scope !== "all" && target && !targetsOverlap(target, registration.target)) return false;
|
|
31
31
|
return parsed.slice(0, Math.min(parsed.length, bindings.length)).every((a, index) => {
|
|
32
|
-
const b =
|
|
32
|
+
const b = parseHotkey(bindings[index], registration.options.platform);
|
|
33
33
|
const event = options.events?.[index];
|
|
34
|
-
return
|
|
34
|
+
return parsedChordsEqual(a, b) || event !== void 0 && matchesKeyboardEvent(event, a, platform) && matchesKeyboardEvent(event, b, registration.options.platform);
|
|
35
35
|
});
|
|
36
36
|
};
|
|
37
37
|
const registrations = managerRef.current?.registrations.state.values() ?? [];
|
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
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, codeToLogicalKey, modifierAlias, physicalCodeOf,
|
|
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);
|
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
|
|