@tanstack/hotkeys 0.23.0 → 0.25.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/dist/_app-hotkeys-factory.d.ts +1 -3
- package/dist/_app-hotkeys-factory.js +2 -3
- package/dist/_event-target.js +40 -7
- package/dist/_keyboard-event.js +8 -5
- package/dist/_manager-ref.js +1 -2
- package/dist/_match.js +5 -3
- package/dist/_named-keys.d.ts +1 -3
- package/dist/_named-keys.js +1 -2
- package/dist/_recorder-chord.js +7 -13
- package/dist/_recording-guard.js +1 -2
- package/dist/_registration.d.ts +1 -3
- package/dist/_registration.js +11 -6
- package/dist/_server.js +3 -4
- package/dist/_store.d.ts +1 -3
- package/dist/_store.js +1 -2
- package/dist/_target-listeners.js +11 -3
- package/dist/adapter.d.ts +10 -8
- package/dist/adapter.js +9 -5
- package/dist/app-hotkeys.d.ts +4 -5
- package/dist/app-hotkeys.js +10 -21
- package/dist/conflicts.d.ts +2 -4
- package/dist/conflicts.js +5 -6
- package/dist/constants.d.ts +2 -4
- package/dist/constants.js +1 -2
- package/dist/display-labels.d.ts +1 -3
- package/dist/display-labels.js +1 -2
- package/dist/format.d.ts +2 -4
- package/dist/format.js +4 -5
- package/dist/hint.d.ts +1 -3
- package/dist/hint.js +1 -2
- package/dist/hotkey-manager.d.ts +23 -4
- package/dist/hotkey-manager.js +138 -30
- package/dist/hotkey-recorder.d.ts +19 -7
- package/dist/hotkey-recorder.js +8 -11
- package/dist/hotkey.types.d.ts +1 -3
- package/dist/index.d.ts +8 -8
- package/dist/index.js +3 -3
- package/dist/key-state-tracker.d.ts +1 -3
- package/dist/key-state-tracker.js +2 -3
- package/dist/key.types.d.ts +5 -6
- package/dist/match.d.ts +4 -5
- package/dist/match.js +7 -7
- package/dist/parse.d.ts +2 -3
- package/dist/parse.js +10 -19
- package/dist/platform.d.ts +1 -3
- package/dist/platform.js +1 -2
- package/dist/validate.d.ts +33 -13
- package/dist/validate.js +51 -34
- package/dist/vendor/@tanstack/store/dist/store.d.ts +0 -2
- package/dist/vendor/@tanstack/store/dist/types.d.ts +0 -2
- package/package.json +4 -4
- package/skills/app-hotkeys/SKILL.md +1 -1
- package/skills/hotkeys-core/SKILL.md +3 -1
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import { AppHotkeys, AppHotkeysStore, AppHotkeysStoreOptions } from "./app-hotkeys.js";
|
|
2
|
-
//#region src/_app-hotkeys-factory.d.ts
|
|
3
2
|
/** Creates the store for app hotkeys from `defineAppHotkeys`. */
|
|
4
|
-
export declare function createAppHotkeysStore<T extends AppHotkeys>(appHotkeys: T, options: AppHotkeysStoreOptions<T>): AppHotkeysStore<T>;
|
|
5
|
-
//#endregion
|
|
3
|
+
export declare function createAppHotkeysStore<T extends AppHotkeys>(appHotkeys: T, options: AppHotkeysStoreOptions<T>): AppHotkeysStore<T>;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// src/_app-hotkeys-factory.ts
|
|
2
2
|
let factory;
|
|
3
3
|
function setAppHotkeysStoreFactory(create) {
|
|
4
4
|
factory = create;
|
|
5
5
|
}
|
|
6
6
|
/** Creates the store for app hotkeys from `defineAppHotkeys`. */
|
|
7
7
|
function createAppHotkeysStore(appHotkeys, options) {
|
|
8
|
-
if (!factory) throw new Error("[hotkeys] App hotkeys must come from defineAppHotkeys
|
|
8
|
+
if (!factory) throw new Error("[hotkeys] App hotkeys must come from defineAppHotkeys");
|
|
9
9
|
return factory(appHotkeys, options);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
//#endregion
|
|
13
12
|
export { createAppHotkeysStore, setAppHotkeysStoreFactory };
|
package/dist/_event-target.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { codeToLogicalKey } from "./_keyboard-event.js";
|
|
1
2
|
import { normalizeKeyName } from "./constants.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// src/_event-target.ts
|
|
4
5
|
function isElement(node) {
|
|
5
6
|
return node?.nodeType === 1;
|
|
6
7
|
}
|
|
@@ -220,11 +221,11 @@ function getOwnedKeys(element) {
|
|
|
220
221
|
* are never owned; Enter and Space are owned only without modifiers, while
|
|
221
222
|
* navigation and typeahead keys allow Shift (for example, to extend a selection).
|
|
222
223
|
*/
|
|
223
|
-
function ownsKey(element, event, key) {
|
|
224
|
+
function ownsKey(element, event, key, produced) {
|
|
224
225
|
if (event.ctrlKey || event.metaKey || event.altKey) return false;
|
|
225
226
|
const owned = getOwnedKeys(element);
|
|
226
227
|
if (key === "Enter" || key === "Space") return !event.shiftKey && owned.includes(key);
|
|
227
|
-
return owned.includes(key) || owned.includes(TYPEAHEAD) && /^.$/u.test(
|
|
228
|
+
return owned.includes(key) || owned.includes(TYPEAHEAD) && /^.$/u.test(produced);
|
|
228
229
|
}
|
|
229
230
|
/**
|
|
230
231
|
* Finds the first element on the event's way out that blocks it, walking out
|
|
@@ -250,6 +251,38 @@ function findBlocker(event, doc, test) {
|
|
|
250
251
|
function blocks({ elements, index }, target) {
|
|
251
252
|
return index >= 0 && (isDocumentLevel(target) || index < elements.indexOf(target));
|
|
252
253
|
}
|
|
254
|
+
/** Open modal dialogs and popovers that close on Escape, per event. */
|
|
255
|
+
const layerCache = /* @__PURE__ */ new WeakMap();
|
|
256
|
+
/** Whether `node` is `layer` or inside it, across shadow roots. */
|
|
257
|
+
function isInside(node, layer) {
|
|
258
|
+
for (; node; node = composedParent(node)) if (node === layer) return true;
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Returns whether Escape will close a modal `<dialog>` or an open popover that
|
|
263
|
+
* `target` isn't inside. The browser closes it only when Escape's keydown
|
|
264
|
+
* isn't prevented, so hotkeys outside it leave Escape alone.
|
|
265
|
+
*/
|
|
266
|
+
function closesNativeLayer(event, target) {
|
|
267
|
+
if (event.type !== "keydown" || event.key !== "Escape") return false;
|
|
268
|
+
let layers = layerCache.get(event);
|
|
269
|
+
if (!layers) {
|
|
270
|
+
const doc = getTargetDocument(target);
|
|
271
|
+
const focused = getDeepActiveElement(doc);
|
|
272
|
+
const query = (selector) => {
|
|
273
|
+
try {
|
|
274
|
+
return [...doc.querySelectorAll(selector)];
|
|
275
|
+
} catch {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const dialogs = query("dialog:modal");
|
|
280
|
+
const holding = dialogs.filter((dialog) => isInside(focused, dialog));
|
|
281
|
+
layers = [...holding.length ? holding : dialogs, ...query(":popover-open").filter((layer) => layer.popover !== "manual")];
|
|
282
|
+
layerCache.set(event, layers);
|
|
283
|
+
}
|
|
284
|
+
return layers.some((layer) => isDocumentLevel(target) || !isInside(target, layer));
|
|
285
|
+
}
|
|
253
286
|
const ignoreCache = /* @__PURE__ */ new WeakMap();
|
|
254
287
|
/**
|
|
255
288
|
* Returns whether a hotkey on `target` should skip an event because a control
|
|
@@ -264,8 +297,9 @@ const ignoreCache = /* @__PURE__ */ new WeakMap();
|
|
|
264
297
|
function shouldIgnoreInputEvent(event, target) {
|
|
265
298
|
let blocker = ignoreCache.get(event);
|
|
266
299
|
if (!blocker) {
|
|
267
|
-
const
|
|
268
|
-
|
|
300
|
+
const produced = event.key === "Unidentified" ? codeToLogicalKey(event.code) ?? event.code : event.key;
|
|
301
|
+
const key = normalizeKeyName(produced);
|
|
302
|
+
blocker = findBlocker(event, getTargetDocument(target), (element) => isInputElement(element) || ownsKey(element, event, key, produced));
|
|
269
303
|
ignoreCache.set(event, blocker);
|
|
270
304
|
}
|
|
271
305
|
return blocks(blocker, target);
|
|
@@ -279,5 +313,4 @@ function isTypingEvent(event, target) {
|
|
|
279
313
|
return blocks(findBlocker(event, getTargetDocument(target), isInputElement), target);
|
|
280
314
|
}
|
|
281
315
|
|
|
282
|
-
|
|
283
|
-
export { getTargetDocument, isDocumentLevel, isInputElement, isTypingEvent, shouldIgnoreInputEvent, targetHasFocus };
|
|
316
|
+
export { closesNativeLayer, getTargetDocument, isDocumentLevel, isInputElement, isTypingEvent, shouldIgnoreInputEvent, targetHasFocus };
|
package/dist/_keyboard-event.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MODIFIER_ALIASES, PUNCTUATION_CODE_MAP, normalizeKeyName } from "./constants.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/_keyboard-event.ts
|
|
4
4
|
/** Normalizes Unicode key names and modifier flags while retaining physical code and IME state. */
|
|
5
5
|
function normalizeKeyboardEvent(event, platform) {
|
|
6
6
|
let altGraph = false;
|
|
@@ -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) || event.keyCode === 229 || key === "Process" || key === "Unidentified"
|
|
25
|
+
isComposing: Boolean(event.isComposing) || event.keyCode === 229 || key === "Process" || key === "Unidentified" && (!event.code || event.code === "Unidentified" || isModifierCode(event.code))
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -77,6 +77,10 @@ function isCodeName(code) {
|
|
|
77
77
|
function physicalCodeOf(token) {
|
|
78
78
|
return token.startsWith("[") && token.endsWith("]") && isCodeName(token.slice(1, -1)) ? token.slice(1, -1) : void 0;
|
|
79
79
|
}
|
|
80
|
+
/** Whether a `KeyboardEvent.code` is a modifier key, such as `ShiftLeft`. */
|
|
81
|
+
function isModifierCode(code) {
|
|
82
|
+
return /^(Control|Shift|Alt|Meta)(Left|Right)$/.test(code);
|
|
83
|
+
}
|
|
80
84
|
/** Splits modifier tokens while retaining a trailing literal plus: `Control++` → `['Control', '+']`. */
|
|
81
85
|
function splitChordParts(hotkey) {
|
|
82
86
|
if (!hotkey.endsWith("+")) return hotkey.split("+");
|
|
@@ -105,7 +109,7 @@ function keysEqual(left, right) {
|
|
|
105
109
|
}
|
|
106
110
|
/** Rejects reserved physical-token syntax in logical object fields while allowing Unicode characters. */
|
|
107
111
|
function assertLogicalKey(key) {
|
|
108
|
-
if (/^\[.*\]$/s.test(key.trim())) throw new Error(
|
|
112
|
+
if (/^\[.*\]$/s.test(key.trim())) throw new Error(`[hotkeys] Put '${key}' in the code field, not key`);
|
|
109
113
|
}
|
|
110
114
|
/**
|
|
111
115
|
* Whether a key is written like a key the library knows: one character,
|
|
@@ -116,5 +120,4 @@ function isKeyNameShape(key) {
|
|
|
116
120
|
return /^(?:.|F(?:[1-9]|1\d|2[0-4])|[A-Z][a-z][A-Za-z\d]*)$/su.test(key);
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
export { altGraphTypedCharacter, assertLogicalKey, codeToLogicalKey, isAsciiLetter, isCodeName, isKeyNameShape, isLatinLetter, isPrintableKey, keysEqual, modifierAlias, normalizeKeyboardEvent, physicalCodeOf, splitChordParts };
|
|
123
|
+
export { altGraphTypedCharacter, assertLogicalKey, codeToLogicalKey, isAsciiLetter, isCodeName, isKeyNameShape, isLatinLetter, isModifierCode, isPrintableKey, keysEqual, modifierAlias, normalizeKeyboardEvent, physicalCodeOf, splitChordParts };
|
package/dist/_manager-ref.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/_manager-ref.ts
|
|
2
2
|
/**
|
|
3
3
|
* The HotkeyManager singleton, once created. Code that only reads the live
|
|
4
4
|
* registry, such as conflict checks, reads this instead of importing the
|
|
@@ -6,5 +6,4 @@
|
|
|
6
6
|
*/
|
|
7
7
|
const managerRef = { current: null };
|
|
8
8
|
|
|
9
|
-
//#endregion
|
|
10
9
|
export { managerRef };
|
package/dist/_match.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { detectPlatform } from "./platform.js";
|
|
2
1
|
import { altGraphTypedCharacter, codeToLogicalKey, isAsciiLetter, isLatinLetter, isPrintableKey, keysEqual, normalizeKeyboardEvent } from "./_keyboard-event.js";
|
|
3
2
|
import { parseHotkey } from "./parse.js";
|
|
3
|
+
import { detectPlatform } from "./platform.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/_match.ts
|
|
6
6
|
/** Matches an event and reports the winning candidate and release identity. */
|
|
7
7
|
function matchKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
8
8
|
const parsed = typeof hotkey === "string" ? parseHotkey(hotkey, platform) : hotkey;
|
|
@@ -44,6 +44,8 @@ function matchKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
|
44
44
|
* output that can't name a shortcut falls back:
|
|
45
45
|
* - non-Latin letters and symbols, such as Russian `ы` or macOS Option's `–`
|
|
46
46
|
* - dead keys
|
|
47
|
+
* - `Unidentified` keys, except on letter positions, which layouts such as
|
|
48
|
+
* AZERTY rearrange. Chrome on Linux Wayland reports Ctrl+` this way.
|
|
47
49
|
* - anything on the digit row, where AZERTY types `&` and `é` for 1 and 2
|
|
48
50
|
* - ASCII symbols typed with Shift, such as `!` for Shift+1
|
|
49
51
|
* - Latin letters on punctuation keys with Control or Command, so German
|
|
@@ -54,6 +56,7 @@ function matchKeyboardEvent(event, hotkey, platform = detectPlatform()) {
|
|
|
54
56
|
* sits. AZERTY's `+` on the US `/` position is `+`, not `/`.
|
|
55
57
|
*/
|
|
56
58
|
function positionStandsIn({ key, code, ctrl, meta, shift, alt }) {
|
|
59
|
+
if (key === "Unidentified") return !code.startsWith("Key");
|
|
57
60
|
if (key !== "Dead" && !isPrintableKey(key)) return false;
|
|
58
61
|
if (code.startsWith("Digit")) return true;
|
|
59
62
|
if (isAsciiLetter(key)) return false;
|
|
@@ -77,5 +80,4 @@ function parsedChordsEqual(a, b) {
|
|
|
77
80
|
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));
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
//#endregion
|
|
81
83
|
export { matchKeyboardEvent, parsedChordsEqual };
|
package/dist/_named-keys.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//#region src/_named-keys.d.ts
|
|
2
1
|
/** Names defined for both KeyboardEvent.key and KeyboardEvent.code, beyond the basic key groups. */
|
|
3
2
|
export declare const SHARED_NAMED_KEYS: readonly ["CapsLock", "ContextMenu", "Convert", "KanaMode", "NonConvert", "Help", "Insert", "NumLock", "Fn", "FnLock", "PrintScreen", "ScrollLock", "Pause", "BrowserBack", "BrowserFavorites", "BrowserForward", "BrowserHome", "BrowserRefresh", "BrowserSearch", "BrowserStop", "Eject", "LaunchMail", "MediaPlayPause", "MediaStop", "MediaTrackNext", "MediaTrackPrevious", "Power", "AudioVolumeDown", "AudioVolumeMute", "AudioVolumeUp", "WakeUp", "Hyper", "Super", "Again", "Copy", "Cut", "Find", "Open", "Paste", "Props", "Select", "Undo", "Hiragana", "Katakana"];
|
|
4
3
|
/** Logical counterparts whose physical positions use different names (for example Sleep and Lang1). */
|
|
5
|
-
export declare const LOGICAL_ONLY_NAMED_KEYS: readonly ["Clear", "Cancel", "Standby", "LaunchApplication1", "LaunchApplication2", "LaunchMediaPlayer", "HangulMode", "HanjaMode", "Zenkaku", "Hankaku", "ZenkakuHankaku"];
|
|
6
|
-
//#endregion
|
|
4
|
+
export declare const LOGICAL_ONLY_NAMED_KEYS: readonly ["Clear", "Cancel", "Standby", "LaunchApplication1", "LaunchApplication2", "LaunchMediaPlayer", "HangulMode", "HanjaMode", "Zenkaku", "Hankaku", "ZenkakuHankaku"];
|
package/dist/_named-keys.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/_named-keys.ts
|
|
2
2
|
/** Names defined for both KeyboardEvent.key and KeyboardEvent.code, beyond the basic key groups. */
|
|
3
3
|
const SHARED_NAMED_KEYS = [
|
|
4
4
|
"CapsLock",
|
|
@@ -81,5 +81,4 @@ const EDITING_KEY_NAMES = [
|
|
|
81
81
|
"Delete"
|
|
82
82
|
];
|
|
83
83
|
|
|
84
|
-
//#endregion
|
|
85
84
|
export { EDITING_KEY_NAMES, LOGICAL_ONLY_NAMED_KEYS, NAVIGATION_KEY_NAMES, SHARED_NAMED_KEYS };
|
package/dist/_recorder-chord.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { normalizeKeyName } from "./constants.js";
|
|
2
1
|
import { isCodeName, normalizeKeyboardEvent } from "./_keyboard-event.js";
|
|
2
|
+
import { normalizeKeyName } from "./constants.js";
|
|
3
3
|
import { isModifierKey, normalizeHotkey, parseKeyboardEvent } from "./parse.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/_recorder-chord.ts
|
|
6
6
|
/**
|
|
7
7
|
* Converts a keydown to a replayable chord, preserving physical codes by default.
|
|
8
|
-
* Returns null for composition, modifier-only input,
|
|
8
|
+
* Returns null for composition, modifier-only input, unusable physical input,
|
|
9
|
+
* or an `Unidentified` key in key mode.
|
|
9
10
|
* Key mode retains the produced character; neither mode guesses layout letters.
|
|
10
11
|
*/
|
|
11
12
|
function hotkeyChordFromKeydown(event, platform, recordBy = "code") {
|
|
@@ -20,7 +21,7 @@ function hotkeyChordFromKeydown(event, platform, recordBy = "code") {
|
|
|
20
21
|
code: event.code
|
|
21
22
|
}, platform);
|
|
22
23
|
}
|
|
23
|
-
return parsed.key ? normalizeHotkey(parsed, platform) : null;
|
|
24
|
+
return parsed.key && parsed.key !== "Unidentified" ? normalizeHotkey(parsed, platform) : null;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Explains why a physical chord cannot be recorded before syntax/policy checks.
|
|
@@ -28,19 +29,12 @@ function hotkeyChordFromKeydown(event, platform, recordBy = "code") {
|
|
|
28
29
|
*/
|
|
29
30
|
function chordRejection(event, options) {
|
|
30
31
|
if (options.recordBy === "key") return void 0;
|
|
31
|
-
if (normalizeKeyboardEvent(event, options.platform).altGraph) return {
|
|
32
|
-
|
|
33
|
-
message: "AltGraph character entry cannot be recorded as a physical shortcut. Use key mode for characters."
|
|
34
|
-
};
|
|
35
|
-
if (!hasUsableCode(event)) return {
|
|
36
|
-
reason: "missing-code",
|
|
37
|
-
message: "This event has no usable physical key code."
|
|
38
|
-
};
|
|
32
|
+
if (normalizeKeyboardEvent(event, options.platform).altGraph) return { reason: "alt-graph" };
|
|
33
|
+
if (!hasUsableCode(event)) return { reason: "missing-code" };
|
|
39
34
|
}
|
|
40
35
|
/** Whether the event reports a real physical key code to record. */
|
|
41
36
|
function hasUsableCode(event) {
|
|
42
37
|
return isCodeName(event.code) && event.code !== "Unidentified";
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
//#endregion
|
|
46
40
|
export { chordRejection, hotkeyChordFromKeydown };
|
package/dist/_recording-guard.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MODIFIER_KEYS, normalizeKeyName } from "./constants.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/_recording-guard.ts
|
|
4
4
|
const sessions = /* @__PURE__ */ new Set();
|
|
5
5
|
const captured = /* @__PURE__ */ new WeakSet();
|
|
6
6
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -51,5 +51,4 @@ function isRecordingEvent(event) {
|
|
|
51
51
|
return sessions.size > 0 || captured.has(event) || pending.has(identity(event));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//#endregion
|
|
55
54
|
export { beginRecording, captureRecordingEvent, endRecording, isRecordingEvent };
|
package/dist/_registration.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/_registration.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Merges option layers left to right. A key set to `undefined` keeps the
|
|
4
3
|
* earlier layer's value, so an unset option keeps its default. Getters are
|
|
@@ -10,5 +9,4 @@ export declare function definedOptions<T extends object>(...layers: ReadonlyArra
|
|
|
10
9
|
* `undefined` as "clear". An unset option takes its default, or stays
|
|
11
10
|
* `undefined` to clear an earlier value when it has no default.
|
|
12
11
|
*/
|
|
13
|
-
export declare function withDefaults<T extends object>(defaults: object | undefined, options: T | undefined): T;
|
|
14
|
-
//#endregion
|
|
12
|
+
export declare function withDefaults<T extends object>(defaults: object | undefined, options: T | undefined): T;
|
package/dist/_registration.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { isModifierCode, modifierAlias } from "./_keyboard-event.js";
|
|
2
|
+
|
|
3
|
+
// src/_registration.ts
|
|
2
4
|
/** The scope of hotkeys that do not name one. It is always active unless an exclusive scope is active. */
|
|
3
5
|
const GLOBAL_SCOPE = "global";
|
|
4
6
|
/**
|
|
@@ -42,9 +44,13 @@ function withDefaults(defaults, options) {
|
|
|
42
44
|
...definedOptions(defaults, options)
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Whether a parsed chord has a key that isn't a modifier. A chord without one
|
|
49
|
+
* registers nothing: an empty key would match key-less events, and a
|
|
50
|
+
* modifier-only chord isn't supported.
|
|
51
|
+
*/
|
|
46
52
|
function chordHasKey(chord) {
|
|
47
|
-
return !!(chord.code
|
|
53
|
+
return chord.code !== void 0 ? !!chord.code && !isModifierCode(chord.code) : !!chord.key && !modifierAlias(chord.key);
|
|
48
54
|
}
|
|
49
55
|
/**
|
|
50
56
|
* Resolves `ignoreInputs: 'auto'` from the hotkey (the first step of a sequence).
|
|
@@ -66,10 +72,10 @@ function getDefaultIgnoreInputs(parsedChord) {
|
|
|
66
72
|
function handleConflict(conflictingId, keyDisplay, conflictBehavior, unregister) {
|
|
67
73
|
if (conflictBehavior === "allow") return;
|
|
68
74
|
if (conflictBehavior === "warn") {
|
|
69
|
-
if (process.env.NODE_ENV !== "production") console.warn(`'${keyDisplay}' is already registered
|
|
75
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") console.warn(`[hotkeys] '${keyDisplay}' is already registered`);
|
|
70
76
|
return;
|
|
71
77
|
}
|
|
72
|
-
if (conflictBehavior === "error") throw new Error(`'${keyDisplay}' is already registered
|
|
78
|
+
if (conflictBehavior === "error") throw new Error(`[hotkeys] '${keyDisplay}' is already registered`);
|
|
73
79
|
unregister(conflictingId);
|
|
74
80
|
}
|
|
75
81
|
/** Compare updates without publishing unchanged options, including fresh metadata objects. */
|
|
@@ -80,5 +86,4 @@ function optionsEqual(left, right) {
|
|
|
80
86
|
return keys.length === Object.keys(b).length && keys.every((key) => key === "meta" && a[key] && b[key] ? optionsEqual(a[key], b[key]) : Object.is(a[key], b[key]));
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
//#endregion
|
|
84
89
|
export { GLOBAL_SCOPE, chordHasKey, defaultHotkeyOptions, definedOptions, getDefaultIgnoreInputs, handleConflict, optionsEqual, withDefaults };
|
package/dist/_server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/_server.ts
|
|
2
2
|
const SERVER_AGENT = /^(?:Node\.js\/|Deno\/|Bun\/|Cloudflare-Workers)/;
|
|
3
3
|
/** Whether this runs on a server: no `navigator`, or a server runtime's. */
|
|
4
4
|
function isServerRuntime() {
|
|
@@ -11,11 +11,10 @@ let warned = false;
|
|
|
11
11
|
* users on macOS and Windows after hydration.
|
|
12
12
|
*/
|
|
13
13
|
function warnServerPlatform() {
|
|
14
|
-
if (process.env.NODE_ENV !== "production" && !warned && isServerRuntime()) {
|
|
14
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && !warned && isServerRuntime()) {
|
|
15
15
|
warned = true;
|
|
16
|
-
console.warn("[
|
|
16
|
+
console.warn("[hotkeys] Server-rendered label uses 'linux'. Set the provider's platform with detectPlatform(userAgent)");
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
//#endregion
|
|
21
20
|
export { isServerRuntime, warnServerPlatform };
|
package/dist/_store.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Store as Store$1 } from "./vendor/@tanstack/store/dist/store.js";
|
|
2
|
-
//#region src/_store.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* The part of TanStack Store's `Store` that TanStack Hotkeys uses. Its stores
|
|
5
4
|
* work with TanStack Store's framework adapters, such as `useSelector` from
|
|
@@ -10,5 +9,4 @@ export type Store<T> = Pick<Store$1<T>, 'state' | 'get' | 'setState' | 'subscrib
|
|
|
10
9
|
* Creates a store. Listeners run synchronously after each change, and an
|
|
11
10
|
* update to an equal value (by `Object.is`) notifies no one.
|
|
12
11
|
*/
|
|
13
|
-
export declare function createStore<T>(initialState: T): Store<T>;
|
|
14
|
-
//#endregion
|
|
12
|
+
export declare function createStore<T>(initialState: T): Store<T>;
|
package/dist/_store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/_store.ts
|
|
2
2
|
const queue = /* @__PURE__ */ new Set();
|
|
3
3
|
function notify(listeners) {
|
|
4
4
|
for (const listener of listeners) queue.add(listener);
|
|
@@ -39,5 +39,4 @@ function createStore(initialState) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
//#endregion
|
|
43
42
|
export { createStore };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/_target-listeners.ts
|
|
2
2
|
/** Shares keyboard listeners within each target and capture phase. */
|
|
3
3
|
var TargetListeners = class {
|
|
4
4
|
dispatch;
|
|
@@ -7,10 +7,19 @@ var TargetListeners = class {
|
|
|
7
7
|
constructor(dispatch) {
|
|
8
8
|
this.dispatch = dispatch;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Adds a registration to the target's shared listener. With `last`, an
|
|
12
|
+
* existing listener moves after listeners added since it, such as one a
|
|
13
|
+
* framework attached later to delegate key events.
|
|
14
|
+
*/
|
|
15
|
+
add(target, capture, id, last = false) {
|
|
11
16
|
if (typeof document === "undefined") return;
|
|
12
17
|
const phases = this.#targets.get(target) ?? /* @__PURE__ */ new Map();
|
|
13
18
|
let group = phases.get(capture);
|
|
19
|
+
if (group && last) for (const type of ["keydown", "keyup"]) {
|
|
20
|
+
target.removeEventListener(type, group[type], capture);
|
|
21
|
+
target.addEventListener(type, group[type], capture);
|
|
22
|
+
}
|
|
14
23
|
if (!group) {
|
|
15
24
|
const ids = /* @__PURE__ */ new Set();
|
|
16
25
|
const listener = (eventType) => (rawEvent) => {
|
|
@@ -66,5 +75,4 @@ var TargetListeners = class {
|
|
|
66
75
|
}
|
|
67
76
|
};
|
|
68
77
|
|
|
69
|
-
//#endregion
|
|
70
78
|
export { TargetListeners };
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { Store, createStore } from "./_store.js";
|
|
2
|
-
import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
|
|
3
|
-
import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
|
|
4
1
|
import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
|
|
2
|
+
import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
|
|
3
|
+
import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
|
|
5
4
|
import { HotkeyPlatform } from "./platform.js";
|
|
5
|
+
import { Store, createStore } from "./_store.js";
|
|
6
6
|
import { definedOptions, withDefaults } from "./_registration.js";
|
|
7
7
|
import { createAppHotkeysStore } from "@tanstack/hotkeys";
|
|
8
|
-
//#region src/adapter.d.ts
|
|
9
8
|
/**
|
|
10
9
|
* A hotkey and its callback, with optional per-binding options. Chords accept
|
|
11
10
|
* `requireReset`; sequences accept `timeout`.
|
|
@@ -26,8 +25,12 @@ export type HotkeyDefinition = {
|
|
|
26
25
|
};
|
|
27
26
|
/** Options shared by every definition. Each option applies only to the kinds that support it. */
|
|
28
27
|
export type HotkeyBindingsCommonOptions = HotkeyChordOptions & HotkeySequenceOptions;
|
|
29
|
-
/**
|
|
30
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Reconciles adapter registrations while retaining handles for option updates.
|
|
30
|
+
* Adapters for frameworks that delegate key events, such as React, pass
|
|
31
|
+
* `{ afterDelegatedHandlers: true }`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createHotkeyBindings(adapterOptions?: Pick<HotkeyOptions, 'afterDelegatedHandlers'>): {
|
|
31
34
|
/**
|
|
32
35
|
* Registers new definitions, updates existing ones in place, and removes
|
|
33
36
|
* stale ones. Definitions with no hotkey (undefined, null, empty string, or
|
|
@@ -37,7 +40,7 @@ export declare function createHotkeyBindings(): {
|
|
|
37
40
|
destroy(): void;
|
|
38
41
|
};
|
|
39
42
|
/** Options with a framework-specific target, such as a ref. */
|
|
40
|
-
export type WithTarget<TOptions, TTarget> = Omit<TOptions, 'target' | 'appHotkeyId'> & {
|
|
43
|
+
export type WithTarget<TOptions, TTarget> = Omit<TOptions, 'target' | 'appHotkeyId' | 'afterDelegatedHandlers'> & {
|
|
41
44
|
/** The element to listen on. Defaults to document. */
|
|
42
45
|
target?: TTarget;
|
|
43
46
|
};
|
|
@@ -180,5 +183,4 @@ export declare function getHotkeysProviderScopeKey(options: {
|
|
|
180
183
|
* Returns a function that releases the activation.
|
|
181
184
|
*/
|
|
182
185
|
export declare function activateHotkeysProviderScope(key: string): () => void;
|
|
183
|
-
//#endregion
|
|
184
186
|
export { type Store, createAppHotkeysStore, createStore, definedOptions, withDefaults };
|
package/dist/adapter.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { warnServerPlatform } from "./_server.js";
|
|
2
1
|
import { chordHasKey, defaultHotkeyOptions, definedOptions, withDefaults } from "./_registration.js";
|
|
2
|
+
import { warnServerPlatform } from "./_server.js";
|
|
3
3
|
import { createStore } from "./_store.js";
|
|
4
4
|
import { createAppHotkeysStore, detectPlatform, getAppHotkeyRegistrationOptions, getHotkeyManager, isAppHotkey, isHotkeySequence, normalizeHotkey, parseHotkey, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
|
|
6
|
+
// src/adapter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Reconciles adapter registrations while retaining handles for option updates.
|
|
9
|
+
* Adapters for frameworks that delegate key events, such as React, pass
|
|
10
|
+
* `{ afterDelegatedHandlers: true }`.
|
|
11
|
+
*/
|
|
12
|
+
function createHotkeyBindings(adapterOptions = {}) {
|
|
9
13
|
const registrations = /* @__PURE__ */ new Map();
|
|
10
14
|
return {
|
|
11
15
|
/**
|
|
@@ -20,6 +24,7 @@ function createHotkeyBindings() {
|
|
|
20
24
|
const options = {
|
|
21
25
|
...defaultHotkeyOptions,
|
|
22
26
|
...merged,
|
|
27
|
+
...adapterOptions,
|
|
23
28
|
target: "target" in merged ? merged.target ?? null : typeof document === "undefined" ? null : document,
|
|
24
29
|
platform: merged.platform ?? detectPlatform()
|
|
25
30
|
};
|
|
@@ -173,5 +178,4 @@ function activateHotkeysProviderScope(key) {
|
|
|
173
178
|
});
|
|
174
179
|
}
|
|
175
180
|
|
|
176
|
-
//#endregion
|
|
177
181
|
export { activateHotkeysProviderScope, createAppHotkeysStore, createHotkeyBindings, createStore, definedOptions, getHotkeysProviderScopeKey, getServerPlatformSnapshot, mergeHotkeysProviderDefaults, prepareHotkeyDefinitions, subscribePublishedAppHotkeys, withDefaults };
|
package/dist/app-hotkeys.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Store } from "./_store.js";
|
|
2
|
-
import { Hotkey, HotkeyChord, HotkeyMeta, HotkeySequence } from "./hotkey.types.js";
|
|
3
2
|
import { HotkeyChordOptions, HotkeyRegistrationView, HotkeySequenceOptions } from "./hotkey-manager.js";
|
|
4
|
-
|
|
3
|
+
import { Hotkey, HotkeyChord, HotkeyMeta, HotkeySequence } from "./hotkey.types.js";
|
|
5
4
|
type Platform = 'mac' | 'windows' | 'linux';
|
|
6
5
|
type DeclaredOptionKeys = 'capture' | 'conflictBehavior' | 'eventType' | 'ignoreInputs' | 'preventDefault' | 'stopPropagation';
|
|
7
6
|
/** Declared options for an app hotkey whose default is a chord. */
|
|
@@ -136,8 +135,8 @@ export interface AppHotkeysState<T extends AppHotkeys = AppHotkeys> {
|
|
|
136
135
|
/** Options for `AppHotkeysStore`. */
|
|
137
136
|
export interface AppHotkeysStoreOptions<T extends AppHotkeys = AppHotkeys> {
|
|
138
137
|
/**
|
|
139
|
-
* The user's saved custom hotkeys.
|
|
140
|
-
*
|
|
138
|
+
* The user's saved custom hotkeys. Unknown ids and invalid entries are
|
|
139
|
+
* ignored. Passing a different value to `setOptions` later, such as data
|
|
141
140
|
* that finished loading or another user's data, replaces the current
|
|
142
141
|
* custom hotkeys. `undefined` leaves them unchanged; a change to `null`
|
|
143
142
|
* clears them.
|
|
@@ -254,4 +253,4 @@ export declare function provideAppHotkeys<T extends AppHotkeys>(appHotkeys: T, o
|
|
|
254
253
|
store: AppHotkeysStore<T>;
|
|
255
254
|
dispose: () => void;
|
|
256
255
|
};
|
|
257
|
-
|
|
256
|
+
export {};
|
package/dist/app-hotkeys.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isHotkeySequence, simplifyHotkey } from "./parse.js";
|
|
3
|
-
import { validateHotkey } from "./validate.js";
|
|
1
|
+
import { setAppHotkeysStoreFactory } from "./_app-hotkeys-factory.js";
|
|
4
2
|
import { GLOBAL_SCOPE } from "./_registration.js";
|
|
5
|
-
import { areHotkeysEqual } from "./match.js";
|
|
6
3
|
import { createStore } from "./_store.js";
|
|
7
4
|
import { getHotkeyManager } from "./hotkey-manager.js";
|
|
8
|
-
import {
|
|
5
|
+
import { areHotkeysEqual } from "./match.js";
|
|
6
|
+
import { isHotkeySequence, simplifyHotkey } from "./parse.js";
|
|
7
|
+
import { detectPlatform } from "./platform.js";
|
|
8
|
+
import { validateHotkey } from "./validate.js";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// src/app-hotkeys.ts
|
|
11
11
|
/**
|
|
12
12
|
* Declares every app hotkey up front, without registering any, so cheat
|
|
13
13
|
* sheets and settings screens can list hotkeys whose components have not
|
|
@@ -69,22 +69,15 @@ function applyCustomHotkeys(appHotkeys, customHotkeys) {
|
|
|
69
69
|
}
|
|
70
70
|
return result;
|
|
71
71
|
}
|
|
72
|
-
/** Keeps only valid custom hotkeys for known ids
|
|
72
|
+
/** Keeps only valid custom hotkeys for known ids and silently drops the rest. */
|
|
73
73
|
function sanitizeCustomHotkeys(appHotkeys, saved) {
|
|
74
74
|
const result = {};
|
|
75
75
|
if (typeof saved !== "object" || saved === null) return result;
|
|
76
76
|
for (const [id, value] of Object.entries(saved)) {
|
|
77
77
|
if (value === void 0) continue;
|
|
78
|
-
if (!Object.hasOwn(appHotkeys, id))
|
|
79
|
-
if (process.env.NODE_ENV !== "production") console.warn(`Ignoring custom hotkey for unknown app hotkey '${id}'`);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
78
|
+
if (!Object.hasOwn(appHotkeys, id)) continue;
|
|
82
79
|
const hotkey = parseCustomHotkey(value);
|
|
83
|
-
if (hotkey
|
|
84
|
-
if (process.env.NODE_ENV !== "production") console.warn(`Ignoring invalid custom hotkey for app hotkey '${id}':`, value);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
result[id] = hotkey;
|
|
80
|
+
if (hotkey !== void 0) result[id] = hotkey;
|
|
88
81
|
}
|
|
89
82
|
return result;
|
|
90
83
|
}
|
|
@@ -232,10 +225,7 @@ var AppHotkeysStore = class {
|
|
|
232
225
|
*/
|
|
233
226
|
setCustomHotkey(id, hotkey) {
|
|
234
227
|
const appHotkey = this.get(id);
|
|
235
|
-
if (!appHotkey)
|
|
236
|
-
if (process.env.NODE_ENV !== "production") console.warn(`Cannot set a custom hotkey for unknown app hotkey '${id}'`);
|
|
237
|
-
return [];
|
|
238
|
-
}
|
|
228
|
+
if (!appHotkey) return [];
|
|
239
229
|
const next = hotkey === null ? null : simplifyHotkey(hotkey);
|
|
240
230
|
if (next !== null) {
|
|
241
231
|
const conflicts = this.findConflicts(id, next);
|
|
@@ -346,5 +336,4 @@ function provideAppHotkeys(appHotkeys, options = {}) {
|
|
|
346
336
|
};
|
|
347
337
|
}
|
|
348
338
|
|
|
349
|
-
//#endregion
|
|
350
339
|
export { AppHotkeysStore, defineAppHotkeys, getAppHotkeyRegistrationOptions, isAppHotkey, isCustomHotkey, provideAppHotkeys, publishAppHotkeysStore, resolveAppHotkey };
|
package/dist/conflicts.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { RegisterableHotkey } from "./hotkey.types.js";
|
|
2
1
|
import { HotkeyRegistrationView } from "./hotkey-manager.js";
|
|
3
|
-
|
|
2
|
+
import { RegisterableHotkey } from "./hotkey.types.js";
|
|
4
3
|
/** A live registration that overlaps a candidate hotkey. */
|
|
5
4
|
export interface HotkeyConflict {
|
|
6
5
|
registration: HotkeyRegistrationView;
|
|
@@ -28,5 +27,4 @@ export interface HotkeyConflictOptions {
|
|
|
28
27
|
* With source events, also checks observed logical/physical overlap. Does not
|
|
29
28
|
* discover unmounted handlers, external listeners, or other keyboard layouts.
|
|
30
29
|
*/
|
|
31
|
-
export declare function findHotkeyConflicts(candidate: RegisterableHotkey, options?: HotkeyConflictOptions): Array<HotkeyConflict>;
|
|
32
|
-
//#endregion
|
|
30
|
+
export declare function findHotkeyConflicts(candidate: RegisterableHotkey, options?: HotkeyConflictOptions): Array<HotkeyConflict>;
|
package/dist/conflicts.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { detectPlatform } from "./platform.js";
|
|
2
|
-
import { isHotkeySequence, parseHotkey } from "./parse.js";
|
|
3
|
-
import { parsedChordsEqual } from "./_match.js";
|
|
4
|
-
import { matchesKeyboardEvent } from "./match.js";
|
|
5
1
|
import { getTargetDocument } from "./_event-target.js";
|
|
6
2
|
import { managerRef } from "./_manager-ref.js";
|
|
3
|
+
import { parsedChordsEqual } from "./_match.js";
|
|
7
4
|
import { toHotkeyRegistrationView } from "./hotkey-manager.js";
|
|
5
|
+
import { matchesKeyboardEvent } from "./match.js";
|
|
6
|
+
import { isHotkeySequence, parseHotkey } from "./parse.js";
|
|
7
|
+
import { detectPlatform } from "./platform.js";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// src/conflicts.ts
|
|
10
10
|
/** Whether two targets share a document and one contains the other (or is its window). */
|
|
11
11
|
function targetsOverlap(a, b) {
|
|
12
12
|
if (a === b) return true;
|
|
@@ -42,5 +42,4 @@ function findHotkeyConflicts(candidate, options = {}) {
|
|
|
42
42
|
return conflicts;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
//#endregion
|
|
46
45
|
export { findHotkeyConflicts };
|