hotkey-router 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -4
- package/data/browser-hotkeys.json +349 -0
- package/dist/auto.js +967 -0
- package/dist/auto.js.map +7 -0
- package/dist/auto.min.js +3 -0
- package/dist/hotkey-router.cjs +40 -17
- package/dist/hotkey-router.cjs.map +2 -2
- package/dist/hotkey-router.js +40 -17
- package/dist/hotkey-router.js.map +2 -2
- package/dist/hotkey-router.min.js +2 -2
- package/dist/reservations.js +528 -0
- package/dist/reservations.js.map +7 -0
- package/package.json +14 -4
package/dist/auto.js
ADDED
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
// hotkey-router v0.2.0 | SEE LICENSE IN LICENSE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// hotkey-router.js
|
|
5
|
+
function on(target, type, fn, options) {
|
|
6
|
+
target.addEventListener(type, fn, options);
|
|
7
|
+
return () => target.removeEventListener(type, fn, options);
|
|
8
|
+
}
|
|
9
|
+
var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
10
|
+
var modifierTokens = /* @__PURE__ */ new Set(["ctrl", "shift", "alt", "meta"]);
|
|
11
|
+
var MODIFIER_KEY_TOKEN = Object.freeze({
|
|
12
|
+
Alt: "alt",
|
|
13
|
+
AltGraph: "alt",
|
|
14
|
+
Control: "ctrl",
|
|
15
|
+
Shift: "shift",
|
|
16
|
+
Meta: "meta",
|
|
17
|
+
OS: "meta"
|
|
18
|
+
});
|
|
19
|
+
var ahkPrefixMap = /* @__PURE__ */ new Map([
|
|
20
|
+
["^", "ctrl"],
|
|
21
|
+
// Ctrl
|
|
22
|
+
["!", "alt"],
|
|
23
|
+
// Alt
|
|
24
|
+
["+", "shift"],
|
|
25
|
+
// Shift
|
|
26
|
+
["#", "meta"]
|
|
27
|
+
// Win / Command
|
|
28
|
+
]);
|
|
29
|
+
var keyAliases = Object.freeze({
|
|
30
|
+
// common
|
|
31
|
+
esc: "escape",
|
|
32
|
+
escape: "escape",
|
|
33
|
+
space: " ",
|
|
34
|
+
spacebar: " ",
|
|
35
|
+
enter: "enter",
|
|
36
|
+
return: "enter",
|
|
37
|
+
del: "delete",
|
|
38
|
+
delete: "delete",
|
|
39
|
+
backspace: "backspace",
|
|
40
|
+
tab: "tab",
|
|
41
|
+
// arrows
|
|
42
|
+
up: "arrowup",
|
|
43
|
+
down: "arrowdown",
|
|
44
|
+
left: "arrowleft",
|
|
45
|
+
right: "arrowright",
|
|
46
|
+
// navigation
|
|
47
|
+
home: "home",
|
|
48
|
+
end: "end",
|
|
49
|
+
pageup: "pageup",
|
|
50
|
+
pagedown: "pagedown",
|
|
51
|
+
pgup: "pageup",
|
|
52
|
+
pgdn: "pagedown",
|
|
53
|
+
// modifiers (words)
|
|
54
|
+
cmd: "meta",
|
|
55
|
+
command: "meta",
|
|
56
|
+
win: "meta",
|
|
57
|
+
meta: "meta",
|
|
58
|
+
control: "ctrl",
|
|
59
|
+
ctrl: "ctrl",
|
|
60
|
+
option: "alt",
|
|
61
|
+
alt: "alt",
|
|
62
|
+
shift: "shift",
|
|
63
|
+
// modifiers (symbols)
|
|
64
|
+
"\u2318": "meta",
|
|
65
|
+
"\u2303": "ctrl",
|
|
66
|
+
"\u2325": "alt",
|
|
67
|
+
"\u21E7": "shift",
|
|
68
|
+
// cross-platform "mod"
|
|
69
|
+
mod: isMac ? "meta" : "ctrl",
|
|
70
|
+
// plus key ergonomics
|
|
71
|
+
plus: "+"
|
|
72
|
+
});
|
|
73
|
+
var ignoreEditable = true;
|
|
74
|
+
var defaultTarget = typeof window !== "undefined" ? window : null;
|
|
75
|
+
var bindHooks = [];
|
|
76
|
+
var registry = /* @__PURE__ */ new Map();
|
|
77
|
+
var plugins = /* @__PURE__ */ new Map();
|
|
78
|
+
var bindingIndex = /* @__PURE__ */ new Map();
|
|
79
|
+
var paused = false;
|
|
80
|
+
var nextId = 1;
|
|
81
|
+
function normalizeKey(key) {
|
|
82
|
+
const raw = String(key ?? "").trim();
|
|
83
|
+
const k = raw.toLowerCase();
|
|
84
|
+
return keyAliases[k] ?? (raw === " " ? " " : k);
|
|
85
|
+
}
|
|
86
|
+
function comboKeyFromParts({ ctrl, shift, alt, meta, key, code, bareModifier }) {
|
|
87
|
+
if (bareModifier) return `__bare__:${bareModifier}`;
|
|
88
|
+
let s = "";
|
|
89
|
+
if (ctrl) s += "ctrl+";
|
|
90
|
+
if (shift) s += "shift+";
|
|
91
|
+
if (alt) s += "alt+";
|
|
92
|
+
if (meta) s += "meta+";
|
|
93
|
+
if (code) return s + "code:" + code;
|
|
94
|
+
return s ? s + key : key;
|
|
95
|
+
}
|
|
96
|
+
function comboKeyFromEvent(e) {
|
|
97
|
+
const bareMod = MODIFIER_KEY_TOKEN[e.key];
|
|
98
|
+
if (bareMod) return `__bare__:${bareMod}`;
|
|
99
|
+
return comboKeyFromParts({
|
|
100
|
+
ctrl: e.ctrlKey,
|
|
101
|
+
shift: e.shiftKey,
|
|
102
|
+
alt: e.altKey,
|
|
103
|
+
meta: e.metaKey,
|
|
104
|
+
key: normalizeKey(e.key)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function codeKeyFromEvent(e) {
|
|
108
|
+
if (!e.code) return null;
|
|
109
|
+
let s = "";
|
|
110
|
+
if (e.ctrlKey) s += "ctrl+";
|
|
111
|
+
if (e.shiftKey) s += "shift+";
|
|
112
|
+
if (e.altKey) s += "alt+";
|
|
113
|
+
if (e.metaKey) s += "meta+";
|
|
114
|
+
return s + "code:" + e.code;
|
|
115
|
+
}
|
|
116
|
+
function isModifierToken(token) {
|
|
117
|
+
const t = normalizeKey(token);
|
|
118
|
+
return modifierTokens.has(t);
|
|
119
|
+
}
|
|
120
|
+
var CODE_TOKEN_RE = /(^|\+)code:([A-Za-z][A-Za-z0-9_]*)/;
|
|
121
|
+
function parseHotkey(hotkeyStr) {
|
|
122
|
+
const raw = String(hotkeyStr || "").trim();
|
|
123
|
+
const isUp = /\s+up$/i.test(raw);
|
|
124
|
+
const baseRaw = isUp ? raw.replace(/\s+up$/i, "").trim() : raw;
|
|
125
|
+
const combo = {
|
|
126
|
+
ctrl: false,
|
|
127
|
+
shift: false,
|
|
128
|
+
alt: false,
|
|
129
|
+
meta: false,
|
|
130
|
+
key: null,
|
|
131
|
+
code: null,
|
|
132
|
+
bareModifier: null
|
|
133
|
+
};
|
|
134
|
+
let stripped = baseRaw;
|
|
135
|
+
while (stripped.length) {
|
|
136
|
+
const mod = ahkPrefixMap.get(stripped[0]) ?? null;
|
|
137
|
+
if (!mod) break;
|
|
138
|
+
combo[mod] = true;
|
|
139
|
+
stripped = stripped.slice(1).trimStart();
|
|
140
|
+
}
|
|
141
|
+
const codeMatches = stripped.match(/code:[A-Za-z][A-Za-z0-9_]*/g);
|
|
142
|
+
if (codeMatches && codeMatches.length > 1) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Invalid hotkey "${hotkeyStr}": multiple code: tokens not supported (got ${codeMatches.length}).`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
let codeValue = null;
|
|
148
|
+
const scrubbed = stripped.replace(CODE_TOKEN_RE, (_, sep, val) => {
|
|
149
|
+
codeValue = val;
|
|
150
|
+
return sep + "__code__";
|
|
151
|
+
});
|
|
152
|
+
let cleanStr = scrubbed.toLowerCase();
|
|
153
|
+
const rawParts = cleanStr.split("+").map((p) => p.trim());
|
|
154
|
+
const parts = rawParts.filter(Boolean);
|
|
155
|
+
const impliedPlusKey = rawParts.some((p) => p === "") && cleanStr.includes("++");
|
|
156
|
+
if (!impliedPlusKey) {
|
|
157
|
+
let seenBase = false;
|
|
158
|
+
for (const part of parts) {
|
|
159
|
+
if (isModifierToken(part)) {
|
|
160
|
+
if (seenBase) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Invalid hotkey "${hotkeyStr}": modifiers must come before the base key (e.g. "ctrl+k", not "k+ctrl")`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
if (!seenBase) seenBase = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const part of parts) {
|
|
171
|
+
if (part === "__code__") {
|
|
172
|
+
combo.code = codeValue;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const actual = keyAliases[part] ?? part;
|
|
176
|
+
if (actual === "ctrl") combo.ctrl = true;
|
|
177
|
+
else if (actual === "shift") combo.shift = true;
|
|
178
|
+
else if (actual === "alt") combo.alt = true;
|
|
179
|
+
else if (actual === "meta") combo.meta = true;
|
|
180
|
+
else combo.key = normalizeKey(actual);
|
|
181
|
+
}
|
|
182
|
+
if (!combo.key && !combo.code && impliedPlusKey) combo.key = "+";
|
|
183
|
+
if (!combo.key && !combo.code) {
|
|
184
|
+
const modCount = (combo.ctrl ? 1 : 0) + (combo.shift ? 1 : 0) + (combo.alt ? 1 : 0) + (combo.meta ? 1 : 0);
|
|
185
|
+
if (modCount === 1) {
|
|
186
|
+
combo.bareModifier = combo.ctrl ? "ctrl" : combo.shift ? "shift" : combo.alt ? "alt" : "meta";
|
|
187
|
+
} else if (modCount > 1) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Invalid hotkey "${hotkeyStr}": multi-modifier bare bindings not supported. Add a base key (e.g. "ctrl+alt+x").`
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
throw new Error(`Invalid hotkey "${hotkeyStr}" (missing base key)`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { combo, type: isUp ? "keyup" : "keydown", raw: hotkeyStr };
|
|
196
|
+
}
|
|
197
|
+
function isFromInputTarget(e) {
|
|
198
|
+
const t = e?.target;
|
|
199
|
+
if (!t) return false;
|
|
200
|
+
const tag = String(t.tagName || "").toUpperCase();
|
|
201
|
+
return t.isContentEditable || tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || t.getAttribute?.("role") === "textbox";
|
|
202
|
+
}
|
|
203
|
+
function normalizeOptions(type, options) {
|
|
204
|
+
const o = options ? { ...options } : {};
|
|
205
|
+
if (type === "keydown" && o.repeat == null) o.repeat = false;
|
|
206
|
+
if (o.priority == null) o.priority = 0;
|
|
207
|
+
return o;
|
|
208
|
+
}
|
|
209
|
+
function ensureSlot(comboStr, type) {
|
|
210
|
+
let typeMap = registry.get(comboStr);
|
|
211
|
+
if (!typeMap) {
|
|
212
|
+
typeMap = /* @__PURE__ */ new Map();
|
|
213
|
+
registry.set(comboStr, typeMap);
|
|
214
|
+
}
|
|
215
|
+
let arr = typeMap.get(type);
|
|
216
|
+
if (!arr) {
|
|
217
|
+
arr = [];
|
|
218
|
+
typeMap.set(type, arr);
|
|
219
|
+
}
|
|
220
|
+
return arr;
|
|
221
|
+
}
|
|
222
|
+
function removeById(id) {
|
|
223
|
+
const idx = bindingIndex.get(id);
|
|
224
|
+
if (!idx) return false;
|
|
225
|
+
const { comboStr, type } = idx;
|
|
226
|
+
const typeMap = registry.get(comboStr);
|
|
227
|
+
const arr = typeMap?.get(type);
|
|
228
|
+
if (!arr?.length) {
|
|
229
|
+
bindingIndex.delete(id);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const next = arr.filter((b) => b.id !== id);
|
|
233
|
+
if (next.length) typeMap.set(type, next);
|
|
234
|
+
else typeMap.delete(type);
|
|
235
|
+
if (!typeMap.size) registry.delete(comboStr);
|
|
236
|
+
bindingIndex.delete(id);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
function bind(hotkeyStr, handler, plugin = null, options = {}) {
|
|
240
|
+
const { combo, type, raw } = parseHotkey(hotkeyStr);
|
|
241
|
+
const comboStr = comboKeyFromParts(combo);
|
|
242
|
+
const opts = normalizeOptions(type, options);
|
|
243
|
+
const binding = {
|
|
244
|
+
id: nextId++,
|
|
245
|
+
comboStr,
|
|
246
|
+
type,
|
|
247
|
+
combo,
|
|
248
|
+
handler,
|
|
249
|
+
plugin,
|
|
250
|
+
raw,
|
|
251
|
+
options: opts
|
|
252
|
+
};
|
|
253
|
+
ensureSlot(comboStr, type).push(binding);
|
|
254
|
+
bindingIndex.set(binding.id, { comboStr, type });
|
|
255
|
+
if (bindHooks.length) {
|
|
256
|
+
for (const hook of bindHooks) {
|
|
257
|
+
try {
|
|
258
|
+
hook({ combo, raw, options: opts, plugin, id: binding.id });
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error("[hotkey-router] onBind hook threw:", err);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const off = () => removeById(binding.id);
|
|
265
|
+
off.id = binding.id;
|
|
266
|
+
off.hotkey = raw;
|
|
267
|
+
return off;
|
|
268
|
+
}
|
|
269
|
+
function onBind(hook) {
|
|
270
|
+
if (typeof hook !== "function") {
|
|
271
|
+
throw new TypeError("hotkeys.onBind(hook) expects a function");
|
|
272
|
+
}
|
|
273
|
+
bindHooks.push(hook);
|
|
274
|
+
return () => {
|
|
275
|
+
bindHooks = bindHooks.filter((h) => h !== hook);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function unbind(hotkeyStr, handler) {
|
|
279
|
+
const { combo, type } = parseHotkey(hotkeyStr);
|
|
280
|
+
const comboStr = comboKeyFromParts(combo);
|
|
281
|
+
const typeMap = registry.get(comboStr);
|
|
282
|
+
if (!typeMap) return;
|
|
283
|
+
const arr = typeMap.get(type);
|
|
284
|
+
if (!arr?.length) return;
|
|
285
|
+
if (!handler) {
|
|
286
|
+
for (const b of arr) bindingIndex.delete(b.id);
|
|
287
|
+
typeMap.delete(type);
|
|
288
|
+
} else {
|
|
289
|
+
const keep = [];
|
|
290
|
+
for (const b of arr) {
|
|
291
|
+
if (b.handler === handler) bindingIndex.delete(b.id);
|
|
292
|
+
else keep.push(b);
|
|
293
|
+
}
|
|
294
|
+
if (keep.length) typeMap.set(type, keep);
|
|
295
|
+
else typeMap.delete(type);
|
|
296
|
+
}
|
|
297
|
+
if (!typeMap.size) registry.delete(comboStr);
|
|
298
|
+
}
|
|
299
|
+
function registerPlugin(name, hotkeyMap) {
|
|
300
|
+
if (plugins.has(name)) throw new Error(`Plugin "${name}" already registered`);
|
|
301
|
+
const ids = [];
|
|
302
|
+
for (const [hotkeyStr, fnOrTuple] of Object.entries(hotkeyMap)) {
|
|
303
|
+
const [fn, options] = Array.isArray(fnOrTuple) ? fnOrTuple : [fnOrTuple, {}];
|
|
304
|
+
const off = bind(hotkeyStr, fn, name, options);
|
|
305
|
+
ids.push(off.id);
|
|
306
|
+
}
|
|
307
|
+
plugins.set(name, ids);
|
|
308
|
+
return () => unregisterPlugin(name);
|
|
309
|
+
}
|
|
310
|
+
function unregisterPlugin(name) {
|
|
311
|
+
const ids = plugins.get(name);
|
|
312
|
+
if (!ids) return;
|
|
313
|
+
for (const id of ids) removeById(id);
|
|
314
|
+
plugins.delete(name);
|
|
315
|
+
}
|
|
316
|
+
function pause() {
|
|
317
|
+
paused = true;
|
|
318
|
+
}
|
|
319
|
+
function resume() {
|
|
320
|
+
paused = false;
|
|
321
|
+
}
|
|
322
|
+
function ignoreInput(value = true) {
|
|
323
|
+
ignoreEditable = !!value;
|
|
324
|
+
}
|
|
325
|
+
function setTarget(target) {
|
|
326
|
+
defaultTarget = target;
|
|
327
|
+
}
|
|
328
|
+
function pickWinner(bindings) {
|
|
329
|
+
let best = null;
|
|
330
|
+
for (const b of bindings) {
|
|
331
|
+
if (!best) best = b;
|
|
332
|
+
else if (b.options.priority > best.options.priority) best = b;
|
|
333
|
+
else if (b.options.priority === best.options.priority && b.id > best.id) best = b;
|
|
334
|
+
}
|
|
335
|
+
return best;
|
|
336
|
+
}
|
|
337
|
+
function handleEvent(type) {
|
|
338
|
+
return (e) => {
|
|
339
|
+
if (paused) return;
|
|
340
|
+
const fromInput = ignoreEditable && isFromInputTarget(e);
|
|
341
|
+
const comboStr = comboKeyFromEvent(e);
|
|
342
|
+
const codeStr = codeKeyFromEvent(e);
|
|
343
|
+
const seen = /* @__PURE__ */ new Set();
|
|
344
|
+
const candidates = [];
|
|
345
|
+
for (const lookupKey of [comboStr, codeStr]) {
|
|
346
|
+
if (!lookupKey || seen.has(lookupKey)) continue;
|
|
347
|
+
seen.add(lookupKey);
|
|
348
|
+
const bindings = registry.get(lookupKey)?.get(type);
|
|
349
|
+
if (!bindings?.length) continue;
|
|
350
|
+
for (const b of bindings) {
|
|
351
|
+
const o2 = b.options;
|
|
352
|
+
if (fromInput && !o2.allowIn?.(e)) continue;
|
|
353
|
+
if (type === "keydown" && o2.repeat === false && e.repeat) continue;
|
|
354
|
+
if (o2.when && o2.when(e) === false) continue;
|
|
355
|
+
candidates.push(b);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (!candidates.length) return;
|
|
359
|
+
const winner = pickWinner(candidates);
|
|
360
|
+
const o = winner.options;
|
|
361
|
+
if (o.preventDefault) e.preventDefault();
|
|
362
|
+
if (o.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
363
|
+
else if (o.stopPropagation) e.stopPropagation();
|
|
364
|
+
winner.handler(e);
|
|
365
|
+
if (o.once) removeById(winner.id);
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
var stopDown = null;
|
|
369
|
+
var stopUp = null;
|
|
370
|
+
function init({ target = defaultTarget, capture = false } = {}) {
|
|
371
|
+
if (!target) throw new Error("hotkeys.init() requires a target (e.g. window)");
|
|
372
|
+
if (stopDown || stopUp) destroy();
|
|
373
|
+
stopDown = on(target, "keydown", handleEvent("keydown"), { capture });
|
|
374
|
+
stopUp = on(target, "keyup", handleEvent("keyup"), { capture });
|
|
375
|
+
}
|
|
376
|
+
function destroy() {
|
|
377
|
+
stopDown?.();
|
|
378
|
+
stopUp?.();
|
|
379
|
+
stopDown = null;
|
|
380
|
+
stopUp = null;
|
|
381
|
+
registry.clear();
|
|
382
|
+
plugins.clear();
|
|
383
|
+
bindingIndex.clear();
|
|
384
|
+
}
|
|
385
|
+
var BARE_MOD_TO_KEY = {
|
|
386
|
+
ctrl: "Control",
|
|
387
|
+
shift: "Shift",
|
|
388
|
+
alt: "Alt",
|
|
389
|
+
meta: "Meta"
|
|
390
|
+
};
|
|
391
|
+
function trigger(hotkeyStr, { type: forcedType } = {}) {
|
|
392
|
+
const { combo, type } = parseHotkey(hotkeyStr);
|
|
393
|
+
const comboStr = comboKeyFromParts(combo);
|
|
394
|
+
const actualType = forcedType || type;
|
|
395
|
+
const bindings = registry.get(comboStr)?.get(actualType);
|
|
396
|
+
if (!bindings?.length) return false;
|
|
397
|
+
const eventKey = combo.bareModifier ? BARE_MOD_TO_KEY[combo.bareModifier] : combo.key;
|
|
398
|
+
let altKey = !!combo.alt;
|
|
399
|
+
let ctrlKey = !!combo.ctrl;
|
|
400
|
+
let shiftKey = !!combo.shift;
|
|
401
|
+
let metaKey = !!combo.meta;
|
|
402
|
+
if (combo.bareModifier && actualType === "keyup") {
|
|
403
|
+
if (combo.bareModifier === "alt") altKey = false;
|
|
404
|
+
else if (combo.bareModifier === "ctrl") ctrlKey = false;
|
|
405
|
+
else if (combo.bareModifier === "shift") shiftKey = false;
|
|
406
|
+
else if (combo.bareModifier === "meta") metaKey = false;
|
|
407
|
+
}
|
|
408
|
+
const e = {
|
|
409
|
+
type: actualType,
|
|
410
|
+
key: eventKey,
|
|
411
|
+
code: combo.code || null,
|
|
412
|
+
ctrlKey,
|
|
413
|
+
shiftKey,
|
|
414
|
+
altKey,
|
|
415
|
+
metaKey,
|
|
416
|
+
repeat: false,
|
|
417
|
+
preventDefault() {
|
|
418
|
+
},
|
|
419
|
+
stopPropagation() {
|
|
420
|
+
},
|
|
421
|
+
stopImmediatePropagation() {
|
|
422
|
+
},
|
|
423
|
+
target: null
|
|
424
|
+
};
|
|
425
|
+
const winner = pickWinner(
|
|
426
|
+
bindings.filter((b) => b.options.when ? b.options.when(e) !== false : true)
|
|
427
|
+
);
|
|
428
|
+
if (!winner) return false;
|
|
429
|
+
winner.handler(e);
|
|
430
|
+
if (winner.options.once) removeById(winner.id);
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
if (defaultTarget) init();
|
|
434
|
+
var hotkey_router_default = {
|
|
435
|
+
// lifecycle
|
|
436
|
+
init,
|
|
437
|
+
destroy,
|
|
438
|
+
setTarget,
|
|
439
|
+
// bindings
|
|
440
|
+
bind,
|
|
441
|
+
unbind,
|
|
442
|
+
registerPlugin,
|
|
443
|
+
unregisterPlugin,
|
|
444
|
+
// extension hooks
|
|
445
|
+
onBind,
|
|
446
|
+
// control
|
|
447
|
+
pause,
|
|
448
|
+
resume,
|
|
449
|
+
ignoreInput,
|
|
450
|
+
// testing / automation
|
|
451
|
+
trigger
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// data/browser-hotkeys.json
|
|
455
|
+
var browser_hotkeys_default = {
|
|
456
|
+
$comment: "Machine-readable companion to docs/browser-hotkeys.md. Each combo entry describes who claims the binding and how strongly. Intended for runtime conflict warnings in keyboard-routing libraries (e.g. hotkey-router) and dev-time validation in apps like WebRadar. Update both this file and the markdown together \u2014 the markdown is the human source of truth, this file is the machine projection.",
|
|
457
|
+
version: {
|
|
458
|
+
generated: "2026-05-17",
|
|
459
|
+
schema: "1.0",
|
|
460
|
+
sources: [
|
|
461
|
+
"https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly",
|
|
462
|
+
"https://firefox-source-docs.mozilla.org/devtools-user/keyboard_shortcuts/index.html",
|
|
463
|
+
"https://support.google.com/chrome/answer/157179",
|
|
464
|
+
"https://developer.chrome.com/docs/devtools/shortcuts",
|
|
465
|
+
"https://support.apple.com/guide/safari/keyboard-shortcuts-and-gestures-cpsh003/mac",
|
|
466
|
+
"https://developer.apple.com/documentation/safari-developer-tools/web-inspector",
|
|
467
|
+
"https://support.microsoft.com/en-us/microsoft-edge/keyboard-shortcuts-in-microsoft-edge-50d3edab-30d9-c7e4-21ce-37fe2713cfad",
|
|
468
|
+
"https://support.apple.com/en-us/102650",
|
|
469
|
+
"https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec",
|
|
470
|
+
"https://help.gnome.org/gnome-help/shell-keyboard-shortcuts.html"
|
|
471
|
+
]
|
|
472
|
+
},
|
|
473
|
+
severityLevels: {
|
|
474
|
+
hard: "Browser or OS intercepts before page-world keydown fires. preventDefault() can't recover it.",
|
|
475
|
+
"find-bar-only": "Reserved only when the browser's find bar has focus. Fires in page world during normal browsing.",
|
|
476
|
+
"menu-activation": "Win/Linux Alt+letter activates the browser menu bar. Hard to suppress reliably.",
|
|
477
|
+
"system-text": "Only reserved while a text input has focus (terminal-style cursor controls on macOS).",
|
|
478
|
+
compose: "macOS Option+letter types a special character in text inputs. Outside inputs, free for page world.",
|
|
479
|
+
os: "Operating system intercepts globally, before any application sees it.",
|
|
480
|
+
"devtools-open": "Only relevant when DevTools is already open; outside DevTools it fires normally."
|
|
481
|
+
},
|
|
482
|
+
$keyFormat: "Each combo key uses hotkey-router-compatible syntax: modifiers in canonical order (ctrl|meta|alt|shift) joined by '+', then the key (lowercased letter, or symbolic name). Per-platform tables use the platform's native modifier name \u2014 `meta` on macOS = Cmd, `meta` on Windows = Win, `ctrl` on Linux/Windows = the primary modifier.",
|
|
483
|
+
platforms: {
|
|
484
|
+
mac: {
|
|
485
|
+
browsers: {
|
|
486
|
+
firefox: {
|
|
487
|
+
"meta+a": { action: "Select all", severity: "hard" },
|
|
488
|
+
"meta+b": { action: "Bookmarks sidebar", severity: "hard" },
|
|
489
|
+
"meta+c": { action: "Copy", severity: "hard" },
|
|
490
|
+
"meta+d": { action: "Bookmark this page", severity: "hard" },
|
|
491
|
+
"meta+e": { action: "Search from page", severity: "hard" },
|
|
492
|
+
"meta+f": { action: "Find in page", severity: "hard" },
|
|
493
|
+
"meta+g": { action: "Find next", severity: "hard" },
|
|
494
|
+
"meta+j": { action: "Downloads", severity: "hard" },
|
|
495
|
+
"meta+l": { action: "Focus address bar", severity: "hard" },
|
|
496
|
+
"meta+n": { action: "New window", severity: "hard" },
|
|
497
|
+
"meta+o": { action: "Open file", severity: "hard" },
|
|
498
|
+
"meta+p": { action: "Print", severity: "hard" },
|
|
499
|
+
"meta+r": { action: "Reload", severity: "hard" },
|
|
500
|
+
"meta+s": { action: "Save page", severity: "hard" },
|
|
501
|
+
"meta+t": { action: "New tab", severity: "hard" },
|
|
502
|
+
"meta+u": { action: "View source", severity: "hard" },
|
|
503
|
+
"meta+v": { action: "Paste", severity: "hard" },
|
|
504
|
+
"meta+w": { action: "Close tab", severity: "hard" },
|
|
505
|
+
"meta+x": { action: "Cut", severity: "hard" },
|
|
506
|
+
"meta+y": { action: "Redo", severity: "hard" },
|
|
507
|
+
"meta+z": { action: "Undo", severity: "hard" },
|
|
508
|
+
"meta+shift+a": { action: "Add-ons manager", severity: "hard" },
|
|
509
|
+
"meta+shift+b": { action: "Bookmarks toolbar", severity: "hard" },
|
|
510
|
+
"meta+shift+c": { action: "Inspector (element picker)", severity: "hard" },
|
|
511
|
+
"meta+shift+d": { action: "Bookmark all tabs", severity: "hard" },
|
|
512
|
+
"meta+shift+e": { action: "Network Monitor", severity: "hard" },
|
|
513
|
+
"meta+shift+f": { action: "Toggle fullscreen", severity: "hard" },
|
|
514
|
+
"meta+shift+g": { action: "Find previous", severity: "find-bar-only" },
|
|
515
|
+
"meta+shift+j": { action: "Browser Console", severity: "hard" },
|
|
516
|
+
"meta+shift+n": { action: "New private window", severity: "hard" },
|
|
517
|
+
"meta+shift+o": { action: "Bookmarks library", severity: "hard" },
|
|
518
|
+
"meta+shift+p": { action: "New private window (alt)", severity: "hard" },
|
|
519
|
+
"meta+shift+q": { action: "Quit (with confirm)", severity: "hard" },
|
|
520
|
+
"meta+shift+s": { action: "Firefox Screenshot", severity: "hard" },
|
|
521
|
+
"meta+shift+t": { action: "Reopen closed tab", severity: "hard" },
|
|
522
|
+
"meta+shift+v": { action: "Paste as plain text", severity: "hard" },
|
|
523
|
+
"meta+shift+w": { action: "Close window", severity: "hard" },
|
|
524
|
+
"meta+shift+z": { action: "Redo", severity: "hard" },
|
|
525
|
+
"meta+alt+c": { action: "Inspector", severity: "hard" },
|
|
526
|
+
"meta+alt+e": { action: "Network Monitor", severity: "hard" },
|
|
527
|
+
"meta+alt+i": { action: "DevTools Toolbox", severity: "hard" },
|
|
528
|
+
"meta+alt+j": { action: "Browser Console", severity: "hard" },
|
|
529
|
+
"meta+alt+k": { action: "Web Console", severity: "hard" },
|
|
530
|
+
"meta+alt+m": { action: "Responsive Design Mode", severity: "hard" },
|
|
531
|
+
"meta+alt+z": { action: "Debugger", severity: "hard" }
|
|
532
|
+
},
|
|
533
|
+
chrome: {
|
|
534
|
+
"meta+a": { action: "Select all", severity: "hard" },
|
|
535
|
+
"meta+c": { action: "Copy", severity: "hard" },
|
|
536
|
+
"meta+d": { action: "Bookmark this page", severity: "hard" },
|
|
537
|
+
"meta+e": { action: "Search selected text", severity: "find-bar-only" },
|
|
538
|
+
"meta+f": { action: "Find in page", severity: "hard" },
|
|
539
|
+
"meta+g": { action: "Find next", severity: "hard" },
|
|
540
|
+
"meta+l": { action: "Focus address bar", severity: "hard" },
|
|
541
|
+
"meta+n": { action: "New window", severity: "hard" },
|
|
542
|
+
"meta+o": { action: "Open file", severity: "hard" },
|
|
543
|
+
"meta+p": { action: "Print", severity: "hard" },
|
|
544
|
+
"meta+r": { action: "Reload", severity: "hard" },
|
|
545
|
+
"meta+s": { action: "Save page", severity: "hard" },
|
|
546
|
+
"meta+t": { action: "New tab", severity: "hard" },
|
|
547
|
+
"meta+v": { action: "Paste", severity: "hard" },
|
|
548
|
+
"meta+w": { action: "Close tab", severity: "hard" },
|
|
549
|
+
"meta+x": { action: "Cut", severity: "hard" },
|
|
550
|
+
"meta+y": { action: "History page", severity: "hard" },
|
|
551
|
+
"meta+z": { action: "Undo", severity: "hard" },
|
|
552
|
+
"meta+shift+b": { action: "Bookmarks bar toggle", severity: "hard" },
|
|
553
|
+
"meta+shift+c": { action: "DevTools Inspector", severity: "hard" },
|
|
554
|
+
"meta+shift+d": { action: "Bookmark all tabs", severity: "hard" },
|
|
555
|
+
"meta+shift+g": { action: "Find previous", severity: "find-bar-only" },
|
|
556
|
+
"meta+shift+j": { action: "Downloads page", severity: "hard" },
|
|
557
|
+
"meta+shift+m": { action: "Switch user / guest", severity: "hard" },
|
|
558
|
+
"meta+shift+n": { action: "New incognito window", severity: "hard" },
|
|
559
|
+
"meta+shift+r": { action: "Hard reload", severity: "hard" },
|
|
560
|
+
"meta+shift+t": { action: "Reopen closed tab", severity: "hard" },
|
|
561
|
+
"meta+shift+v": { action: "Paste as plain text", severity: "hard" },
|
|
562
|
+
"meta+shift+w": { action: "Close window", severity: "hard" },
|
|
563
|
+
"meta+shift+z": { action: "Redo", severity: "hard" },
|
|
564
|
+
"meta+alt+i": { action: "DevTools", severity: "hard" },
|
|
565
|
+
"meta+alt+j": { action: "DevTools (console)", severity: "hard" },
|
|
566
|
+
"meta+alt+u": { action: "View source", severity: "hard" }
|
|
567
|
+
},
|
|
568
|
+
safari: {
|
|
569
|
+
"meta+a": { action: "Select all", severity: "hard" },
|
|
570
|
+
"meta+c": { action: "Copy", severity: "hard" },
|
|
571
|
+
"meta+d": { action: "Add to Reading List", severity: "hard" },
|
|
572
|
+
"meta+f": { action: "Find in page", severity: "hard" },
|
|
573
|
+
"meta+g": { action: "Find next", severity: "hard" },
|
|
574
|
+
"meta+j": { action: "Downloads", severity: "hard" },
|
|
575
|
+
"meta+l": { action: "Focus Smart Search", severity: "hard" },
|
|
576
|
+
"meta+n": { action: "New window", severity: "hard" },
|
|
577
|
+
"meta+p": { action: "Print", severity: "hard" },
|
|
578
|
+
"meta+r": { action: "Reload", severity: "hard" },
|
|
579
|
+
"meta+s": { action: "Save page", severity: "hard" },
|
|
580
|
+
"meta+t": { action: "New tab", severity: "hard" },
|
|
581
|
+
"meta+v": { action: "Paste", severity: "hard" },
|
|
582
|
+
"meta+w": { action: "Close tab", severity: "hard" },
|
|
583
|
+
"meta+x": { action: "Cut", severity: "hard" },
|
|
584
|
+
"meta+z": { action: "Undo", severity: "hard" },
|
|
585
|
+
"meta+shift+c": { action: "Inspector pick element", severity: "hard" },
|
|
586
|
+
"meta+shift+d": { action: "Add Reading List item", severity: "hard" },
|
|
587
|
+
"meta+shift+g": { action: "Find previous", severity: "find-bar-only" },
|
|
588
|
+
"meta+shift+h": { action: "Go to homepage", severity: "hard" },
|
|
589
|
+
"meta+shift+n": { action: "New private window", severity: "hard" },
|
|
590
|
+
"meta+shift+r": { action: "Reader mode", severity: "hard" },
|
|
591
|
+
"meta+shift+t": { action: "Reopen last closed tab", severity: "hard" },
|
|
592
|
+
"meta+shift+v": { action: "Paste as plain text", severity: "hard" },
|
|
593
|
+
"meta+shift+w": { action: "Close window", severity: "hard" },
|
|
594
|
+
"meta+shift+z": { action: "Redo", severity: "hard" },
|
|
595
|
+
"meta+alt+c": { action: "Web Inspector pick", severity: "hard" },
|
|
596
|
+
"meta+alt+i": { action: "Web Inspector", severity: "hard" }
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
system: {
|
|
600
|
+
"meta+space": { action: "Spotlight", severity: "os" },
|
|
601
|
+
"meta+tab": { action: "App switcher", severity: "os" },
|
|
602
|
+
"meta+h": { action: "Hide app", severity: "os" },
|
|
603
|
+
"meta+m": { action: "Minimize window", severity: "os" },
|
|
604
|
+
"meta+q": { action: "Quit app", severity: "os" },
|
|
605
|
+
"meta+shift+3": { action: "Screenshot full screen", severity: "os" },
|
|
606
|
+
"meta+shift+4": { action: "Screenshot selection", severity: "os" },
|
|
607
|
+
"meta+shift+5": { action: "Screen recording tool", severity: "os" },
|
|
608
|
+
"meta+shift+q": { action: "Log out", severity: "os" },
|
|
609
|
+
"meta+ctrl+q": { action: "Lock screen", severity: "os" },
|
|
610
|
+
"meta+ctrl+space": { action: "Character Viewer", severity: "os" },
|
|
611
|
+
"meta+alt+esc": { action: "Force-quit dialog", severity: "os" }
|
|
612
|
+
},
|
|
613
|
+
noteForLetterCombos: {
|
|
614
|
+
"alt+a..z": { behavior: "compose", note: "Option+letter types special characters in text inputs (Option+E\u2192\xB4, Option+A\u2192\xE5, Option+F\u2192\u0192). Outside inputs, free for page world." },
|
|
615
|
+
"ctrl+a..z": { behavior: "system-text", note: "Mac uses Ctrl+letter as terminal-style cursor controls inside text inputs (Ctrl+A=line start, Ctrl+E=line end, Ctrl+K=delete-to-end, etc.). Outside inputs, free for page world." }
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
windows: {
|
|
619
|
+
browsers: {
|
|
620
|
+
firefox: {
|
|
621
|
+
"ctrl+a": { action: "Select all", severity: "hard" },
|
|
622
|
+
"ctrl+b": { action: "Bookmarks sidebar", severity: "hard" },
|
|
623
|
+
"ctrl+c": { action: "Copy", severity: "hard" },
|
|
624
|
+
"ctrl+d": { action: "Bookmark this page", severity: "hard" },
|
|
625
|
+
"ctrl+f": { action: "Find in page", severity: "hard" },
|
|
626
|
+
"ctrl+g": { action: "Find next", severity: "hard" },
|
|
627
|
+
"ctrl+h": { action: "History sidebar", severity: "hard" },
|
|
628
|
+
"ctrl+j": { action: "Downloads", severity: "hard" },
|
|
629
|
+
"ctrl+l": { action: "Focus address bar", severity: "hard" },
|
|
630
|
+
"ctrl+n": { action: "New window", severity: "hard" },
|
|
631
|
+
"ctrl+o": { action: "Open file", severity: "hard" },
|
|
632
|
+
"ctrl+p": { action: "Print", severity: "hard" },
|
|
633
|
+
"ctrl+r": { action: "Reload", severity: "hard" },
|
|
634
|
+
"ctrl+s": { action: "Save page", severity: "hard" },
|
|
635
|
+
"ctrl+t": { action: "New tab", severity: "hard" },
|
|
636
|
+
"ctrl+u": { action: "View source", severity: "hard" },
|
|
637
|
+
"ctrl+v": { action: "Paste", severity: "hard" },
|
|
638
|
+
"ctrl+w": { action: "Close tab", severity: "hard" },
|
|
639
|
+
"ctrl+x": { action: "Cut", severity: "hard" },
|
|
640
|
+
"ctrl+y": { action: "Redo", severity: "hard" },
|
|
641
|
+
"ctrl+z": { action: "Undo", severity: "hard" },
|
|
642
|
+
"ctrl+shift+a": { action: "Add-ons manager", severity: "hard" },
|
|
643
|
+
"ctrl+shift+b": { action: "Bookmarks toolbar", severity: "hard" },
|
|
644
|
+
"ctrl+shift+c": { action: "Inspector (element picker)", severity: "hard" },
|
|
645
|
+
"ctrl+shift+d": { action: "Bookmark all tabs", severity: "hard" },
|
|
646
|
+
"ctrl+shift+e": { action: "Network Monitor", severity: "hard" },
|
|
647
|
+
"ctrl+shift+g": { action: "Find previous", severity: "find-bar-only" },
|
|
648
|
+
"ctrl+shift+i": { action: "Developer Tools", severity: "hard" },
|
|
649
|
+
"ctrl+shift+j": { action: "Browser Console", severity: "hard" },
|
|
650
|
+
"ctrl+shift+k": { action: "Web Console", severity: "hard" },
|
|
651
|
+
"ctrl+shift+m": { action: "Responsive Design Mode", severity: "hard" },
|
|
652
|
+
"ctrl+shift+n": { action: "New private window", severity: "hard" },
|
|
653
|
+
"ctrl+shift+o": { action: "Bookmarks library", severity: "hard" },
|
|
654
|
+
"ctrl+shift+p": { action: "New private window (alt)", severity: "hard" },
|
|
655
|
+
"ctrl+shift+s": { action: "Firefox Screenshot", severity: "hard" },
|
|
656
|
+
"ctrl+shift+t": { action: "Reopen closed tab", severity: "hard" },
|
|
657
|
+
"ctrl+shift+v": { action: "Paste as plain text", severity: "hard" },
|
|
658
|
+
"ctrl+shift+w": { action: "Close window", severity: "hard" },
|
|
659
|
+
"ctrl+shift+y": { action: "Downloads (alt)", severity: "hard" },
|
|
660
|
+
"ctrl+shift+z": { action: "Debugger", severity: "hard" },
|
|
661
|
+
"alt+f": { action: "File menu", severity: "menu-activation" },
|
|
662
|
+
"alt+e": { action: "Edit menu", severity: "menu-activation" },
|
|
663
|
+
"alt+v": { action: "View menu", severity: "menu-activation" },
|
|
664
|
+
"alt+b": { action: "Bookmarks menu", severity: "menu-activation" },
|
|
665
|
+
"alt+t": { action: "Tools menu", severity: "menu-activation" },
|
|
666
|
+
"alt+h": { action: "Help menu", severity: "menu-activation" },
|
|
667
|
+
"alt+d": { action: "Focus address bar", severity: "hard" }
|
|
668
|
+
},
|
|
669
|
+
chrome: {
|
|
670
|
+
"ctrl+a": { action: "Select all", severity: "hard" },
|
|
671
|
+
"ctrl+c": { action: "Copy", severity: "hard" },
|
|
672
|
+
"ctrl+d": { action: "Bookmark this page", severity: "hard" },
|
|
673
|
+
"ctrl+e": { action: "Search from page", severity: "hard" },
|
|
674
|
+
"ctrl+f": { action: "Find in page", severity: "hard" },
|
|
675
|
+
"ctrl+g": { action: "Find next", severity: "hard" },
|
|
676
|
+
"ctrl+h": { action: "History page", severity: "hard" },
|
|
677
|
+
"ctrl+j": { action: "Downloads", severity: "hard" },
|
|
678
|
+
"ctrl+k": { action: "Search from page", severity: "hard" },
|
|
679
|
+
"ctrl+l": { action: "Focus address bar", severity: "hard" },
|
|
680
|
+
"ctrl+n": { action: "New window", severity: "hard" },
|
|
681
|
+
"ctrl+o": { action: "Open file", severity: "hard" },
|
|
682
|
+
"ctrl+p": { action: "Print", severity: "hard" },
|
|
683
|
+
"ctrl+r": { action: "Reload", severity: "hard" },
|
|
684
|
+
"ctrl+s": { action: "Save page", severity: "hard" },
|
|
685
|
+
"ctrl+t": { action: "New tab", severity: "hard" },
|
|
686
|
+
"ctrl+u": { action: "View source", severity: "hard" },
|
|
687
|
+
"ctrl+v": { action: "Paste", severity: "hard" },
|
|
688
|
+
"ctrl+w": { action: "Close tab", severity: "hard" },
|
|
689
|
+
"ctrl+x": { action: "Cut", severity: "hard" },
|
|
690
|
+
"ctrl+z": { action: "Undo", severity: "hard" },
|
|
691
|
+
"ctrl+shift+b": { action: "Bookmarks bar toggle", severity: "hard" },
|
|
692
|
+
"ctrl+shift+d": { action: "Bookmark all tabs", severity: "hard" },
|
|
693
|
+
"ctrl+shift+g": { action: "Find previous", severity: "find-bar-only" },
|
|
694
|
+
"ctrl+shift+i": { action: "DevTools", severity: "hard" },
|
|
695
|
+
"ctrl+shift+j": { action: "DevTools (console)", severity: "hard" },
|
|
696
|
+
"ctrl+shift+m": { action: "Switch user / guest", severity: "hard" },
|
|
697
|
+
"ctrl+shift+n": { action: "Incognito window", severity: "hard" },
|
|
698
|
+
"ctrl+shift+r": { action: "Hard reload", severity: "hard" },
|
|
699
|
+
"ctrl+shift+t": { action: "Reopen closed tab", severity: "hard" },
|
|
700
|
+
"ctrl+shift+v": { action: "Paste as plain text", severity: "hard" },
|
|
701
|
+
"ctrl+shift+w": { action: "Close window", severity: "hard" },
|
|
702
|
+
"alt+d": { action: "Focus address bar", severity: "hard" },
|
|
703
|
+
"alt+e": { action: "Customize menu", severity: "menu-activation" },
|
|
704
|
+
"alt+f": { action: "Customize menu (alt)", severity: "menu-activation" }
|
|
705
|
+
},
|
|
706
|
+
edge: {
|
|
707
|
+
"ctrl+a": { action: "Select all", severity: "hard" },
|
|
708
|
+
"ctrl+c": { action: "Copy", severity: "hard" },
|
|
709
|
+
"ctrl+d": { action: "Favorite this page", severity: "hard" },
|
|
710
|
+
"ctrl+e": { action: "Search from page", severity: "hard" },
|
|
711
|
+
"ctrl+f": { action: "Find in page", severity: "hard" },
|
|
712
|
+
"ctrl+g": { action: "Find next", severity: "hard" },
|
|
713
|
+
"ctrl+h": { action: "History", severity: "hard" },
|
|
714
|
+
"ctrl+j": { action: "Downloads", severity: "hard" },
|
|
715
|
+
"ctrl+k": { action: "Search from page", severity: "hard" },
|
|
716
|
+
"ctrl+l": { action: "Focus address bar", severity: "hard" },
|
|
717
|
+
"ctrl+m": { action: "Mute current tab", severity: "hard" },
|
|
718
|
+
"ctrl+n": { action: "New window", severity: "hard" },
|
|
719
|
+
"ctrl+o": { action: "Open file", severity: "hard" },
|
|
720
|
+
"ctrl+p": { action: "Print", severity: "hard" },
|
|
721
|
+
"ctrl+r": { action: "Reload", severity: "hard" },
|
|
722
|
+
"ctrl+s": { action: "Save page", severity: "hard" },
|
|
723
|
+
"ctrl+t": { action: "New tab", severity: "hard" },
|
|
724
|
+
"ctrl+u": { action: "View source", severity: "hard" },
|
|
725
|
+
"ctrl+v": { action: "Paste", severity: "hard" },
|
|
726
|
+
"ctrl+w": { action: "Close tab", severity: "hard" },
|
|
727
|
+
"ctrl+x": { action: "Cut", severity: "hard" },
|
|
728
|
+
"ctrl+z": { action: "Undo", severity: "hard" },
|
|
729
|
+
"ctrl+shift+b": { action: "Favorites bar toggle", severity: "hard" },
|
|
730
|
+
"ctrl+shift+d": { action: "Bookmark all tabs in folder", severity: "hard" },
|
|
731
|
+
"ctrl+shift+e": { action: "Search in sidebar", severity: "hard" },
|
|
732
|
+
"ctrl+shift+g": { action: "Find previous", severity: "find-bar-only" },
|
|
733
|
+
"ctrl+shift+i": { action: "DevTools", severity: "hard" },
|
|
734
|
+
"ctrl+shift+j": { action: "DevTools (console)", severity: "hard" },
|
|
735
|
+
"ctrl+shift+k": { action: "Duplicate tab", severity: "hard" },
|
|
736
|
+
"ctrl+shift+l": { action: "Paste and search", severity: "hard" },
|
|
737
|
+
"ctrl+shift+m": { action: "Switch user / guest", severity: "hard" },
|
|
738
|
+
"ctrl+shift+n": { action: "InPrivate window", severity: "hard" },
|
|
739
|
+
"ctrl+shift+o": { action: "Open favorites", severity: "hard" },
|
|
740
|
+
"ctrl+shift+p": { action: "Print (system dialog)", severity: "hard" },
|
|
741
|
+
"ctrl+shift+r": { action: "Hard reload", severity: "hard" },
|
|
742
|
+
"ctrl+shift+t": { action: "Reopen closed tab", severity: "hard" },
|
|
743
|
+
"ctrl+shift+u": { action: "Read Aloud toggle", severity: "hard" },
|
|
744
|
+
"ctrl+shift+v": { action: "Paste without formatting", severity: "hard" },
|
|
745
|
+
"ctrl+shift+w": { action: "Close window", severity: "hard" },
|
|
746
|
+
"ctrl+shift+y": { action: "Collections", severity: "hard" },
|
|
747
|
+
"alt+d": { action: "Focus address bar", severity: "hard" },
|
|
748
|
+
"alt+e": { action: "Settings menu", severity: "menu-activation" },
|
|
749
|
+
"alt+f": { action: "Settings menu (alt)", severity: "menu-activation" },
|
|
750
|
+
"alt+shift+b": { action: "Focus favorites bar", severity: "hard" },
|
|
751
|
+
"alt+shift+i": { action: "Send feedback dialog", severity: "hard" },
|
|
752
|
+
"alt+shift+t": { action: "Focus app bar toolbar", severity: "hard" }
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
system: {
|
|
756
|
+
"meta+a": { action: "Action Center", severity: "os" },
|
|
757
|
+
"meta+c": { action: "Windows search", severity: "os" },
|
|
758
|
+
"meta+d": { action: "Show desktop", severity: "os" },
|
|
759
|
+
"meta+e": { action: "File Explorer", severity: "os" },
|
|
760
|
+
"meta+g": { action: "Game bar", severity: "os" },
|
|
761
|
+
"meta+h": { action: "Voice dictation", severity: "os" },
|
|
762
|
+
"meta+i": { action: "Settings", severity: "os" },
|
|
763
|
+
"meta+l": { action: "Lock", severity: "os" },
|
|
764
|
+
"meta+n": { action: "Notification center", severity: "os" },
|
|
765
|
+
"meta+p": { action: "Project / display mode", severity: "os" },
|
|
766
|
+
"meta+r": { action: "Run dialog", severity: "os" },
|
|
767
|
+
"meta+s": { action: "Search", severity: "os" },
|
|
768
|
+
"meta+v": { action: "Clipboard history", severity: "os" },
|
|
769
|
+
"meta+w": { action: "Widgets", severity: "os" },
|
|
770
|
+
"meta+x": { action: "Quick Link menu", severity: "os" },
|
|
771
|
+
"meta+z": { action: "Snap layouts", severity: "os" },
|
|
772
|
+
"meta+tab": { action: "Task View", severity: "os" },
|
|
773
|
+
"meta+shift+s": { action: "Screenshot region", severity: "os" },
|
|
774
|
+
"ctrl+shift+esc": { action: "Task Manager", severity: "os" },
|
|
775
|
+
"ctrl+alt+del": { action: "Security screen", severity: "os" },
|
|
776
|
+
"alt+tab": { action: "Switch windows", severity: "os" },
|
|
777
|
+
"alt+f4": { action: "Close active window", severity: "os" }
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
linux: {
|
|
781
|
+
browsers: {
|
|
782
|
+
firefox: "$ref:platforms.windows.browsers.firefox",
|
|
783
|
+
chrome: "$ref:platforms.windows.browsers.chrome",
|
|
784
|
+
edge: "$ref:platforms.windows.browsers.edge"
|
|
785
|
+
},
|
|
786
|
+
system: {
|
|
787
|
+
$comment: "GNOME defaults. KDE and other DEs have similar-shaped reservations but vary; this is a conservative baseline.",
|
|
788
|
+
"meta+tab": { action: "Switch windows", severity: "os" },
|
|
789
|
+
"meta+a": { action: "Show applications", severity: "os" },
|
|
790
|
+
"meta+l": { action: "Lock screen", severity: "os" },
|
|
791
|
+
"meta+v": { action: "Notification list", severity: "os" },
|
|
792
|
+
"alt+tab": { action: "Switch applications", severity: "os" },
|
|
793
|
+
"alt+esc": { action: "Cycle windows", severity: "os" },
|
|
794
|
+
"alt+f2": { action: "Run command", severity: "os" },
|
|
795
|
+
"alt+f4": { action: "Close window", severity: "os" },
|
|
796
|
+
"ctrl+alt+tab": { action: "Top-bar accessibility switcher", severity: "os" },
|
|
797
|
+
"ctrl+alt+del": { action: "Power-off dialog", severity: "os" },
|
|
798
|
+
"ctrl+shift+alt+r": { action: "Toggle screencast recording", severity: "os" }
|
|
799
|
+
},
|
|
800
|
+
noteForCtrlAlt: "Some Linux setups map right-Alt (AltGr) to 'compose' for typing accented characters, in which case AltGr+letter is reported as ctrl+alt+letter and types a special character. Avoid the ctrl+alt+letter namespace on Linux."
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// reservations.js
|
|
806
|
+
var SUPPORTED_SCHEMA = "1.0";
|
|
807
|
+
var HARD_SEVERITIES = /* @__PURE__ */ new Set(["hard", "os", "menu-activation"]);
|
|
808
|
+
function detectPlatform(nav = typeof navigator !== "undefined" ? navigator : null) {
|
|
809
|
+
if (!nav) return null;
|
|
810
|
+
const platform = nav.platform || "";
|
|
811
|
+
if (/Mac|iPhone|iPad|iPod/.test(platform)) return "mac";
|
|
812
|
+
if (/Win/i.test(platform)) return "windows";
|
|
813
|
+
if (/Linux|X11/i.test(platform)) return "linux";
|
|
814
|
+
const ua = nav.userAgent || "";
|
|
815
|
+
if (/Mac OS X/i.test(ua)) return "mac";
|
|
816
|
+
if (/Windows/i.test(ua)) return "windows";
|
|
817
|
+
if (/Linux|X11/i.test(ua)) return "linux";
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
function detectBrowser(nav = typeof navigator !== "undefined" ? navigator : null) {
|
|
821
|
+
if (!nav) return null;
|
|
822
|
+
const ua = nav.userAgent || "";
|
|
823
|
+
if (/Edg\//.test(ua)) return "edge";
|
|
824
|
+
if (/Firefox/.test(ua)) return "firefox";
|
|
825
|
+
if (/Chrome/.test(ua)) return "chrome";
|
|
826
|
+
if (/Safari/.test(ua)) return "safari";
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
function canonicalize(rawKey) {
|
|
830
|
+
const parts = String(rawKey).toLowerCase().split("+").map((p) => p.trim()).filter(Boolean);
|
|
831
|
+
if (!parts.length) return "";
|
|
832
|
+
const base = parts[parts.length - 1];
|
|
833
|
+
const mods = new Set(parts.slice(0, -1));
|
|
834
|
+
const out = [];
|
|
835
|
+
if (mods.has("ctrl")) out.push("ctrl");
|
|
836
|
+
if (mods.has("meta")) out.push("meta");
|
|
837
|
+
if (mods.has("alt")) out.push("alt");
|
|
838
|
+
if (mods.has("shift")) out.push("shift");
|
|
839
|
+
out.push(base);
|
|
840
|
+
return out.join("+");
|
|
841
|
+
}
|
|
842
|
+
function resolveRef(value, root) {
|
|
843
|
+
if (typeof value !== "string" || !value.startsWith("$ref:")) return value;
|
|
844
|
+
const path = value.slice("$ref:".length).split(".");
|
|
845
|
+
let node = root;
|
|
846
|
+
for (const step of path) node = node?.[step];
|
|
847
|
+
return node;
|
|
848
|
+
}
|
|
849
|
+
function normalizeTable(table) {
|
|
850
|
+
if (!table || typeof table !== "object") return null;
|
|
851
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
852
|
+
for (const [k, v] of Object.entries(table)) {
|
|
853
|
+
if (!v || typeof v !== "object" || !("severity" in v)) continue;
|
|
854
|
+
out[canonicalize(k)] = v;
|
|
855
|
+
}
|
|
856
|
+
return out;
|
|
857
|
+
}
|
|
858
|
+
function buildIndex(data) {
|
|
859
|
+
const idx = { platforms: {} };
|
|
860
|
+
const platforms = data?.platforms || {};
|
|
861
|
+
for (const [platform, pdata] of Object.entries(platforms)) {
|
|
862
|
+
const browsers = {};
|
|
863
|
+
for (const [name, btable] of Object.entries(pdata.browsers || {})) {
|
|
864
|
+
browsers[name] = normalizeTable(resolveRef(btable, data));
|
|
865
|
+
}
|
|
866
|
+
idx.platforms[platform] = {
|
|
867
|
+
browsers,
|
|
868
|
+
system: normalizeTable(pdata.system)
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return idx;
|
|
872
|
+
}
|
|
873
|
+
var SCHEMA_OK = browser_hotkeys_default?.version?.schema === SUPPORTED_SCHEMA;
|
|
874
|
+
var INDEX = SCHEMA_OK ? buildIndex(browser_hotkeys_default) : { platforms: {} };
|
|
875
|
+
function codeToKey(code) {
|
|
876
|
+
if (!code) return null;
|
|
877
|
+
const letter = /^Key([A-Za-z])$/.exec(code);
|
|
878
|
+
if (letter) return letter[1].toLowerCase();
|
|
879
|
+
const digit = /^Digit(\d)$/.exec(code);
|
|
880
|
+
if (digit) return digit[1];
|
|
881
|
+
return code.toLowerCase();
|
|
882
|
+
}
|
|
883
|
+
function lookupKeyFromCombo(combo) {
|
|
884
|
+
if (!combo) return null;
|
|
885
|
+
if (combo.bareModifier) return null;
|
|
886
|
+
const base = combo.code ? codeToKey(combo.code) : combo.key;
|
|
887
|
+
if (!base) return null;
|
|
888
|
+
const parts = [];
|
|
889
|
+
if (combo.ctrl) parts.push("ctrl");
|
|
890
|
+
if (combo.meta) parts.push("meta");
|
|
891
|
+
if (combo.alt) parts.push("alt");
|
|
892
|
+
if (combo.shift) parts.push("shift");
|
|
893
|
+
parts.push(base);
|
|
894
|
+
return parts.join("+");
|
|
895
|
+
}
|
|
896
|
+
function lookupReservation(combo, { platform, browser } = {}) {
|
|
897
|
+
if (!SCHEMA_OK) return null;
|
|
898
|
+
const lookupKey = lookupKeyFromCombo(combo);
|
|
899
|
+
if (!lookupKey) return null;
|
|
900
|
+
const pdata = platform && INDEX.platforms[platform];
|
|
901
|
+
if (!pdata) return null;
|
|
902
|
+
const btable = browser ? pdata.browsers?.[browser] : null;
|
|
903
|
+
const bclaim = btable?.[lookupKey];
|
|
904
|
+
if (bclaim) {
|
|
905
|
+
return {
|
|
906
|
+
source: "browser",
|
|
907
|
+
platform,
|
|
908
|
+
browser,
|
|
909
|
+
lookupKey,
|
|
910
|
+
action: bclaim.action,
|
|
911
|
+
severity: bclaim.severity
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const sclaim = pdata.system?.[lookupKey];
|
|
915
|
+
if (sclaim) {
|
|
916
|
+
return {
|
|
917
|
+
source: "system",
|
|
918
|
+
platform,
|
|
919
|
+
browser,
|
|
920
|
+
lookupKey,
|
|
921
|
+
action: sclaim.action,
|
|
922
|
+
severity: sclaim.severity
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
var PLATFORM_LABEL = { mac: "macOS", windows: "Windows", linux: "Linux" };
|
|
928
|
+
function severityIsHard(severity) {
|
|
929
|
+
return HARD_SEVERITIES.has(severity);
|
|
930
|
+
}
|
|
931
|
+
function formatReservationWarning(reservation, rawHotkey) {
|
|
932
|
+
const platformLabel = PLATFORM_LABEL[reservation.platform] || reservation.platform;
|
|
933
|
+
const sourceLabel = reservation.source === "browser" ? `${reservation.browser} on ${platformLabel}` : `${platformLabel}`;
|
|
934
|
+
const tag = severityIsHard(reservation.severity) ? "will not fire" : "fires in normal browsing";
|
|
935
|
+
return `[hotkey-router] "${rawHotkey}" (${reservation.lookupKey}) reserved by ${sourceLabel}: "${reservation.action}" [${reservation.severity}] \u2014 ${tag}.`;
|
|
936
|
+
}
|
|
937
|
+
function emitReservationWarning(reservation, rawHotkey, console_ = console) {
|
|
938
|
+
const msg = formatReservationWarning(reservation, rawHotkey);
|
|
939
|
+
const fn = severityIsHard(reservation.severity) ? console_.warn : console_.info;
|
|
940
|
+
fn.call(console_, msg);
|
|
941
|
+
}
|
|
942
|
+
function installReservationWarnings(hotkeys, opts = {}) {
|
|
943
|
+
if (!hotkeys || typeof hotkeys.onBind !== "function") {
|
|
944
|
+
throw new TypeError(
|
|
945
|
+
"installReservationWarnings(hotkeys) requires a hotkey-router instance with onBind() (>= v0.2.0)"
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
const platform = opts.platform !== void 0 ? opts.platform : detectPlatform();
|
|
949
|
+
const browser = opts.browser !== void 0 ? opts.browser : detectBrowser();
|
|
950
|
+
const console_ = opts.console || console;
|
|
951
|
+
return hotkeys.onBind(({ combo, raw, options }) => {
|
|
952
|
+
if (options?.warnOnReserved === false) return;
|
|
953
|
+
if (!platform || !browser) return;
|
|
954
|
+
const reservation = lookupReservation(combo, { platform, browser });
|
|
955
|
+
if (reservation) emitReservationWarning(reservation, raw, console_);
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
var SCHEMA_VERSION = browser_hotkeys_default?.version?.schema;
|
|
959
|
+
var DATA_GENERATED = browser_hotkeys_default?.version?.generated;
|
|
960
|
+
|
|
961
|
+
// auto.js
|
|
962
|
+
installReservationWarnings(hotkey_router_default);
|
|
963
|
+
var auto_default = hotkey_router_default;
|
|
964
|
+
export {
|
|
965
|
+
auto_default as default
|
|
966
|
+
};
|
|
967
|
+
//# sourceMappingURL=auto.js.map
|