@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/parse.d.ts
CHANGED
|
@@ -1,56 +1,42 @@
|
|
|
1
1
|
import { Key } from "./key.types.js";
|
|
2
|
-
import { Hotkey, HotkeyChord, HotkeySequence,
|
|
3
|
-
import { MODIFIER_ALIASES } from "./constants.js";
|
|
2
|
+
import { Hotkey, HotkeyChord, HotkeySequence, ParsedHotkeyChord, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
|
|
4
3
|
//#region src/parse.d.ts
|
|
4
|
+
type Platform = 'mac' | 'windows' | 'linux';
|
|
5
|
+
/** A chord in any form: a string, a chord object, or an already-parsed chord. */
|
|
6
|
+
type AnyChord = RegisterableHotkeyChord | ParsedHotkeyChord | (string & {});
|
|
5
7
|
/**
|
|
6
8
|
* Whether a hotkey is a sequence. Use this instead of `Array.isArray`, which
|
|
7
9
|
* does not narrow readonly arrays.
|
|
8
10
|
*/
|
|
9
|
-
export declare function isHotkeySequence(hotkey:
|
|
11
|
+
export declare function isHotkeySequence<T extends RegisterableHotkey>(hotkey: T): hotkey is Extract<T, ReadonlyArray<unknown>>;
|
|
10
12
|
/**
|
|
11
13
|
* Collapses a one-step sequence to its chord without otherwise changing the hotkey.
|
|
12
14
|
* Registration APIs treat `['Mod+S']` exactly like `'Mod+S'`.
|
|
13
15
|
*/
|
|
14
|
-
export declare function simplifyHotkey<T extends RegisterableHotkey>(hotkey: T): T extends HotkeySequence ? Hotkey : T;
|
|
16
|
+
export declare function simplifyHotkey<T extends RegisterableHotkey>(hotkey: T): T extends HotkeySequence ? Hotkey : T extends RegisterableHotkeySequence ? RegisterableHotkey : T;
|
|
15
17
|
/**
|
|
16
|
-
* Parses a hotkey
|
|
18
|
+
* Parses a hotkey, resolving `Mod` for the platform. A chord, written as a
|
|
19
|
+
* string or an object, returns one parsed chord. A sequence returns one
|
|
20
|
+
* parsed chord per step. Physical tokens such as `[KeyQ]` keep `code`; they
|
|
21
|
+
* never become a logical Q.
|
|
17
22
|
*
|
|
18
|
-
* @param hotkey -
|
|
23
|
+
* @param hotkey - A chord string such as `'Mod+S'`, a chord object, or a sequence
|
|
19
24
|
* @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
|
|
20
|
-
* @returns A ParsedHotkey with either logical key or physical code and modifier flags
|
|
21
25
|
*
|
|
22
26
|
* @example
|
|
23
27
|
* ```ts
|
|
24
|
-
* parseHotkey('Mod+S') //
|
|
25
|
-
* parseHotkey(
|
|
26
|
-
* parseHotkey('
|
|
28
|
+
* parseHotkey('Mod+S', 'mac') // { key: 'S', ctrl: false, shift: false, alt: false, meta: true, modifiers: ['Meta'] }
|
|
29
|
+
* parseHotkey({ key: 'S', mod: true }, 'windows') // { key: 'S', ctrl: true, ..., modifiers: ['Control'] }
|
|
30
|
+
* parseHotkey(['G', 'Shift+G']) // [{ key: 'G', ... }, { key: 'G', shift: true, ... }]
|
|
27
31
|
* ```
|
|
28
32
|
*/
|
|
29
|
-
export declare function parseHotkey(hotkey:
|
|
33
|
+
export declare function parseHotkey(hotkey: AnyChord, platform?: Platform): ParsedHotkeyChord;
|
|
34
|
+
export declare function parseHotkey(hotkey: ReadonlyArray<AnyChord>, platform?: Platform): Array<ParsedHotkeyChord>;
|
|
35
|
+
export declare function parseHotkey(hotkey: AnyChord | ReadonlyArray<AnyChord>, platform?: Platform): ParsedHotkeyChord | Array<ParsedHotkeyChord>;
|
|
30
36
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @param raw - The raw hotkey object
|
|
36
|
-
* @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
|
|
37
|
-
* @returns A ParsedHotkey suitable for matching and formatting
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```ts
|
|
41
|
-
* rawHotkeyToParsedHotkey({ key: 'Escape' })
|
|
42
|
-
* // { key: 'Escape', ctrl: false, shift: false, alt: false, meta: false, modifiers: [] }
|
|
43
|
-
*
|
|
44
|
-
* rawHotkeyToParsedHotkey({ key: 'S', mod: true }, 'mac')
|
|
45
|
-
* // { key: 'S', ctrl: false, shift: false, alt: false, meta: true, modifiers: ['Meta'] }
|
|
46
|
-
*
|
|
47
|
-
* rawHotkeyToParsedHotkey({ key: 'S', mod: true, shift: true }, 'windows')
|
|
48
|
-
* // { key: 'S', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
export declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
|
|
52
|
-
/**
|
|
53
|
-
* Normalizes a hotkey string to its canonical form.
|
|
37
|
+
* Normalizes a hotkey to its canonical string form. Accepts a chord string, a
|
|
38
|
+
* chord object, a parsed chord, or a sequence of any of these. Each sequence
|
|
39
|
+
* step is normalized, and a one-step sequence becomes its chord.
|
|
54
40
|
*
|
|
55
41
|
* - When `Mod` is allowed for the platform (Command on Mac without Control;
|
|
56
42
|
* Control on Windows/Linux without Meta): emits `Mod`, then `Alt`, then `Shift`,
|
|
@@ -58,29 +44,20 @@ export declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac'
|
|
|
58
44
|
* - Otherwise: literal modifiers in `Control+Alt+Shift+Meta` order, then the key.
|
|
59
45
|
* - Resolves aliases and normalizes key casing (e.g. `esc` → `Escape`, `a` → `A`).
|
|
60
46
|
*
|
|
61
|
-
* @param hotkey - The hotkey
|
|
47
|
+
* @param hotkey - The hotkey to normalize
|
|
62
48
|
* @param platform - The target platform for resolving `Mod` (defaults to auto-detection)
|
|
63
|
-
* @returns The normalized hotkey string
|
|
64
49
|
*
|
|
65
50
|
* @example
|
|
66
51
|
* ```ts
|
|
67
52
|
* normalizeHotkey('shift+meta+e', 'mac') // 'Mod+Shift+E'
|
|
68
53
|
* normalizeHotkey('ctrl+a', 'windows') // 'Mod+A'
|
|
69
|
-
* normalizeHotkey('
|
|
54
|
+
* normalizeHotkey({ key: 's', mod: true }) // 'Mod+S'
|
|
55
|
+
* normalizeHotkey(['g', 'shift+g']) // ['G', 'Shift+G']
|
|
56
|
+
* normalizeHotkey(parseKeyboardEvent(event)) // The chord the event pressed
|
|
70
57
|
* ```
|
|
71
58
|
*/
|
|
72
|
-
export declare function normalizeHotkey(hotkey:
|
|
73
|
-
|
|
74
|
-
* Same canonical string as {@link normalizeHotkey}, but from an already-parsed hotkey.
|
|
75
|
-
*/
|
|
76
|
-
export declare function normalizeHotkeyFromParsed(parsed: ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): HotkeyChord;
|
|
77
|
-
/**
|
|
78
|
-
* Normalizes a chord (string or {@link RawHotkey} object) or a sequence to canonical form.
|
|
79
|
-
* Each sequence step is normalized, and a one-step sequence becomes its chord.
|
|
80
|
-
* Use this in framework adapters instead of branching on `formatHotkey(rawHotkeyToParsedHotkey(...))`.
|
|
81
|
-
*/
|
|
82
|
-
export declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkeyChord, platform?: 'mac' | 'windows' | 'linux'): HotkeyChord;
|
|
83
|
-
export declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
59
|
+
export declare function normalizeHotkey(hotkey: AnyChord, platform?: Platform): HotkeyChord;
|
|
60
|
+
export declare function normalizeHotkey(hotkey: AnyChord | ReadonlyArray<AnyChord>, platform?: Platform): Hotkey;
|
|
84
61
|
/**
|
|
85
62
|
* Checks if a string is a recognized modifier token (including aliases).
|
|
86
63
|
*
|
|
@@ -90,16 +67,14 @@ export declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey,
|
|
|
90
67
|
* @param key - Key name or alias (e.g. from a hotkey string or `event.key`)
|
|
91
68
|
* @returns True if the string is a recognized modifier
|
|
92
69
|
*/
|
|
93
|
-
export declare function isModifierKey(key: Key | (string & {})):
|
|
70
|
+
export declare function isModifierKey(key: Key | (string & {})): boolean;
|
|
94
71
|
/**
|
|
95
|
-
* Parses a KeyboardEvent into
|
|
96
|
-
*
|
|
97
|
-
* This function extracts the key and modifier state from a keyboard event
|
|
98
|
-
* and converts it into the same format used by `parseHotkey()`.
|
|
72
|
+
* Parses a KeyboardEvent into the chord it pressed, in the same format as
|
|
73
|
+
* `parseHotkey()`. Pass the result to `normalizeHotkey` for its canonical string.
|
|
99
74
|
*
|
|
100
75
|
* @param event - The KeyboardEvent to parse
|
|
101
76
|
* @param platform - The target platform for resolving modifiers (defaults to auto-detection)
|
|
102
|
-
* @returns
|
|
77
|
+
* @returns The parsed chord for the keyboard event
|
|
103
78
|
*
|
|
104
79
|
* @example
|
|
105
80
|
* ```ts
|
|
@@ -109,23 +84,16 @@ export declare function isModifierKey(key: Key | (string & {})): key is keyof ty
|
|
|
109
84
|
* })
|
|
110
85
|
* ```
|
|
111
86
|
*/
|
|
112
|
-
export declare function parseKeyboardEvent(event: KeyboardEvent, platform?:
|
|
87
|
+
export declare function parseKeyboardEvent(event: KeyboardEvent, platform?: Platform): ParsedHotkeyChord;
|
|
113
88
|
/**
|
|
114
|
-
*
|
|
89
|
+
* Checks if a chord contains at least one non-modifier key.
|
|
115
90
|
*
|
|
116
|
-
*
|
|
117
|
-
* @param platform - Target platform for `Mod` eligibility
|
|
118
|
-
*/
|
|
119
|
-
export declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): HotkeyChord;
|
|
120
|
-
/**
|
|
121
|
-
* Checks if a hotkey or ParsedHotkey contains at least one non-modifier key.
|
|
122
|
-
*
|
|
123
|
-
* This is useful for validating that a recorded hotkey is complete and not
|
|
91
|
+
* This is useful for validating that a recorded chord is complete and not
|
|
124
92
|
* just a combination of modifiers without an action key.
|
|
125
93
|
*
|
|
126
|
-
* @param
|
|
94
|
+
* @param chord - The chord string or parsed chord to check
|
|
127
95
|
* @param platform - The target platform for parsing (defaults to auto-detection)
|
|
128
|
-
* @returns True if the
|
|
96
|
+
* @returns True if the chord contains at least one non-modifier key
|
|
129
97
|
*
|
|
130
98
|
* @example
|
|
131
99
|
* ```ts
|
|
@@ -134,10 +102,5 @@ export declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?
|
|
|
134
102
|
* hasNonModifierKey(parseHotkey('Mod+A')) // true
|
|
135
103
|
* ```
|
|
136
104
|
*/
|
|
137
|
-
export declare function hasNonModifierKey(
|
|
138
|
-
/**
|
|
139
|
-
* Parses a string or raw binding, resolving Mod for the supplied platform.
|
|
140
|
-
* Physical tokens such as `[KeyQ]` retain `code`; they never become logical Q.
|
|
141
|
-
*/
|
|
142
|
-
export declare function parseRegisterableHotkey(hotkey: RegisterableHotkeyChord, platform?: "mac" | "windows" | "linux"): ParsedHotkey;
|
|
105
|
+
export declare function hasNonModifierKey(chord: HotkeyChord | ParsedHotkeyChord | (string & {}), platform?: Platform): boolean;
|
|
143
106
|
//#endregion
|
package/dist/parse.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MODIFIER_ORDER, normalizeKeyName } from "./constants.js";
|
|
2
2
|
import { detectPlatform, resolveModifier } from "./platform.js";
|
|
3
|
-
import { assertLogicalKey, normalizeKeyboardEvent,
|
|
3
|
+
import { assertLogicalKey, modifierAlias, normalizeKeyboardEvent, physicalCodeOf, splitChordParts } from "./_keyboard-event.js";
|
|
4
4
|
|
|
5
5
|
//#region src/parse.ts
|
|
6
|
+
/** Builds a ParsedHotkeyChord, listing held modifiers in canonical order. */
|
|
7
|
+
function toParsedChord(identity, ctrl, shift, alt, meta) {
|
|
8
|
+
const held = {
|
|
9
|
+
Control: ctrl,
|
|
10
|
+
Alt: alt,
|
|
11
|
+
Shift: shift,
|
|
12
|
+
Meta: meta
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
...identity,
|
|
16
|
+
ctrl,
|
|
17
|
+
shift,
|
|
18
|
+
alt,
|
|
19
|
+
meta,
|
|
20
|
+
modifiers: MODIFIER_ORDER.filter((modifier) => held[modifier])
|
|
21
|
+
};
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Whether a hotkey is a sequence. Use this instead of `Array.isArray`, which
|
|
8
25
|
* does not narrow readonly arrays.
|
|
@@ -13,29 +30,15 @@ function isHotkeySequence(hotkey) {
|
|
|
13
30
|
function simplifyHotkey(hotkey) {
|
|
14
31
|
return isHotkeySequence(hotkey) && hotkey.length === 1 ? hotkey[0] : hotkey;
|
|
15
32
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* @param hotkey - The hotkey string to parse (e.g., 'Mod+Shift+S')
|
|
20
|
-
* @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
|
|
21
|
-
* @returns A ParsedHotkey with either logical key or physical code and modifier flags
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* parseHotkey('Mod+S') // On Mac: { key: 'S', ctrl: false, shift: false, alt: false, meta: true, modifiers: ['Meta'] }
|
|
26
|
-
* parseHotkey('Mod+S') // On Windows: { key: 'S', ctrl: true, shift: false, alt: false, meta: false, modifiers: ['Control'] }
|
|
27
|
-
* parseHotkey('Control+Shift+A') // { key: 'A', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
function parseHotkey(hotkey, platform = detectPlatform()) {
|
|
31
|
-
const parts = splitHotkeyParts(hotkey);
|
|
33
|
+
function parseChordString(chord, platform) {
|
|
34
|
+
const parts = splitChordParts(chord);
|
|
32
35
|
const modifiers = /* @__PURE__ */ new Set();
|
|
33
36
|
let key = "";
|
|
34
37
|
for (let i = 0; i < parts.length; i++) {
|
|
35
38
|
const part = parts[i].trim();
|
|
36
39
|
if (i === parts.length - 1) key = normalizeKeyName(part);
|
|
37
40
|
else {
|
|
38
|
-
const alias =
|
|
41
|
+
const alias = modifierAlias(part);
|
|
39
42
|
if (alias) {
|
|
40
43
|
const resolved = resolveModifier(alias, platform);
|
|
41
44
|
modifiers.add(resolved);
|
|
@@ -43,71 +46,31 @@ function parseHotkey(hotkey, platform = detectPlatform()) {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
if (!key && parts.length > 0) key = normalizeKeyName(parts[parts.length - 1].trim());
|
|
46
|
-
const code =
|
|
47
|
-
return {
|
|
48
|
-
...code !== void 0 ? { code } : { key },
|
|
49
|
-
ctrl: modifiers.has("Control"),
|
|
50
|
-
shift: modifiers.has("Shift"),
|
|
51
|
-
alt: modifiers.has("Alt"),
|
|
52
|
-
meta: modifiers.has("Meta"),
|
|
53
|
-
modifiers: MODIFIER_ORDER.filter((m) => modifiers.has(m))
|
|
54
|
-
};
|
|
49
|
+
const code = physicalCodeOf(key);
|
|
50
|
+
return toParsedChord(code !== void 0 ? { code } : { key }, modifiers.has("Control"), modifiers.has("Shift"), modifiers.has("Alt"), modifiers.has("Meta"));
|
|
55
51
|
}
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```ts
|
|
67
|
-
* rawHotkeyToParsedHotkey({ key: 'Escape' })
|
|
68
|
-
* // { key: 'Escape', ctrl: false, shift: false, alt: false, meta: false, modifiers: [] }
|
|
69
|
-
*
|
|
70
|
-
* rawHotkeyToParsedHotkey({ key: 'S', mod: true }, 'mac')
|
|
71
|
-
* // { key: 'S', ctrl: false, shift: false, alt: false, meta: true, modifiers: ['Meta'] }
|
|
72
|
-
*
|
|
73
|
-
* rawHotkeyToParsedHotkey({ key: 'S', mod: true, shift: true }, 'windows')
|
|
74
|
-
* // { key: 'S', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
|
|
78
|
-
if (raw.code === void 0) assertLogicalKey(raw.key);
|
|
79
|
-
let ctrl = raw.ctrl ?? false;
|
|
80
|
-
const shift = raw.shift ?? false;
|
|
81
|
-
const alt = raw.alt ?? false;
|
|
82
|
-
let meta = raw.meta ?? false;
|
|
83
|
-
if (raw.mod) {
|
|
52
|
+
/** Resolves a chord object's modifiers. Omitted modifiers are false. */
|
|
53
|
+
function parseChordObject(chord, platform) {
|
|
54
|
+
if (chord.code === void 0) assertLogicalKey(chord.key);
|
|
55
|
+
let ctrl = chord.ctrl ?? false;
|
|
56
|
+
const shift = chord.shift ?? false;
|
|
57
|
+
const alt = chord.alt ?? false;
|
|
58
|
+
let meta = chord.meta ?? false;
|
|
59
|
+
if (chord.mod) {
|
|
84
60
|
if (resolveModifier("Mod", platform) === "Control") ctrl = true;
|
|
85
61
|
else meta = true;
|
|
86
62
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
case "Alt": return alt;
|
|
92
|
-
case "Meta": return meta;
|
|
93
|
-
default: return false;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
return {
|
|
97
|
-
...raw.code !== void 0 ? { code: raw.code } : { key: normalizeKeyName(raw.key) },
|
|
98
|
-
ctrl,
|
|
99
|
-
shift,
|
|
100
|
-
alt,
|
|
101
|
-
meta,
|
|
102
|
-
modifiers
|
|
103
|
-
};
|
|
63
|
+
return toParsedChord(chord.code !== void 0 ? { code: chord.code } : { key: normalizeKeyName(chord.key) }, ctrl, shift, alt, meta);
|
|
64
|
+
}
|
|
65
|
+
function parseChord(chord, platform) {
|
|
66
|
+
return typeof chord === "string" ? parseChordString(chord, platform) : parseChordObject(chord, platform);
|
|
104
67
|
}
|
|
105
68
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
69
|
+
* The canonical string for a parsed chord: `Mod` first when the platform
|
|
70
|
+
* allows, then `Alt`, then `Shift`; otherwise full `Control+Alt+Shift+Meta`
|
|
71
|
+
* order.
|
|
109
72
|
*/
|
|
110
|
-
function
|
|
73
|
+
function normalizeParsedChord(parsed, platform) {
|
|
111
74
|
if (parsed.code === void 0) assertLogicalKey(parsed.key);
|
|
112
75
|
const canUseMod = platform === "mac" ? parsed.meta && !parsed.ctrl : parsed.ctrl && !parsed.meta;
|
|
113
76
|
const parts = [];
|
|
@@ -119,41 +82,15 @@ function normalizedHotkeyStringFromParsed(parsed, platform) {
|
|
|
119
82
|
parts.push(parsed.code !== void 0 ? `[${parsed.code}]` : normalizeKeyName(parsed.key));
|
|
120
83
|
return parts.join("+");
|
|
121
84
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*
|
|
125
|
-
* - When `Mod` is allowed for the platform (Command on Mac without Control;
|
|
126
|
-
* Control on Windows/Linux without Meta): emits `Mod`, then `Alt`, then `Shift`,
|
|
127
|
-
* then the key (e.g. `Mod+Shift+E`, `Mod+S`).
|
|
128
|
-
* - Otherwise: literal modifiers in `Control+Alt+Shift+Meta` order, then the key.
|
|
129
|
-
* - Resolves aliases and normalizes key casing (e.g. `esc` → `Escape`, `a` → `A`).
|
|
130
|
-
*
|
|
131
|
-
* @param hotkey - The hotkey string to normalize
|
|
132
|
-
* @param platform - The target platform for resolving `Mod` (defaults to auto-detection)
|
|
133
|
-
* @returns The normalized hotkey string
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```ts
|
|
137
|
-
* normalizeHotkey('shift+meta+e', 'mac') // 'Mod+Shift+E'
|
|
138
|
-
* normalizeHotkey('ctrl+a', 'windows') // 'Mod+A'
|
|
139
|
-
* normalizeHotkey('esc') // 'Escape'
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
function normalizeHotkey(hotkey, platform = detectPlatform()) {
|
|
143
|
-
return normalizedHotkeyStringFromParsed(parseHotkey(hotkey, platform), platform);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Same canonical string as {@link normalizeHotkey}, but from an already-parsed hotkey.
|
|
147
|
-
*/
|
|
148
|
-
function normalizeHotkeyFromParsed(parsed, platform = detectPlatform()) {
|
|
149
|
-
return normalizedHotkeyStringFromParsed(parsed, platform);
|
|
85
|
+
function parseHotkey(hotkey, platform = detectPlatform()) {
|
|
86
|
+
return Array.isArray(hotkey) ? hotkey.map((step) => parseChord(step, platform)) : parseChord(hotkey, platform);
|
|
150
87
|
}
|
|
151
|
-
function
|
|
152
|
-
if (
|
|
153
|
-
const steps = hotkey.map((step) =>
|
|
88
|
+
function normalizeHotkey(hotkey, platform = detectPlatform()) {
|
|
89
|
+
if (Array.isArray(hotkey)) {
|
|
90
|
+
const steps = hotkey.map((step) => normalizeParsedChord(parseChord(step, platform), platform));
|
|
154
91
|
return steps.length === 1 ? steps[0] : steps;
|
|
155
92
|
}
|
|
156
|
-
return
|
|
93
|
+
return normalizeParsedChord(parseChord(hotkey, platform), platform);
|
|
157
94
|
}
|
|
158
95
|
/**
|
|
159
96
|
* Checks if a string is a recognized modifier token (including aliases).
|
|
@@ -165,17 +102,15 @@ function normalizeRegisterableHotkey(hotkey, platform = detectPlatform()) {
|
|
|
165
102
|
* @returns True if the string is a recognized modifier
|
|
166
103
|
*/
|
|
167
104
|
function isModifierKey(key) {
|
|
168
|
-
return key
|
|
105
|
+
return modifierAlias(key) !== void 0;
|
|
169
106
|
}
|
|
170
107
|
/**
|
|
171
|
-
* Parses a KeyboardEvent into
|
|
172
|
-
*
|
|
173
|
-
* This function extracts the key and modifier state from a keyboard event
|
|
174
|
-
* and converts it into the same format used by `parseHotkey()`.
|
|
108
|
+
* Parses a KeyboardEvent into the chord it pressed, in the same format as
|
|
109
|
+
* `parseHotkey()`. Pass the result to `normalizeHotkey` for its canonical string.
|
|
175
110
|
*
|
|
176
111
|
* @param event - The KeyboardEvent to parse
|
|
177
112
|
* @param platform - The target platform for resolving modifiers (defaults to auto-detection)
|
|
178
|
-
* @returns
|
|
113
|
+
* @returns The parsed chord for the keyboard event
|
|
179
114
|
*
|
|
180
115
|
* @example
|
|
181
116
|
* ```ts
|
|
@@ -190,38 +125,17 @@ function parseKeyboardEvent(event, platform) {
|
|
|
190
125
|
const ctrl = normalized.altGraph ? false : normalized.ctrl;
|
|
191
126
|
const alt = normalized.altGraph ? false : normalized.alt;
|
|
192
127
|
const shift = normalized.shift;
|
|
193
|
-
|
|
194
|
-
if (ctrl) modifiers.push("Control");
|
|
195
|
-
if (alt) modifiers.push("Alt");
|
|
196
|
-
if (shift) modifiers.push("Shift");
|
|
197
|
-
if (normalized.meta) modifiers.push("Meta");
|
|
198
|
-
return {
|
|
199
|
-
key: normalized.key,
|
|
200
|
-
ctrl,
|
|
201
|
-
shift,
|
|
202
|
-
alt,
|
|
203
|
-
meta: normalized.meta,
|
|
204
|
-
modifiers
|
|
205
|
-
};
|
|
128
|
+
return toParsedChord({ key: normalized.key }, ctrl, shift, alt, normalized.meta);
|
|
206
129
|
}
|
|
207
130
|
/**
|
|
208
|
-
*
|
|
131
|
+
* Checks if a chord contains at least one non-modifier key.
|
|
209
132
|
*
|
|
210
|
-
*
|
|
211
|
-
* @param platform - Target platform for `Mod` eligibility
|
|
212
|
-
*/
|
|
213
|
-
function normalizeHotkeyFromEvent(event, platform = detectPlatform()) {
|
|
214
|
-
return normalizedHotkeyStringFromParsed(parseKeyboardEvent(event, platform), platform);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Checks if a hotkey or ParsedHotkey contains at least one non-modifier key.
|
|
218
|
-
*
|
|
219
|
-
* This is useful for validating that a recorded hotkey is complete and not
|
|
133
|
+
* This is useful for validating that a recorded chord is complete and not
|
|
220
134
|
* just a combination of modifiers without an action key.
|
|
221
135
|
*
|
|
222
|
-
* @param
|
|
136
|
+
* @param chord - The chord string or parsed chord to check
|
|
223
137
|
* @param platform - The target platform for parsing (defaults to auto-detection)
|
|
224
|
-
* @returns True if the
|
|
138
|
+
* @returns True if the chord contains at least one non-modifier key
|
|
225
139
|
*
|
|
226
140
|
* @example
|
|
227
141
|
* ```ts
|
|
@@ -230,18 +144,11 @@ function normalizeHotkeyFromEvent(event, platform = detectPlatform()) {
|
|
|
230
144
|
* hasNonModifierKey(parseHotkey('Mod+A')) // true
|
|
231
145
|
* ```
|
|
232
146
|
*/
|
|
233
|
-
function hasNonModifierKey(
|
|
234
|
-
const parsed = typeof
|
|
147
|
+
function hasNonModifierKey(chord, platform = detectPlatform()) {
|
|
148
|
+
const parsed = typeof chord === "string" ? parseChordString(chord, platform) : chord;
|
|
235
149
|
if (parsed.code !== void 0) return parsed.code.length > 0 && !/^(Control|Shift|Alt|Meta)(Left|Right)$/.test(parsed.code);
|
|
236
150
|
return !isModifierKey(parsed.key) && parsed.key.length > 0;
|
|
237
151
|
}
|
|
238
|
-
/**
|
|
239
|
-
* Parses a string or raw binding, resolving Mod for the supplied platform.
|
|
240
|
-
* Physical tokens such as `[KeyQ]` retain `code`; they never become logical Q.
|
|
241
|
-
*/
|
|
242
|
-
function parseRegisterableHotkey(hotkey, platform = detectPlatform()) {
|
|
243
|
-
return typeof hotkey === "string" ? parseHotkey(hotkey, platform) : rawHotkeyToParsedHotkey(hotkey, platform);
|
|
244
|
-
}
|
|
245
152
|
|
|
246
153
|
//#endregion
|
|
247
|
-
export { hasNonModifierKey, isHotkeySequence, isModifierKey, normalizeHotkey,
|
|
154
|
+
export { hasNonModifierKey, isHotkeySequence, isModifierKey, normalizeHotkey, parseHotkey, parseKeyboardEvent, simplifyHotkey };
|
package/dist/validate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ALL_KEYS,
|
|
2
|
-
import {
|
|
1
|
+
import { ALL_KEYS, normalizeKeyName } from "./constants.js";
|
|
2
|
+
import { modifierAlias, physicalCodeOf, splitChordParts } from "./_keyboard-event.js";
|
|
3
3
|
|
|
4
4
|
//#region src/validate.ts
|
|
5
5
|
/**
|
|
@@ -52,7 +52,7 @@ function validateChord(hotkey) {
|
|
|
52
52
|
warnings: [],
|
|
53
53
|
errors: ["Hotkey cannot be empty"]
|
|
54
54
|
};
|
|
55
|
-
const parts =
|
|
55
|
+
const parts = splitChordParts(hotkey).map((p) => p.trim());
|
|
56
56
|
if (parts.length === 0 || parts.some((p) => p === "")) return {
|
|
57
57
|
valid: false,
|
|
58
58
|
warnings: [],
|
|
@@ -60,34 +60,18 @@ function validateChord(hotkey) {
|
|
|
60
60
|
};
|
|
61
61
|
const modifierParts = parts.slice(0, -1);
|
|
62
62
|
const keyPart = parts[parts.length - 1];
|
|
63
|
-
for (const modifier of modifierParts) if (!(
|
|
64
|
-
if (keyPart.length > 1 && (keyPart.startsWith("[") || keyPart.endsWith("]")) &&
|
|
65
|
-
if (!isKnownKey(normalizeKeyName(keyPart))
|
|
63
|
+
for (const modifier of modifierParts) if (!modifierAlias(modifier)) errors.push(`Unknown modifier: '${modifier}'`);
|
|
64
|
+
if (keyPart.length > 1 && (keyPart.startsWith("[") || keyPart.endsWith("]")) && physicalCodeOf(keyPart) === void 0) errors.push("Invalid physical key: use a bracketed KeyboardEvent.code, such as [KeyS]");
|
|
65
|
+
if (!isKnownKey(normalizeKeyName(keyPart))) warnings.push(`Unknown key: '${keyPart}'. This may still work but won't have type-safe autocomplete.`);
|
|
66
66
|
return {
|
|
67
67
|
valid: errors.length === 0,
|
|
68
68
|
warnings,
|
|
69
69
|
errors
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Checks if a key is in the known keys set.
|
|
74
|
-
*/
|
|
72
|
+
/** Physical codes, single characters, and named keys are known. */
|
|
75
73
|
function isKnownKey(key) {
|
|
76
|
-
|
|
77
|
-
if (Array.from(key).length === 1) return true;
|
|
78
|
-
if (ALL_KEYS.has(key)) return true;
|
|
79
|
-
if (key.length === 1 && ALL_KEYS.has(key.toUpperCase())) return true;
|
|
80
|
-
return key in {
|
|
81
|
-
Esc: true,
|
|
82
|
-
Return: true,
|
|
83
|
-
Space: true,
|
|
84
|
-
" ": true,
|
|
85
|
-
Del: true,
|
|
86
|
-
Up: true,
|
|
87
|
-
Down: true,
|
|
88
|
-
Left: true,
|
|
89
|
-
Right: true
|
|
90
|
-
};
|
|
74
|
+
return physicalCodeOf(key) !== void 0 || Array.from(key).length === 1 || ALL_KEYS.has(key);
|
|
91
75
|
}
|
|
92
76
|
/**
|
|
93
77
|
* Validates a hotkey and throws an error if invalid.
|
package/package.json
CHANGED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Hotkey, ParsedHotkey } from "./hotkey.types.js";
|
|
2
|
-
import { HotkeyConflict, HotkeyConflictOptions } from "./conflicts.js";
|
|
3
|
-
//#region src/recorder-options.d.ts
|
|
4
|
-
/** Event property used to record a binding. Physical codes are the default. */
|
|
5
|
-
export type RecorderKeyMode = 'code' | 'key';
|
|
6
|
-
export interface RecorderRejection {
|
|
7
|
-
reason: 'missing-code' | 'alt-graph' | 'invalid' | 'conflict' | 'validation';
|
|
8
|
-
message: string;
|
|
9
|
-
/** The rejected chord or sequence, when one was recorded. */
|
|
10
|
-
hotkey?: Hotkey;
|
|
11
|
-
conflicts?: Array<HotkeyConflict>;
|
|
12
|
-
}
|
|
13
|
-
export interface RecorderOptions {
|
|
14
|
-
/** Physical code recording is the default. Choose key to record logical characters. */
|
|
15
|
-
recordBy?: RecorderKeyMode;
|
|
16
|
-
/** Platform used for Mod and AltGraph handling. Defaults to platform detection. */
|
|
17
|
-
platform?: 'mac' | 'windows' | 'linux';
|
|
18
|
-
/** Opt in to live-registry conflicts, including sequence prefixes. Default: false. */
|
|
19
|
-
detectConflicts?: boolean | HotkeyConflictOptions;
|
|
20
|
-
/** Rejected candidates leave recording active. */
|
|
21
|
-
onReject?: (rejection: RecorderRejection) => void;
|
|
22
|
-
}
|
|
23
|
-
/** Validation context for a recorded chord or sequence. */
|
|
24
|
-
export interface HotkeyRecorderValidationContext {
|
|
25
|
-
/** The event that recorded the last chord. */
|
|
26
|
-
event: KeyboardEvent;
|
|
27
|
-
/** The last chord, parsed. */
|
|
28
|
-
parsedHotkey: ParsedHotkey;
|
|
29
|
-
/** The event that recorded each step. */
|
|
30
|
-
events: ReadonlyArray<KeyboardEvent>;
|
|
31
|
-
/** Every step, parsed. */
|
|
32
|
-
parsedSequence: ReadonlyArray<ParsedHotkey>;
|
|
33
|
-
}
|
|
34
|
-
//#endregion
|