@remcostoeten/use-shortcut 1.3.0 → 2.0.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/CHANGELOG.md +8 -0
- package/README.md +33 -313
- package/dist/cli/index.mjs +88 -216
- package/dist/index.d.mts +39 -68
- package/dist/index.d.ts +39 -68
- package/dist/index.js +350 -361
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +351 -354
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/src/__tests__/features.test.ts +43 -12
- package/src/builder.ts +37 -476
- package/src/constants.ts +59 -13
- package/src/formatter.ts +37 -22
- package/src/hook.ts +150 -31
- package/src/index.ts +1 -12
- package/src/parser.ts +6 -3
- package/src/runtime/binding.ts +136 -0
- package/src/runtime/conflicts.ts +43 -0
- package/src/runtime/debug.ts +6 -0
- package/src/runtime/guards.ts +82 -0
- package/src/runtime/keys.ts +63 -0
- package/src/runtime/listener.ts +142 -0
- package/src/runtime/recording.ts +39 -0
- package/src/runtime/types.ts +48 -0
- package/src/types.ts +16 -19
package/dist/index.js
CHANGED
|
@@ -3,17 +3,35 @@
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
|
|
5
5
|
// src/constants.ts
|
|
6
|
-
var
|
|
6
|
+
var OS = {
|
|
7
7
|
MAC: "mac",
|
|
8
8
|
WINDOWS: "windows",
|
|
9
9
|
LINUX: "linux"
|
|
10
10
|
};
|
|
11
|
+
var Platform = OS;
|
|
12
|
+
var _cachedPlatform = null;
|
|
11
13
|
function detectPlatform() {
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (_cachedPlatform) return _cachedPlatform;
|
|
15
|
+
if (typeof navigator === "undefined") {
|
|
16
|
+
_cachedPlatform = OS.WINDOWS;
|
|
17
|
+
return _cachedPlatform;
|
|
18
|
+
}
|
|
19
|
+
const uaPlatform = navigator.userAgentData?.platform?.toLowerCase();
|
|
20
|
+
const platform = (uaPlatform ?? navigator.platform ?? navigator.userAgent ?? "").toLowerCase();
|
|
21
|
+
if (platform.includes("mac") || platform.includes("iphone") || platform.includes("ipad") || platform.includes("ipod")) {
|
|
22
|
+
_cachedPlatform = OS.MAC;
|
|
23
|
+
return _cachedPlatform;
|
|
24
|
+
}
|
|
25
|
+
if (platform.includes("linux") || platform.includes("android")) {
|
|
26
|
+
_cachedPlatform = OS.LINUX;
|
|
27
|
+
return _cachedPlatform;
|
|
28
|
+
}
|
|
29
|
+
if (platform.includes("win")) {
|
|
30
|
+
_cachedPlatform = OS.WINDOWS;
|
|
31
|
+
return _cachedPlatform;
|
|
32
|
+
}
|
|
33
|
+
_cachedPlatform = OS.WINDOWS;
|
|
34
|
+
return _cachedPlatform;
|
|
17
35
|
}
|
|
18
36
|
var ModifierKey = {
|
|
19
37
|
META: "meta",
|
|
@@ -83,19 +101,19 @@ var SpecialKeyMap = {
|
|
|
83
101
|
closebracket: "]"
|
|
84
102
|
};
|
|
85
103
|
var ModifierDisplaySymbols = {
|
|
86
|
-
[
|
|
104
|
+
[OS.MAC]: {
|
|
87
105
|
[ModifierKey.META]: "\u2318",
|
|
88
106
|
[ModifierKey.CTRL]: "\u2303",
|
|
89
107
|
[ModifierKey.ALT]: "\u2325",
|
|
90
108
|
[ModifierKey.SHIFT]: "\u21E7"
|
|
91
109
|
},
|
|
92
|
-
[
|
|
110
|
+
[OS.WINDOWS]: {
|
|
93
111
|
[ModifierKey.META]: "Ctrl",
|
|
94
112
|
[ModifierKey.CTRL]: "Ctrl",
|
|
95
113
|
[ModifierKey.ALT]: "Alt",
|
|
96
114
|
[ModifierKey.SHIFT]: "Shift"
|
|
97
115
|
},
|
|
98
|
-
[
|
|
116
|
+
[OS.LINUX]: {
|
|
99
117
|
[ModifierKey.META]: "Super",
|
|
100
118
|
[ModifierKey.CTRL]: "Ctrl",
|
|
101
119
|
[ModifierKey.ALT]: "Alt",
|
|
@@ -103,12 +121,15 @@ var ModifierDisplaySymbols = {
|
|
|
103
121
|
}
|
|
104
122
|
};
|
|
105
123
|
var ModifierDisplayOrder = {
|
|
106
|
-
[
|
|
107
|
-
[
|
|
108
|
-
[
|
|
124
|
+
[OS.MAC]: [ModifierKey.CTRL, ModifierKey.ALT, ModifierKey.SHIFT, ModifierKey.META],
|
|
125
|
+
[OS.WINDOWS]: [ModifierKey.META, ModifierKey.ALT, ModifierKey.SHIFT, ModifierKey.CTRL],
|
|
126
|
+
[OS.LINUX]: [ModifierKey.META, ModifierKey.ALT, ModifierKey.SHIFT, ModifierKey.CTRL]
|
|
109
127
|
};
|
|
110
128
|
|
|
111
129
|
// src/parser.ts
|
|
130
|
+
function _normalizeKeyToken(key) {
|
|
131
|
+
return key === " " ? "space" : key.toLowerCase();
|
|
132
|
+
}
|
|
112
133
|
function parseShortcut(shortcut) {
|
|
113
134
|
const platform = detectPlatform();
|
|
114
135
|
const normalized = shortcut.toLowerCase().trim();
|
|
@@ -160,9 +181,9 @@ function getModifiersFromEvent(event) {
|
|
|
160
181
|
}
|
|
161
182
|
function matchesShortcut(event, parsed) {
|
|
162
183
|
const eventModifiers = getModifiersFromEvent(event);
|
|
163
|
-
const eventKey = event.key
|
|
184
|
+
const eventKey = _normalizeKeyToken(event.key);
|
|
164
185
|
const modifiersMatch = eventModifiers.meta === parsed.modifiers.meta && eventModifiers.ctrl === parsed.modifiers.ctrl && eventModifiers.alt === parsed.modifiers.alt && eventModifiers.shift === parsed.modifiers.shift;
|
|
165
|
-
const keyMatches = eventKey === parsed.key
|
|
186
|
+
const keyMatches = eventKey === _normalizeKeyToken(parsed.key);
|
|
166
187
|
return modifiersMatch && keyMatches;
|
|
167
188
|
}
|
|
168
189
|
function matchesAnyShortcut(event, parsedShortcuts) {
|
|
@@ -170,6 +191,34 @@ function matchesAnyShortcut(event, parsedShortcuts) {
|
|
|
170
191
|
}
|
|
171
192
|
|
|
172
193
|
// src/formatter.ts
|
|
194
|
+
var _BASE_DISPLAY_NAMES = {
|
|
195
|
+
ArrowUp: "\u2191",
|
|
196
|
+
ArrowDown: "\u2193",
|
|
197
|
+
ArrowLeft: "\u2190",
|
|
198
|
+
ArrowRight: "\u2192",
|
|
199
|
+
Home: "Home",
|
|
200
|
+
End: "End",
|
|
201
|
+
PageUp: "PgUp",
|
|
202
|
+
PageDown: "PgDn"
|
|
203
|
+
};
|
|
204
|
+
var _MAC_DISPLAY_NAMES = {
|
|
205
|
+
..._BASE_DISPLAY_NAMES,
|
|
206
|
+
Enter: "\u21A9",
|
|
207
|
+
Tab: "\u21E5",
|
|
208
|
+
Escape: "\u238B",
|
|
209
|
+
Backspace: "\u232B",
|
|
210
|
+
Delete: "\u2326",
|
|
211
|
+
" ": "\u2423"
|
|
212
|
+
};
|
|
213
|
+
var _NON_MAC_DISPLAY_NAMES = {
|
|
214
|
+
..._BASE_DISPLAY_NAMES,
|
|
215
|
+
Enter: "Enter",
|
|
216
|
+
Tab: "Tab",
|
|
217
|
+
Escape: "Esc",
|
|
218
|
+
Backspace: "Backspace",
|
|
219
|
+
Delete: "Del",
|
|
220
|
+
" ": "Space"
|
|
221
|
+
};
|
|
173
222
|
function formatShortcut(shortcut, platform) {
|
|
174
223
|
const targetPlatform = platform ?? detectPlatform();
|
|
175
224
|
const parsed = parseShortcut(shortcut);
|
|
@@ -181,151 +230,87 @@ function formatShortcut(shortcut, platform) {
|
|
|
181
230
|
parts.push(symbols[modifier]);
|
|
182
231
|
}
|
|
183
232
|
}
|
|
184
|
-
const displayKey =
|
|
233
|
+
const displayKey = _formatKey(parsed.key, targetPlatform);
|
|
185
234
|
parts.push(displayKey);
|
|
186
|
-
const separator = targetPlatform ===
|
|
235
|
+
const separator = targetPlatform === OS.MAC ? "" : "+";
|
|
187
236
|
return parts.join(separator);
|
|
188
237
|
}
|
|
189
|
-
function
|
|
190
|
-
const displayNames =
|
|
191
|
-
ArrowUp: "\u2191",
|
|
192
|
-
ArrowDown: "\u2193",
|
|
193
|
-
ArrowLeft: "\u2190",
|
|
194
|
-
ArrowRight: "\u2192",
|
|
195
|
-
Enter: platform === Platform.MAC ? "\u21A9" : "Enter",
|
|
196
|
-
Tab: platform === Platform.MAC ? "\u21E5" : "Tab",
|
|
197
|
-
Escape: platform === Platform.MAC ? "\u238B" : "Esc",
|
|
198
|
-
Backspace: platform === Platform.MAC ? "\u232B" : "Backspace",
|
|
199
|
-
Delete: platform === Platform.MAC ? "\u2326" : "Del",
|
|
200
|
-
" ": platform === Platform.MAC ? "\u2423" : "Space",
|
|
201
|
-
Home: "Home",
|
|
202
|
-
End: "End",
|
|
203
|
-
PageUp: "PgUp",
|
|
204
|
-
PageDown: "PgDn"
|
|
205
|
-
};
|
|
238
|
+
function _formatKey(key, platform) {
|
|
239
|
+
const displayNames = platform === OS.MAC ? _MAC_DISPLAY_NAMES : _NON_MAC_DISPLAY_NAMES;
|
|
206
240
|
return displayNames[key] || key.toUpperCase();
|
|
207
241
|
}
|
|
208
|
-
function getModifierSymbols(platform) {
|
|
209
|
-
const targetPlatform = platform ?? detectPlatform();
|
|
210
|
-
return ModifierDisplaySymbols[targetPlatform];
|
|
211
|
-
}
|
|
212
242
|
|
|
213
|
-
// src/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
input: (e) => {
|
|
218
|
-
const target = e.target;
|
|
219
|
-
return IGNORED_TAGS.has(target.tagName);
|
|
220
|
-
},
|
|
221
|
-
editable: (e) => {
|
|
222
|
-
const target = e.target;
|
|
223
|
-
return target.isContentEditable;
|
|
224
|
-
},
|
|
225
|
-
typing: (e) => {
|
|
226
|
-
const target = e.target;
|
|
227
|
-
return IGNORED_TAGS.has(target.tagName) || target.isContentEditable;
|
|
228
|
-
},
|
|
229
|
-
modal: () => {
|
|
230
|
-
return document.querySelector('[data-modal="true"], [role="dialog"]') !== null;
|
|
231
|
-
},
|
|
232
|
-
disabled: (e) => {
|
|
233
|
-
const target = e.target;
|
|
234
|
-
return target.hasAttribute("disabled") || target.getAttribute("aria-disabled") === "true";
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
function shouldExcept(event, except) {
|
|
238
|
-
if (!except) return false;
|
|
239
|
-
if (typeof except === "function") {
|
|
240
|
-
return except(event);
|
|
241
|
-
}
|
|
242
|
-
if (Array.isArray(except)) {
|
|
243
|
-
return except.some((preset) => EXCEPT_PREDICATES[preset]?.(event));
|
|
244
|
-
}
|
|
245
|
-
return EXCEPT_PREDICATES[except]?.(event) ?? false;
|
|
246
|
-
}
|
|
247
|
-
function normalizeScopes(scopes) {
|
|
248
|
-
if (!scopes) return [];
|
|
249
|
-
return (Array.isArray(scopes) ? scopes : [scopes]).map((scope) => scope.trim()).filter(Boolean);
|
|
250
|
-
}
|
|
251
|
-
function scopeMatch(requiredScopes, activeScopes) {
|
|
252
|
-
if (requiredScopes.size === 0) return true;
|
|
253
|
-
for (const required of requiredScopes) {
|
|
254
|
-
if (activeScopes.has(required)) return true;
|
|
243
|
+
// src/runtime/debug.ts
|
|
244
|
+
function _debugLog(debug, ...args) {
|
|
245
|
+
if (debug) {
|
|
246
|
+
console.log("[useShortcut]", ...args);
|
|
255
247
|
}
|
|
256
|
-
return false;
|
|
257
248
|
}
|
|
258
|
-
|
|
249
|
+
|
|
250
|
+
// src/runtime/keys.ts
|
|
251
|
+
function _getActiveModifierTokens(modifiers) {
|
|
259
252
|
const platform = detectPlatform();
|
|
260
253
|
const order = ModifierDisplayOrder[platform];
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (key === ModifierKey.
|
|
264
|
-
if (key === ModifierKey.
|
|
265
|
-
if (key === ModifierKey.
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (key === ModifierKey.ALT) return "alt";
|
|
270
|
-
if (key === ModifierKey.SHIFT) return "shift";
|
|
271
|
-
if (key === ModifierKey.META) return "cmd";
|
|
272
|
-
return "";
|
|
273
|
-
});
|
|
254
|
+
const tokens = [];
|
|
255
|
+
for (const key of order) {
|
|
256
|
+
if (key === ModifierKey.CTRL && modifiers.ctrl) tokens.push("ctrl");
|
|
257
|
+
if (key === ModifierKey.ALT && modifiers.alt) tokens.push("alt");
|
|
258
|
+
if (key === ModifierKey.SHIFT && modifiers.shift) tokens.push("shift");
|
|
259
|
+
if (key === ModifierKey.META && modifiers.cmd) tokens.push("cmd");
|
|
260
|
+
}
|
|
261
|
+
return tokens;
|
|
274
262
|
}
|
|
275
|
-
function
|
|
276
|
-
const tokens =
|
|
263
|
+
function _buildComboString(modifiers, key) {
|
|
264
|
+
const tokens = _getActiveModifierTokens(modifiers);
|
|
277
265
|
return [...tokens, key].join("+");
|
|
278
266
|
}
|
|
279
|
-
function
|
|
267
|
+
function _formatSequenceDisplay(steps) {
|
|
280
268
|
return steps.map((step) => formatShortcut(step)).join(" then ");
|
|
281
269
|
}
|
|
282
|
-
function
|
|
283
|
-
if (debug) {
|
|
284
|
-
console.log("[useShortcut]", ...args);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function canonicalizeParsed(parsed) {
|
|
270
|
+
function _canonicalizeParsed(parsed) {
|
|
288
271
|
const modifiers = [];
|
|
289
272
|
if (parsed.modifiers.ctrl) modifiers.push("ctrl");
|
|
290
273
|
if (parsed.modifiers.alt) modifiers.push("alt");
|
|
291
274
|
if (parsed.modifiers.shift) modifiers.push("shift");
|
|
292
275
|
if (parsed.modifiers.meta) modifiers.push("cmd");
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
function isPureModifier(event) {
|
|
296
|
-
const key = event.key.toLowerCase();
|
|
297
|
-
return key === "shift" || key === "control" || key === "alt" || key === "meta";
|
|
276
|
+
const key = parsed.key === " " || parsed.key === "Spacebar" ? "space" : parsed.key.toLowerCase();
|
|
277
|
+
return [...modifiers, key].join("+");
|
|
298
278
|
}
|
|
299
|
-
function
|
|
300
|
-
const platform = detectPlatform();
|
|
301
|
-
const symbols = ModifierDisplaySymbols[platform];
|
|
279
|
+
function _eventToCombo(event) {
|
|
302
280
|
const modifiers = [];
|
|
303
|
-
if (event.ctrlKey) modifiers.push(
|
|
281
|
+
if (event.ctrlKey) modifiers.push("ctrl");
|
|
304
282
|
if (event.altKey) modifiers.push("alt");
|
|
305
283
|
if (event.shiftKey) modifiers.push("shift");
|
|
306
284
|
if (event.metaKey) modifiers.push("cmd");
|
|
307
|
-
const key = event.key
|
|
285
|
+
const key = event.key === " " || event.key === "Spacebar" ? "space" : event.key.toLowerCase();
|
|
308
286
|
return [...modifiers, key].join("+");
|
|
309
287
|
}
|
|
310
|
-
function
|
|
288
|
+
function _eventToMatchStep(event) {
|
|
289
|
+
return _eventToCombo(event);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/runtime/conflicts.ts
|
|
293
|
+
function _isPrefix(a, b) {
|
|
311
294
|
if (a.length > b.length) return false;
|
|
312
295
|
for (let i = 0; i < a.length; i += 1) {
|
|
313
|
-
if (
|
|
296
|
+
if (a[i] !== b[i]) {
|
|
314
297
|
return false;
|
|
315
298
|
}
|
|
316
299
|
}
|
|
317
300
|
return true;
|
|
318
301
|
}
|
|
319
|
-
function
|
|
320
|
-
const
|
|
321
|
-
const
|
|
302
|
+
function _detectConflict(newSteps, existingSteps) {
|
|
303
|
+
const newCanonicalSteps = newSteps.map(_canonicalizeParsed);
|
|
304
|
+
const existingCanonicalSteps = existingSteps.map(_canonicalizeParsed);
|
|
305
|
+
const newCombo = newCanonicalSteps.join(" ");
|
|
306
|
+
const existingCombo = existingCanonicalSteps.join(" ");
|
|
322
307
|
if (newCombo === existingCombo) return "exact";
|
|
323
|
-
if (
|
|
308
|
+
if (_isPrefix(newCanonicalSteps, existingCanonicalSteps) || _isPrefix(existingCanonicalSteps, newCanonicalSteps)) {
|
|
324
309
|
return "sequence-prefix";
|
|
325
310
|
}
|
|
326
311
|
return null;
|
|
327
312
|
}
|
|
328
|
-
function
|
|
313
|
+
function _emitConflict(registry, conflict) {
|
|
329
314
|
const conflictWarnings = registry.options.conflictWarnings ?? true;
|
|
330
315
|
if (registry.options.onConflict) {
|
|
331
316
|
registry.options.onConflict(conflict);
|
|
@@ -336,37 +321,195 @@ function emitConflict(registry, conflict) {
|
|
|
336
321
|
`[useShortcut] Conflict detected (${conflict.reason}) between "${conflict.combo}" and "${conflict.existingCombo}"`
|
|
337
322
|
);
|
|
338
323
|
}
|
|
339
|
-
|
|
324
|
+
|
|
325
|
+
// src/runtime/guards.ts
|
|
326
|
+
var _IGNORED_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
327
|
+
var _EXCEPT_PREDICATES = {
|
|
328
|
+
input: (e) => {
|
|
329
|
+
if (!(e.target instanceof HTMLElement)) return false;
|
|
330
|
+
const target = e.target;
|
|
331
|
+
return _IGNORED_TAGS.has(target.tagName);
|
|
332
|
+
},
|
|
333
|
+
editable: (e) => {
|
|
334
|
+
if (!(e.target instanceof HTMLElement)) return false;
|
|
335
|
+
const target = e.target;
|
|
336
|
+
return target.isContentEditable;
|
|
337
|
+
},
|
|
338
|
+
typing: (e) => {
|
|
339
|
+
if (!(e.target instanceof HTMLElement)) return false;
|
|
340
|
+
const target = e.target;
|
|
341
|
+
return _IGNORED_TAGS.has(target.tagName) || target.isContentEditable;
|
|
342
|
+
},
|
|
343
|
+
modal: () => {
|
|
344
|
+
if (typeof document === "undefined" || typeof document.querySelector !== "function")
|
|
345
|
+
return false;
|
|
346
|
+
return document.querySelector('[data-modal="true"], [role="dialog"]') !== null;
|
|
347
|
+
},
|
|
348
|
+
disabled: (e) => {
|
|
349
|
+
if (!(e.target instanceof HTMLElement)) return false;
|
|
350
|
+
const target = e.target;
|
|
351
|
+
return target.hasAttribute("disabled") || target.getAttribute("aria-disabled") === "true";
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function _shouldExcept(event, except) {
|
|
355
|
+
if (!except) return false;
|
|
356
|
+
if (typeof except === "function") {
|
|
357
|
+
return except(event);
|
|
358
|
+
}
|
|
359
|
+
if (Array.isArray(except)) {
|
|
360
|
+
return except.some((preset) => _EXCEPT_PREDICATES[preset]?.(event));
|
|
361
|
+
}
|
|
362
|
+
return _EXCEPT_PREDICATES[except]?.(event) ?? false;
|
|
363
|
+
}
|
|
364
|
+
function _normalizeScopes(scopes) {
|
|
365
|
+
if (!scopes) return [];
|
|
366
|
+
return (Array.isArray(scopes) ? scopes : [scopes]).map((scope) => scope.trim()).filter(Boolean);
|
|
367
|
+
}
|
|
368
|
+
function _scopeMatch(requiredScopes, activeScopes) {
|
|
369
|
+
if (requiredScopes.size === 0) return true;
|
|
370
|
+
for (const required of requiredScopes) {
|
|
371
|
+
if (activeScopes.has(required)) return true;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
function _isPureModifier(event) {
|
|
376
|
+
const key = event.key.toLowerCase();
|
|
377
|
+
return key === "shift" || key === "control" || key === "alt" || key === "meta";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/runtime/listener.ts
|
|
381
|
+
function _sortEntries(entries) {
|
|
382
|
+
if (entries.length <= 1) return entries;
|
|
340
383
|
return [...entries].sort((a, b) => {
|
|
341
384
|
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
342
385
|
return a.id - b.id;
|
|
343
386
|
});
|
|
344
387
|
}
|
|
345
|
-
function
|
|
388
|
+
function _dispatchRegistryEvent(registry, event) {
|
|
389
|
+
const runtimeOptions = registry.options;
|
|
390
|
+
if (runtimeOptions.disabled) return;
|
|
391
|
+
if (runtimeOptions.eventFilter && !runtimeOptions.eventFilter(event)) return;
|
|
392
|
+
const candidateCombos = /* @__PURE__ */ new Set();
|
|
393
|
+
const firstStepCombos = registry.firstStepIndex.get(_eventToMatchStep(event));
|
|
394
|
+
if (firstStepCombos) {
|
|
395
|
+
for (const combo of firstStepCombos) candidateCombos.add(combo);
|
|
396
|
+
}
|
|
397
|
+
for (const combo of registry.activeSequenceCombos) {
|
|
398
|
+
candidateCombos.add(combo);
|
|
399
|
+
}
|
|
400
|
+
for (const combo of candidateCombos) {
|
|
401
|
+
const comboEntries = registry.listeners.get(combo);
|
|
402
|
+
if (!comboEntries) continue;
|
|
403
|
+
const orderedEntries = _sortEntries(comboEntries);
|
|
404
|
+
for (const item of orderedEntries) {
|
|
405
|
+
if (!item.isEnabled) continue;
|
|
406
|
+
if (!_scopeMatch(item.scopes, registry.activeScopes)) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (runtimeOptions.ignoreInputs !== false && !item.except) {
|
|
410
|
+
const targetEl = event.target;
|
|
411
|
+
if (targetEl && (_IGNORED_TAGS.has(targetEl.tagName) || targetEl.isContentEditable)) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (_shouldExcept(event, item.except)) {
|
|
416
|
+
_debugLog(runtimeOptions.debug, "Skipped due to except condition:", combo);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const expected = item.parsedSteps[item.progress];
|
|
420
|
+
const now = Date.now();
|
|
421
|
+
if (item.progress > 0 && now - item.lastMatchedAt > item.sequenceTimeout) {
|
|
422
|
+
item.progress = 0;
|
|
423
|
+
}
|
|
424
|
+
let matched = false;
|
|
425
|
+
if (matchesShortcut(event, expected)) {
|
|
426
|
+
item.progress += 1;
|
|
427
|
+
item.lastMatchedAt = now;
|
|
428
|
+
if (item.progress === item.parsedSteps.length) {
|
|
429
|
+
matched = true;
|
|
430
|
+
item.progress = 0;
|
|
431
|
+
}
|
|
432
|
+
} else if (item.progress > 0 && matchesShortcut(event, item.parsedSteps[0])) {
|
|
433
|
+
item.progress = 1;
|
|
434
|
+
item.lastMatchedAt = now;
|
|
435
|
+
} else {
|
|
436
|
+
item.progress = 0;
|
|
437
|
+
}
|
|
438
|
+
for (const cb of item.attemptCallbacks) {
|
|
439
|
+
cb(matched, event);
|
|
440
|
+
}
|
|
441
|
+
if (!matched) continue;
|
|
442
|
+
_debugLog(runtimeOptions.debug, "MATCHED:", combo);
|
|
443
|
+
if (item.preventDefault) {
|
|
444
|
+
event.preventDefault();
|
|
445
|
+
}
|
|
446
|
+
if (item.stopPropagation) {
|
|
447
|
+
event.stopPropagation();
|
|
448
|
+
}
|
|
449
|
+
const executeHandler = () => item.userHandler(event);
|
|
450
|
+
if (item.delay > 0) {
|
|
451
|
+
_debugLog(runtimeOptions.debug, "Delaying execution by", item.delay, "ms");
|
|
452
|
+
setTimeout(executeHandler, item.delay);
|
|
453
|
+
} else {
|
|
454
|
+
executeHandler();
|
|
455
|
+
}
|
|
456
|
+
if (item.stopOnMatch) {
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (comboEntries.some((entry) => entry.progress > 0)) {
|
|
461
|
+
registry.activeSequenceCombos.add(combo);
|
|
462
|
+
} else {
|
|
463
|
+
registry.activeSequenceCombos.delete(combo);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function _attachRegistryListener(registry) {
|
|
468
|
+
if (registry.listener) return;
|
|
469
|
+
const target = registry.options.target ?? (typeof window !== "undefined" ? window : null);
|
|
470
|
+
if (!target) return;
|
|
471
|
+
const eventType = registry.options.eventType ?? "keydown";
|
|
472
|
+
const listener = (event) => _dispatchRegistryEvent(registry, event);
|
|
473
|
+
target.addEventListener(eventType, listener);
|
|
474
|
+
registry.listener = listener;
|
|
475
|
+
registry.listenerTarget = target;
|
|
476
|
+
registry.listenerEventType = eventType;
|
|
477
|
+
_debugLog(registry.options.debug, "Listener attached");
|
|
478
|
+
}
|
|
479
|
+
function _detachRegistryListener(registry) {
|
|
480
|
+
if (!registry.listener || !registry.listenerTarget) return;
|
|
481
|
+
registry.listenerTarget.removeEventListener(registry.listenerEventType, registry.listener);
|
|
482
|
+
registry.listener = null;
|
|
483
|
+
registry.listenerTarget = null;
|
|
484
|
+
_debugLog(registry.options.debug, "Listener detached");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/runtime/binding.ts
|
|
488
|
+
function _createBinding(state, handler, handlerOptions = {}, registry) {
|
|
346
489
|
const { options, except: stateExcept } = state;
|
|
347
490
|
const rawSteps = state.steps;
|
|
348
491
|
if (rawSteps.length === 0) {
|
|
349
492
|
throw new Error("[useShortcut] No key specified. Use .key() to set the action key.");
|
|
350
493
|
}
|
|
351
494
|
const parsedSteps = rawSteps.map((step) => parseShortcut(step));
|
|
352
|
-
const combo = parsedSteps.map(
|
|
353
|
-
const display =
|
|
495
|
+
const combo = parsedSteps.map(_canonicalizeParsed).join(" ");
|
|
496
|
+
const display = _formatSequenceDisplay(rawSteps);
|
|
354
497
|
const debug = options.debug ?? false;
|
|
355
498
|
const except = stateExcept ?? handlerOptions.except;
|
|
356
|
-
for (const [existingCombo,
|
|
357
|
-
for (const existing of
|
|
499
|
+
for (const [existingCombo, entries] of registry.listeners.entries()) {
|
|
500
|
+
for (const existing of entries) {
|
|
358
501
|
if (existingCombo === combo) continue;
|
|
359
|
-
const reason =
|
|
502
|
+
const reason = _detectConflict(parsedSteps, existing.parsedSteps);
|
|
360
503
|
if (!reason) continue;
|
|
361
|
-
|
|
504
|
+
_emitConflict(registry, { combo, existingCombo, reason });
|
|
362
505
|
}
|
|
363
506
|
}
|
|
364
507
|
const isEnabled = !handlerOptions.disabled && !options.disabled;
|
|
365
508
|
const delay = handlerOptions.delay ?? options.delay ?? 0;
|
|
366
509
|
const sequenceTimeout = handlerOptions.sequenceTimeout ?? options.sequenceTimeout ?? 800;
|
|
367
|
-
const requiredScopes = new Set(
|
|
510
|
+
const requiredScopes = new Set(_normalizeScopes(state.scopes ?? handlerOptions.scopes));
|
|
368
511
|
const attemptCallbacks = /* @__PURE__ */ new Set();
|
|
369
|
-
|
|
512
|
+
_debugLog(debug, "Registering:", combo, "\u2192", display, {
|
|
370
513
|
parsedSteps,
|
|
371
514
|
except: !!except,
|
|
372
515
|
scopes: [...requiredScopes]
|
|
@@ -388,97 +531,41 @@ function createBinding(state, handler, handlerOptions = {}, registry) {
|
|
|
388
531
|
stopOnMatch: handlerOptions.stopOnMatch ?? false,
|
|
389
532
|
priority: handlerOptions.priority ?? 0
|
|
390
533
|
};
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
for (const item of orderedEntries) {
|
|
403
|
-
if (!item.isEnabled) continue;
|
|
404
|
-
if (!scopeMatch(item.scopes, registry.activeScopes)) {
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
if (runtimeOptions.ignoreInputs !== false && !item.except) {
|
|
408
|
-
const targetEl = event.target;
|
|
409
|
-
if (targetEl && (IGNORED_TAGS.has(targetEl.tagName) || targetEl.isContentEditable)) {
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (shouldExcept(event, item.except)) {
|
|
414
|
-
debugLog(debug, "Skipped due to except condition:", combo);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
const expected = item.parsedSteps[item.progress];
|
|
418
|
-
const now = Date.now();
|
|
419
|
-
if (item.progress > 0 && now - item.lastMatchedAt > item.sequenceTimeout) {
|
|
420
|
-
item.progress = 0;
|
|
421
|
-
}
|
|
422
|
-
let matched = false;
|
|
423
|
-
if (matchesShortcut(event, expected)) {
|
|
424
|
-
item.progress += 1;
|
|
425
|
-
item.lastMatchedAt = now;
|
|
426
|
-
if (item.progress === item.parsedSteps.length) {
|
|
427
|
-
matched = true;
|
|
428
|
-
item.progress = 0;
|
|
429
|
-
}
|
|
430
|
-
} else if (item.progress > 0 && matchesShortcut(event, item.parsedSteps[0])) {
|
|
431
|
-
item.progress = 1;
|
|
432
|
-
item.lastMatchedAt = now;
|
|
433
|
-
} else {
|
|
434
|
-
item.progress = 0;
|
|
435
|
-
}
|
|
436
|
-
item.attemptCallbacks.forEach((cb) => cb(matched, event));
|
|
437
|
-
if (!matched) continue;
|
|
438
|
-
debugLog(debug, "MATCHED:", combo, "\u2192", display);
|
|
439
|
-
if (item.preventDefault) {
|
|
440
|
-
event.preventDefault();
|
|
441
|
-
}
|
|
442
|
-
if (item.stopPropagation) {
|
|
443
|
-
event.stopPropagation();
|
|
444
|
-
}
|
|
445
|
-
const executeHandler = () => item.userHandler(event);
|
|
446
|
-
if (item.delay > 0) {
|
|
447
|
-
debugLog(debug, "Delaying execution by", item.delay, "ms");
|
|
448
|
-
setTimeout(executeHandler, item.delay);
|
|
449
|
-
} else {
|
|
450
|
-
executeHandler();
|
|
451
|
-
}
|
|
452
|
-
if (item.stopOnMatch) {
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
if (target) {
|
|
458
|
-
target.addEventListener(eventType, listener);
|
|
459
|
-
debugLog(debug, "Listener attached for:", combo);
|
|
534
|
+
const comboEntries = registry.listeners.get(combo);
|
|
535
|
+
if (comboEntries) {
|
|
536
|
+
comboEntries.push(entry);
|
|
537
|
+
} else {
|
|
538
|
+
registry.listeners.set(combo, [entry]);
|
|
539
|
+
const firstStep = _canonicalizeParsed(parsedSteps[0]);
|
|
540
|
+
const indexedCombos = registry.firstStepIndex.get(firstStep);
|
|
541
|
+
if (indexedCombos) {
|
|
542
|
+
indexedCombos.add(combo);
|
|
543
|
+
} else {
|
|
544
|
+
registry.firstStepIndex.set(firstStep, /* @__PURE__ */ new Set([combo]));
|
|
460
545
|
}
|
|
461
|
-
const unbind = () => {
|
|
462
|
-
if (target) {
|
|
463
|
-
target.removeEventListener(eventType, listener);
|
|
464
|
-
registry.listeners.delete(combo);
|
|
465
|
-
debugLog(debug, "Unregistered:", combo);
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
comboListener = {
|
|
469
|
-
listener,
|
|
470
|
-
entries: [],
|
|
471
|
-
unbind
|
|
472
|
-
};
|
|
473
|
-
registry.listeners.set(combo, comboListener);
|
|
474
546
|
}
|
|
475
|
-
|
|
547
|
+
_attachRegistryListener(registry);
|
|
476
548
|
const unbindEntry = () => {
|
|
477
|
-
const
|
|
478
|
-
if (!
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
549
|
+
const currentEntries = registry.listeners.get(combo);
|
|
550
|
+
if (!currentEntries) return;
|
|
551
|
+
const nextEntries = currentEntries.filter((item) => item.id !== entry.id);
|
|
552
|
+
if (nextEntries.length === 0) {
|
|
553
|
+
registry.listeners.delete(combo);
|
|
554
|
+
registry.activeSequenceCombos.delete(combo);
|
|
555
|
+
const firstStep = _canonicalizeParsed(parsedSteps[0]);
|
|
556
|
+
const indexedCombos = registry.firstStepIndex.get(firstStep);
|
|
557
|
+
if (indexedCombos) {
|
|
558
|
+
indexedCombos.delete(combo);
|
|
559
|
+
if (indexedCombos.size === 0) {
|
|
560
|
+
registry.firstStepIndex.delete(firstStep);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
_debugLog(debug, "Unregistered:", combo);
|
|
564
|
+
} else {
|
|
565
|
+
registry.listeners.set(combo, nextEntries);
|
|
566
|
+
}
|
|
567
|
+
if (registry.listeners.size === 0) {
|
|
568
|
+
_detachRegistryListener(registry);
|
|
482
569
|
}
|
|
483
570
|
};
|
|
484
571
|
return {
|
|
@@ -501,7 +588,9 @@ function createBinding(state, handler, handlerOptions = {}, registry) {
|
|
|
501
588
|
}
|
|
502
589
|
};
|
|
503
590
|
}
|
|
504
|
-
|
|
591
|
+
|
|
592
|
+
// src/runtime/recording.ts
|
|
593
|
+
function _createRecorder(options) {
|
|
505
594
|
return (recordingOptions = {}) => {
|
|
506
595
|
return new Promise((resolve, reject) => {
|
|
507
596
|
const target = recordingOptions.target ?? options.target ?? (typeof window !== "undefined" ? window : null);
|
|
@@ -513,13 +602,13 @@ function createRecorder(options) {
|
|
|
513
602
|
let timeout;
|
|
514
603
|
const listener = (event) => {
|
|
515
604
|
const keyboardEvent = event;
|
|
516
|
-
if (
|
|
605
|
+
if (_isPureModifier(keyboardEvent)) return;
|
|
517
606
|
keyboardEvent.preventDefault();
|
|
518
607
|
target.removeEventListener(eventType, listener);
|
|
519
608
|
if (timeout) clearTimeout(timeout);
|
|
520
|
-
resolve(
|
|
609
|
+
resolve(_eventToCombo(keyboardEvent));
|
|
521
610
|
};
|
|
522
|
-
target.addEventListener(eventType, listener
|
|
611
|
+
target.addEventListener(eventType, listener);
|
|
523
612
|
const timeoutMs = recordingOptions.timeoutMs;
|
|
524
613
|
if (timeoutMs && timeoutMs > 0) {
|
|
525
614
|
timeout = setTimeout(() => {
|
|
@@ -530,43 +619,51 @@ function createRecorder(options) {
|
|
|
530
619
|
});
|
|
531
620
|
};
|
|
532
621
|
}
|
|
533
|
-
|
|
622
|
+
|
|
623
|
+
// src/builder.ts
|
|
624
|
+
var _MODIFIER_KEYS = /* @__PURE__ */ new Set(["ctrl", "shift", "alt", "cmd", "mod"]);
|
|
625
|
+
function _createShortcutBuilder(options = {}) {
|
|
534
626
|
const registry = {
|
|
535
627
|
listeners: /* @__PURE__ */ new Map(),
|
|
628
|
+
firstStepIndex: /* @__PURE__ */ new Map(),
|
|
629
|
+
activeSequenceCombos: /* @__PURE__ */ new Set(),
|
|
536
630
|
options,
|
|
537
|
-
activeScopes: new Set(
|
|
538
|
-
nextId: 1
|
|
631
|
+
activeScopes: new Set(_normalizeScopes(options.activeScopes)),
|
|
632
|
+
nextId: 1,
|
|
633
|
+
listener: null,
|
|
634
|
+
listenerTarget: null,
|
|
635
|
+
listenerEventType: options.eventType ?? "keydown"
|
|
539
636
|
};
|
|
540
|
-
|
|
541
|
-
function
|
|
637
|
+
_debugLog(options.debug, "Builder created with options:", options);
|
|
638
|
+
function _createProxy(currentState) {
|
|
542
639
|
return new Proxy({}, {
|
|
543
640
|
get(_, prop) {
|
|
544
641
|
if (prop === "__debug") {
|
|
545
642
|
return currentState.options.debug;
|
|
546
643
|
}
|
|
547
|
-
if (
|
|
644
|
+
if (_MODIFIER_KEYS.has(prop)) {
|
|
548
645
|
const platform = detectPlatform();
|
|
549
646
|
const modKey = prop === "mod" ? platform === Platform.MAC ? "cmd" : "ctrl" : prop;
|
|
550
647
|
const newState = {
|
|
551
648
|
...currentState,
|
|
552
649
|
modifiers: { ...currentState.modifiers, [modKey]: true }
|
|
553
650
|
};
|
|
554
|
-
|
|
555
|
-
return
|
|
651
|
+
_debugLog(currentState.options.debug, `Chain: +${prop} \u2192`, newState.modifiers);
|
|
652
|
+
return _createProxy(newState);
|
|
556
653
|
}
|
|
557
654
|
if (prop === "in") {
|
|
558
655
|
return (scopes) => {
|
|
559
|
-
const nextScopes = [...
|
|
656
|
+
const nextScopes = [..._normalizeScopes(currentState.scopes), ..._normalizeScopes(scopes)];
|
|
560
657
|
const newState = {
|
|
561
658
|
...currentState,
|
|
562
659
|
scopes: nextScopes
|
|
563
660
|
};
|
|
564
|
-
return
|
|
661
|
+
return _createProxy(newState);
|
|
565
662
|
};
|
|
566
663
|
}
|
|
567
664
|
if (prop === "setScopes") {
|
|
568
665
|
return (scopes) => {
|
|
569
|
-
registry.activeScopes = new Set(
|
|
666
|
+
registry.activeScopes = new Set(_normalizeScopes(scopes));
|
|
570
667
|
};
|
|
571
668
|
}
|
|
572
669
|
if (prop === "enableScope") {
|
|
@@ -588,18 +685,18 @@ function createShortcutBuilder(options = {}) {
|
|
|
588
685
|
return (scope) => registry.activeScopes.has(scope);
|
|
589
686
|
}
|
|
590
687
|
if (prop === "record") {
|
|
591
|
-
return
|
|
688
|
+
return _createRecorder(registry.options);
|
|
592
689
|
}
|
|
593
690
|
if (prop === "key") {
|
|
594
691
|
return (key) => {
|
|
595
|
-
const nextStep =
|
|
692
|
+
const nextStep = _buildComboString(currentState.modifiers, key);
|
|
596
693
|
const newState = {
|
|
597
694
|
...currentState,
|
|
598
695
|
modifiers: {},
|
|
599
696
|
steps: [...currentState.steps, nextStep]
|
|
600
697
|
};
|
|
601
|
-
|
|
602
|
-
return
|
|
698
|
+
_debugLog(currentState.options.debug, `Chain: .key("${key}")`);
|
|
699
|
+
return _createProxy(newState);
|
|
603
700
|
};
|
|
604
701
|
}
|
|
605
702
|
if (prop === "then") {
|
|
@@ -612,8 +709,8 @@ function createShortcutBuilder(options = {}) {
|
|
|
612
709
|
...currentState,
|
|
613
710
|
steps: [...currentState.steps, nextStep]
|
|
614
711
|
};
|
|
615
|
-
|
|
616
|
-
return
|
|
712
|
+
_debugLog(currentState.options.debug, `Chain: .then("${nextStep}")`);
|
|
713
|
+
return _createProxy(newState);
|
|
617
714
|
};
|
|
618
715
|
}
|
|
619
716
|
if (prop === "except") {
|
|
@@ -622,19 +719,19 @@ function createShortcutBuilder(options = {}) {
|
|
|
622
719
|
...currentState,
|
|
623
720
|
except: condition
|
|
624
721
|
};
|
|
625
|
-
|
|
626
|
-
return
|
|
722
|
+
_debugLog(currentState.options.debug, "Chain: .except()", condition);
|
|
723
|
+
return _createProxy(newState);
|
|
627
724
|
};
|
|
628
725
|
}
|
|
629
726
|
if (prop === "on") {
|
|
630
727
|
return (handler, handlerOptions) => {
|
|
631
|
-
return
|
|
728
|
+
return _createBinding(currentState, handler, handlerOptions, registry);
|
|
632
729
|
};
|
|
633
730
|
}
|
|
634
731
|
if (prop === "handle") {
|
|
635
732
|
return (opts) => {
|
|
636
733
|
const { handler, ...rest } = opts;
|
|
637
|
-
return
|
|
734
|
+
return _createBinding(currentState, handler, rest, registry);
|
|
638
735
|
};
|
|
639
736
|
}
|
|
640
737
|
return void 0;
|
|
@@ -647,79 +744,17 @@ function createShortcutBuilder(options = {}) {
|
|
|
647
744
|
options
|
|
648
745
|
};
|
|
649
746
|
return {
|
|
650
|
-
builder:
|
|
747
|
+
builder: _createProxy(initialState),
|
|
651
748
|
registry
|
|
652
749
|
};
|
|
653
750
|
}
|
|
654
751
|
|
|
655
752
|
// src/hook.ts
|
|
656
|
-
function normalizeShortcutMapKeys(keys) {
|
|
657
|
-
if (Array.isArray(keys)) {
|
|
658
|
-
return keys.map((key) => key.trim()).filter(Boolean);
|
|
659
|
-
}
|
|
660
|
-
const trimmed = keys.trim();
|
|
661
|
-
if (!trimmed) return [];
|
|
662
|
-
if (trimmed.includes(" then ")) {
|
|
663
|
-
return trimmed.split(/\s+then\s+/i).map((key) => key.trim()).filter(Boolean);
|
|
664
|
-
}
|
|
665
|
-
if (trimmed.includes(" ") && !trimmed.includes("+")) {
|
|
666
|
-
return trimmed.split(/\s+/).map((key) => key.trim()).filter(Boolean);
|
|
667
|
-
}
|
|
668
|
-
return [trimmed];
|
|
669
|
-
}
|
|
670
|
-
function applyStep(builder, step) {
|
|
671
|
-
const tokens = step.toLowerCase().split("+").map((token) => token.trim()).filter(Boolean);
|
|
672
|
-
if (tokens.length === 0) {
|
|
673
|
-
throw new Error("[useShortcutMap] Invalid step: empty shortcut step");
|
|
674
|
-
}
|
|
675
|
-
const key = tokens.pop();
|
|
676
|
-
let chain = builder;
|
|
677
|
-
for (const token of tokens) {
|
|
678
|
-
if (token === "ctrl" || token === "control") {
|
|
679
|
-
chain = chain.ctrl;
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
if (token === "shift") {
|
|
683
|
-
chain = chain.shift;
|
|
684
|
-
continue;
|
|
685
|
-
}
|
|
686
|
-
if (token === "alt" || token === "option") {
|
|
687
|
-
chain = chain.alt;
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
if (token === "cmd" || token === "command" || token === "meta") {
|
|
691
|
-
chain = chain.cmd;
|
|
692
|
-
continue;
|
|
693
|
-
}
|
|
694
|
-
if (token === "mod") {
|
|
695
|
-
chain = chain.mod;
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
throw new Error(`[useShortcutMap] Unsupported modifier token "${token}" in step "${step}"`);
|
|
699
|
-
}
|
|
700
|
-
return chain.key(key);
|
|
701
|
-
}
|
|
702
|
-
function registerShortcutMap(builder, shortcutMap) {
|
|
703
|
-
const results = {};
|
|
704
|
-
for (const id of Object.keys(shortcutMap)) {
|
|
705
|
-
const entry = shortcutMap[id];
|
|
706
|
-
const steps = normalizeShortcutMapKeys(entry.keys);
|
|
707
|
-
if (steps.length === 0) {
|
|
708
|
-
throw new Error(`[useShortcutMap] Shortcut "${String(id)}" has no key steps`);
|
|
709
|
-
}
|
|
710
|
-
let chain = applyStep(builder, steps[0]);
|
|
711
|
-
for (const step of steps.slice(1)) {
|
|
712
|
-
chain = chain.then(step);
|
|
713
|
-
}
|
|
714
|
-
results[id] = chain.on(entry.handler, entry.options);
|
|
715
|
-
}
|
|
716
|
-
return results;
|
|
717
|
-
}
|
|
718
753
|
function useShortcut(options = {}) {
|
|
719
754
|
const optionsRef = react.useRef(options);
|
|
720
755
|
optionsRef.current = options;
|
|
721
756
|
const { builder, registry } = react.useMemo(() => {
|
|
722
|
-
return
|
|
757
|
+
return _createShortcutBuilder(optionsRef.current);
|
|
723
758
|
}, []);
|
|
724
759
|
react.useEffect(() => {
|
|
725
760
|
registry.options = optionsRef.current;
|
|
@@ -727,59 +762,21 @@ function useShortcut(options = {}) {
|
|
|
727
762
|
const scopes = Array.isArray(optionsRef.current.activeScopes) ? optionsRef.current.activeScopes : [optionsRef.current.activeScopes];
|
|
728
763
|
registry.activeScopes = new Set(scopes.map((scope) => scope.trim()).filter(Boolean));
|
|
729
764
|
}
|
|
730
|
-
});
|
|
765
|
+
}, [registry, options]);
|
|
731
766
|
react.useEffect(() => {
|
|
732
767
|
return () => {
|
|
733
|
-
registry.listeners.forEach((entry) => entry.unbind());
|
|
734
768
|
registry.listeners.clear();
|
|
769
|
+
registry.firstStepIndex.clear();
|
|
770
|
+
registry.activeSequenceCombos.clear();
|
|
771
|
+
if (registry.listener && registry.listenerTarget) {
|
|
772
|
+
registry.listenerTarget.removeEventListener(registry.listenerEventType, registry.listener);
|
|
773
|
+
registry.listener = null;
|
|
774
|
+
registry.listenerTarget = null;
|
|
775
|
+
}
|
|
735
776
|
};
|
|
736
777
|
}, [registry]);
|
|
737
778
|
return builder;
|
|
738
779
|
}
|
|
739
|
-
function useShortcutMap(shortcutMap, options = {}) {
|
|
740
|
-
const $ = useShortcut(options);
|
|
741
|
-
return registerShortcutMap($, shortcutMap);
|
|
742
|
-
}
|
|
743
|
-
function createShortcut(options = {}) {
|
|
744
|
-
const { builder } = createShortcutBuilder(options);
|
|
745
|
-
return builder;
|
|
746
|
-
}
|
|
747
|
-
function createShortcutMap(shortcutMap, options = {}) {
|
|
748
|
-
const builder = createShortcut(options);
|
|
749
|
-
return registerShortcutMap(builder, shortcutMap);
|
|
750
|
-
}
|
|
751
|
-
function createShortcutGroup() {
|
|
752
|
-
const results = [];
|
|
753
|
-
return {
|
|
754
|
-
add: (...entries) => {
|
|
755
|
-
results.push(...entries);
|
|
756
|
-
},
|
|
757
|
-
addMany: (entries) => {
|
|
758
|
-
if (Array.isArray(entries)) {
|
|
759
|
-
results.push(...entries);
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
results.push(...Object.values(entries));
|
|
763
|
-
},
|
|
764
|
-
unbindAll: () => {
|
|
765
|
-
for (const entry of results) {
|
|
766
|
-
entry.unbind();
|
|
767
|
-
}
|
|
768
|
-
results.length = 0;
|
|
769
|
-
},
|
|
770
|
-
clear: () => {
|
|
771
|
-
results.length = 0;
|
|
772
|
-
},
|
|
773
|
-
getResults: () => [...results]
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
function useShortcutGroup() {
|
|
777
|
-
const groupRef = react.useRef(null);
|
|
778
|
-
if (!groupRef.current) {
|
|
779
|
-
groupRef.current = createShortcutGroup();
|
|
780
|
-
}
|
|
781
|
-
return groupRef.current;
|
|
782
|
-
}
|
|
783
780
|
|
|
784
781
|
exports.ModifierAliases = ModifierAliases;
|
|
785
782
|
exports.ModifierDisplayOrder = ModifierDisplayOrder;
|
|
@@ -787,20 +784,12 @@ exports.ModifierDisplaySymbols = ModifierDisplaySymbols;
|
|
|
787
784
|
exports.ModifierKey = ModifierKey;
|
|
788
785
|
exports.Platform = Platform;
|
|
789
786
|
exports.SpecialKeyMap = SpecialKeyMap;
|
|
790
|
-
exports.createShortcut = createShortcut;
|
|
791
|
-
exports.createShortcutGroup = createShortcutGroup;
|
|
792
|
-
exports.createShortcutMap = createShortcutMap;
|
|
793
787
|
exports.detectPlatform = detectPlatform;
|
|
794
788
|
exports.formatShortcut = formatShortcut;
|
|
795
|
-
exports.getModifierSymbols = getModifierSymbols;
|
|
796
|
-
exports.getModifiersFromEvent = getModifiersFromEvent;
|
|
797
789
|
exports.matchesAnyShortcut = matchesAnyShortcut;
|
|
798
790
|
exports.matchesShortcut = matchesShortcut;
|
|
799
791
|
exports.parseShortcut = parseShortcut;
|
|
800
792
|
exports.parseShortcuts = parseShortcuts;
|
|
801
|
-
exports.registerShortcutMap = registerShortcutMap;
|
|
802
793
|
exports.useShortcut = useShortcut;
|
|
803
|
-
exports.useShortcutGroup = useShortcutGroup;
|
|
804
|
-
exports.useShortcutMap = useShortcutMap;
|
|
805
794
|
//# sourceMappingURL=index.js.map
|
|
806
795
|
//# sourceMappingURL=index.js.map
|