@tanstack/hotkeys 0.21.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_keyboard-event.js +8 -3
- package/dist/_match.js +1 -1
- package/dist/_registration.js +1 -1
- package/dist/_target-listeners.js +3 -6
- package/dist/adapter.d.ts +1 -1
- package/dist/adapter.js +1 -5
- package/dist/hotkey-manager.js +24 -24
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/key-state-tracker.js +25 -3
- package/dist/validate.d.ts +6 -2
- package/dist/validate.js +6 -2
- package/package.json +1 -1
- package/skills/hotkeys-core/SKILL.md +1 -1
- package/skills/hotkeys-core/references/testing.md +1 -1
package/dist/_keyboard-event.js
CHANGED
|
@@ -22,7 +22,7 @@ function normalizeKeyboardEvent(event, platform) {
|
|
|
22
22
|
meta: Boolean(event.metaKey),
|
|
23
23
|
altGraph: altGraph && !controlAlt,
|
|
24
24
|
location: event.location,
|
|
25
|
-
isComposing: Boolean(event.isComposing) || key === "Process" || key === "Unidentified"
|
|
25
|
+
isComposing: Boolean(event.isComposing) || event.keyCode === 229 || key === "Process" || key === "Unidentified"
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -93,10 +93,15 @@ function isAsciiLetter(key) {
|
|
|
93
93
|
function isLatinLetter(key) {
|
|
94
94
|
return /^\p{Script_Extensions=Latin}$/u.test(key);
|
|
95
95
|
}
|
|
96
|
+
let collator;
|
|
96
97
|
/** Compares letters without case, preserving accents and avoiding expansions such as ß → SS. */
|
|
97
98
|
function keysEqual(left, right) {
|
|
98
|
-
if (
|
|
99
|
-
|
|
99
|
+
if (left === right) return true;
|
|
100
|
+
if (/^\p{Letter}$/u.test(left) && /^\p{Letter}$/u.test(right)) {
|
|
101
|
+
collator ??= new Intl.Collator(void 0, { sensitivity: "accent" });
|
|
102
|
+
return collator.compare(left, right) === 0;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
100
105
|
}
|
|
101
106
|
/** Rejects reserved physical-token syntax in logical object fields while allowing Unicode characters. */
|
|
102
107
|
function assertLogicalKey(key) {
|
package/dist/_match.js
CHANGED
|
@@ -11,7 +11,7 @@ function matchKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
|
11
11
|
matched: false,
|
|
12
12
|
score: 0
|
|
13
13
|
};
|
|
14
|
-
if (normalized.isComposing
|
|
14
|
+
if (normalized.isComposing) return noMatch;
|
|
15
15
|
if (normalized.altGraph && (parsed.ctrl || parsed.alt)) return noMatch;
|
|
16
16
|
if (normalized.altGraph && !altGraphTypedCharacter(normalized)) return noMatch;
|
|
17
17
|
if (parsed.code !== void 0) {
|
package/dist/_registration.js
CHANGED
|
@@ -44,7 +44,7 @@ function withDefaults(defaults, options) {
|
|
|
44
44
|
}
|
|
45
45
|
/** Whether a parsed chord has a key. An empty one would match key-less events. */
|
|
46
46
|
function chordHasKey(chord) {
|
|
47
|
-
return chord.code
|
|
47
|
+
return !!(chord.code || chord.key);
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Resolves `ignoreInputs: 'auto'` from the hotkey (the first step of a sequence).
|
|
@@ -9,11 +9,7 @@ var TargetListeners = class {
|
|
|
9
9
|
}
|
|
10
10
|
add(target, capture, id) {
|
|
11
11
|
if (typeof document === "undefined") return;
|
|
12
|
-
|
|
13
|
-
if (!phases) {
|
|
14
|
-
phases = /* @__PURE__ */ new Map();
|
|
15
|
-
this.#targets.set(target, phases);
|
|
16
|
-
}
|
|
12
|
+
const phases = this.#targets.get(target) ?? /* @__PURE__ */ new Map();
|
|
17
13
|
let group = phases.get(capture);
|
|
18
14
|
if (!group) {
|
|
19
15
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -41,9 +37,10 @@ var TargetListeners = class {
|
|
|
41
37
|
keydown: listener("keydown"),
|
|
42
38
|
keyup: listener("keyup")
|
|
43
39
|
};
|
|
44
|
-
phases.set(capture, group);
|
|
45
40
|
target.addEventListener("keydown", group.keydown, capture);
|
|
46
41
|
target.addEventListener("keyup", group.keyup, capture);
|
|
42
|
+
phases.set(capture, group);
|
|
43
|
+
this.#targets.set(target, phases);
|
|
47
44
|
}
|
|
48
45
|
group.ids.add(id);
|
|
49
46
|
}
|
package/dist/adapter.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { Store, createStore } from "./_store.js";
|
|
|
2
2
|
import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
|
|
3
3
|
import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
|
|
4
4
|
import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
|
|
5
|
-
import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
|
|
6
5
|
import { definedOptions, withDefaults } from "./_registration.js";
|
|
6
|
+
import { createAppHotkeysStore } from "@tanstack/hotkeys";
|
|
7
7
|
//#region src/adapter.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* A hotkey and its callback, with optional per-binding options. Chords accept
|
package/dist/adapter.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { detectPlatform } from "./platform.js";
|
|
2
|
-
import { isHotkeySequence, normalizeHotkey, parseHotkey } from "./parse.js";
|
|
3
1
|
import { chordHasKey, defaultHotkeyOptions, definedOptions, withDefaults } from "./_registration.js";
|
|
4
2
|
import { createStore } from "./_store.js";
|
|
5
|
-
import { getHotkeyManager } from "
|
|
6
|
-
import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
|
|
7
|
-
import { getAppHotkeyRegistrationOptions, isAppHotkey, resolveAppHotkey } from "./app-hotkeys.js";
|
|
3
|
+
import { createAppHotkeysStore, detectPlatform, getAppHotkeyRegistrationOptions, getHotkeyManager, isAppHotkey, isHotkeySequence, normalizeHotkey, parseHotkey, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
8
4
|
|
|
9
5
|
//#region src/adapter.ts
|
|
10
6
|
/** Reconciles adapter registrations while retaining handles for option updates. */
|
package/dist/hotkey-manager.js
CHANGED
|
@@ -57,6 +57,24 @@ function chordString(chord, parsed) {
|
|
|
57
57
|
function generateId() {
|
|
58
58
|
return `hotkey_${++registrationIdCounter}`;
|
|
59
59
|
}
|
|
60
|
+
/** A handle for a registration that listens for nothing. */
|
|
61
|
+
function inactiveHandle(callback) {
|
|
62
|
+
let currentCallback = callback;
|
|
63
|
+
return {
|
|
64
|
+
id: generateId(),
|
|
65
|
+
unregister: () => {},
|
|
66
|
+
get callback() {
|
|
67
|
+
return currentCallback;
|
|
68
|
+
},
|
|
69
|
+
set callback(newCallback) {
|
|
70
|
+
currentCallback = newCallback;
|
|
71
|
+
},
|
|
72
|
+
setOptions: () => {},
|
|
73
|
+
get isActive() {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
60
78
|
/** Defaults for a registration of the given kind. */
|
|
61
79
|
function registrationDefaults(kind) {
|
|
62
80
|
return {
|
|
@@ -303,26 +321,8 @@ var HotkeyManager = class HotkeyManager {
|
|
|
303
321
|
this.registrations.setState((prev) => new Map(prev));
|
|
304
322
|
}
|
|
305
323
|
register(input, callback, options = {}) {
|
|
306
|
-
if (input == null) throw new Error("A hotkey is required. Skip registration when there is no hotkey.");
|
|
307
324
|
const hotkey = simplifyHotkey(input);
|
|
308
|
-
if (isHotkeySequence(hotkey) && hotkey.length === 0
|
|
309
|
-
if (typeof document === "undefined" && !options.target) {
|
|
310
|
-
let currentCallback = callback;
|
|
311
|
-
return {
|
|
312
|
-
id: generateId(),
|
|
313
|
-
unregister: () => {},
|
|
314
|
-
get callback() {
|
|
315
|
-
return currentCallback;
|
|
316
|
-
},
|
|
317
|
-
set callback(newCallback) {
|
|
318
|
-
currentCallback = newCallback;
|
|
319
|
-
},
|
|
320
|
-
setOptions: () => {},
|
|
321
|
-
get isActive() {
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
}
|
|
325
|
+
if (hotkey == null || isHotkeySequence(hotkey) && hotkey.length === 0 || typeof document === "undefined" && !options.target) return inactiveHandle(callback);
|
|
326
326
|
const id = generateId();
|
|
327
327
|
const platform = options.platform ?? this.#platform;
|
|
328
328
|
const target = options.target ?? document;
|
|
@@ -361,14 +361,14 @@ var HotkeyManager = class HotkeyManager {
|
|
|
361
361
|
};
|
|
362
362
|
}
|
|
363
363
|
const steps = registration.kind === "sequence" ? registration.parsedSequence : [registration.parsedChord];
|
|
364
|
-
if (!steps.every(chordHasKey))
|
|
364
|
+
if (!steps.every(chordHasKey)) return inactiveHandle(callback);
|
|
365
365
|
if (process.env.NODE_ENV !== "production") {
|
|
366
366
|
for (const { key } of steps) if (key !== void 0 && !isKeyNameShape(key)) console.warn(`Unknown key '${key}' in '${formatForMessage(registration.hotkey)}'. Key names are case-sensitive, such as 'Escape' and 'F1', so this hotkey may never fire.`);
|
|
367
367
|
}
|
|
368
368
|
const conflicting = this.#findConflictingRegistration(registration);
|
|
369
369
|
if (conflicting) handleConflict(conflicting.id, formatForMessage(registration.hotkey), options.conflictBehavior ?? "warn", (conflictId) => this.#unregister(conflictId));
|
|
370
|
-
this.registrations.setState((prev) => new Map(prev).set(id, registration));
|
|
371
370
|
this.#listeners.add(target, !!registration.options.capture, id);
|
|
371
|
+
this.registrations.setState((prev) => new Map(prev).set(id, registration));
|
|
372
372
|
const manager = this;
|
|
373
373
|
return {
|
|
374
374
|
get id() {
|
|
@@ -517,7 +517,7 @@ var HotkeyManager = class HotkeyManager {
|
|
|
517
517
|
this.#fire(registration, event);
|
|
518
518
|
continue;
|
|
519
519
|
}
|
|
520
|
-
if (!registration.options.requireReset || !registration.hasFired) {
|
|
520
|
+
if (!registration.options.requireReset || !registration.hasFired || !event.repeat) {
|
|
521
521
|
if (registration.options.requireReset) {
|
|
522
522
|
registration.hasFired = true;
|
|
523
523
|
registration.activeMatch = match.identity;
|
|
@@ -648,13 +648,13 @@ var HotkeyManager = class HotkeyManager {
|
|
|
648
648
|
};
|
|
649
649
|
}
|
|
650
650
|
/**
|
|
651
|
-
* Finds an existing registration on the same target and
|
|
651
|
+
* Finds an existing registration on the same target, scope, and event type whose steps
|
|
652
652
|
* resolve to the same identity. Each side keeps its own platform, so `Mod+S` on macOS
|
|
653
653
|
* and `Mod+S` on Windows are different bindings.
|
|
654
654
|
*/
|
|
655
655
|
#findConflictingRegistration(candidate) {
|
|
656
656
|
const steps = resolvedSteps(candidate);
|
|
657
|
-
for (const registration of this.registrations.state.values()) if (registration.target === candidate.target && registration.options.scope === candidate.options.scope && sameResolvedSteps(resolvedSteps(registration), steps)) return registration;
|
|
657
|
+
for (const registration of this.registrations.state.values()) if (registration.target === candidate.target && registration.options.scope === candidate.options.scope && registration.options.eventType === candidate.options.eventType && sameResolvedSteps(resolvedSteps(registration), steps)) return registration;
|
|
658
658
|
return null;
|
|
659
659
|
}
|
|
660
660
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -13,4 +13,5 @@ import { HeldModifierOptions, matchesHeldModifiers } from "./hint.js";
|
|
|
13
13
|
import { HotkeyConflict, HotkeyConflictOptions, findHotkeyConflicts } from "./conflicts.js";
|
|
14
14
|
import { KeyStateTracker, KeyStateTrackerState, getKeyStateTracker } from "./key-state-tracker.js";
|
|
15
15
|
import { HotkeyChordRecorderOptions, HotkeyChordRecorderValidationContext, HotkeyRecorder, HotkeyRecorderCommitKeys, HotkeyRecorderOptions, HotkeyRecorderState, HotkeyRecorderValidationContext, HotkeySequenceRecorderOptions, HotkeySequenceRecorderValidationContext, RecorderKeyMode, RecorderOptions, RecorderRejection } from "./hotkey-recorder.js";
|
|
16
|
-
|
|
16
|
+
import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
|
|
17
|
+
export { ALL_KEYS, ActivateHotkeyScopeOptions, AppHotkey, AppHotkeyChordOptions, AppHotkeyDefaultKind, AppHotkeyId, AppHotkeyInput, AppHotkeyListItem, AppHotkeyOptions, AppHotkeySequenceOptions, AppHotkeyStatus, AppHotkeys, AppHotkeysState, AppHotkeysStore, AppHotkeysStoreOptions, CanonicalModifier, ConflictBehavior, CreateHotkeyChordHandlerOptions, CustomHotkeys, DEFAULT_SEQUENCE_TIMEOUT, DefinedAppHotkeys, DisplayHotkey, DisplayHotkeyChord, EDITING_KEYS, EditingKey, FUNCTION_KEYS, FormatDisplayOptions, FourModifierHotkeyChord, FunctionKey, HeldModifierOptions, Hotkey, HotkeyCallback, HotkeyCallbackContext, HotkeyCallbackContextFor, HotkeyChord, HotkeyChordCallbackContext, HotkeyChordOptions, HotkeyChordRecorderOptions, HotkeyChordRecorderValidationContext, HotkeyChordRegistration, HotkeyChordRegistrationView, HotkeyConflict, HotkeyConflictOptions, HotkeyManager, HotkeyMeta, HotkeyOptions, HotkeyOptionsFor, HotkeyRecorder, HotkeyRecorderCommitKeys, HotkeyRecorderOptions, HotkeyRecorderState, HotkeyRecorderValidationContext, HotkeyRegistration, HotkeyRegistrationHandle, HotkeyRegistrationStatus, HotkeyRegistrationView, HotkeyScopesState, HotkeySequence, HotkeySequenceCallbackContext, HotkeySequenceOptions, HotkeySequenceRecorderOptions, HotkeySequenceRecorderValidationContext, HotkeySequenceRegistration, HotkeySequenceRegistrationView, IndividualKey, KEY_DISPLAY_SYMBOLS, Key, KeyStateTracker, KeyStateTrackerState, KeyboardEventMatch, LETTER_KEYS, LINUX_MODIFIER_LABELS, LetterKey, LogicalKey, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, Modifier, MultiHotkeyChordHandler, NAVIGATION_KEYS, NUMBER_KEYS, NamedKey, NavigationKey, NonPunctuationKey, NormalizedKeyboardEvent, NumberKey, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, ParsedHotkeyChord, ParsedModifiers, PhysicalKey, PhysicalKeyCode, PunctuationKey, RawHotkeyChord, RawModifiers, RecorderKeyMode, RecorderOptions, RecorderRejection, Register, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence, RegisteredAppHotkeys, SingleModifierHotkeyChord, Target, ThreeModifierHotkeyChord, TwoModifierHotkeyChord, ValidatableHotkey, ValidationResult, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createAppHotkeysStore, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,9 @@ import { DEFAULT_SEQUENCE_TIMEOUT, areHotkeysEqual, createHotkeyChordHandler, cr
|
|
|
8
8
|
import { matchesHeldModifiers } from "./hint.js";
|
|
9
9
|
import { HotkeyManager, getHotkeyManager, toHotkeyRegistrationView } from "./hotkey-manager.js";
|
|
10
10
|
import { findHotkeyConflicts } from "./conflicts.js";
|
|
11
|
+
import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
|
|
11
12
|
import { AppHotkeysStore, defineAppHotkeys, getAppHotkeyRegistrationOptions, isAppHotkey, isCustomHotkey, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey } from "./app-hotkeys.js";
|
|
12
13
|
import { KeyStateTracker, getKeyStateTracker } from "./key-state-tracker.js";
|
|
13
14
|
import { HotkeyRecorder } from "./hotkey-recorder.js";
|
|
14
15
|
|
|
15
|
-
export { ALL_KEYS, AppHotkeysStore, DEFAULT_SEQUENCE_TIMEOUT, EDITING_KEYS, FUNCTION_KEYS, HotkeyManager, HotkeyRecorder, KEY_DISPLAY_SYMBOLS, KeyStateTracker, LETTER_KEYS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, NAVIGATION_KEYS, NUMBER_KEYS, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
|
|
16
|
+
export { ALL_KEYS, AppHotkeysStore, DEFAULT_SEQUENCE_TIMEOUT, EDITING_KEYS, FUNCTION_KEYS, HotkeyManager, HotkeyRecorder, KEY_DISPLAY_SYMBOLS, KeyStateTracker, LETTER_KEYS, LINUX_MODIFIER_LABELS, MAC_MODIFIER_LABELS, MAC_MODIFIER_SYMBOLS, MODIFIER_ALIASES, MODIFIER_KEYS, MODIFIER_ORDER, NAVIGATION_KEYS, NUMBER_KEYS, PUNCTUATION_CODE_MAP, PUNCTUATION_KEYS, PUNCTUATION_KEY_DISPLAY_LABELS, WINDOWS_MODIFIER_LABELS, areHotkeysEqual, assertValidHotkey, checkHotkey, createAppHotkeysStore, createHotkeyChordHandler, createMultiHotkeyChordHandler, createSequenceMatcher, defineAppHotkeys, detectPlatform, findHotkeyConflicts, formatForDisplay, formatHotkey, getAppHotkeyRegistrationOptions, getHotkeyManager, getKeyStateTracker, hasNonModifierKey, isAppHotkey, isCustomHotkey, isHotkeySequence, isModifierKey, isSingleLetterKey, matchesHeldModifiers, matchesKeyboardEvent, normalizeHotkey, normalizeKeyName, parseHotkey, parseKeyboardEvent, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey, resolveModifier, simplifyHotkey, toHotkeyRegistrationView, validateHotkey };
|
|
@@ -100,11 +100,14 @@ var KeyStateTracker = class KeyStateTracker {
|
|
|
100
100
|
for (const [entryIdentity, entry] of this.#heldEntries) if (entry.key === key) this.#heldEntries.delete(entryIdentity);
|
|
101
101
|
}
|
|
102
102
|
this.#reconcileModifiers(event);
|
|
103
|
-
if (MODIFIER_KEYS.has(key))
|
|
104
|
-
for (const [entryIdentity, entry] of this.#heldEntries) if (!MODIFIER_KEYS.has(entry.key)) this.#heldEntries.delete(entryIdentity);
|
|
105
|
-
}
|
|
103
|
+
if (MODIFIER_KEYS.has(key)) this.#releaseNonModifiers();
|
|
106
104
|
this.#syncState();
|
|
107
105
|
};
|
|
106
|
+
const onContextMenu = (event) => {
|
|
107
|
+
if (event.defaultPrevented || !event.isTrusted) return;
|
|
108
|
+
const reconciled = this.#reconcileModifiers(event);
|
|
109
|
+
if (this.#releaseNonModifiers() || reconciled) this.#syncState();
|
|
110
|
+
};
|
|
108
111
|
const onMouse = (event) => {
|
|
109
112
|
if (this.#reconcileModifiers(event)) this.#syncState();
|
|
110
113
|
};
|
|
@@ -139,6 +142,16 @@ var KeyStateTracker = class KeyStateTracker {
|
|
|
139
142
|
"blur",
|
|
140
143
|
onBlur,
|
|
141
144
|
false
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
"contextmenu",
|
|
148
|
+
onContextMenu,
|
|
149
|
+
false
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
"visibilitychange",
|
|
153
|
+
() => document.hidden && onBlur(),
|
|
154
|
+
false
|
|
142
155
|
]
|
|
143
156
|
];
|
|
144
157
|
for (const [type, listener, capture] of this.#listeners) window.addEventListener(type, listener, capture);
|
|
@@ -159,6 +172,15 @@ var KeyStateTracker = class KeyStateTracker {
|
|
|
159
172
|
}
|
|
160
173
|
return changed;
|
|
161
174
|
}
|
|
175
|
+
/** Forgets every held key that isn't a modifier. Returns whether any was held. */
|
|
176
|
+
#releaseNonModifiers() {
|
|
177
|
+
let changed = false;
|
|
178
|
+
for (const [identity, entry] of this.#heldEntries) if (!MODIFIER_KEYS.has(entry.key)) {
|
|
179
|
+
this.#heldEntries.delete(identity);
|
|
180
|
+
changed = true;
|
|
181
|
+
}
|
|
182
|
+
return changed;
|
|
183
|
+
}
|
|
162
184
|
/**
|
|
163
185
|
* Syncs the internal Set to the Store state.
|
|
164
186
|
*/
|
package/dist/validate.d.ts
CHANGED
|
@@ -50,9 +50,13 @@ export declare function assertValidHotkey(hotkey: ValidatableHotkey): void;
|
|
|
50
50
|
*
|
|
51
51
|
* @example
|
|
52
52
|
* ```ts
|
|
53
|
-
* checkHotkey('
|
|
54
|
-
* // Console:
|
|
53
|
+
* checkHotkey('Control+escape')
|
|
54
|
+
* // Console: Hotkey 'Control+escape' warnings: Unknown key: 'escape'. Key names are case-sensitive, such as 'Escape' and 'F1'.
|
|
55
55
|
* // Returns: true
|
|
56
|
+
*
|
|
57
|
+
* checkHotkey('Ctl+K')
|
|
58
|
+
* // Console: Hotkey 'Ctl+K' errors: Unknown modifier: 'Ctl'
|
|
59
|
+
* // Returns: false
|
|
56
60
|
* ```
|
|
57
61
|
*/
|
|
58
62
|
export declare function checkHotkey(hotkey: ValidatableHotkey): boolean;
|
package/dist/validate.js
CHANGED
|
@@ -94,9 +94,13 @@ function assertValidHotkey(hotkey) {
|
|
|
94
94
|
*
|
|
95
95
|
* @example
|
|
96
96
|
* ```ts
|
|
97
|
-
* checkHotkey('
|
|
98
|
-
* // Console:
|
|
97
|
+
* checkHotkey('Control+escape')
|
|
98
|
+
* // Console: Hotkey 'Control+escape' warnings: Unknown key: 'escape'. Key names are case-sensitive, such as 'Escape' and 'F1'.
|
|
99
99
|
* // Returns: true
|
|
100
|
+
*
|
|
101
|
+
* checkHotkey('Ctl+K')
|
|
102
|
+
* // Console: Hotkey 'Ctl+K' errors: Unknown modifier: 'Ctl'
|
|
103
|
+
* // Returns: false
|
|
100
104
|
* ```
|
|
101
105
|
*/
|
|
102
106
|
function checkHotkey(hotkey) {
|
package/package.json
CHANGED
|
@@ -50,7 +50,7 @@ Use the first mechanism that fits:
|
|
|
50
50
|
1. **Lifetime.** A hotkey registered in a component exists while that component is mounted. A dialog's `Escape` belongs in the dialog.
|
|
51
51
|
2. **`enabled`.** A condition inside a mounted component.
|
|
52
52
|
3. **Scope.** A group that turns on and off together, such as a dialog, pane, or tab. An `exclusive` scope suspends every other scope, including global hotkeys, while it is active.
|
|
53
|
-
4. **`target`.** Only while focus is
|
|
53
|
+
4. **`target`.** Only while focus is on one element or inside it. If nothing there can take focus, give the element `tabindex="0"`.
|
|
54
54
|
|
|
55
55
|
When nesting scopes, stacking dialogs, or keeping some hotkeys working during an exclusive scope, read [scopes](references/scopes.md).
|
|
56
56
|
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
- **Reset between tests.** Call `HotkeyManager.resetInstance()`, plus `KeyStateTracker.resetInstance()` for held-key tests, in `afterEach`. Registrations and scope activations otherwise outlive a test that fails before unmounting.
|
|
5
5
|
- **Dispatch real events.** Use `new KeyboardEvent('keydown', { key: 's', code: 'KeyS', metaKey: true, bubbles: true, cancelable: true })`. Physical bindings such as `[KeyS]` match only `code`. Check `event.defaultPrevented` to assert the browser action was blocked.
|
|
6
6
|
- **Dispatch where focus is.** For a hotkey with a `target`, or to test typing in a field, focus the element and dispatch on it with `bubbles: true`.
|
|
7
|
-
- **Repeats and sequences.** `repeat: true` simulates holding a key; it fires chords again unless they set `requireReset`, and it never advances a sequence. Use fake timers to test a sequence's `timeout` (1000 ms by default).
|
|
7
|
+
- **Repeats and sequences.** `repeat: true` simulates holding a key; it fires chords again unless they set `requireReset`, and it never advances a sequence. A keydown without `repeat` is a new press, so it fires a `requireReset` chord even without a keyup in between. Use fake timers to test a sequence's `timeout` (1000 ms by default).
|