hotkey-router 0.2.3 → 0.3.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/ADDITIONAL_TERMS.md +122 -122
- package/LICENSE +694 -694
- package/README.md +99 -2
- package/data/browser-hotkeys.json +349 -349
- package/data/site-hotkeys.json +4571 -0
- package/data/site-hotkeys.runtime.json +1 -0
- package/dist/auto.js +52 -5
- package/dist/auto.js.map +2 -2
- package/dist/auto.min.js +2 -2
- package/dist/hotkey-router.cjs +55 -6
- package/dist/hotkey-router.cjs.map +2 -2
- package/dist/hotkey-router.js +55 -6
- package/dist/hotkey-router.js.map +2 -2
- package/dist/hotkey-router.min.js +2 -2
- package/dist/reservations.js +1 -1
- package/dist/reservations.js.map +1 -1
- package/dist/sites.js +185 -0
- package/dist/sites.js.map +7 -0
- package/dist/types/auto.d.ts +2 -0
- package/dist/types/hotkey-router.d.ts +196 -0
- package/dist/types/reservations.d.ts +80 -0
- package/dist/types/sites.d.ts +109 -0
- package/dist/types/validate.d.ts +42 -0
- package/dist/validate.js +106 -0
- package/dist/validate.js.map +7 -0
- package/package.json +18 -7
- package/robots.txt +79 -79
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
declare namespace _default {
|
|
2
|
+
export { init };
|
|
3
|
+
export { destroy };
|
|
4
|
+
export { setTarget };
|
|
5
|
+
export { bind };
|
|
6
|
+
export { unbind };
|
|
7
|
+
export { registerPlugin };
|
|
8
|
+
export { unregisterPlugin };
|
|
9
|
+
export { onBind };
|
|
10
|
+
export { pause };
|
|
11
|
+
export { resume };
|
|
12
|
+
export { ignoreInput };
|
|
13
|
+
export { trigger };
|
|
14
|
+
export { parseHotkey };
|
|
15
|
+
export { comboFromEvent };
|
|
16
|
+
}
|
|
17
|
+
export default _default;
|
|
18
|
+
/**
|
|
19
|
+
* Handler invoked when a bound hotkey fires. Receives the underlying
|
|
20
|
+
* `KeyboardEvent` (real one from the DOM, or a synthetic one produced by
|
|
21
|
+
* {@link trigger}).
|
|
22
|
+
*/
|
|
23
|
+
export type HotkeyHandler = (e: KeyboardEvent) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Optional per-binding behavior modifiers. See the inline comment on
|
|
26
|
+
* `normalizeOptions` for the canonical list.
|
|
27
|
+
*/
|
|
28
|
+
export type BindOptions = {
|
|
29
|
+
preventDefault?: boolean;
|
|
30
|
+
stopPropagation?: boolean;
|
|
31
|
+
stopImmediatePropagation?: boolean;
|
|
32
|
+
repeat?: boolean;
|
|
33
|
+
once?: boolean;
|
|
34
|
+
when?: (e: KeyboardEvent) => boolean;
|
|
35
|
+
allowIn?: (e: KeyboardEvent) => boolean;
|
|
36
|
+
priority?: number;
|
|
37
|
+
capture?: boolean;
|
|
38
|
+
altGraph?: boolean;
|
|
39
|
+
composing?: boolean;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Return value of {@link bind}: callable to unbind, plus `id` (the unique
|
|
43
|
+
* binding id used internally) and `hotkey` (the original input string).
|
|
44
|
+
*/
|
|
45
|
+
export type BindOff = (() => boolean) & {
|
|
46
|
+
id: number;
|
|
47
|
+
hotkey: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Payload passed to subscribers registered via {@link onBind} after each
|
|
51
|
+
* successful bind.
|
|
52
|
+
*/
|
|
53
|
+
export type BindEvent = {
|
|
54
|
+
combo: ReturnType<typeof parseHotkey>["combo"];
|
|
55
|
+
raw: string;
|
|
56
|
+
options: BindOptions;
|
|
57
|
+
plugin: string | null;
|
|
58
|
+
id: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Plugin shape accepted by {@link registerPlugin}: a map from hotkey
|
|
62
|
+
* strings to either a handler or a `[handler, options]` tuple.
|
|
63
|
+
*/
|
|
64
|
+
export type HotkeyMap = Record<string, HotkeyHandler | [HotkeyHandler, BindOptions]>;
|
|
65
|
+
/**
|
|
66
|
+
* Initialize the router by attaching `keydown`/`keyup` listeners to a target.
|
|
67
|
+
* Re-initializes cleanly if already running (calls {@link destroy} first).
|
|
68
|
+
* Auto-runs on `window` at module-load time in browser environments.
|
|
69
|
+
*
|
|
70
|
+
* `capture: true` handles every binding in the capture phase. With the
|
|
71
|
+
* default (`false`), bindings run in the bubble phase except those bound
|
|
72
|
+
* with `{ capture: true }`, which a second, capture-phase listener handles:
|
|
73
|
+
* they see the key before the page's own listeners and can stop it.
|
|
74
|
+
*
|
|
75
|
+
* @param {{ target?: EventTarget, capture?: boolean }} [opts]
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
declare function init({ target, capture }?: {
|
|
79
|
+
target?: EventTarget;
|
|
80
|
+
capture?: boolean;
|
|
81
|
+
}): void;
|
|
82
|
+
/**
|
|
83
|
+
* Tear down listeners and clear all bindings + plugins. Bind-hook
|
|
84
|
+
* subscribers ({@link onBind}) deliberately survive — consumer
|
|
85
|
+
* extensions should install once and live across `init`/`destroy` cycles.
|
|
86
|
+
*
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
declare function destroy(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Replace the default target (used by {@link init} when no `target` is
|
|
92
|
+
* provided). Useful for iframes and tests. Does NOT auto-rebind existing
|
|
93
|
+
* listeners — call {@link destroy} + {@link init} to re-attach.
|
|
94
|
+
*
|
|
95
|
+
* @param {EventTarget | null} target
|
|
96
|
+
* @returns {void}
|
|
97
|
+
*/
|
|
98
|
+
declare function setTarget(target: EventTarget | null): void;
|
|
99
|
+
/**
|
|
100
|
+
* Bind a hotkey combo string to a handler.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} hotkeyStr e.g. `'ctrl+shift+k'`, `'meta+slash'`, `'alt+code:KeyX'`
|
|
103
|
+
* @param {HotkeyHandler} handler
|
|
104
|
+
* @param {string | null} [plugin] optional plugin/namespace tag for bulk cleanup
|
|
105
|
+
* @param {BindOptions} [options]
|
|
106
|
+
* @returns {BindOff} unbind function with `id` + `hotkey` props
|
|
107
|
+
*/
|
|
108
|
+
declare function bind(hotkeyStr: string, handler: HotkeyHandler, plugin?: string | null, options?: BindOptions): BindOff;
|
|
109
|
+
/**
|
|
110
|
+
* Remove bindings for a combo string. Omit `handler` to remove all bindings
|
|
111
|
+
* for the combo/type; pass a specific handler to remove just that one.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} hotkeyStr
|
|
114
|
+
* @param {HotkeyHandler} [handler]
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
declare function unbind(hotkeyStr: string, handler?: HotkeyHandler): void;
|
|
118
|
+
/**
|
|
119
|
+
* Register a named plugin that bulk-binds a {@link HotkeyMap}. All bindings
|
|
120
|
+
* are tagged with the plugin name so {@link unregisterPlugin} can clean them
|
|
121
|
+
* up together without touching other plugins' bindings.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} name plugin name (must be unique)
|
|
124
|
+
* @param {HotkeyMap} hotkeyMap
|
|
125
|
+
* @returns {() => void} unregister fn for this plugin
|
|
126
|
+
*/
|
|
127
|
+
declare function registerPlugin(name: string, hotkeyMap: HotkeyMap): () => void;
|
|
128
|
+
/**
|
|
129
|
+
* Remove all bindings owned by the named plugin. Silent no-op if no such
|
|
130
|
+
* plugin is registered.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} name
|
|
133
|
+
* @returns {void}
|
|
134
|
+
*/
|
|
135
|
+
declare function unregisterPlugin(name: string): void;
|
|
136
|
+
/**
|
|
137
|
+
* Subscribe to {@link bind} events. The hook is called after each successful
|
|
138
|
+
* bind with a {@link BindEvent} payload.
|
|
139
|
+
*
|
|
140
|
+
* @param {(info: BindEvent) => void} hook
|
|
141
|
+
* @returns {() => void} unsubscribe
|
|
142
|
+
*/
|
|
143
|
+
declare function onBind(hook: (info: BindEvent) => void): () => void;
|
|
144
|
+
/** Pause dispatch — bindings stay registered but no handlers fire. @returns {void} */
|
|
145
|
+
declare function pause(): void;
|
|
146
|
+
/** Resume dispatch after a {@link pause}. @returns {void} */
|
|
147
|
+
declare function resume(): void;
|
|
148
|
+
/**
|
|
149
|
+
* Toggle whether handlers fire when the event target is an editable element
|
|
150
|
+
* (input/textarea/contenteditable). Default `true`. Override per-binding via
|
|
151
|
+
* `options.allowIn`.
|
|
152
|
+
*
|
|
153
|
+
* @param {boolean} [value]
|
|
154
|
+
* @returns {void}
|
|
155
|
+
*/
|
|
156
|
+
declare function ignoreInput(value?: boolean): void;
|
|
157
|
+
/**
|
|
158
|
+
* Programmatically fire any bindings for `hotkeyStr`. Used by tests and
|
|
159
|
+
* automation; not a typical app-runtime path. Returns `true` if a binding
|
|
160
|
+
* was found and invoked, `false` otherwise.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} hotkeyStr
|
|
163
|
+
* @param {{ type?: 'keydown' | 'keyup' }} [opts]
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
166
|
+
declare function trigger(hotkeyStr: string, { type: forcedType }?: {
|
|
167
|
+
type?: "keydown" | "keyup";
|
|
168
|
+
}): boolean;
|
|
169
|
+
export function parseHotkey(hotkeyStr: any): {
|
|
170
|
+
combo: {
|
|
171
|
+
ctrl: boolean;
|
|
172
|
+
shift: boolean;
|
|
173
|
+
alt: boolean;
|
|
174
|
+
meta: boolean;
|
|
175
|
+
key: any;
|
|
176
|
+
code: any;
|
|
177
|
+
bareModifier: any;
|
|
178
|
+
};
|
|
179
|
+
type: string;
|
|
180
|
+
raw: any;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* The hotkey string for a keyboard event, for a "press your keys" recorder.
|
|
184
|
+
* Physical by default (`ctrl+shift+code:Period`), so the binding survives
|
|
185
|
+
* keyboard layouts. With `useMod`, the platform's primary modifier (Cmd on
|
|
186
|
+
* macOS, Ctrl elsewhere) is written as `mod`. Returns null for a bare
|
|
187
|
+
* modifier press (the user hasn't pressed the base key yet).
|
|
188
|
+
*
|
|
189
|
+
* @param {KeyboardEvent} e
|
|
190
|
+
* @param {{ physical?: boolean, useMod?: boolean }} [opts]
|
|
191
|
+
* @returns {string | null}
|
|
192
|
+
*/
|
|
193
|
+
export function comboFromEvent(e: KeyboardEvent, { physical, useMod }?: {
|
|
194
|
+
physical?: boolean;
|
|
195
|
+
useMod?: boolean;
|
|
196
|
+
}): string | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function detectPlatform(nav?: Navigator): "mac" | "windows" | "linux";
|
|
2
|
+
export function detectBrowser(nav?: Navigator): "edge" | "firefox" | "chrome" | "safari";
|
|
3
|
+
export function lookupKeyFromCombo(combo: any): string;
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {'mac' | 'windows' | 'linux'} Platform
|
|
6
|
+
* @typedef {'chrome' | 'firefox' | 'safari' | 'edge'} Browser
|
|
7
|
+
* @typedef {'hard' | 'hard-fullscreen' | 'soft' | 'soft-warn'} Severity
|
|
8
|
+
*
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* source: 'browser' | 'system',
|
|
11
|
+
* platform: Platform,
|
|
12
|
+
* browser: Browser | undefined,
|
|
13
|
+
* lookupKey: string,
|
|
14
|
+
* action: string,
|
|
15
|
+
* severity: Severity
|
|
16
|
+
* }} Reservation
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Look up whether a parsed hotkey combo is reserved by the host platform
|
|
20
|
+
* or browser. Returns `null` when not reserved or when the data table
|
|
21
|
+
* hasn't loaded.
|
|
22
|
+
*
|
|
23
|
+
* @param {object | null} combo parsed combo from `hotkey-router`'s `parseHotkey`
|
|
24
|
+
* @param {{ platform?: Platform, browser?: Browser }} [opts]
|
|
25
|
+
* @returns {Reservation | null}
|
|
26
|
+
*/
|
|
27
|
+
export function lookupReservation(combo: object | null, { platform, browser }?: {
|
|
28
|
+
platform?: Platform;
|
|
29
|
+
browser?: Browser;
|
|
30
|
+
}): Reservation | null;
|
|
31
|
+
/** @param {Severity | string} severity @returns {boolean} */
|
|
32
|
+
export function severityIsHard(severity: Severity | string): boolean;
|
|
33
|
+
/** @param {Severity | string} severity @returns {boolean} */
|
|
34
|
+
export function severityIsSoft(severity: Severity | string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Render a console-friendly warning string for a reservation.
|
|
37
|
+
*
|
|
38
|
+
* @param {Reservation} reservation
|
|
39
|
+
* @param {string} rawHotkey
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function formatReservationWarning(reservation: Reservation, rawHotkey: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Emit a reservation warning via the given console (or the global `console`
|
|
45
|
+
* by default). Hard reservations log via `warn`; soft ones via `info`.
|
|
46
|
+
*
|
|
47
|
+
* @param {Reservation} reservation
|
|
48
|
+
* @param {string} rawHotkey
|
|
49
|
+
* @param {Console} [console_]
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
export function emitReservationWarning(reservation: Reservation, rawHotkey: string, console_?: Console): void;
|
|
53
|
+
/**
|
|
54
|
+
* @param {object} hotkeys a hotkey-router default-export instance
|
|
55
|
+
* @param {{
|
|
56
|
+
* platform?: Platform,
|
|
57
|
+
* browser?: Browser,
|
|
58
|
+
* console?: Console
|
|
59
|
+
* }} [opts]
|
|
60
|
+
* @returns {() => void} uninstall function
|
|
61
|
+
*/
|
|
62
|
+
export function installReservationWarnings(hotkeys: object, opts?: {
|
|
63
|
+
platform?: Platform;
|
|
64
|
+
browser?: Browser;
|
|
65
|
+
console?: Console;
|
|
66
|
+
}): () => void;
|
|
67
|
+
export const SCHEMA_VERSION: any;
|
|
68
|
+
export const DATA_GENERATED: any;
|
|
69
|
+
export { HOTKEY_DATA };
|
|
70
|
+
export type Platform = "mac" | "windows" | "linux";
|
|
71
|
+
export type Browser = "chrome" | "firefox" | "safari" | "edge";
|
|
72
|
+
export type Severity = "hard" | "hard-fullscreen" | "soft" | "soft-warn";
|
|
73
|
+
export type Reservation = {
|
|
74
|
+
source: "browser" | "system";
|
|
75
|
+
platform: Platform;
|
|
76
|
+
browser: Browser | undefined;
|
|
77
|
+
lookupKey: string;
|
|
78
|
+
action: string;
|
|
79
|
+
severity: Severity;
|
|
80
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** @typedef {'mac' | 'windows' | 'linux' | 'chromeos'} SitePlatform */
|
|
2
|
+
/**
|
|
3
|
+
* @param {Navigator | null} [nav]
|
|
4
|
+
* @returns {SitePlatform | null}
|
|
5
|
+
*/
|
|
6
|
+
export function detectSitePlatform(nav?: Navigator | null): SitePlatform | null;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a hotkey string (`mod+shift+code:Period`, `ctrl+alt+,`) into modifier
|
|
9
|
+
* flags and a canonical base key name (`period`), resolving `mod` for the
|
|
10
|
+
* platform. Shared with validate.js.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} str
|
|
13
|
+
* @param {SitePlatform} platform
|
|
14
|
+
* @returns {{ ctrl: boolean, meta: boolean, alt: boolean, shift: boolean, base: string | null }}
|
|
15
|
+
*/
|
|
16
|
+
export function parseComboString(str: string, platform: SitePlatform): {
|
|
17
|
+
ctrl: boolean;
|
|
18
|
+
meta: boolean;
|
|
19
|
+
alt: boolean;
|
|
20
|
+
shift: boolean;
|
|
21
|
+
base: string | null;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The canonical lookup key for a combo on a platform (`ctrl+shift+period`).
|
|
25
|
+
* Accepts a hotkey string or a parsed combo from hotkey-router (`onBind`).
|
|
26
|
+
*
|
|
27
|
+
* @param {string | { ctrl?: boolean, meta?: boolean, alt?: boolean, shift?: boolean, key?: string, code?: string }} combo
|
|
28
|
+
* @param {SitePlatform} [platform]
|
|
29
|
+
* @returns {string | null}
|
|
30
|
+
*/
|
|
31
|
+
export function siteLookupKey(combo: string | {
|
|
32
|
+
ctrl?: boolean;
|
|
33
|
+
meta?: boolean;
|
|
34
|
+
alt?: boolean;
|
|
35
|
+
shift?: boolean;
|
|
36
|
+
key?: string;
|
|
37
|
+
code?: string;
|
|
38
|
+
}, platform?: SitePlatform): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* The researched sites whose hosts (and path prefixes, when listed) match a
|
|
41
|
+
* URL. Defaults to the current page.
|
|
42
|
+
*
|
|
43
|
+
* @param {string | URL} [url]
|
|
44
|
+
* @returns {Array<object>}
|
|
45
|
+
*/
|
|
46
|
+
export function matchSites(url?: string | URL): Array<object>;
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {{
|
|
49
|
+
* siteId: string,
|
|
50
|
+
* siteName: string,
|
|
51
|
+
* action: string,
|
|
52
|
+
* verified: boolean,
|
|
53
|
+
* lookupKey: string
|
|
54
|
+
* }} SiteConflict
|
|
55
|
+
*
|
|
56
|
+
* Sources for each row are in data/site-hotkeys.json (look it up by siteId).
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* The shortcuts on the page (or a given URL) that use the same chord.
|
|
60
|
+
*
|
|
61
|
+
* @param {string | object} combo hotkey string or parsed combo
|
|
62
|
+
* @param {{ url?: string | URL, platform?: SitePlatform, verifiedOnly?: boolean }} [opts]
|
|
63
|
+
* @returns {SiteConflict[]}
|
|
64
|
+
*/
|
|
65
|
+
export function lookupSiteConflicts(combo: string | object, { url, platform, verifiedOnly }?: {
|
|
66
|
+
url?: string | URL;
|
|
67
|
+
platform?: SitePlatform;
|
|
68
|
+
verifiedOnly?: boolean;
|
|
69
|
+
}): SiteConflict[];
|
|
70
|
+
/**
|
|
71
|
+
* A `when()` gate for a binding: true on sites that don't use the chord;
|
|
72
|
+
* on sites that do, true only while `engaged(e)` says the user is focused
|
|
73
|
+
* on the app. Conflicts are looked up once per page URL.
|
|
74
|
+
*
|
|
75
|
+
* @param {string | object} combo the binding's hotkey string (or parsed combo)
|
|
76
|
+
* @param {{
|
|
77
|
+
* engaged: (e?: KeyboardEvent) => boolean,
|
|
78
|
+
* url?: () => string,
|
|
79
|
+
* platform?: SitePlatform,
|
|
80
|
+
* verifiedOnly?: boolean,
|
|
81
|
+
* onYield?: (conflicts: SiteConflict[], e?: KeyboardEvent) => void
|
|
82
|
+
* }} opts
|
|
83
|
+
* @returns {(e?: KeyboardEvent) => boolean}
|
|
84
|
+
*/
|
|
85
|
+
export function siteAware(combo: string | object, { engaged, url, platform, verifiedOnly, onYield }?: {
|
|
86
|
+
engaged: (e?: KeyboardEvent) => boolean;
|
|
87
|
+
url?: () => string;
|
|
88
|
+
platform?: SitePlatform;
|
|
89
|
+
verifiedOnly?: boolean;
|
|
90
|
+
onYield?: (conflicts: SiteConflict[], e?: KeyboardEvent) => void;
|
|
91
|
+
}): (e?: KeyboardEvent) => boolean;
|
|
92
|
+
/**
|
|
93
|
+
* All researched sites, compact form: `{ id, name, hosts, pathPrefixes,
|
|
94
|
+
* shortcuts: [windows, mac, chromeos, action, verified][] }`.
|
|
95
|
+
*/
|
|
96
|
+
export function listSites(): any;
|
|
97
|
+
export const SITE_DATA_RESEARCHED: any;
|
|
98
|
+
export const SITE_SCHEMA_VERSION: any;
|
|
99
|
+
export type SitePlatform = "mac" | "windows" | "linux" | "chromeos";
|
|
100
|
+
/**
|
|
101
|
+
* Sources for each row are in data/site-hotkeys.json (look it up by siteId).
|
|
102
|
+
*/
|
|
103
|
+
export type SiteConflict = {
|
|
104
|
+
siteId: string;
|
|
105
|
+
siteName: string;
|
|
106
|
+
action: string;
|
|
107
|
+
verified: boolean;
|
|
108
|
+
lookupKey: string;
|
|
109
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* code: 'invalid' | 'no-key' | 'no-modifier' | 'alt-only' | 'needs-primary' |
|
|
4
|
+
* 'too-few-modifiers' | 'taken' | 'reserved' | 'reserved-soft' |
|
|
5
|
+
* 'altgr' | 'site-conflict',
|
|
6
|
+
* severity: 'error' | 'warning',
|
|
7
|
+
* message: string,
|
|
8
|
+
* platforms?: string[],
|
|
9
|
+
* details?: object
|
|
10
|
+
* }} ComboReason
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} combo hotkey string, e.g. `mod+shift+code:Period`
|
|
14
|
+
* @param {{
|
|
15
|
+
* platforms?: Array<'mac' | 'windows' | 'linux'>,
|
|
16
|
+
* browsers?: Array<'chrome' | 'firefox' | 'edge' | 'safari'>,
|
|
17
|
+
* requirePrimary?: boolean, // must include Ctrl (Cmd on macOS)
|
|
18
|
+
* minModifiers?: number, // default 1
|
|
19
|
+
* taken?: string[], // combos the app already uses
|
|
20
|
+
* sites?: boolean, // warn about sites that use the keys (default true)
|
|
21
|
+
* }} [opts]
|
|
22
|
+
* @returns {{ ok: boolean, reasons: ComboReason[], lookupKeys: Record<string, string> }}
|
|
23
|
+
*/
|
|
24
|
+
export function validateCombo(combo: string, { platforms, browsers, requirePrimary, minModifiers, taken, sites, }?: {
|
|
25
|
+
platforms?: Array<"mac" | "windows" | "linux">;
|
|
26
|
+
browsers?: Array<"chrome" | "firefox" | "edge" | "safari">;
|
|
27
|
+
requirePrimary?: boolean;
|
|
28
|
+
minModifiers?: number;
|
|
29
|
+
taken?: string[];
|
|
30
|
+
sites?: boolean;
|
|
31
|
+
}): {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
reasons: ComboReason[];
|
|
34
|
+
lookupKeys: Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
export type ComboReason = {
|
|
37
|
+
code: "invalid" | "no-key" | "no-modifier" | "alt-only" | "needs-primary" | "too-few-modifiers" | "taken" | "reserved" | "reserved-soft" | "altgr" | "site-conflict";
|
|
38
|
+
severity: "error" | "warning";
|
|
39
|
+
message: string;
|
|
40
|
+
platforms?: string[];
|
|
41
|
+
details?: object;
|
|
42
|
+
};
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// hotkey-router v0.3.0 | SEE LICENSE IN LICENSE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// validate.js
|
|
5
|
+
import { lookupReservation, severityIsHard } from "./reservations.js";
|
|
6
|
+
import { parseComboString, listSites } from "./sites.js";
|
|
7
|
+
var ALL_PLATFORMS = ["mac", "windows", "linux"];
|
|
8
|
+
var ALL_BROWSERS = ["chrome", "firefox", "edge", "safari"];
|
|
9
|
+
var PLATFORM_LABEL = { mac: "macOS", windows: "Windows", linux: "Linux" };
|
|
10
|
+
var BROWSER_LABEL = { chrome: "Chrome", firefox: "Firefox", edge: "Edge", safari: "Safari" };
|
|
11
|
+
var SITE_COL = { windows: 0, mac: 1, linux: 0 };
|
|
12
|
+
function validateCombo(combo, {
|
|
13
|
+
platforms = ALL_PLATFORMS,
|
|
14
|
+
browsers = ALL_BROWSERS,
|
|
15
|
+
requirePrimary = false,
|
|
16
|
+
minModifiers = 1,
|
|
17
|
+
taken = [],
|
|
18
|
+
sites = true
|
|
19
|
+
} = {}) {
|
|
20
|
+
const reasons = [];
|
|
21
|
+
const lookupKeys = {};
|
|
22
|
+
const add = (reason) => {
|
|
23
|
+
const same = reasons.find((r) => r.code === reason.code && r.message === reason.message);
|
|
24
|
+
if (same) {
|
|
25
|
+
for (const p of reason.platforms || []) if (!same.platforms?.includes(p)) same.platforms = [...same.platforms || [], p];
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
reasons.push(reason);
|
|
29
|
+
};
|
|
30
|
+
if (typeof combo !== "string" || !combo.trim()) {
|
|
31
|
+
return { ok: false, reasons: [{ code: "invalid", severity: "error", message: "No keys were recorded." }], lookupKeys };
|
|
32
|
+
}
|
|
33
|
+
for (const platform of platforms) {
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = parseComboString(combo, platform);
|
|
37
|
+
} catch {
|
|
38
|
+
parsed = null;
|
|
39
|
+
}
|
|
40
|
+
if (!parsed) {
|
|
41
|
+
add({ code: "invalid", severity: "error", message: "These keys could not be read.", platforms: [platform] });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!parsed.base) {
|
|
45
|
+
add({ code: "no-key", severity: "error", message: "Add a key to the modifiers, for example a letter or punctuation key.", platforms: [platform] });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const mods = ["ctrl", "meta", "alt", "shift"].filter((m) => parsed[m]);
|
|
49
|
+
const key = [...mods, parsed.base].join("+");
|
|
50
|
+
lookupKeys[platform] = key;
|
|
51
|
+
if (mods.length === 0) {
|
|
52
|
+
add({ code: "no-modifier", severity: "error", message: "Use at least one modifier (Ctrl, Alt, Shift or Cmd), or the key would fire while typing.", platforms: [platform] });
|
|
53
|
+
}
|
|
54
|
+
if (platform !== "mac" && mods.length === 1 && parsed.alt) {
|
|
55
|
+
add({ code: "alt-only", severity: "error", message: "Alt on its own moves focus to the browser menu when released. Add Ctrl or Shift.", platforms: [platform] });
|
|
56
|
+
}
|
|
57
|
+
const primary = platform === "mac" ? parsed.meta : parsed.ctrl;
|
|
58
|
+
if (requirePrimary && !primary) {
|
|
59
|
+
add({ code: "needs-primary", severity: "error", message: `Include ${platform === "mac" ? "Cmd" : "Ctrl"}.`, platforms: [platform] });
|
|
60
|
+
}
|
|
61
|
+
if (mods.length > 0 && mods.length < minModifiers) {
|
|
62
|
+
add({ code: "too-few-modifiers", severity: "error", message: `Use at least ${minModifiers} modifiers together.`, platforms: [platform] });
|
|
63
|
+
}
|
|
64
|
+
for (const other of taken) {
|
|
65
|
+
let otherKey = null;
|
|
66
|
+
try {
|
|
67
|
+
const o = parseComboString(other, platform);
|
|
68
|
+
if (o?.base) otherKey = [...["ctrl", "meta", "alt", "shift"].filter((m) => o[m]), o.base].join("+");
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
if (otherKey === key) {
|
|
72
|
+
add({ code: "taken", severity: "error", message: "These keys are already used for something else here.", platforms: [platform], details: { combo: other } });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const shape = { ctrl: parsed.ctrl, meta: parsed.meta, alt: parsed.alt, shift: parsed.shift, key: parsed.base, code: null, bareModifier: null };
|
|
76
|
+
for (const browser of browsers) {
|
|
77
|
+
if (platform !== "mac" && browser === "safari") continue;
|
|
78
|
+
const hit = lookupReservation(shape, { platform, browser });
|
|
79
|
+
if (!hit) continue;
|
|
80
|
+
const where = hit.source === "system" ? PLATFORM_LABEL[platform] : `${BROWSER_LABEL[browser]} on ${PLATFORM_LABEL[platform]}`;
|
|
81
|
+
if (severityIsHard(hit.severity)) {
|
|
82
|
+
add({ code: "reserved", severity: "error", message: `${where} uses these keys (${hit.action}), so a page never sees them.`, platforms: [platform], details: { browser, action: hit.action } });
|
|
83
|
+
} else {
|
|
84
|
+
add({ code: "reserved-soft", severity: "warning", message: `${where} uses these keys in some situations (${hit.action}).`, platforms: [platform], details: { browser, action: hit.action, severity: hit.severity } });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (platform === "windows" && parsed.ctrl && parsed.alt && !parsed.meta) {
|
|
88
|
+
add({ code: "altgr", severity: "warning", message: "On some keyboard layouts Ctrl+Alt acts as AltGr and types characters; those keystrokes are skipped.", platforms: [platform] });
|
|
89
|
+
}
|
|
90
|
+
if (sites) {
|
|
91
|
+
const col = SITE_COL[platform];
|
|
92
|
+
const names = [];
|
|
93
|
+
for (const site of listSites()) {
|
|
94
|
+
if (site.shortcuts.some((row) => row[col] === key)) names.push(site.name);
|
|
95
|
+
}
|
|
96
|
+
if (names.length) {
|
|
97
|
+
add({ code: "site-conflict", severity: "warning", message: `Also used by ${names.join(", ")}.`, platforms: [platform], details: { sites: names } });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { ok: !reasons.some((r) => r.severity === "error"), reasons, lookupKeys };
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
validateCombo
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../validate.js"],
|
|
4
|
+
"sourcesContent": ["// validate.js\n// Check a hotkey before an app saves it, e.g. from a \"press your keys\"\n// recorder. Returns plain-language reasons the app can show as they are.\n//\n// import { comboFromEvent } from 'hotkey-router'\n// import { validateCombo } from 'hotkey-router/validate'\n//\n// const combo = comboFromEvent(e, { useMod: true }) // 'mod+shift+code:Period'\n// const { ok, reasons } = validateCombo(combo, { requirePrimary: true, minModifiers: 2 })\n//\n// Errors make `ok` false (the combo would not work, or breaks a rule the\n// app asked for). Warnings leave `ok` true (it works, with a caveat worth\n// telling the user: AltGr on some layouts, sites that use the same keys).\n//\n// Data: data/browser-hotkeys.json (keys the browser or OS take before the\n// page sees them) and data/site-hotkeys.runtime.json (keys popular sites use).\n\nimport { lookupReservation, severityIsHard } from './reservations.js'\nimport { parseComboString, listSites } from './sites.js'\n\nconst ALL_PLATFORMS = ['mac', 'windows', 'linux']\nconst ALL_BROWSERS = ['chrome', 'firefox', 'edge', 'safari']\nconst PLATFORM_LABEL = { mac: 'macOS', windows: 'Windows', linux: 'Linux' }\nconst BROWSER_LABEL = { chrome: 'Chrome', firefox: 'Firefox', edge: 'Edge', safari: 'Safari' }\n// Runtime site rows: [windows, mac, chromeos, action, verified]\nconst SITE_COL = { windows: 0, mac: 1, linux: 0 }\n\n/**\n * @typedef {{\n * code: 'invalid' | 'no-key' | 'no-modifier' | 'alt-only' | 'needs-primary' |\n * 'too-few-modifiers' | 'taken' | 'reserved' | 'reserved-soft' |\n * 'altgr' | 'site-conflict',\n * severity: 'error' | 'warning',\n * message: string,\n * platforms?: string[],\n * details?: object\n * }} ComboReason\n */\n\n/**\n * @param {string} combo hotkey string, e.g. `mod+shift+code:Period`\n * @param {{\n * platforms?: Array<'mac' | 'windows' | 'linux'>,\n * browsers?: Array<'chrome' | 'firefox' | 'edge' | 'safari'>,\n * requirePrimary?: boolean, // must include Ctrl (Cmd on macOS)\n * minModifiers?: number, // default 1\n * taken?: string[], // combos the app already uses\n * sites?: boolean, // warn about sites that use the keys (default true)\n * }} [opts]\n * @returns {{ ok: boolean, reasons: ComboReason[], lookupKeys: Record<string, string> }}\n */\nexport function validateCombo(combo, {\n platforms = ALL_PLATFORMS,\n browsers = ALL_BROWSERS,\n requirePrimary = false,\n minModifiers = 1,\n taken = [],\n sites = true,\n} = {}) {\n /** @type {ComboReason[]} */\n const reasons = []\n const lookupKeys = {}\n const add = (reason) => {\n const same = reasons.find((r) => r.code === reason.code && r.message === reason.message)\n if (same) {\n for (const p of reason.platforms || []) if (!same.platforms?.includes(p)) same.platforms = [...(same.platforms || []), p]\n return\n }\n reasons.push(reason)\n }\n\n if (typeof combo !== 'string' || !combo.trim()) {\n return { ok: false, reasons: [{ code: 'invalid', severity: 'error', message: 'No keys were recorded.' }], lookupKeys }\n }\n\n for (const platform of platforms) {\n let parsed\n try {\n parsed = parseComboString(combo, platform)\n } catch {\n parsed = null\n }\n if (!parsed) {\n add({ code: 'invalid', severity: 'error', message: 'These keys could not be read.', platforms: [platform] })\n continue\n }\n if (!parsed.base) {\n add({ code: 'no-key', severity: 'error', message: 'Add a key to the modifiers, for example a letter or punctuation key.', platforms: [platform] })\n continue\n }\n const mods = ['ctrl', 'meta', 'alt', 'shift'].filter((m) => parsed[m])\n const key = [...mods, parsed.base].join('+')\n lookupKeys[platform] = key\n\n if (mods.length === 0) {\n add({ code: 'no-modifier', severity: 'error', message: 'Use at least one modifier (Ctrl, Alt, Shift or Cmd), or the key would fire while typing.', platforms: [platform] })\n }\n if (platform !== 'mac' && mods.length === 1 && parsed.alt) {\n add({ code: 'alt-only', severity: 'error', message: 'Alt on its own moves focus to the browser menu when released. Add Ctrl or Shift.', platforms: [platform] })\n }\n const primary = platform === 'mac' ? parsed.meta : parsed.ctrl\n if (requirePrimary && !primary) {\n add({ code: 'needs-primary', severity: 'error', message: `Include ${platform === 'mac' ? 'Cmd' : 'Ctrl'}.`, platforms: [platform] })\n }\n if (mods.length > 0 && mods.length < minModifiers) {\n add({ code: 'too-few-modifiers', severity: 'error', message: `Use at least ${minModifiers} modifiers together.`, platforms: [platform] })\n }\n\n for (const other of taken) {\n let otherKey = null\n try {\n const o = parseComboString(other, platform)\n if (o?.base) otherKey = [...['ctrl', 'meta', 'alt', 'shift'].filter((m) => o[m]), o.base].join('+')\n } catch { /* ignore unreadable entries */ }\n if (otherKey === key) {\n add({ code: 'taken', severity: 'error', message: 'These keys are already used for something else here.', platforms: [platform], details: { combo: other } })\n }\n }\n\n const shape = { ctrl: parsed.ctrl, meta: parsed.meta, alt: parsed.alt, shift: parsed.shift, key: parsed.base, code: null, bareModifier: null }\n for (const browser of browsers) {\n if (platform !== 'mac' && browser === 'safari') continue\n const hit = lookupReservation(shape, { platform, browser })\n if (!hit) continue\n const where = hit.source === 'system' ? PLATFORM_LABEL[platform] : `${BROWSER_LABEL[browser]} on ${PLATFORM_LABEL[platform]}`\n if (severityIsHard(hit.severity)) {\n add({ code: 'reserved', severity: 'error', message: `${where} uses these keys (${hit.action}), so a page never sees them.`, platforms: [platform], details: { browser, action: hit.action } })\n } else {\n add({ code: 'reserved-soft', severity: 'warning', message: `${where} uses these keys in some situations (${hit.action}).`, platforms: [platform], details: { browser, action: hit.action, severity: hit.severity } })\n }\n }\n\n if (platform === 'windows' && parsed.ctrl && parsed.alt && !parsed.meta) {\n add({ code: 'altgr', severity: 'warning', message: 'On some keyboard layouts Ctrl+Alt acts as AltGr and types characters; those keystrokes are skipped.', platforms: [platform] })\n }\n\n if (sites) {\n const col = SITE_COL[platform]\n const names = []\n for (const site of listSites()) {\n if (site.shortcuts.some((row) => row[col] === key)) names.push(site.name)\n }\n if (names.length) {\n add({ code: 'site-conflict', severity: 'warning', message: `Also used by ${names.join(', ')}.`, platforms: [platform], details: { sites: names } })\n }\n }\n }\n\n return { ok: !reasons.some((r) => r.severity === 'error'), reasons, lookupKeys }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hotkey-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A tiny, deterministic keyboard routing engine for modern web apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "WATT3D <WATT3D@protonmail.com>",
|
|
@@ -29,7 +29,17 @@
|
|
|
29
29
|
"types": "./dist/types/reservations.d.ts",
|
|
30
30
|
"import": "./dist/reservations.js"
|
|
31
31
|
},
|
|
32
|
-
"./
|
|
32
|
+
"./sites": {
|
|
33
|
+
"types": "./dist/types/sites.d.ts",
|
|
34
|
+
"import": "./dist/sites.js"
|
|
35
|
+
},
|
|
36
|
+
"./validate": {
|
|
37
|
+
"types": "./dist/types/validate.d.ts",
|
|
38
|
+
"import": "./dist/validate.js"
|
|
39
|
+
},
|
|
40
|
+
"./data/browser-hotkeys.json": "./data/browser-hotkeys.json",
|
|
41
|
+
"./data/site-hotkeys.json": "./data/site-hotkeys.json",
|
|
42
|
+
"./data/site-hotkeys.runtime.json": "./data/site-hotkeys.runtime.json"
|
|
33
43
|
},
|
|
34
44
|
"sideEffects": false,
|
|
35
45
|
"files": [
|
|
@@ -43,21 +53,22 @@
|
|
|
43
53
|
"scripts": {
|
|
44
54
|
"build": "node build.js",
|
|
45
55
|
"build:types": "tsc -p tsconfig.types.json",
|
|
46
|
-
"prepare": "node build.js",
|
|
47
|
-
"prepublishOnly": "
|
|
56
|
+
"prepare": "node build.js && npm run build:types",
|
|
57
|
+
"prepublishOnly": "npm test",
|
|
48
58
|
"test": "vitest run --environment jsdom",
|
|
49
59
|
"test:watch": "vitest --environment jsdom",
|
|
50
60
|
"scrape:diff": "node scripts/scrape-reservations.mjs",
|
|
51
|
-
"scrape:apply": "node scripts/scrape-reservations.mjs --apply"
|
|
61
|
+
"scrape:apply": "node scripts/scrape-reservations.mjs --apply",
|
|
62
|
+
"sites:build": "node scripts/build-site-hotkeys.cjs"
|
|
52
63
|
},
|
|
53
64
|
"devDependencies": {
|
|
54
|
-
"esbuild": "^0.
|
|
65
|
+
"esbuild": "^0.28.1",
|
|
55
66
|
"jsdom": "^24.0.0",
|
|
56
67
|
"typescript": "^5.9.3",
|
|
57
68
|
"vitest": "^3.1.2"
|
|
58
69
|
},
|
|
59
70
|
"engines": {
|
|
60
|
-
"node": ">=
|
|
71
|
+
"node": ">=18"
|
|
61
72
|
},
|
|
62
73
|
"repository": {
|
|
63
74
|
"type": "git",
|