@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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/display-labels.ts
|
|
2
|
+
/**
|
|
3
|
+
* Modifier key symbols for macOS display.
|
|
4
|
+
*
|
|
5
|
+
* Used by formatting functions to display hotkeys with macOS-style symbols
|
|
6
|
+
* (e.g., ⌘ for Command, ⌃ for Control) instead of text labels. This provides
|
|
7
|
+
* a native macOS look and feel in hotkey displays.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* MAC_MODIFIER_SYMBOLS['Meta'] // '⌘'
|
|
12
|
+
* MAC_MODIFIER_SYMBOLS['Control'] // '⌃'
|
|
13
|
+
* MAC_MODIFIER_SYMBOLS['Alt'] // '⌥'
|
|
14
|
+
* MAC_MODIFIER_SYMBOLS['Shift'] // '⇧'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const MAC_MODIFIER_SYMBOLS = {
|
|
18
|
+
Control: "⌃",
|
|
19
|
+
Alt: "⌥",
|
|
20
|
+
Shift: "⇧",
|
|
21
|
+
Meta: "⌘",
|
|
22
|
+
Mod: "⌘"
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Modifier key labels for macOS display.
|
|
26
|
+
*
|
|
27
|
+
* Used by formatting functions to display hotkeys with macOS-style text labels
|
|
28
|
+
* (e.g., 'Control' for Control, 'Option' for Alt, 'Cmd' for Meta) instead of symbols.
|
|
29
|
+
* This provides a familiar macOS look and feel in hotkey displays.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* MAC_MODIFIER_LABELS['Control'] // 'control'
|
|
34
|
+
* MAC_MODIFIER_LABELS['Alt'] // 'option'
|
|
35
|
+
* MAC_MODIFIER_LABELS['Shift'] // 'shift'
|
|
36
|
+
* MAC_MODIFIER_LABELS['Meta'] // 'cmd'
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
const MAC_MODIFIER_LABELS = {
|
|
40
|
+
Control: "Control",
|
|
41
|
+
Alt: "Option",
|
|
42
|
+
Shift: "Shift",
|
|
43
|
+
Meta: "Cmd",
|
|
44
|
+
Mod: "Cmd"
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Modifier key labels for Windows/Linux display.
|
|
48
|
+
*
|
|
49
|
+
* Used by formatting functions to display hotkeys with standard text labels
|
|
50
|
+
* (e.g., 'Ctrl' for Control, 'Win' for Meta/Windows key) instead of symbols.
|
|
51
|
+
* This provides a familiar Windows/Linux look and feel in hotkey displays.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* STANDARD_MODIFIER_LABELS['Control'] // 'Ctrl'
|
|
56
|
+
* STANDARD_MODIFIER_LABELS['Meta'] // 'Win'
|
|
57
|
+
* STANDARD_MODIFIER_LABELS['Alt'] // 'Alt'
|
|
58
|
+
* STANDARD_MODIFIER_LABELS['Shift'] // 'Shift'
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
const WINDOWS_MODIFIER_LABELS = {
|
|
62
|
+
Control: "Ctrl",
|
|
63
|
+
Alt: "Alt",
|
|
64
|
+
Shift: "Shift",
|
|
65
|
+
Meta: "Win",
|
|
66
|
+
Mod: "Ctrl"
|
|
67
|
+
};
|
|
68
|
+
const LINUX_MODIFIER_LABELS = {
|
|
69
|
+
...WINDOWS_MODIFIER_LABELS,
|
|
70
|
+
Meta: "Super"
|
|
71
|
+
};
|
|
72
|
+
const PUNCTUATION_KEY_DISPLAY_LABELS = {
|
|
73
|
+
"`": "Backquote",
|
|
74
|
+
"\\": "Backslash",
|
|
75
|
+
"[": "Left Bracket",
|
|
76
|
+
"]": "Right Bracket",
|
|
77
|
+
",": "Comma",
|
|
78
|
+
"=": "Equal",
|
|
79
|
+
"-": "Minus",
|
|
80
|
+
".": "Period",
|
|
81
|
+
";": "Semicolon"
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Special key symbols for display formatting.
|
|
85
|
+
*
|
|
86
|
+
* Maps certain keys to their visual symbols for better readability in hotkey displays.
|
|
87
|
+
* Used by formatting functions to show symbols like ↑ for ArrowUp or ↵ for Enter
|
|
88
|
+
* instead of text labels.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* KEY_DISPLAY_SYMBOLS['ArrowUp'] // '↑'
|
|
93
|
+
* KEY_DISPLAY_SYMBOLS['Enter'] // '↵'
|
|
94
|
+
* KEY_DISPLAY_SYMBOLS['Escape'] // 'Esc'
|
|
95
|
+
* KEY_DISPLAY_SYMBOLS['Space'] // '␣'
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
const KEY_DISPLAY_SYMBOLS = {
|
|
99
|
+
ArrowUp: "↑",
|
|
100
|
+
ArrowDown: "↓",
|
|
101
|
+
ArrowLeft: "←",
|
|
102
|
+
ArrowRight: "→",
|
|
103
|
+
Enter: "↵",
|
|
104
|
+
Escape: "Esc",
|
|
105
|
+
Backspace: "⌫",
|
|
106
|
+
Delete: "⌦",
|
|
107
|
+
Tab: "⇥",
|
|
108
|
+
Space: "␣"
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { KEY_DISPLAY_SYMBOLS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS };
|
package/dist/format.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Hotkey, ParsedHotkey, RegisterableHotkey } from "./hotkey.types.js";
|
|
3
2
|
//#region src/format.d.ts
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
4
|
+
* Joins stored sequence steps with spaces, preserving physical code tokens.
|
|
5
|
+
* For user-facing labels, call formatForDisplay on each step instead.
|
|
6
6
|
*
|
|
7
7
|
* @param sequence - Array of hotkey strings that form the sequence
|
|
8
8
|
* @returns A space-separated string (e.g. ['G','G'] → 'G G')
|
|
@@ -13,7 +13,7 @@ import { FormatDisplayOptions, Hotkey, ParsedHotkey, RegisterableHotkey } from "
|
|
|
13
13
|
* formatHotkeySequence(['D', 'I', 'W']) // 'D I W'
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
|
-
declare function formatHotkeySequence(sequence: Array<Hotkey>): string;
|
|
16
|
+
export declare function formatHotkeySequence(sequence: Array<Hotkey>): string;
|
|
17
17
|
/**
|
|
18
18
|
* Converts a ParsedHotkey back to a hotkey string.
|
|
19
19
|
*
|
|
@@ -26,17 +26,22 @@ declare function formatHotkeySequence(sequence: Array<Hotkey>): string;
|
|
|
26
26
|
* // Returns: 'Control+Shift+S'
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
|
-
declare function formatHotkey(parsed: ParsedHotkey): string;
|
|
29
|
+
export declare function formatHotkey(parsed: ParsedHotkey): string;
|
|
30
|
+
export type DisplayHotkey = RegisterableHotkey | ParsedHotkey | (string & {});
|
|
30
31
|
/**
|
|
31
32
|
* Formats a hotkey for display in a user interface.
|
|
32
33
|
*
|
|
33
34
|
* On macOS, uses symbols (⌘⇧S) in the same modifier order as {@link normalizeHotkeyFromParsed}.
|
|
34
35
|
* On Windows/Linux, uses text (Ctrl+Shift+S) with `+` separators.
|
|
35
36
|
* The separator can be customized with `separatorToken`.
|
|
37
|
+
* Physical codes use conventional display labels: `[KeyS]` becomes `S` and
|
|
38
|
+
* `[Digit2]` becomes `2`. These labels do not infer the active keyboard layout
|
|
39
|
+
* or change matching. Supply a resolved `layoutMap` for layout-aware labels;
|
|
40
|
+
* `keyLabels` overrides both layout entries and fallback labels.
|
|
36
41
|
*
|
|
37
|
-
* @param hotkey -
|
|
42
|
+
* @param hotkey - A logical or physical string, raw binding, or ParsedHotkey
|
|
38
43
|
* @param options - Formatting options
|
|
39
|
-
* @returns
|
|
44
|
+
* @returns Display text, or individual key labels when parts is true
|
|
40
45
|
*
|
|
41
46
|
* @example
|
|
42
47
|
* ```ts
|
|
@@ -50,11 +55,42 @@ declare function formatHotkey(parsed: ParsedHotkey): string;
|
|
|
50
55
|
* // Returns: 'Esc' (on all platforms)
|
|
51
56
|
* ```
|
|
52
57
|
*/
|
|
53
|
-
declare function formatForDisplay(hotkey:
|
|
58
|
+
export declare function formatForDisplay(hotkey: DisplayHotkey, options: FormatDisplayOptions & {
|
|
59
|
+
parts: true;
|
|
60
|
+
}): Array<string>;
|
|
61
|
+
export declare function formatForDisplay(hotkey: DisplayHotkey, options?: FormatDisplayOptions & {
|
|
62
|
+
parts?: false;
|
|
63
|
+
}): string;
|
|
64
|
+
export declare function formatForDisplay(hotkey: DisplayHotkey, options: FormatDisplayOptions): string | Array<string>;
|
|
54
65
|
/**
|
|
55
66
|
* @deprecated Use {@link formatForDisplay} instead with `useSymbols: false` option.
|
|
56
67
|
*/
|
|
57
|
-
declare function formatWithLabels(hotkey: RegisterableHotkey, options?: Omit<FormatDisplayOptions, 'useSymbols'>): string;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
export declare function formatWithLabels(hotkey: RegisterableHotkey, options?: Omit<FormatDisplayOptions, 'useSymbols' | 'parts'>): string;
|
|
69
|
+
/**
|
|
70
|
+
* Options for formatting hotkeys for display.
|
|
71
|
+
*/
|
|
72
|
+
export interface FormatDisplayOptions {
|
|
73
|
+
/** The target platform. Defaults to auto-detection. */
|
|
74
|
+
platform?: 'mac' | 'windows' | 'linux';
|
|
75
|
+
/** Whether to use symbols for the display. Defaults to true. */
|
|
76
|
+
useSymbols?: boolean | {
|
|
77
|
+
modifiers?: boolean;
|
|
78
|
+
keys?: boolean;
|
|
79
|
+
};
|
|
80
|
+
/** Return individual key labels instead of a joined string. Default: false. */
|
|
81
|
+
parts?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* An already-resolved code-to-key map, such as KeyboardLayoutMap or Map.
|
|
84
|
+
* Used only for physical bindings. Mapped keys receive normal display formatting;
|
|
85
|
+
* missing or empty entries use conventional fallback labels. The caller owns
|
|
86
|
+
* loading and refreshing the map; this formatter never calls browser APIs.
|
|
87
|
+
*/
|
|
88
|
+
layoutMap?: {
|
|
89
|
+
get: (code: string) => string | undefined;
|
|
90
|
+
};
|
|
91
|
+
/** Final display overrides for logical keys or physical codes; take precedence over layoutMap. Never changes matching. */
|
|
92
|
+
keyLabels?: Readonly<Record<string, string>>;
|
|
93
|
+
/** Override the separator between display tokens. Defaults to platform-specific formatting when null/undefined. */
|
|
94
|
+
separatorToken?: string | null;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
package/dist/format.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { MODIFIER_ALIASES, MODIFIER_ORDER, PUNCTUATION_CODE_MAP, normalizeKeyName } from "./constants.js";
|
|
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
|
+
import { detectPlatform } from "./platform.js";
|
|
4
|
+
import { assertLogicalKey, splitHotkeyParts } from "./_keyboard-event.js";
|
|
5
|
+
import { isModifierKey, normalizeHotkeyFromParsed, normalizeRegisterableHotkey } from "./parse.js";
|
|
3
6
|
|
|
4
7
|
//#region src/format.ts
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
9
|
+
* Joins stored sequence steps with spaces, preserving physical code tokens.
|
|
10
|
+
* For user-facing labels, call formatForDisplay on each step instead.
|
|
7
11
|
*
|
|
8
12
|
* @param sequence - Array of hotkey strings that form the sequence
|
|
9
13
|
* @returns A space-separated string (e.g. ['G','G'] → 'G G')
|
|
@@ -30,48 +34,41 @@ function formatHotkeySequence(sequence) {
|
|
|
30
34
|
* ```
|
|
31
35
|
*/
|
|
32
36
|
function formatHotkey(parsed) {
|
|
37
|
+
if (parsed.code === void 0) assertLogicalKey(parsed.key);
|
|
33
38
|
const parts = [];
|
|
34
39
|
for (const modifier of MODIFIER_ORDER) if (parsed.modifiers.includes(modifier)) parts.push(modifier);
|
|
35
|
-
parts.push(parsed.key);
|
|
40
|
+
parts.push(parsed.code !== void 0 ? `[${parsed.code}]` : parsed.key);
|
|
36
41
|
return parts.join("+");
|
|
37
42
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Formats a hotkey for display in a user interface.
|
|
40
|
-
*
|
|
41
|
-
* On macOS, uses symbols (⌘⇧S) in the same modifier order as {@link normalizeHotkeyFromParsed}.
|
|
42
|
-
* On Windows/Linux, uses text (Ctrl+Shift+S) with `+` separators.
|
|
43
|
-
* The separator can be customized with `separatorToken`.
|
|
44
|
-
*
|
|
45
|
-
* @param hotkey - The hotkey string or ParsedHotkey to format
|
|
46
|
-
* @param options - Formatting options
|
|
47
|
-
* @returns A formatted string suitable for display
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* formatForDisplay('Mod+Shift+S', { platform: 'mac' })
|
|
52
|
-
* // Returns: '⌘ ⇧ S' (symbols separated by spaces on macOS)
|
|
53
|
-
*
|
|
54
|
-
* formatForDisplay('Mod+Shift+S', { platform: 'windows' })
|
|
55
|
-
* // Returns: 'Ctrl+Shift+S'
|
|
56
|
-
*
|
|
57
|
-
* formatForDisplay('Escape')
|
|
58
|
-
* // Returns: 'Esc' (on all platforms)
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
43
|
function formatForDisplay(hotkey, options = {}) {
|
|
62
44
|
const platform = options.platform ?? detectPlatform();
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
45
|
+
const symbols = options.useSymbols ?? true;
|
|
46
|
+
const modifierSymbols = typeof symbols === "boolean" ? symbols : symbols.modifiers ?? true;
|
|
47
|
+
const keySymbols = typeof symbols === "boolean" ? symbols : symbols.keys ?? true;
|
|
48
|
+
const separator = options.separatorToken ?? (platform === "mac" && modifierSymbols ? " " : "+");
|
|
49
|
+
const normalized = typeof hotkey === "object" && "modifiers" in hotkey ? normalizeHotkeyFromParsed(hotkey, platform) : normalizeRegisterableHotkey(hotkey, platform);
|
|
50
|
+
const parts = splitHotkeyParts(normalized).map((segment) => {
|
|
66
51
|
if (isModifierKey(segment)) {
|
|
67
|
-
const
|
|
68
|
-
return platform === "mac" ?
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
const modifier = MODIFIER_ALIASES[segment] ?? MODIFIER_ALIASES[segment.toLowerCase()];
|
|
53
|
+
return platform === "mac" ? modifierSymbols ? MAC_MODIFIER_SYMBOLS[modifier] : MAC_MODIFIER_LABELS[modifier] : platform === "windows" ? WINDOWS_MODIFIER_LABELS[modifier] : LINUX_MODIFIER_LABELS[modifier];
|
|
54
|
+
}
|
|
55
|
+
const code = /^\[([A-Za-z][A-Za-z0-9]*)\]$/.exec(segment)?.[1];
|
|
56
|
+
const label = options.keyLabels?.[code ?? segment];
|
|
57
|
+
if (label !== void 0) return label;
|
|
58
|
+
const numpad = code?.startsWith("Numpad") ?? false;
|
|
59
|
+
let displayKey = code ?? segment;
|
|
60
|
+
const mappedKey = code ? options.layoutMap?.get(code) : void 0;
|
|
61
|
+
if (mappedKey) displayKey = normalizeKeyName(mappedKey);
|
|
62
|
+
else if (code) {
|
|
63
|
+
if (/^Key[A-Z]$/.test(code)) displayKey = code.slice(3);
|
|
64
|
+
else if (/^Digit[0-9]$/.test(code)) displayKey = code.slice(5);
|
|
65
|
+
else if (numpad) displayKey = code.slice(6);
|
|
66
|
+
else displayKey = PUNCTUATION_CODE_MAP[code] ?? code;
|
|
73
67
|
}
|
|
74
|
-
|
|
68
|
+
const display = (keySymbols ? KEY_DISPLAY_SYMBOLS : PUNCTUATION_KEY_DISPLAY_LABELS)[displayKey] ?? displayKey;
|
|
69
|
+
return numpad ? `Numpad ${display.replace(/([a-z])([A-Z])/g, "$1 $2")}` : display;
|
|
70
|
+
});
|
|
71
|
+
return options.parts ? parts : parts.join(separator);
|
|
75
72
|
}
|
|
76
73
|
/**
|
|
77
74
|
* @deprecated Use {@link formatForDisplay} instead with `useSymbols: false` option.
|
|
@@ -84,5 +81,4 @@ function formatWithLabels(hotkey, options = {}) {
|
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
//#endregion
|
|
87
|
-
export { formatForDisplay, formatHotkey, formatHotkeySequence, formatWithLabels };
|
|
88
|
-
//# sourceMappingURL=format.js.map
|
|
84
|
+
export { formatForDisplay, formatHotkey, formatHotkeySequence, formatWithLabels };
|
package/dist/hint.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RegisterableHotkey } from "./hotkey.types.js";
|
|
2
|
+
//#region src/hint.d.ts
|
|
3
|
+
export interface HeldModifierOptions {
|
|
4
|
+
/** Resolve Mod for this platform. Defaults to platform detection. */
|
|
5
|
+
platform?: 'mac' | 'windows' | 'linux';
|
|
6
|
+
/** Require every binding modifier to be held. Default: false (nonempty subset). */
|
|
7
|
+
exact?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Returns whether held modifiers should reveal a shortcut hint.
|
|
11
|
+
* By default, any nonempty subset of the binding's modifiers qualifies; an
|
|
12
|
+
* unrelated modifier hides the hint. `exact` requires every binding modifier.
|
|
13
|
+
* Nonmodifier keys are ignored. This does not check registration or focus.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* matchesHeldModifiers('Alt+Shift+[KeyK]', ['Alt']) // true
|
|
17
|
+
* matchesHeldModifiers('Alt+Shift+[KeyK]', ['Alt', 'Control']) // false
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchesHeldModifiers(hotkey: RegisterableHotkey, heldKeys: ReadonlyArray<string>, options?: HeldModifierOptions): boolean;
|
|
20
|
+
//#endregion
|
package/dist/hint.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MODIFIER_KEYS, normalizeKeyName } from "./constants.js";
|
|
2
|
+
import { parseRegisterableHotkey } from "./parse.js";
|
|
3
|
+
|
|
4
|
+
//#region src/hint.ts
|
|
5
|
+
/**
|
|
6
|
+
* Returns whether held modifiers should reveal a shortcut hint.
|
|
7
|
+
* By default, any nonempty subset of the binding's modifiers qualifies; an
|
|
8
|
+
* unrelated modifier hides the hint. `exact` requires every binding modifier.
|
|
9
|
+
* Nonmodifier keys are ignored. This does not check registration or focus.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* matchesHeldModifiers('Alt+Shift+[KeyK]', ['Alt']) // true
|
|
13
|
+
* matchesHeldModifiers('Alt+Shift+[KeyK]', ['Alt', 'Control']) // false
|
|
14
|
+
*/
|
|
15
|
+
function matchesHeldModifiers(hotkey, heldKeys, options = {}) {
|
|
16
|
+
if (heldKeys.includes("AltGraph")) return false;
|
|
17
|
+
const held = [...new Set(heldKeys.map(normalizeKeyName).filter((key) => MODIFIER_KEYS.has(key)))];
|
|
18
|
+
const { modifiers } = parseRegisterableHotkey(hotkey, options.platform);
|
|
19
|
+
return held.length > 0 && held.every((key) => modifiers.includes(key)) && (!options.exact || held.length === modifiers.length);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { matchesHeldModifiers };
|
package/dist/hotkey-manager.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { Hotkey, HotkeyCallback, HotkeyMeta, ParsedHotkey, RegisterableHotkey } from "./hotkey.js";
|
|
2
|
-
import { ConflictBehavior } from "./manager.utils.js";
|
|
1
|
+
import { ConflictBehavior, Hotkey, HotkeyCallback, HotkeyMeta, ParsedHotkey, RegisterableHotkey } from "./hotkey.types.js";
|
|
3
2
|
import { Store } from "@tanstack/store";
|
|
4
|
-
|
|
5
3
|
//#region src/hotkey-manager.d.ts
|
|
6
4
|
/**
|
|
7
5
|
* Options for registering a hotkey.
|
|
8
6
|
*/
|
|
9
|
-
interface HotkeyOptions {
|
|
7
|
+
export interface HotkeyOptions {
|
|
10
8
|
/** Behavior when this hotkey conflicts with an existing registration on the same target. Defaults to 'warn' */
|
|
11
9
|
conflictBehavior?: ConflictBehavior;
|
|
12
10
|
/**
|
|
@@ -35,11 +33,16 @@ interface HotkeyOptions {
|
|
|
35
33
|
/**
|
|
36
34
|
* A registered hotkey handler in the HotkeyManager.
|
|
37
35
|
*/
|
|
38
|
-
interface HotkeyRegistration {
|
|
36
|
+
export interface HotkeyRegistration {
|
|
39
37
|
/** The callback to invoke */
|
|
40
38
|
callback: HotkeyCallback;
|
|
41
39
|
/** Whether this registration has fired and needs reset (for requireReset) */
|
|
42
40
|
hasFired: boolean;
|
|
41
|
+
/** The concrete key/code that activated a requireReset registration. */
|
|
42
|
+
activeMatch?: {
|
|
43
|
+
key: string;
|
|
44
|
+
code: string;
|
|
45
|
+
};
|
|
43
46
|
/** The original hotkey string */
|
|
44
47
|
hotkey: Hotkey;
|
|
45
48
|
/** Unique identifier for this registration */
|
|
@@ -57,7 +60,7 @@ interface HotkeyRegistration {
|
|
|
57
60
|
* Public view of a hotkey registration for display and introspection.
|
|
58
61
|
* Omits the callback function which is an internal implementation detail.
|
|
59
62
|
*/
|
|
60
|
-
interface HotkeyRegistrationView {
|
|
63
|
+
export interface HotkeyRegistrationView {
|
|
61
64
|
/** The original hotkey string */
|
|
62
65
|
hotkey: Hotkey;
|
|
63
66
|
/** Unique identifier for this registration */
|
|
@@ -77,7 +80,7 @@ interface HotkeyRegistrationView {
|
|
|
77
80
|
* Creates a public view from an internal registration,
|
|
78
81
|
* stripping the callback function.
|
|
79
82
|
*/
|
|
80
|
-
declare function toHotkeyRegistrationView(reg: HotkeyRegistration): HotkeyRegistrationView;
|
|
83
|
+
export declare function toHotkeyRegistrationView(reg: HotkeyRegistration): HotkeyRegistrationView;
|
|
81
84
|
/**
|
|
82
85
|
* A handle returned from HotkeyManager.register() that allows updating
|
|
83
86
|
* the callback and options without re-registering the hotkey.
|
|
@@ -101,7 +104,7 @@ declare function toHotkeyRegistrationView(reg: HotkeyRegistration): HotkeyRegist
|
|
|
101
104
|
* handle.unregister()
|
|
102
105
|
* ```
|
|
103
106
|
*/
|
|
104
|
-
interface HotkeyRegistrationHandle {
|
|
107
|
+
export interface HotkeyRegistrationHandle {
|
|
105
108
|
/**
|
|
106
109
|
* The callback function. Can be set directly to update without re-registering.
|
|
107
110
|
* This avoids stale closures when the callback references React state.
|
|
@@ -138,7 +141,7 @@ interface HotkeyRegistrationHandle {
|
|
|
138
141
|
* unregister()
|
|
139
142
|
* ```
|
|
140
143
|
*/
|
|
141
|
-
declare class HotkeyManager {
|
|
144
|
+
export declare class HotkeyManager {
|
|
142
145
|
#private;
|
|
143
146
|
/**
|
|
144
147
|
* The TanStack Store containing all hotkey registrations.
|
|
@@ -224,7 +227,6 @@ declare class HotkeyManager {
|
|
|
224
227
|
* Gets the singleton HotkeyManager instance.
|
|
225
228
|
* Convenience function for accessing the manager.
|
|
226
229
|
*/
|
|
227
|
-
declare function getHotkeyManager(): HotkeyManager;
|
|
230
|
+
export declare function getHotkeyManager(): HotkeyManager;
|
|
228
231
|
//#endregion
|
|
229
|
-
export {
|
|
230
|
-
//# sourceMappingURL=hotkey-manager.d.ts.map
|
|
232
|
+
export type { ConflictBehavior };
|
package/dist/hotkey-manager.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeKeyName } from "./constants.js";
|
|
2
|
+
import { detectPlatform } from "./platform.js";
|
|
2
3
|
import { parseHotkey, rawHotkeyToParsedHotkey } from "./parse.js";
|
|
3
4
|
import { formatHotkey } from "./format.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { matchKeyboardEvent } from "./_match.js";
|
|
6
|
+
import { areHotkeysEqual } from "./match.js";
|
|
7
|
+
import { isRecordingEvent } from "./_recording-guard.js";
|
|
8
|
+
import { defaultHotkeyOptions, getDefaultIgnoreInputs, handleConflict, optionsEqual } from "./_registration.js";
|
|
9
|
+
import { isEventForTarget, shouldIgnoreInputEvent } from "./_event-target.js";
|
|
6
10
|
import { Store } from "@tanstack/store";
|
|
7
11
|
|
|
8
12
|
//#region src/hotkey-manager.ts
|
|
@@ -49,11 +53,30 @@ function generateId() {
|
|
|
49
53
|
*/
|
|
50
54
|
var HotkeyManager = class HotkeyManager {
|
|
51
55
|
static #instance = null;
|
|
56
|
+
/**
|
|
57
|
+
* The TanStack Store containing all hotkey registrations.
|
|
58
|
+
* Use this to subscribe to registration changes or access current registrations.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const manager = HotkeyManager.getInstance()
|
|
63
|
+
*
|
|
64
|
+
* // Subscribe to registration changes
|
|
65
|
+
* const unsubscribe = manager.registrations.subscribe(() => {
|
|
66
|
+
* console.log('Registrations changed:', manager.registrations.state.size)
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* // Access current registrations
|
|
70
|
+
* for (const [id, reg] of manager.registrations.state) {
|
|
71
|
+
* console.log(reg.hotkey, reg.options.enabled)
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
registrations = new Store(/* @__PURE__ */ new Map());
|
|
52
76
|
#platform;
|
|
53
77
|
#targetListeners = /* @__PURE__ */ new Map();
|
|
54
78
|
#targetRegistrations = /* @__PURE__ */ new Map();
|
|
55
79
|
constructor() {
|
|
56
|
-
this.registrations = new Store(/* @__PURE__ */ new Map());
|
|
57
80
|
this.#platform = detectPlatform();
|
|
58
81
|
}
|
|
59
82
|
/**
|
|
@@ -162,7 +185,10 @@ var HotkeyManager = class HotkeyManager {
|
|
|
162
185
|
setOptions: (newOptions) => {
|
|
163
186
|
manager.registrations.setState((prev) => {
|
|
164
187
|
const reg = prev.get(id);
|
|
165
|
-
if (reg
|
|
188
|
+
if (reg && !optionsEqual(reg.options, {
|
|
189
|
+
...reg.options,
|
|
190
|
+
...newOptions
|
|
191
|
+
})) {
|
|
166
192
|
const next = new Map(prev);
|
|
167
193
|
next.set(id, {
|
|
168
194
|
...reg,
|
|
@@ -230,8 +256,27 @@ var HotkeyManager = class HotkeyManager {
|
|
|
230
256
|
* Processes keyboard events for a specific target and event type.
|
|
231
257
|
*/
|
|
232
258
|
#processTargetEvent(event, target, eventType) {
|
|
259
|
+
if (eventType === "keyup") for (const id of this.#targetRegistrations.get(target) ?? []) {
|
|
260
|
+
const registration = this.registrations.state.get(id);
|
|
261
|
+
if (registration?.hasFired && this.#shouldResetRegistration(registration, event)) {
|
|
262
|
+
registration.hasFired = false;
|
|
263
|
+
registration.activeMatch = void 0;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (isRecordingEvent(event)) return;
|
|
233
267
|
const targetRegs = this.#targetRegistrations.get(target);
|
|
234
268
|
if (!targetRegs) return;
|
|
269
|
+
const matches = /* @__PURE__ */ new Map();
|
|
270
|
+
let bestScore = 0;
|
|
271
|
+
for (const id of targetRegs) {
|
|
272
|
+
const registration = this.registrations.state.get(id);
|
|
273
|
+
if (!registration || !registration.options.enabled || registration.options.eventType !== eventType || !isEventForTarget(event, target) || registration.options.ignoreInputs !== false && shouldIgnoreInputEvent(event, target, registration.target)) continue;
|
|
274
|
+
const result = matchKeyboardEvent(event, registration.parsedHotkey, registration.options.platform);
|
|
275
|
+
if (result.matched) {
|
|
276
|
+
matches.set(id, result);
|
|
277
|
+
bestScore = Math.max(bestScore, result.score);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
235
280
|
for (const id of targetRegs) {
|
|
236
281
|
const registration = this.registrations.state.get(id);
|
|
237
282
|
if (!registration) continue;
|
|
@@ -242,21 +287,21 @@ var HotkeyManager = class HotkeyManager {
|
|
|
242
287
|
}
|
|
243
288
|
if (eventType === "keydown") {
|
|
244
289
|
if (registration.options.eventType !== "keydown") continue;
|
|
245
|
-
|
|
290
|
+
const match = matches.get(id);
|
|
291
|
+
if (match?.matched && match.score === bestScore) {
|
|
246
292
|
if (registration.options.preventDefault) event.preventDefault();
|
|
247
293
|
if (registration.options.stopPropagation) event.stopPropagation();
|
|
248
294
|
if (!registration.options.requireReset || !registration.hasFired) {
|
|
249
295
|
this.#executeHotkeyCallback(registration, event);
|
|
250
|
-
if (registration.options.requireReset)
|
|
296
|
+
if (registration.options.requireReset) {
|
|
297
|
+
registration.hasFired = true;
|
|
298
|
+
registration.activeMatch = match.identity;
|
|
299
|
+
}
|
|
251
300
|
}
|
|
252
301
|
}
|
|
253
|
-
} else {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
if (registration.options.requireReset && registration.hasFired) {
|
|
258
|
-
if (this.#shouldResetRegistration(registration, event)) registration.hasFired = false;
|
|
259
|
-
}
|
|
302
|
+
} else if (registration.options.eventType === "keyup") {
|
|
303
|
+
const match = matches.get(id);
|
|
304
|
+
if (match?.matched && match.score === bestScore) this.#executeHotkeyCallback(registration, event);
|
|
260
305
|
}
|
|
261
306
|
}
|
|
262
307
|
}
|
|
@@ -294,7 +339,7 @@ var HotkeyManager = class HotkeyManager {
|
|
|
294
339
|
* Finds an existing registration with the same hotkey and target.
|
|
295
340
|
*/
|
|
296
341
|
#findConflictingRegistration(hotkey, target) {
|
|
297
|
-
for (const registration of this.registrations.state.values()) if (registration.hotkey
|
|
342
|
+
for (const registration of this.registrations.state.values()) if (areHotkeysEqual(registration.hotkey, hotkey, registration.options.platform) && registration.target === target) return registration;
|
|
298
343
|
return null;
|
|
299
344
|
}
|
|
300
345
|
/**
|
|
@@ -303,8 +348,13 @@ var HotkeyManager = class HotkeyManager {
|
|
|
303
348
|
#shouldResetRegistration(registration, event) {
|
|
304
349
|
const parsed = registration.parsedHotkey;
|
|
305
350
|
const releasedKey = normalizeKeyName(event.key);
|
|
306
|
-
|
|
307
|
-
if (
|
|
351
|
+
if (registration.activeMatch?.code && event.code === registration.activeMatch.code) return true;
|
|
352
|
+
if (parsed.code !== void 0) {
|
|
353
|
+
if (event.code === parsed.code) return true;
|
|
354
|
+
} else {
|
|
355
|
+
const parsedKeyNormalized = parsed.key.length === 1 ? parsed.key.toUpperCase() : parsed.key;
|
|
356
|
+
if ((releasedKey.length === 1 ? releasedKey.toUpperCase() : releasedKey) === parsedKeyNormalized) return true;
|
|
357
|
+
}
|
|
308
358
|
if (parsed.ctrl && releasedKey === "Control") return true;
|
|
309
359
|
if (parsed.shift && releasedKey === "Shift") return true;
|
|
310
360
|
if (parsed.alt && releasedKey === "Alt") return true;
|
|
@@ -323,7 +373,7 @@ var HotkeyManager = class HotkeyManager {
|
|
|
323
373
|
if (!registration) return false;
|
|
324
374
|
const parsed = registration.parsedHotkey;
|
|
325
375
|
const syntheticEvent = new KeyboardEvent(registration.options.eventType ?? "keydown", {
|
|
326
|
-
key: parsed.key,
|
|
376
|
+
...parsed.code !== void 0 ? { code: parsed.code } : { key: parsed.key },
|
|
327
377
|
ctrlKey: parsed.ctrl,
|
|
328
378
|
shiftKey: parsed.shift,
|
|
329
379
|
altKey: parsed.alt,
|
|
@@ -353,7 +403,7 @@ var HotkeyManager = class HotkeyManager {
|
|
|
353
403
|
* @returns True if a matching registration exists
|
|
354
404
|
*/
|
|
355
405
|
isRegistered(hotkey, target) {
|
|
356
|
-
for (const registration of this.registrations.state.values()) if (registration.hotkey
|
|
406
|
+
for (const registration of this.registrations.state.values()) if (areHotkeysEqual(registration.hotkey, hotkey, registration.options.platform)) {
|
|
357
407
|
if (target === void 0 || registration.target === target) return true;
|
|
358
408
|
}
|
|
359
409
|
return false;
|
|
@@ -377,5 +427,4 @@ function getHotkeyManager() {
|
|
|
377
427
|
}
|
|
378
428
|
|
|
379
429
|
//#endregion
|
|
380
|
-
export { HotkeyManager, getHotkeyManager, toHotkeyRegistrationView };
|
|
381
|
-
//# sourceMappingURL=hotkey-manager.js.map
|
|
430
|
+
export { HotkeyManager, getHotkeyManager, toHotkeyRegistrationView };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Hotkey } from "./hotkey.js";
|
|
1
|
+
import { Hotkey } from "./hotkey.types.js";
|
|
2
|
+
import { HotkeyRecorderValidationContext, RecorderOptions } from "./recorder-options.js";
|
|
2
3
|
import { Store } from "@tanstack/store";
|
|
3
|
-
|
|
4
4
|
//#region src/hotkey-recorder.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* State interface for the HotkeyRecorder.
|
|
7
7
|
*/
|
|
8
|
-
interface HotkeyRecorderState {
|
|
8
|
+
export interface HotkeyRecorderState {
|
|
9
9
|
/** Whether recording is currently active */
|
|
10
10
|
isRecording: boolean;
|
|
11
11
|
/** The currently recorded hotkey (for live preview) */
|
|
@@ -14,7 +14,9 @@ interface HotkeyRecorderState {
|
|
|
14
14
|
/**
|
|
15
15
|
* Options for configuring a HotkeyRecorder instance.
|
|
16
16
|
*/
|
|
17
|
-
interface HotkeyRecorderOptions {
|
|
17
|
+
export interface HotkeyRecorderOptions extends RecorderOptions {
|
|
18
|
+
/** Return true to accept, or false/a message to reject while staying in recording mode. */
|
|
19
|
+
validate?: (hotkey: Hotkey, context: HotkeyRecorderValidationContext) => boolean | string;
|
|
18
20
|
/** Callback when a hotkey is successfully recorded */
|
|
19
21
|
onRecord: (hotkey: Hotkey) => void;
|
|
20
22
|
/** Optional callback when recording is cancelled (Escape pressed) */
|
|
@@ -69,7 +71,7 @@ interface HotkeyRecorderOptions {
|
|
|
69
71
|
* unsubscribe()
|
|
70
72
|
* ```
|
|
71
73
|
*/
|
|
72
|
-
declare class HotkeyRecorder {
|
|
74
|
+
export declare class HotkeyRecorder {
|
|
73
75
|
#private;
|
|
74
76
|
/**
|
|
75
77
|
* The TanStack Store instance containing the recorder state.
|
|
@@ -111,6 +113,4 @@ declare class HotkeyRecorder {
|
|
|
111
113
|
*/
|
|
112
114
|
destroy(): void;
|
|
113
115
|
}
|
|
114
|
-
//#endregion
|
|
115
|
-
export { HotkeyRecorder, HotkeyRecorderOptions, HotkeyRecorderState };
|
|
116
|
-
//# sourceMappingURL=hotkey-recorder.d.ts.map
|
|
116
|
+
//#endregion
|