@tanstack/hotkeys 0.8.0 → 0.10.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 +15 -1
- package/dist/{manager.utils.js → _event-target.js} +2 -41
- package/dist/_keyboard-event.js +75 -0
- package/dist/_match.js +48 -0
- package/dist/_named-keys.d.ts +6 -0
- package/dist/_named-keys.js +65 -0
- package/dist/_recorder-chord.js +42 -0
- package/dist/_recording-guard.js +55 -0
- package/dist/_registration.js +49 -0
- package/dist/conflicts.d.ts +36 -0
- package/dist/conflicts.js +54 -0
- package/dist/constants.d.ts +16 -144
- package/dist/constants.js +55 -172
- package/dist/display-labels.d.ts +90 -0
- package/dist/display-labels.js +112 -0
- package/dist/format.d.ts +48 -12
- package/dist/format.js +35 -39
- package/dist/hint.d.ts +20 -0
- package/dist/hint.js +23 -0
- package/dist/hotkey-manager.d.ts +14 -12
- package/dist/hotkey-manager.js +70 -21
- package/dist/hotkey-recorder.d.ts +8 -8
- package/dist/hotkey-recorder.js +74 -15
- package/dist/hotkey-sequence-recorder.d.ts +13 -9
- package/dist/hotkey-sequence-recorder.js +90 -28
- package/dist/{hotkey.d.ts → hotkey.types.d.ts} +58 -126
- package/dist/index.d.ts +14 -9
- package/dist/index.js +10 -6
- package/dist/key-state-tracker.d.ts +4 -7
- package/dist/key-state-tracker.js +30 -27
- package/dist/key.types.d.ts +79 -0
- package/dist/match.d.ts +24 -11
- package/dist/match.js +19 -30
- package/dist/parse.d.ts +18 -15
- package/dist/parse.js +39 -22
- package/dist/platform.d.ts +41 -0
- package/dist/platform.js +52 -0
- package/dist/recorder-options.d.ts +31 -0
- package/dist/sequence-manager.d.ts +27 -15
- package/dist/sequence-manager.js +61 -30
- package/dist/validate.d.ts +16 -8
- package/dist/validate.js +8 -13
- package/package.json +8 -13
- package/dist/constants.cjs +0 -535
- package/dist/constants.cjs.map +0 -1
- package/dist/constants.d.cts +0 -290
- package/dist/constants.js.map +0 -1
- package/dist/format.cjs +0 -91
- package/dist/format.cjs.map +0 -1
- package/dist/format.d.cts +0 -60
- package/dist/format.js.map +0 -1
- package/dist/hotkey-manager.cjs +0 -383
- package/dist/hotkey-manager.cjs.map +0 -1
- package/dist/hotkey-manager.d.cts +0 -230
- package/dist/hotkey-manager.js.map +0 -1
- package/dist/hotkey-recorder.cjs +0 -179
- package/dist/hotkey-recorder.cjs.map +0 -1
- package/dist/hotkey-recorder.d.cts +0 -116
- package/dist/hotkey-recorder.js.map +0 -1
- package/dist/hotkey-sequence-recorder.cjs +0 -192
- package/dist/hotkey-sequence-recorder.cjs.map +0 -1
- package/dist/hotkey-sequence-recorder.d.cts +0 -79
- package/dist/hotkey-sequence-recorder.js.map +0 -1
- package/dist/hotkey.d.cts +0 -301
- package/dist/index.cjs +0 -63
- package/dist/index.d.cts +0 -13
- package/dist/key-state-tracker.cjs +0 -197
- package/dist/key-state-tracker.cjs.map +0 -1
- package/dist/key-state-tracker.d.cts +0 -107
- package/dist/key-state-tracker.js.map +0 -1
- package/dist/manager.utils.cjs +0 -115
- package/dist/manager.utils.cjs.map +0 -1
- package/dist/manager.utils.d.cts +0 -13
- package/dist/manager.utils.d.ts +0 -13
- package/dist/manager.utils.js.map +0 -1
- package/dist/match.cjs +0 -153
- package/dist/match.cjs.map +0 -1
- package/dist/match.d.cts +0 -85
- package/dist/match.js.map +0 -1
- package/dist/parse.cjs +0 -228
- package/dist/parse.cjs.map +0 -1
- package/dist/parse.d.cts +0 -128
- package/dist/parse.js.map +0 -1
- package/dist/recorder-chord.cjs +0 -18
- package/dist/recorder-chord.cjs.map +0 -1
- package/dist/recorder-chord.js +0 -18
- package/dist/recorder-chord.js.map +0 -1
- package/dist/sequence-manager.cjs +0 -415
- package/dist/sequence-manager.cjs.map +0 -1
- package/dist/sequence-manager.d.cts +0 -157
- package/dist/sequence-manager.js.map +0 -1
- package/dist/validate.cjs +0 -116
- package/dist/validate.cjs.map +0 -1
- package/dist/validate.d.cts +0 -56
- package/dist/validate.js.map +0 -1
- package/src/constants.ts +0 -603
- package/src/format.ts +0 -142
- package/src/hotkey-manager.ts +0 -759
- package/src/hotkey-recorder.ts +0 -272
- package/src/hotkey-sequence-recorder.ts +0 -328
- package/src/hotkey.ts +0 -436
- package/src/index.ts +0 -11
- package/src/key-state-tracker.ts +0 -249
- package/src/manager.utils.ts +0 -231
- package/src/match.ts +0 -259
- package/src/parse.ts +0 -328
- package/src/recorder-chord.ts +0 -25
- package/src/sequence-manager.ts +0 -734
- package/src/validate.ts +0 -171
package/dist/match.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { detectPlatform } from "./platform.js";
|
|
2
|
+
import { keysEqual } from "./_keyboard-event.js";
|
|
3
|
+
import { parseHotkey, parseRegisterableHotkey } from "./parse.js";
|
|
4
|
+
import { matchKeyboardEvent } from "./_match.js";
|
|
3
5
|
|
|
4
6
|
//#region src/match.ts
|
|
5
7
|
/**
|
|
6
8
|
* Checks if a KeyboardEvent matches a hotkey.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* Physical bindings such as `Mod+[KeyS]` match `event.code` exactly.
|
|
11
|
+
* Logical bindings use `event.key`, with a fallback to `code`
|
|
9
12
|
* for letter keys, digit keys (0-9), and punctuation keys when `key` produces special
|
|
10
13
|
* characters (e.g., macOS Option+letter, Shift+number, or Option+punctuation).
|
|
11
14
|
* Letter keys are matched case-insensitively.
|
|
@@ -31,30 +34,7 @@ import { parseHotkey } from "./parse.js";
|
|
|
31
34
|
* ```
|
|
32
35
|
*/
|
|
33
36
|
function matchesKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
34
|
-
|
|
35
|
-
if (event.ctrlKey !== parsed.ctrl) return false;
|
|
36
|
-
if (event.shiftKey !== parsed.shift) return false;
|
|
37
|
-
if (event.altKey !== parsed.alt) return false;
|
|
38
|
-
if (event.metaKey !== parsed.meta) return false;
|
|
39
|
-
const eventKey = normalizeKeyName(event.key);
|
|
40
|
-
const hotkeyKey = parsed.key;
|
|
41
|
-
if (eventKey !== "Dead" && eventKey.length === 1 && hotkeyKey.length === 1) {
|
|
42
|
-
if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) return true;
|
|
43
|
-
if (isSingleLetterKey(eventKey) && (/^[A-Za-z]$/.test(eventKey) || !event.altKey)) return false;
|
|
44
|
-
}
|
|
45
|
-
if (event.code && (eventKey === "Dead" || eventKey.length === 1 && hotkeyKey.length === 1)) {
|
|
46
|
-
if (event.code.startsWith("Key")) {
|
|
47
|
-
const codeLetter = event.code.slice(3);
|
|
48
|
-
if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) return codeLetter.toUpperCase() === hotkeyKey.toUpperCase();
|
|
49
|
-
}
|
|
50
|
-
if (event.code.startsWith("Digit")) {
|
|
51
|
-
const codeDigit = event.code.slice(5);
|
|
52
|
-
if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) return codeDigit === hotkeyKey;
|
|
53
|
-
}
|
|
54
|
-
if (event.code in PUNCTUATION_CODE_MAP) return PUNCTUATION_CODE_MAP[event.code] === hotkeyKey;
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
return eventKey === hotkeyKey;
|
|
37
|
+
return matchKeyboardEvent(event, hotkey, platform).matched;
|
|
58
38
|
}
|
|
59
39
|
/**
|
|
60
40
|
* Creates a keyboard event handler that calls the callback when the hotkey matches.
|
|
@@ -142,10 +122,19 @@ function formatParsedHotkey(parsed) {
|
|
|
142
122
|
if (parsed.alt) parts.push("Alt");
|
|
143
123
|
if (parsed.shift) parts.push("Shift");
|
|
144
124
|
if (parsed.meta) parts.push("Meta");
|
|
145
|
-
parts.push(parsed.key);
|
|
125
|
+
parts.push(parsed.code !== void 0 ? `[${parsed.code}]` : parsed.key);
|
|
146
126
|
return parts.join("+");
|
|
147
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Compares normalized binding identity, including all four modifiers.
|
|
130
|
+
* Aliases resolve on the supplied platform. A physical code and a logical key
|
|
131
|
+
* remain distinct even when one keyboard event could match both.
|
|
132
|
+
*/
|
|
133
|
+
function areHotkeysEqual(left, right, platform = detectPlatform()) {
|
|
134
|
+
const a = parseRegisterableHotkey(left, platform);
|
|
135
|
+
const b = parseRegisterableHotkey(right, platform);
|
|
136
|
+
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));
|
|
137
|
+
}
|
|
148
138
|
|
|
149
139
|
//#endregion
|
|
150
|
-
export { createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
|
|
151
|
-
//# sourceMappingURL=match.js.map
|
|
140
|
+
export { areHotkeysEqual, createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
|
package/dist/parse.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Key } from "./key.types.js";
|
|
2
2
|
import { MODIFIER_ALIASES } from "./constants.js";
|
|
3
|
-
|
|
3
|
+
import { Hotkey, ParsedHotkey, RawHotkey, RegisterableHotkey } from "./hotkey.types.js";
|
|
4
4
|
//#region src/parse.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Parses a hotkey string into its component parts.
|
|
7
7
|
*
|
|
8
8
|
* @param hotkey - The hotkey string to parse (e.g., 'Mod+Shift+S')
|
|
9
9
|
* @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
|
|
10
|
-
* @returns A ParsedHotkey
|
|
10
|
+
* @returns A ParsedHotkey with either logical key or physical code and modifier flags
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```ts
|
|
@@ -16,7 +16,7 @@ import { MODIFIER_ALIASES } from "./constants.js";
|
|
|
16
16
|
* parseHotkey('Control+Shift+A') // { key: 'A', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
declare function parseHotkey(hotkey: Hotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
|
|
19
|
+
export declare function parseHotkey(hotkey: Hotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
|
|
20
20
|
/**
|
|
21
21
|
* Converts a RawHotkey object to a ParsedHotkey.
|
|
22
22
|
* Optional modifier booleans default to false; modifiers array is derived from them.
|
|
@@ -38,7 +38,7 @@ declare function parseHotkey(hotkey: Hotkey | (string & {}), platform?: 'mac' |
|
|
|
38
38
|
* // { key: 'S', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
|
|
41
|
+
export declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
|
|
42
42
|
/**
|
|
43
43
|
* Normalizes a hotkey string to its canonical form.
|
|
44
44
|
*
|
|
@@ -59,16 +59,16 @@ declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'win
|
|
|
59
59
|
* normalizeHotkey('esc') // 'Escape'
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
|
-
declare function normalizeHotkey(hotkey: Key | (string & {}), platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
62
|
+
export declare function normalizeHotkey(hotkey: Key | (string & {}), platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
63
63
|
/**
|
|
64
64
|
* Same canonical string as {@link normalizeHotkey}, but from an already-parsed hotkey.
|
|
65
65
|
*/
|
|
66
|
-
declare function normalizeHotkeyFromParsed(parsed: ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
66
|
+
export declare function normalizeHotkeyFromParsed(parsed: ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
67
67
|
/**
|
|
68
68
|
* Normalizes a string or {@link RawHotkey} object to the same canonical hotkey string.
|
|
69
69
|
* Use this in framework adapters instead of branching on `formatHotkey(rawHotkeyToParsedHotkey(...))`.
|
|
70
70
|
*/
|
|
71
|
-
declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
71
|
+
export declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
72
72
|
/**
|
|
73
73
|
* Checks if a string is a recognized modifier token (including aliases).
|
|
74
74
|
*
|
|
@@ -78,7 +78,7 @@ declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platfor
|
|
|
78
78
|
* @param key - Key name or alias (e.g. from a hotkey string or `event.key`)
|
|
79
79
|
* @returns True if the string is a recognized modifier
|
|
80
80
|
*/
|
|
81
|
-
declare function isModifierKey(key: Key | (string & {})): key is keyof typeof MODIFIER_ALIASES;
|
|
81
|
+
export declare function isModifierKey(key: Key | (string & {})): key is keyof typeof MODIFIER_ALIASES;
|
|
82
82
|
/**
|
|
83
83
|
* Parses a KeyboardEvent into a ParsedHotkey object.
|
|
84
84
|
*
|
|
@@ -97,14 +97,14 @@ declare function isModifierKey(key: Key | (string & {})): key is keyof typeof MO
|
|
|
97
97
|
* })
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
|
100
|
-
declare function parseKeyboardEvent(event: KeyboardEvent): ParsedHotkey;
|
|
100
|
+
export declare function parseKeyboardEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
|
|
101
101
|
/**
|
|
102
102
|
* Normalizes a keyboard event to the same canonical hotkey string as {@link normalizeHotkey}.
|
|
103
103
|
*
|
|
104
104
|
* @param event - The keyboard event (typically `keydown`)
|
|
105
105
|
* @param platform - Target platform for `Mod` eligibility
|
|
106
106
|
*/
|
|
107
|
-
declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
107
|
+
export declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
|
|
108
108
|
/**
|
|
109
109
|
* Checks if a hotkey or ParsedHotkey contains at least one non-modifier key.
|
|
110
110
|
*
|
|
@@ -122,7 +122,10 @@ declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac'
|
|
|
122
122
|
* hasNonModifierKey(parseHotkey('Mod+A')) // true
|
|
123
123
|
* ```
|
|
124
124
|
*/
|
|
125
|
-
declare function hasNonModifierKey(hotkey: Hotkey | ParsedHotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): boolean;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
export declare function hasNonModifierKey(hotkey: Hotkey | ParsedHotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Parses a string or raw binding, resolving Mod for the supplied platform.
|
|
128
|
+
* Physical tokens such as `[KeyQ]` retain `code`; they never become logical Q.
|
|
129
|
+
*/
|
|
130
|
+
export declare function parseRegisterableHotkey(hotkey: RegisterableHotkey, platform?: "mac" | "windows" | "linux"): ParsedHotkey;
|
|
131
|
+
//#endregion
|
package/dist/parse.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { MODIFIER_ALIASES, MODIFIER_ORDER,
|
|
1
|
+
import { MODIFIER_ALIASES, MODIFIER_ORDER, normalizeKeyName } from "./constants.js";
|
|
2
|
+
import { detectPlatform, resolveModifier } from "./platform.js";
|
|
3
|
+
import { assertLogicalKey, normalizeKeyboardEvent, splitHotkeyParts } from "./_keyboard-event.js";
|
|
2
4
|
|
|
3
5
|
//#region src/parse.ts
|
|
4
6
|
/**
|
|
@@ -6,7 +8,7 @@ import { MODIFIER_ALIASES, MODIFIER_ORDER, detectPlatform, normalizeKeyName, res
|
|
|
6
8
|
*
|
|
7
9
|
* @param hotkey - The hotkey string to parse (e.g., 'Mod+Shift+S')
|
|
8
10
|
* @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
|
|
9
|
-
* @returns A ParsedHotkey
|
|
11
|
+
* @returns A ParsedHotkey with either logical key or physical code and modifier flags
|
|
10
12
|
*
|
|
11
13
|
* @example
|
|
12
14
|
* ```ts
|
|
@@ -16,7 +18,7 @@ import { MODIFIER_ALIASES, MODIFIER_ORDER, detectPlatform, normalizeKeyName, res
|
|
|
16
18
|
* ```
|
|
17
19
|
*/
|
|
18
20
|
function parseHotkey(hotkey, platform = detectPlatform()) {
|
|
19
|
-
const parts = hotkey
|
|
21
|
+
const parts = splitHotkeyParts(hotkey);
|
|
20
22
|
const modifiers = /* @__PURE__ */ new Set();
|
|
21
23
|
let key = "";
|
|
22
24
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -31,8 +33,9 @@ function parseHotkey(hotkey, platform = detectPlatform()) {
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
if (!key && parts.length > 0) key = normalizeKeyName(parts[parts.length - 1].trim());
|
|
36
|
+
const code = /^\[([A-Za-z][A-Za-z0-9]*)\]$/.exec(key)?.[1];
|
|
34
37
|
return {
|
|
35
|
-
key,
|
|
38
|
+
...code !== void 0 ? { code } : { key },
|
|
36
39
|
ctrl: modifiers.has("Control"),
|
|
37
40
|
shift: modifiers.has("Shift"),
|
|
38
41
|
alt: modifiers.has("Alt"),
|
|
@@ -62,12 +65,15 @@ function parseHotkey(hotkey, platform = detectPlatform()) {
|
|
|
62
65
|
* ```
|
|
63
66
|
*/
|
|
64
67
|
function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
|
|
68
|
+
if (raw.code === void 0) assertLogicalKey(raw.key);
|
|
65
69
|
let ctrl = raw.ctrl ?? false;
|
|
66
70
|
const shift = raw.shift ?? false;
|
|
67
71
|
const alt = raw.alt ?? false;
|
|
68
72
|
let meta = raw.meta ?? false;
|
|
69
|
-
if (raw.mod)
|
|
70
|
-
|
|
73
|
+
if (raw.mod) {
|
|
74
|
+
if (resolveModifier("Mod", platform) === "Control") ctrl = true;
|
|
75
|
+
else meta = true;
|
|
76
|
+
}
|
|
71
77
|
const modifiers = MODIFIER_ORDER.filter((m) => {
|
|
72
78
|
switch (m) {
|
|
73
79
|
case "Control": return ctrl;
|
|
@@ -78,7 +84,7 @@ function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
|
|
|
78
84
|
}
|
|
79
85
|
});
|
|
80
86
|
return {
|
|
81
|
-
key: raw.key,
|
|
87
|
+
...raw.code !== void 0 ? { code: raw.code } : { key: normalizeKeyName(raw.key) },
|
|
82
88
|
ctrl,
|
|
83
89
|
shift,
|
|
84
90
|
alt,
|
|
@@ -92,6 +98,7 @@ function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
|
|
|
92
98
|
* otherwise full `Control+Alt+Shift+Meta` order.
|
|
93
99
|
*/
|
|
94
100
|
function normalizedHotkeyStringFromParsed(parsed, platform) {
|
|
101
|
+
if (parsed.code === void 0) assertLogicalKey(parsed.key);
|
|
95
102
|
const canUseMod = platform === "mac" ? parsed.meta && !parsed.ctrl : parsed.ctrl && !parsed.meta;
|
|
96
103
|
const parts = [];
|
|
97
104
|
if (canUseMod) {
|
|
@@ -99,7 +106,7 @@ function normalizedHotkeyStringFromParsed(parsed, platform) {
|
|
|
99
106
|
if (parsed.alt) parts.push("Alt");
|
|
100
107
|
if (parsed.shift) parts.push("Shift");
|
|
101
108
|
} else for (const modifier of MODIFIER_ORDER) if (parsed.modifiers.includes(modifier)) parts.push(modifier);
|
|
102
|
-
parts.push(normalizeKeyName(parsed.key));
|
|
109
|
+
parts.push(parsed.code !== void 0 ? `[${parsed.code}]` : normalizeKeyName(parsed.key));
|
|
103
110
|
return parts.join("+");
|
|
104
111
|
}
|
|
105
112
|
/**
|
|
@@ -168,19 +175,22 @@ function isModifierKey(key) {
|
|
|
168
175
|
* })
|
|
169
176
|
* ```
|
|
170
177
|
*/
|
|
171
|
-
function parseKeyboardEvent(event) {
|
|
172
|
-
const
|
|
178
|
+
function parseKeyboardEvent(event, platform) {
|
|
179
|
+
const normalized = normalizeKeyboardEvent(event, platform);
|
|
180
|
+
const ctrl = normalized.altGraph ? false : normalized.ctrl;
|
|
181
|
+
const alt = normalized.altGraph ? false : normalized.alt;
|
|
182
|
+
const shift = normalized.shift;
|
|
173
183
|
const modifiers = [];
|
|
174
|
-
if (
|
|
175
|
-
if (
|
|
176
|
-
if (
|
|
177
|
-
if (
|
|
184
|
+
if (ctrl) modifiers.push("Control");
|
|
185
|
+
if (alt) modifiers.push("Alt");
|
|
186
|
+
if (shift) modifiers.push("Shift");
|
|
187
|
+
if (normalized.meta) modifiers.push("Meta");
|
|
178
188
|
return {
|
|
179
|
-
key:
|
|
180
|
-
ctrl
|
|
181
|
-
shift
|
|
182
|
-
alt
|
|
183
|
-
meta:
|
|
189
|
+
key: normalized.key,
|
|
190
|
+
ctrl,
|
|
191
|
+
shift,
|
|
192
|
+
alt,
|
|
193
|
+
meta: normalized.meta,
|
|
184
194
|
modifiers
|
|
185
195
|
};
|
|
186
196
|
}
|
|
@@ -191,7 +201,7 @@ function parseKeyboardEvent(event) {
|
|
|
191
201
|
* @param platform - Target platform for `Mod` eligibility
|
|
192
202
|
*/
|
|
193
203
|
function normalizeHotkeyFromEvent(event, platform = detectPlatform()) {
|
|
194
|
-
return normalizedHotkeyStringFromParsed(parseKeyboardEvent(event), platform);
|
|
204
|
+
return normalizedHotkeyStringFromParsed(parseKeyboardEvent(event, platform), platform);
|
|
195
205
|
}
|
|
196
206
|
/**
|
|
197
207
|
* Checks if a hotkey or ParsedHotkey contains at least one non-modifier key.
|
|
@@ -212,9 +222,16 @@ function normalizeHotkeyFromEvent(event, platform = detectPlatform()) {
|
|
|
212
222
|
*/
|
|
213
223
|
function hasNonModifierKey(hotkey, platform = detectPlatform()) {
|
|
214
224
|
const parsed = typeof hotkey === "string" ? parseHotkey(hotkey, platform) : hotkey;
|
|
225
|
+
if (parsed.code !== void 0) return parsed.code.length > 0 && !/^(Control|Shift|Alt|Meta)(Left|Right)$/.test(parsed.code);
|
|
215
226
|
return !isModifierKey(parsed.key) && parsed.key.length > 0;
|
|
216
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Parses a string or raw binding, resolving Mod for the supplied platform.
|
|
230
|
+
* Physical tokens such as `[KeyQ]` retain `code`; they never become logical Q.
|
|
231
|
+
*/
|
|
232
|
+
function parseRegisterableHotkey(hotkey, platform = detectPlatform()) {
|
|
233
|
+
return typeof hotkey === "string" ? parseHotkey(hotkey, platform) : rawHotkeyToParsedHotkey(hotkey, platform);
|
|
234
|
+
}
|
|
217
235
|
|
|
218
236
|
//#endregion
|
|
219
|
-
export { hasNonModifierKey, isModifierKey, normalizeHotkey, normalizeHotkeyFromEvent, normalizeHotkeyFromParsed, normalizeRegisterableHotkey, parseHotkey, parseKeyboardEvent, rawHotkeyToParsedHotkey };
|
|
220
|
-
//# sourceMappingURL=parse.js.map
|
|
237
|
+
export { hasNonModifierKey, isModifierKey, normalizeHotkey, normalizeHotkeyFromEvent, normalizeHotkeyFromParsed, normalizeRegisterableHotkey, parseHotkey, parseKeyboardEvent, parseRegisterableHotkey, rawHotkeyToParsedHotkey };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CanonicalModifier } from "./key.types.js";
|
|
2
|
+
//#region src/platform.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Detects the current platform based on browser navigator properties.
|
|
5
|
+
*
|
|
6
|
+
* Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
|
|
7
|
+
* Control elsewhere) and for platform-specific hotkey formatting.
|
|
8
|
+
*
|
|
9
|
+
* @returns The detected platform: 'mac', 'windows', or 'linux'
|
|
10
|
+
* @remarks Defaults to 'linux' in SSR environments where navigator is undefined
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
|
|
15
|
+
* const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function detectPlatform(): 'mac' | 'windows' | 'linux';
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the platform-adaptive 'Mod' modifier to the appropriate canonical modifier.
|
|
21
|
+
*
|
|
22
|
+
* The 'Mod' token represents the "primary modifier" on each platform:
|
|
23
|
+
* - macOS: 'Meta' (Command key ⌘)
|
|
24
|
+
* - Windows/Linux: 'Control' (Ctrl key)
|
|
25
|
+
*
|
|
26
|
+
* This enables cross-platform hotkey definitions like 'Mod+S' that automatically
|
|
27
|
+
* map to Command+S on Mac and Ctrl+S on Windows/Linux.
|
|
28
|
+
*
|
|
29
|
+
* @param modifier - The modifier to resolve. If 'Mod', resolves based on platform.
|
|
30
|
+
* @param platform - The target platform. Defaults to auto-detection.
|
|
31
|
+
* @returns The canonical modifier name ('Control', 'Shift', 'Alt', or 'Meta')
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* resolveModifier('Mod', 'mac') // 'Meta'
|
|
36
|
+
* resolveModifier('Mod', 'windows') // 'Control'
|
|
37
|
+
* resolveModifier('Control', 'mac') // 'Control' (unchanged)
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveModifier(modifier: CanonicalModifier | 'Mod', platform?: 'mac' | 'windows' | 'linux'): CanonicalModifier;
|
|
41
|
+
//#endregion
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/platform.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detects the current platform based on browser navigator properties.
|
|
4
|
+
*
|
|
5
|
+
* Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
|
|
6
|
+
* Control elsewhere) and for platform-specific hotkey formatting.
|
|
7
|
+
*
|
|
8
|
+
* @returns The detected platform: 'mac', 'windows', or 'linux'
|
|
9
|
+
* @remarks Defaults to 'linux' in SSR environments where navigator is undefined
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
|
|
14
|
+
* const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function detectPlatform() {
|
|
18
|
+
if (typeof navigator === "undefined") return "linux";
|
|
19
|
+
const platform = navigator.platform?.toLowerCase() ?? "";
|
|
20
|
+
const userAgent = navigator.userAgent?.toLowerCase() ?? "";
|
|
21
|
+
if (platform.includes("mac") || userAgent.includes("mac")) return "mac";
|
|
22
|
+
if (platform.includes("win") || userAgent.includes("win")) return "windows";
|
|
23
|
+
return "linux";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the platform-adaptive 'Mod' modifier to the appropriate canonical modifier.
|
|
27
|
+
*
|
|
28
|
+
* The 'Mod' token represents the "primary modifier" on each platform:
|
|
29
|
+
* - macOS: 'Meta' (Command key ⌘)
|
|
30
|
+
* - Windows/Linux: 'Control' (Ctrl key)
|
|
31
|
+
*
|
|
32
|
+
* This enables cross-platform hotkey definitions like 'Mod+S' that automatically
|
|
33
|
+
* map to Command+S on Mac and Ctrl+S on Windows/Linux.
|
|
34
|
+
*
|
|
35
|
+
* @param modifier - The modifier to resolve. If 'Mod', resolves based on platform.
|
|
36
|
+
* @param platform - The target platform. Defaults to auto-detection.
|
|
37
|
+
* @returns The canonical modifier name ('Control', 'Shift', 'Alt', or 'Meta')
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* resolveModifier('Mod', 'mac') // 'Meta'
|
|
42
|
+
* resolveModifier('Mod', 'windows') // 'Control'
|
|
43
|
+
* resolveModifier('Control', 'mac') // 'Control' (unchanged)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
function resolveModifier(modifier, platform = detectPlatform()) {
|
|
47
|
+
if (modifier === "Mod") return platform === "mac" ? "Meta" : "Control";
|
|
48
|
+
return modifier;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { detectPlatform, resolveModifier };
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
hotkey?: Hotkey;
|
|
10
|
+
sequence?: Array<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
|
+
export interface HotkeyRecorderValidationContext {
|
|
24
|
+
event: KeyboardEvent;
|
|
25
|
+
parsedHotkey: ParsedHotkey;
|
|
26
|
+
}
|
|
27
|
+
export interface HotkeySequenceRecorderValidationContext {
|
|
28
|
+
events: ReadonlyArray<KeyboardEvent>;
|
|
29
|
+
parsedSequence: ReadonlyArray<ParsedHotkey>;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { Hotkey, HotkeyCallback } from "./hotkey.js";
|
|
1
|
+
import { Hotkey, HotkeyCallback, ParsedHotkey } from "./hotkey.types.js";
|
|
2
2
|
import { HotkeyOptions } from "./hotkey-manager.js";
|
|
3
3
|
import { Store } from "@tanstack/store";
|
|
4
|
-
|
|
5
4
|
//#region src/sequence-manager.d.ts
|
|
6
|
-
type Target = HTMLElement | Document | Window;
|
|
5
|
+
export type Target = HTMLElement | Document | Window;
|
|
7
6
|
/**
|
|
8
7
|
* Options for hotkey sequence matching.
|
|
9
8
|
* Extends HotkeyOptions but excludes requireReset (not applicable to sequences).
|
|
10
9
|
*/
|
|
11
|
-
interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
|
|
10
|
+
export interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
|
|
12
11
|
/** Timeout between keys in milliseconds. Default: 1000 */
|
|
13
12
|
timeout?: number;
|
|
14
13
|
}
|
|
@@ -18,7 +17,7 @@ interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
|
|
|
18
17
|
* Each element is one step (a `Hotkey` string). Steps may include modifiers;
|
|
19
18
|
* the same modifier can appear on consecutive steps (e.g. `Shift+R` then
|
|
20
19
|
* `Shift+T`). Modifier-only key events do not advance or reset matching—see
|
|
21
|
-
* `SequenceManager`.
|
|
20
|
+
* `SequenceManager`. Automatic keydown repeats and IME composition are also ignored.
|
|
22
21
|
*
|
|
23
22
|
* @example
|
|
24
23
|
* ```ts
|
|
@@ -28,17 +27,17 @@ interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
|
|
|
28
27
|
* const chainedShift: HotkeySequence = ['Shift+R', 'Shift+T']
|
|
29
28
|
* ```
|
|
30
29
|
*/
|
|
31
|
-
type HotkeySequence = Array<Hotkey>;
|
|
30
|
+
export type HotkeySequence = Array<Hotkey>;
|
|
32
31
|
/**
|
|
33
32
|
* Default timeout between keys in a sequence (in milliseconds).
|
|
34
33
|
* Exported for consumers that display sequence state (e.g. devtools).
|
|
35
34
|
*/
|
|
36
|
-
declare const DEFAULT_SEQUENCE_TIMEOUT = 1000;
|
|
35
|
+
export declare const DEFAULT_SEQUENCE_TIMEOUT = 1000;
|
|
37
36
|
/**
|
|
38
37
|
* View of a sequence registration for devtools display.
|
|
39
38
|
* Progress fields reflect an in-progress match (between first key and completion or timeout).
|
|
40
39
|
*/
|
|
41
|
-
interface SequenceRegistrationView {
|
|
40
|
+
export interface SequenceRegistrationView {
|
|
42
41
|
id: string;
|
|
43
42
|
sequence: HotkeySequence;
|
|
44
43
|
options: SequenceOptions;
|
|
@@ -51,6 +50,21 @@ interface SequenceRegistrationView {
|
|
|
51
50
|
/** `Date.now()` when the last step in the current attempt matched; 0 if none. */
|
|
52
51
|
partialMatchLastKeyTime: number;
|
|
53
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Internal representation of a sequence registration.
|
|
55
|
+
*/
|
|
56
|
+
export interface SequenceRegistration {
|
|
57
|
+
id: string;
|
|
58
|
+
sequence: HotkeySequence;
|
|
59
|
+
parsedSequence: Array<ParsedHotkey>;
|
|
60
|
+
callback: HotkeyCallback;
|
|
61
|
+
options: SequenceOptions;
|
|
62
|
+
target: Target;
|
|
63
|
+
currentIndex: number;
|
|
64
|
+
lastKeyTime: number;
|
|
65
|
+
triggerCount: number;
|
|
66
|
+
hasFired: boolean;
|
|
67
|
+
}
|
|
54
68
|
/**
|
|
55
69
|
* A handle returned from SequenceManager.register() that allows updating
|
|
56
70
|
* the callback and options without re-registering the sequence.
|
|
@@ -64,14 +78,14 @@ interface SequenceRegistrationView {
|
|
|
64
78
|
* handle.unregister()
|
|
65
79
|
* ```
|
|
66
80
|
*/
|
|
67
|
-
interface SequenceRegistrationHandle {
|
|
81
|
+
export interface SequenceRegistrationHandle {
|
|
68
82
|
readonly id: string;
|
|
69
83
|
readonly isActive: boolean;
|
|
70
84
|
callback: HotkeyCallback;
|
|
71
85
|
setOptions: (options: Partial<SequenceOptions>) => void;
|
|
72
86
|
unregister: () => void;
|
|
73
87
|
}
|
|
74
|
-
declare class SequenceManager {
|
|
88
|
+
export declare class SequenceManager {
|
|
75
89
|
#private;
|
|
76
90
|
/**
|
|
77
91
|
* The TanStack Store containing sequence registration views for devtools.
|
|
@@ -121,7 +135,7 @@ declare class SequenceManager {
|
|
|
121
135
|
* Gets the singleton SequenceManager instance.
|
|
122
136
|
* Convenience function for accessing the manager.
|
|
123
137
|
*/
|
|
124
|
-
declare function getSequenceManager(): SequenceManager;
|
|
138
|
+
export declare function getSequenceManager(): SequenceManager;
|
|
125
139
|
/**
|
|
126
140
|
* Creates a simple sequence matcher for one-off use.
|
|
127
141
|
*
|
|
@@ -144,7 +158,7 @@ declare function getSequenceManager(): SequenceManager;
|
|
|
144
158
|
* })
|
|
145
159
|
* ```
|
|
146
160
|
*/
|
|
147
|
-
declare function createSequenceMatcher(sequence: HotkeySequence, options?: {
|
|
161
|
+
export declare function createSequenceMatcher(sequence: HotkeySequence, options?: {
|
|
148
162
|
timeout?: number;
|
|
149
163
|
platform?: 'mac' | 'windows' | 'linux';
|
|
150
164
|
}): {
|
|
@@ -152,6 +166,4 @@ declare function createSequenceMatcher(sequence: HotkeySequence, options?: {
|
|
|
152
166
|
reset: () => void;
|
|
153
167
|
getProgress: () => number;
|
|
154
168
|
};
|
|
155
|
-
//#endregion
|
|
156
|
-
export { DEFAULT_SEQUENCE_TIMEOUT, HotkeySequence, SequenceManager, SequenceOptions, SequenceRegistrationHandle, SequenceRegistrationView, createSequenceMatcher, getSequenceManager };
|
|
157
|
-
//# sourceMappingURL=sequence-manager.d.ts.map
|
|
169
|
+
//#endregion
|