@xavescor/reatom-hotkey 1.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/README.md +36 -0
- package/__compiled__/cjs/src/compile.d.ts +3 -0
- package/__compiled__/cjs/src/compile.js +17 -0
- package/__compiled__/cjs/src/compile.js.map +1 -0
- package/__compiled__/cjs/src/index.d.ts +2 -0
- package/__compiled__/cjs/src/index.js +33 -0
- package/__compiled__/cjs/src/index.js.map +1 -0
- package/__compiled__/cjs/src/parse.d.ts +11 -0
- package/__compiled__/cjs/src/parse.js +136 -0
- package/__compiled__/cjs/src/parse.js.map +1 -0
- package/__compiled__/esm/src/compile.d.mts +3 -0
- package/__compiled__/esm/src/compile.mjs +17 -0
- package/__compiled__/esm/src/compile.mjs.map +1 -0
- package/__compiled__/esm/src/index.d.mts +2 -0
- package/__compiled__/esm/src/index.mjs +33 -0
- package/__compiled__/esm/src/index.mjs.map +1 -0
- package/__compiled__/esm/src/parse.d.mts +11 -0
- package/__compiled__/esm/src/parse.mjs +136 -0
- package/__compiled__/esm/src/parse.mjs.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.mjs +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @xavescor/reatom-hotkey
|
|
2
|
+
|
|
3
|
+
Physical keyboard shortcuts represented as lazy Reatom actions.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { reatomHotkey } from '@xavescor/reatom-hotkey'
|
|
7
|
+
|
|
8
|
+
const save = reatomHotkey('ctrl+s') // Action<[event: KeyboardEvent], KeyboardEvent>
|
|
9
|
+
|
|
10
|
+
const unsubscribe = save.subscribe((event) => {
|
|
11
|
+
console.log(event.code)
|
|
12
|
+
})
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The listeners are attached when the action gets its first subscriber and are
|
|
16
|
+
removed after its last subscriber disconnects.
|
|
17
|
+
|
|
18
|
+
## Syntax
|
|
19
|
+
|
|
20
|
+
Letters and digits refer to physical keyboard codes:
|
|
21
|
+
|
|
22
|
+
- `a` matches `KeyboardEvent.code === 'KeyA'`
|
|
23
|
+
- `1` matches `KeyboardEvent.code === 'Digit1'`
|
|
24
|
+
- `shift+a` requires exactly Shift and `KeyA`
|
|
25
|
+
- `a+b` matches an order-independent chord
|
|
26
|
+
- `ctrl+a+b` combines modifiers with a chord
|
|
27
|
+
|
|
28
|
+
Supported modifiers are `alt`, `ctrl` (or `control`), `meta`, and `shift`.
|
|
29
|
+
Named codes such as `escape`, `arrowleft`, `f12`, `slash`, and `numpad1` are
|
|
30
|
+
also supported. Named tokens are case-insensitive, but single-letter shortcuts
|
|
31
|
+
must be lowercase. Declarations must contain ASCII characters only. Invalid
|
|
32
|
+
declarations throw a `TypeError` immediately.
|
|
33
|
+
|
|
34
|
+
The action fires on `keydown` and returns the original `KeyboardEvent` as its
|
|
35
|
+
payload. Repeated keydown events are not filtered, editable elements are not
|
|
36
|
+
ignored, and the event's default behavior is not prevented.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const compile = (entries) => {
|
|
4
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
5
|
+
const codes = /* @__PURE__ */ new Set();
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
if (entry.type === "modifier") {
|
|
8
|
+
modifiers.add(entry.modifier);
|
|
9
|
+
} else {
|
|
10
|
+
codes.add(entry.code);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const requiredCodes = [...codes];
|
|
14
|
+
return (event, pressedCodes) => codes.has(event.code) && event.altKey === modifiers.has("alt") && event.ctrlKey === modifiers.has("ctrl") && event.metaKey === modifiers.has("meta") && event.shiftKey === modifiers.has("shift") && requiredCodes.every((code) => pressedCodes.has(code));
|
|
15
|
+
};
|
|
16
|
+
exports.compile = compile;
|
|
17
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sources":["../../../../src/compile.ts"],"sourcesContent":["import type { Entry, Modifier } from './parse'\n\nexport type Matcher = (\n event: KeyboardEvent,\n pressedCodes: ReadonlySet<string>,\n) => boolean\n\nexport const compile = (entries: readonly Entry[]): Matcher => {\n const modifiers = new Set<Modifier>()\n const codes = new Set<string>()\n\n for (const entry of entries) {\n if (entry.type === 'modifier') {\n modifiers.add(entry.modifier)\n } else {\n codes.add(entry.code)\n }\n }\n\n const requiredCodes = [...codes]\n\n return (event, pressedCodes) =>\n codes.has(event.code) &&\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift') &&\n requiredCodes.every((code) => pressedCodes.has(code))\n}\n"],"names":[],"mappings":";;AAOO,MAAM,UAAU,CAAC,YAAuC;AAC7D,QAAM,gCAAgB,IAAA;AACtB,QAAM,4BAAY,IAAA;AAElB,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,SAAS,YAAY;AAC7B,gBAAU,IAAI,MAAM,QAAQ;AAAA,IAC9B,OAAO;AACL,YAAM,IAAI,MAAM,IAAI;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,gBAAgB,CAAC,GAAG,KAAK;AAE/B,SAAO,CAAC,OAAO,iBACb,MAAM,IAAI,MAAM,IAAI,KACpB,MAAM,WAAW,UAAU,IAAI,KAAK,KACpC,MAAM,YAAY,UAAU,IAAI,MAAM,KACtC,MAAM,YAAY,UAAU,IAAI,MAAM,KACtC,MAAM,aAAa,UAAU,IAAI,OAAO,KACxC,cAAc,MAAM,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC;AACxD;;"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const core = require("@reatom/core");
|
|
4
|
+
const compile = require("./compile.js");
|
|
5
|
+
const parse = require("./parse.js");
|
|
6
|
+
const reatomHotkey = (hotkey) => {
|
|
7
|
+
const match = compile.compile(parse.parse(hotkey));
|
|
8
|
+
const hotkeyAction = core.action(
|
|
9
|
+
(event) => event,
|
|
10
|
+
`hotkey.${hotkey.trim().toLowerCase()}`
|
|
11
|
+
);
|
|
12
|
+
return hotkeyAction.extend(
|
|
13
|
+
core.withConnectHook(() => {
|
|
14
|
+
if (typeof document === "undefined") return;
|
|
15
|
+
const pressedCodes = /* @__PURE__ */ new Set();
|
|
16
|
+
core.onEvent(document, "keydown", (event) => {
|
|
17
|
+
pressedCodes.add(event.code);
|
|
18
|
+
if (match(event, pressedCodes)) {
|
|
19
|
+
hotkeyAction(event);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
core.onEvent(document, "keyup", (event) => {
|
|
23
|
+
pressedCodes.delete(event.code);
|
|
24
|
+
});
|
|
25
|
+
core.onEvent(document, "visibilitychange", () => pressedCodes.clear());
|
|
26
|
+
if (document.defaultView !== null) {
|
|
27
|
+
core.onEvent(document.defaultView, "blur", () => pressedCodes.clear());
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
exports.reatomHotkey = reatomHotkey;
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/index.ts"],"sourcesContent":["import { action, onEvent, withConnectHook } from '@reatom/core'\n\nimport { compile } from './compile'\nimport { parse } from './parse'\n\n/** Creates a lazily connected action for a physical keyboard shortcut. */\nexport const reatomHotkey = (hotkey: string) => {\n const match = compile(parse(hotkey))\n const hotkeyAction = action(\n (event: KeyboardEvent) => event,\n `hotkey.${hotkey.trim().toLowerCase()}`,\n )\n\n return hotkeyAction.extend(\n withConnectHook(() => {\n if (typeof document === 'undefined') return\n\n const pressedCodes = new Set<string>()\n\n onEvent<KeyboardEvent>(document, 'keydown', (event) => {\n pressedCodes.add(event.code)\n\n if (match(event, pressedCodes)) {\n hotkeyAction(event)\n }\n })\n onEvent<KeyboardEvent>(document, 'keyup', (event) => {\n pressedCodes.delete(event.code)\n })\n onEvent(document, 'visibilitychange', () => pressedCodes.clear())\n\n if (document.defaultView !== null) {\n onEvent(document.defaultView, 'blur', () => pressedCodes.clear())\n }\n }),\n )\n}\n"],"names":["compile","parse","action","withConnectHook","onEvent"],"mappings":";;;;;AAMO,MAAM,eAAe,CAAC,WAAmB;AAC9C,QAAM,QAAQA,QAAAA,QAAQC,MAAAA,MAAM,MAAM,CAAC;AACnC,QAAM,eAAeC,KAAAA;AAAAA,IACnB,CAAC,UAAyB;AAAA,IAC1B,UAAU,OAAO,KAAA,EAAO,aAAa;AAAA,EAAA;AAGvC,SAAO,aAAa;AAAA,IAClBC,KAAAA,gBAAgB,MAAM;AACpB,UAAI,OAAO,aAAa,YAAa;AAErC,YAAM,mCAAmB,IAAA;AAEzBC,WAAAA,QAAuB,UAAU,WAAW,CAAC,UAAU;AACrD,qBAAa,IAAI,MAAM,IAAI;AAE3B,YAAI,MAAM,OAAO,YAAY,GAAG;AAC9B,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AACDA,WAAAA,QAAuB,UAAU,SAAS,CAAC,UAAU;AACnD,qBAAa,OAAO,MAAM,IAAI;AAAA,MAChC,CAAC;AACDA,WAAAA,QAAQ,UAAU,oBAAoB,MAAM,aAAa,OAAO;AAEhE,UAAI,SAAS,gBAAgB,MAAM;AACjCA,aAAAA,QAAQ,SAAS,aAAa,QAAQ,MAAM,aAAa,OAAO;AAAA,MAClE;AAAA,IACF,CAAC;AAAA,EAAA;AAEL;;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Modifier = 'alt' | 'ctrl' | 'meta' | 'shift';
|
|
2
|
+
export type ModifierEntry = {
|
|
3
|
+
type: 'modifier';
|
|
4
|
+
modifier: Modifier;
|
|
5
|
+
};
|
|
6
|
+
export type KeyEntry = {
|
|
7
|
+
type: 'key';
|
|
8
|
+
code: KeyboardEvent['code'];
|
|
9
|
+
};
|
|
10
|
+
export type Entry = ModifierEntry | KeyEntry;
|
|
11
|
+
export declare const parse: (input: string) => readonly Entry[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const modifierAliases = {
|
|
4
|
+
alt: "alt",
|
|
5
|
+
control: "ctrl",
|
|
6
|
+
ctrl: "ctrl",
|
|
7
|
+
meta: "meta",
|
|
8
|
+
shift: "shift"
|
|
9
|
+
};
|
|
10
|
+
const namedCodes = [
|
|
11
|
+
"ArrowDown",
|
|
12
|
+
"ArrowLeft",
|
|
13
|
+
"ArrowRight",
|
|
14
|
+
"ArrowUp",
|
|
15
|
+
"Backquote",
|
|
16
|
+
"Backslash",
|
|
17
|
+
"Backspace",
|
|
18
|
+
"BracketLeft",
|
|
19
|
+
"BracketRight",
|
|
20
|
+
"CapsLock",
|
|
21
|
+
"Comma",
|
|
22
|
+
"ContextMenu",
|
|
23
|
+
"Delete",
|
|
24
|
+
"End",
|
|
25
|
+
"Enter",
|
|
26
|
+
"Equal",
|
|
27
|
+
"Escape",
|
|
28
|
+
"Home",
|
|
29
|
+
"Insert",
|
|
30
|
+
"IntlBackslash",
|
|
31
|
+
"IntlRo",
|
|
32
|
+
"IntlYen",
|
|
33
|
+
"Minus",
|
|
34
|
+
"NumLock",
|
|
35
|
+
"PageDown",
|
|
36
|
+
"PageUp",
|
|
37
|
+
"Pause",
|
|
38
|
+
"Period",
|
|
39
|
+
"PrintScreen",
|
|
40
|
+
"Quote",
|
|
41
|
+
"ScrollLock",
|
|
42
|
+
"Semicolon",
|
|
43
|
+
"Slash",
|
|
44
|
+
"Space",
|
|
45
|
+
"Tab"
|
|
46
|
+
];
|
|
47
|
+
const codeByToken = new Map(
|
|
48
|
+
namedCodes.map((code) => [code.toLowerCase(), code])
|
|
49
|
+
);
|
|
50
|
+
for (let index = 1; index <= 24; index++) {
|
|
51
|
+
codeByToken.set(`f${index}`, `F${index}`);
|
|
52
|
+
}
|
|
53
|
+
for (let index = 0; index <= 9; index++) {
|
|
54
|
+
codeByToken.set(`digit${index}`, `Digit${index}`);
|
|
55
|
+
codeByToken.set(`numpad${index}`, `Numpad${index}`);
|
|
56
|
+
}
|
|
57
|
+
for (let index = 0; index < 26; index++) {
|
|
58
|
+
const letter = String.fromCharCode(97 + index);
|
|
59
|
+
codeByToken.set(letter, `Key${letter.toUpperCase()}`);
|
|
60
|
+
codeByToken.set(`key${letter}`, `Key${letter.toUpperCase()}`);
|
|
61
|
+
}
|
|
62
|
+
const codeAliases = {
|
|
63
|
+
down: "ArrowDown",
|
|
64
|
+
esc: "Escape",
|
|
65
|
+
left: "ArrowLeft",
|
|
66
|
+
return: "Enter",
|
|
67
|
+
right: "ArrowRight",
|
|
68
|
+
up: "ArrowUp"
|
|
69
|
+
};
|
|
70
|
+
for (const [alias, code] of Object.entries(codeAliases)) {
|
|
71
|
+
codeByToken.set(alias, code);
|
|
72
|
+
}
|
|
73
|
+
const punctuationCodes = {
|
|
74
|
+
",": "Comma",
|
|
75
|
+
"-": "Minus",
|
|
76
|
+
".": "Period",
|
|
77
|
+
"/": "Slash",
|
|
78
|
+
";": "Semicolon",
|
|
79
|
+
"=": "Equal",
|
|
80
|
+
"[": "BracketLeft",
|
|
81
|
+
"\\": "Backslash",
|
|
82
|
+
"]": "BracketRight",
|
|
83
|
+
"`": "Backquote",
|
|
84
|
+
"'": "Quote"
|
|
85
|
+
};
|
|
86
|
+
for (const [key, code] of Object.entries(punctuationCodes)) {
|
|
87
|
+
codeByToken.set(key, code);
|
|
88
|
+
}
|
|
89
|
+
const parse = (input) => {
|
|
90
|
+
if ([...input].some((character) => character.charCodeAt(0) > 127)) {
|
|
91
|
+
throw new TypeError("Hotkey must contain ASCII characters only");
|
|
92
|
+
}
|
|
93
|
+
const normalized = input.trim();
|
|
94
|
+
if (normalized === "") {
|
|
95
|
+
throw new TypeError("Hotkey must not be empty");
|
|
96
|
+
}
|
|
97
|
+
const entries = [];
|
|
98
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
99
|
+
const codes = /* @__PURE__ */ new Set();
|
|
100
|
+
for (const part of normalized.split("+")) {
|
|
101
|
+
const rawToken = part.trim();
|
|
102
|
+
if (rawToken === "") {
|
|
103
|
+
throw new TypeError(`Invalid hotkey: "${input}"`);
|
|
104
|
+
}
|
|
105
|
+
if (/^[A-Z]$/.test(rawToken)) {
|
|
106
|
+
throw new TypeError(
|
|
107
|
+
`Uppercase key "${rawToken}" is ambiguous because it may imply Shift. Please use the lowercase version: "${rawToken.toLowerCase()}".`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const token = rawToken.toLowerCase();
|
|
111
|
+
if (token in modifierAliases) {
|
|
112
|
+
const modifier = modifierAliases[token];
|
|
113
|
+
if (modifiers.has(modifier)) {
|
|
114
|
+
throw new TypeError(`Duplicate key in hotkey: "${token}"`);
|
|
115
|
+
}
|
|
116
|
+
modifiers.add(modifier);
|
|
117
|
+
entries.push({ type: "modifier", modifier });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const code = /^[0-9]$/.test(token) ? `Digit${token}` : codeByToken.get(token);
|
|
121
|
+
if (code === void 0) {
|
|
122
|
+
throw new TypeError(`Unknown key in hotkey: "${token}"`);
|
|
123
|
+
}
|
|
124
|
+
if (codes.has(code)) {
|
|
125
|
+
throw new TypeError(`Duplicate key in hotkey: "${token}"`);
|
|
126
|
+
}
|
|
127
|
+
codes.add(code);
|
|
128
|
+
entries.push({ type: "key", code });
|
|
129
|
+
}
|
|
130
|
+
if (codes.size === 0) {
|
|
131
|
+
throw new TypeError("Hotkey must contain at least one non-modifier key");
|
|
132
|
+
}
|
|
133
|
+
return entries;
|
|
134
|
+
};
|
|
135
|
+
exports.parse = parse;
|
|
136
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sources":["../../../../src/parse.ts"],"sourcesContent":["export type Modifier = 'alt' | 'ctrl' | 'meta' | 'shift'\n\nexport type ModifierEntry = {\n type: 'modifier'\n modifier: Modifier\n}\n\nexport type KeyEntry = {\n type: 'key'\n code: KeyboardEvent['code']\n}\n\nexport type Entry = ModifierEntry | KeyEntry\n\nconst modifierAliases = {\n alt: 'alt',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n shift: 'shift',\n} as const satisfies Record<string, Modifier>\n\nconst namedCodes = [\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'Backquote',\n 'Backslash',\n 'Backspace',\n 'BracketLeft',\n 'BracketRight',\n 'CapsLock',\n 'Comma',\n 'ContextMenu',\n 'Delete',\n 'End',\n 'Enter',\n 'Equal',\n 'Escape',\n 'Home',\n 'Insert',\n 'IntlBackslash',\n 'IntlRo',\n 'IntlYen',\n 'Minus',\n 'NumLock',\n 'PageDown',\n 'PageUp',\n 'Pause',\n 'Period',\n 'PrintScreen',\n 'Quote',\n 'ScrollLock',\n 'Semicolon',\n 'Slash',\n 'Space',\n 'Tab',\n] as const\n\nconst codeByToken = new Map<string, string>(\n namedCodes.map((code) => [code.toLowerCase(), code]),\n)\n\nfor (let index = 1; index <= 24; index++) {\n codeByToken.set(`f${index}`, `F${index}`)\n}\n\nfor (let index = 0; index <= 9; index++) {\n codeByToken.set(`digit${index}`, `Digit${index}`)\n codeByToken.set(`numpad${index}`, `Numpad${index}`)\n}\n\nfor (let index = 0; index < 26; index++) {\n const letter = String.fromCharCode(97 + index)\n codeByToken.set(letter, `Key${letter.toUpperCase()}`)\n codeByToken.set(`key${letter}`, `Key${letter.toUpperCase()}`)\n}\n\nconst codeAliases: Record<string, string> = {\n down: 'ArrowDown',\n esc: 'Escape',\n left: 'ArrowLeft',\n return: 'Enter',\n right: 'ArrowRight',\n up: 'ArrowUp',\n}\n\nfor (const [alias, code] of Object.entries(codeAliases)) {\n codeByToken.set(alias, code)\n}\n\nconst punctuationCodes: Record<string, string> = {\n ',': 'Comma',\n '-': 'Minus',\n '.': 'Period',\n '/': 'Slash',\n ';': 'Semicolon',\n '=': 'Equal',\n '[': 'BracketLeft',\n '\\\\': 'Backslash',\n ']': 'BracketRight',\n '`': 'Backquote',\n \"'\": 'Quote',\n}\n\nfor (const [key, code] of Object.entries(punctuationCodes)) {\n codeByToken.set(key, code)\n}\n\nexport const parse = (input: string): readonly Entry[] => {\n if ([...input].some((character) => character.charCodeAt(0) > 0x7f)) {\n throw new TypeError('Hotkey must contain ASCII characters only')\n }\n\n const normalized = input.trim()\n\n if (normalized === '') {\n throw new TypeError('Hotkey must not be empty')\n }\n\n const entries: Entry[] = []\n const modifiers = new Set<Modifier>()\n const codes = new Set<string>()\n\n for (const part of normalized.split('+')) {\n const rawToken = part.trim()\n\n if (rawToken === '') {\n throw new TypeError(`Invalid hotkey: \"${input}\"`)\n }\n\n if (/^[A-Z]$/.test(rawToken)) {\n throw new TypeError(\n `Uppercase key \"${rawToken}\" is ambiguous because it may imply Shift. Please use the lowercase version: \"${rawToken.toLowerCase()}\".`,\n )\n }\n\n const token = rawToken.toLowerCase()\n\n if (token in modifierAliases) {\n const modifier = modifierAliases[token as keyof typeof modifierAliases]\n\n if (modifiers.has(modifier)) {\n throw new TypeError(`Duplicate key in hotkey: \"${token}\"`)\n }\n\n modifiers.add(modifier)\n entries.push({ type: 'modifier', modifier })\n continue\n }\n\n const code = /^[0-9]$/.test(token)\n ? `Digit${token}`\n : codeByToken.get(token)\n\n if (code === undefined) {\n throw new TypeError(`Unknown key in hotkey: \"${token}\"`)\n }\n\n if (codes.has(code)) {\n throw new TypeError(`Duplicate key in hotkey: \"${token}\"`)\n }\n\n codes.add(code)\n entries.push({ type: 'key', code })\n }\n\n if (codes.size === 0) {\n throw new TypeError('Hotkey must contain at least one non-modifier key')\n }\n\n return entries\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const compile = (entries) => {
|
|
2
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
3
|
+
const codes = /* @__PURE__ */ new Set();
|
|
4
|
+
for (const entry of entries) {
|
|
5
|
+
if (entry.type === "modifier") {
|
|
6
|
+
modifiers.add(entry.modifier);
|
|
7
|
+
} else {
|
|
8
|
+
codes.add(entry.code);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const requiredCodes = [...codes];
|
|
12
|
+
return (event, pressedCodes) => codes.has(event.code) && event.altKey === modifiers.has("alt") && event.ctrlKey === modifiers.has("ctrl") && event.metaKey === modifiers.has("meta") && event.shiftKey === modifiers.has("shift") && requiredCodes.every((code) => pressedCodes.has(code));
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
compile
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=compile.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.mjs","sources":["../../../../src/compile.ts"],"sourcesContent":["import type { Entry, Modifier } from './parse'\n\nexport type Matcher = (\n event: KeyboardEvent,\n pressedCodes: ReadonlySet<string>,\n) => boolean\n\nexport const compile = (entries: readonly Entry[]): Matcher => {\n const modifiers = new Set<Modifier>()\n const codes = new Set<string>()\n\n for (const entry of entries) {\n if (entry.type === 'modifier') {\n modifiers.add(entry.modifier)\n } else {\n codes.add(entry.code)\n }\n }\n\n const requiredCodes = [...codes]\n\n return (event, pressedCodes) =>\n codes.has(event.code) &&\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift') &&\n requiredCodes.every((code) => pressedCodes.has(code))\n}\n"],"names":[],"mappings":"AAOO,MAAM,UAAU,CAAC,YAAuC;AAC7D,QAAM,gCAAgB,IAAA;AACtB,QAAM,4BAAY,IAAA;AAElB,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,SAAS,YAAY;AAC7B,gBAAU,IAAI,MAAM,QAAQ;AAAA,IAC9B,OAAO;AACL,YAAM,IAAI,MAAM,IAAI;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,gBAAgB,CAAC,GAAG,KAAK;AAE/B,SAAO,CAAC,OAAO,iBACb,MAAM,IAAI,MAAM,IAAI,KACpB,MAAM,WAAW,UAAU,IAAI,KAAK,KACpC,MAAM,YAAY,UAAU,IAAI,MAAM,KACtC,MAAM,YAAY,UAAU,IAAI,MAAM,KACtC,MAAM,aAAa,UAAU,IAAI,OAAO,KACxC,cAAc,MAAM,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC;AACxD;"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { action, withConnectHook, onEvent } from "@reatom/core";
|
|
2
|
+
import { compile } from "./compile.mjs";
|
|
3
|
+
import { parse } from "./parse.mjs";
|
|
4
|
+
const reatomHotkey = (hotkey) => {
|
|
5
|
+
const match = compile(parse(hotkey));
|
|
6
|
+
const hotkeyAction = action(
|
|
7
|
+
(event) => event,
|
|
8
|
+
`hotkey.${hotkey.trim().toLowerCase()}`
|
|
9
|
+
);
|
|
10
|
+
return hotkeyAction.extend(
|
|
11
|
+
withConnectHook(() => {
|
|
12
|
+
if (typeof document === "undefined") return;
|
|
13
|
+
const pressedCodes = /* @__PURE__ */ new Set();
|
|
14
|
+
onEvent(document, "keydown", (event) => {
|
|
15
|
+
pressedCodes.add(event.code);
|
|
16
|
+
if (match(event, pressedCodes)) {
|
|
17
|
+
hotkeyAction(event);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
onEvent(document, "keyup", (event) => {
|
|
21
|
+
pressedCodes.delete(event.code);
|
|
22
|
+
});
|
|
23
|
+
onEvent(document, "visibilitychange", () => pressedCodes.clear());
|
|
24
|
+
if (document.defaultView !== null) {
|
|
25
|
+
onEvent(document.defaultView, "blur", () => pressedCodes.clear());
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
reatomHotkey
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../../src/index.ts"],"sourcesContent":["import { action, onEvent, withConnectHook } from '@reatom/core'\n\nimport { compile } from './compile'\nimport { parse } from './parse'\n\n/** Creates a lazily connected action for a physical keyboard shortcut. */\nexport const reatomHotkey = (hotkey: string) => {\n const match = compile(parse(hotkey))\n const hotkeyAction = action(\n (event: KeyboardEvent) => event,\n `hotkey.${hotkey.trim().toLowerCase()}`,\n )\n\n return hotkeyAction.extend(\n withConnectHook(() => {\n if (typeof document === 'undefined') return\n\n const pressedCodes = new Set<string>()\n\n onEvent<KeyboardEvent>(document, 'keydown', (event) => {\n pressedCodes.add(event.code)\n\n if (match(event, pressedCodes)) {\n hotkeyAction(event)\n }\n })\n onEvent<KeyboardEvent>(document, 'keyup', (event) => {\n pressedCodes.delete(event.code)\n })\n onEvent(document, 'visibilitychange', () => pressedCodes.clear())\n\n if (document.defaultView !== null) {\n onEvent(document.defaultView, 'blur', () => pressedCodes.clear())\n }\n }),\n )\n}\n"],"names":[],"mappings":";;;AAMO,MAAM,eAAe,CAAC,WAAmB;AAC9C,QAAM,QAAQ,QAAQ,MAAM,MAAM,CAAC;AACnC,QAAM,eAAe;AAAA,IACnB,CAAC,UAAyB;AAAA,IAC1B,UAAU,OAAO,KAAA,EAAO,aAAa;AAAA,EAAA;AAGvC,SAAO,aAAa;AAAA,IAClB,gBAAgB,MAAM;AACpB,UAAI,OAAO,aAAa,YAAa;AAErC,YAAM,mCAAmB,IAAA;AAEzB,cAAuB,UAAU,WAAW,CAAC,UAAU;AACrD,qBAAa,IAAI,MAAM,IAAI;AAE3B,YAAI,MAAM,OAAO,YAAY,GAAG;AAC9B,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AACD,cAAuB,UAAU,SAAS,CAAC,UAAU;AACnD,qBAAa,OAAO,MAAM,IAAI;AAAA,MAChC,CAAC;AACD,cAAQ,UAAU,oBAAoB,MAAM,aAAa,OAAO;AAEhE,UAAI,SAAS,gBAAgB,MAAM;AACjC,gBAAQ,SAAS,aAAa,QAAQ,MAAM,aAAa,OAAO;AAAA,MAClE;AAAA,IACF,CAAC;AAAA,EAAA;AAEL;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Modifier = 'alt' | 'ctrl' | 'meta' | 'shift';
|
|
2
|
+
export type ModifierEntry = {
|
|
3
|
+
type: 'modifier';
|
|
4
|
+
modifier: Modifier;
|
|
5
|
+
};
|
|
6
|
+
export type KeyEntry = {
|
|
7
|
+
type: 'key';
|
|
8
|
+
code: KeyboardEvent['code'];
|
|
9
|
+
};
|
|
10
|
+
export type Entry = ModifierEntry | KeyEntry;
|
|
11
|
+
export declare const parse: (input: string) => readonly Entry[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const modifierAliases = {
|
|
2
|
+
alt: "alt",
|
|
3
|
+
control: "ctrl",
|
|
4
|
+
ctrl: "ctrl",
|
|
5
|
+
meta: "meta",
|
|
6
|
+
shift: "shift"
|
|
7
|
+
};
|
|
8
|
+
const namedCodes = [
|
|
9
|
+
"ArrowDown",
|
|
10
|
+
"ArrowLeft",
|
|
11
|
+
"ArrowRight",
|
|
12
|
+
"ArrowUp",
|
|
13
|
+
"Backquote",
|
|
14
|
+
"Backslash",
|
|
15
|
+
"Backspace",
|
|
16
|
+
"BracketLeft",
|
|
17
|
+
"BracketRight",
|
|
18
|
+
"CapsLock",
|
|
19
|
+
"Comma",
|
|
20
|
+
"ContextMenu",
|
|
21
|
+
"Delete",
|
|
22
|
+
"End",
|
|
23
|
+
"Enter",
|
|
24
|
+
"Equal",
|
|
25
|
+
"Escape",
|
|
26
|
+
"Home",
|
|
27
|
+
"Insert",
|
|
28
|
+
"IntlBackslash",
|
|
29
|
+
"IntlRo",
|
|
30
|
+
"IntlYen",
|
|
31
|
+
"Minus",
|
|
32
|
+
"NumLock",
|
|
33
|
+
"PageDown",
|
|
34
|
+
"PageUp",
|
|
35
|
+
"Pause",
|
|
36
|
+
"Period",
|
|
37
|
+
"PrintScreen",
|
|
38
|
+
"Quote",
|
|
39
|
+
"ScrollLock",
|
|
40
|
+
"Semicolon",
|
|
41
|
+
"Slash",
|
|
42
|
+
"Space",
|
|
43
|
+
"Tab"
|
|
44
|
+
];
|
|
45
|
+
const codeByToken = new Map(
|
|
46
|
+
namedCodes.map((code) => [code.toLowerCase(), code])
|
|
47
|
+
);
|
|
48
|
+
for (let index = 1; index <= 24; index++) {
|
|
49
|
+
codeByToken.set(`f${index}`, `F${index}`);
|
|
50
|
+
}
|
|
51
|
+
for (let index = 0; index <= 9; index++) {
|
|
52
|
+
codeByToken.set(`digit${index}`, `Digit${index}`);
|
|
53
|
+
codeByToken.set(`numpad${index}`, `Numpad${index}`);
|
|
54
|
+
}
|
|
55
|
+
for (let index = 0; index < 26; index++) {
|
|
56
|
+
const letter = String.fromCharCode(97 + index);
|
|
57
|
+
codeByToken.set(letter, `Key${letter.toUpperCase()}`);
|
|
58
|
+
codeByToken.set(`key${letter}`, `Key${letter.toUpperCase()}`);
|
|
59
|
+
}
|
|
60
|
+
const codeAliases = {
|
|
61
|
+
down: "ArrowDown",
|
|
62
|
+
esc: "Escape",
|
|
63
|
+
left: "ArrowLeft",
|
|
64
|
+
return: "Enter",
|
|
65
|
+
right: "ArrowRight",
|
|
66
|
+
up: "ArrowUp"
|
|
67
|
+
};
|
|
68
|
+
for (const [alias, code] of Object.entries(codeAliases)) {
|
|
69
|
+
codeByToken.set(alias, code);
|
|
70
|
+
}
|
|
71
|
+
const punctuationCodes = {
|
|
72
|
+
",": "Comma",
|
|
73
|
+
"-": "Minus",
|
|
74
|
+
".": "Period",
|
|
75
|
+
"/": "Slash",
|
|
76
|
+
";": "Semicolon",
|
|
77
|
+
"=": "Equal",
|
|
78
|
+
"[": "BracketLeft",
|
|
79
|
+
"\\": "Backslash",
|
|
80
|
+
"]": "BracketRight",
|
|
81
|
+
"`": "Backquote",
|
|
82
|
+
"'": "Quote"
|
|
83
|
+
};
|
|
84
|
+
for (const [key, code] of Object.entries(punctuationCodes)) {
|
|
85
|
+
codeByToken.set(key, code);
|
|
86
|
+
}
|
|
87
|
+
const parse = (input) => {
|
|
88
|
+
if ([...input].some((character) => character.charCodeAt(0) > 127)) {
|
|
89
|
+
throw new TypeError("Hotkey must contain ASCII characters only");
|
|
90
|
+
}
|
|
91
|
+
const normalized = input.trim();
|
|
92
|
+
if (normalized === "") {
|
|
93
|
+
throw new TypeError("Hotkey must not be empty");
|
|
94
|
+
}
|
|
95
|
+
const entries = [];
|
|
96
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
97
|
+
const codes = /* @__PURE__ */ new Set();
|
|
98
|
+
for (const part of normalized.split("+")) {
|
|
99
|
+
const rawToken = part.trim();
|
|
100
|
+
if (rawToken === "") {
|
|
101
|
+
throw new TypeError(`Invalid hotkey: "${input}"`);
|
|
102
|
+
}
|
|
103
|
+
if (/^[A-Z]$/.test(rawToken)) {
|
|
104
|
+
throw new TypeError(
|
|
105
|
+
`Uppercase key "${rawToken}" is ambiguous because it may imply Shift. Please use the lowercase version: "${rawToken.toLowerCase()}".`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const token = rawToken.toLowerCase();
|
|
109
|
+
if (token in modifierAliases) {
|
|
110
|
+
const modifier = modifierAliases[token];
|
|
111
|
+
if (modifiers.has(modifier)) {
|
|
112
|
+
throw new TypeError(`Duplicate key in hotkey: "${token}"`);
|
|
113
|
+
}
|
|
114
|
+
modifiers.add(modifier);
|
|
115
|
+
entries.push({ type: "modifier", modifier });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const code = /^[0-9]$/.test(token) ? `Digit${token}` : codeByToken.get(token);
|
|
119
|
+
if (code === void 0) {
|
|
120
|
+
throw new TypeError(`Unknown key in hotkey: "${token}"`);
|
|
121
|
+
}
|
|
122
|
+
if (codes.has(code)) {
|
|
123
|
+
throw new TypeError(`Duplicate key in hotkey: "${token}"`);
|
|
124
|
+
}
|
|
125
|
+
codes.add(code);
|
|
126
|
+
entries.push({ type: "key", code });
|
|
127
|
+
}
|
|
128
|
+
if (codes.size === 0) {
|
|
129
|
+
throw new TypeError("Hotkey must contain at least one non-modifier key");
|
|
130
|
+
}
|
|
131
|
+
return entries;
|
|
132
|
+
};
|
|
133
|
+
export {
|
|
134
|
+
parse
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=parse.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.mjs","sources":["../../../../src/parse.ts"],"sourcesContent":["export type Modifier = 'alt' | 'ctrl' | 'meta' | 'shift'\n\nexport type ModifierEntry = {\n type: 'modifier'\n modifier: Modifier\n}\n\nexport type KeyEntry = {\n type: 'key'\n code: KeyboardEvent['code']\n}\n\nexport type Entry = ModifierEntry | KeyEntry\n\nconst modifierAliases = {\n alt: 'alt',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n shift: 'shift',\n} as const satisfies Record<string, Modifier>\n\nconst namedCodes = [\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'Backquote',\n 'Backslash',\n 'Backspace',\n 'BracketLeft',\n 'BracketRight',\n 'CapsLock',\n 'Comma',\n 'ContextMenu',\n 'Delete',\n 'End',\n 'Enter',\n 'Equal',\n 'Escape',\n 'Home',\n 'Insert',\n 'IntlBackslash',\n 'IntlRo',\n 'IntlYen',\n 'Minus',\n 'NumLock',\n 'PageDown',\n 'PageUp',\n 'Pause',\n 'Period',\n 'PrintScreen',\n 'Quote',\n 'ScrollLock',\n 'Semicolon',\n 'Slash',\n 'Space',\n 'Tab',\n] as const\n\nconst codeByToken = new Map<string, string>(\n namedCodes.map((code) => [code.toLowerCase(), code]),\n)\n\nfor (let index = 1; index <= 24; index++) {\n codeByToken.set(`f${index}`, `F${index}`)\n}\n\nfor (let index = 0; index <= 9; index++) {\n codeByToken.set(`digit${index}`, `Digit${index}`)\n codeByToken.set(`numpad${index}`, `Numpad${index}`)\n}\n\nfor (let index = 0; index < 26; index++) {\n const letter = String.fromCharCode(97 + index)\n codeByToken.set(letter, `Key${letter.toUpperCase()}`)\n codeByToken.set(`key${letter}`, `Key${letter.toUpperCase()}`)\n}\n\nconst codeAliases: Record<string, string> = {\n down: 'ArrowDown',\n esc: 'Escape',\n left: 'ArrowLeft',\n return: 'Enter',\n right: 'ArrowRight',\n up: 'ArrowUp',\n}\n\nfor (const [alias, code] of Object.entries(codeAliases)) {\n codeByToken.set(alias, code)\n}\n\nconst punctuationCodes: Record<string, string> = {\n ',': 'Comma',\n '-': 'Minus',\n '.': 'Period',\n '/': 'Slash',\n ';': 'Semicolon',\n '=': 'Equal',\n '[': 'BracketLeft',\n '\\\\': 'Backslash',\n ']': 'BracketRight',\n '`': 'Backquote',\n \"'\": 'Quote',\n}\n\nfor (const [key, code] of Object.entries(punctuationCodes)) {\n codeByToken.set(key, code)\n}\n\nexport const parse = (input: string): readonly Entry[] => {\n if ([...input].some((character) => character.charCodeAt(0) > 0x7f)) {\n throw new TypeError('Hotkey must contain ASCII characters only')\n }\n\n const normalized = input.trim()\n\n if (normalized === '') {\n throw new TypeError('Hotkey must not be empty')\n }\n\n const entries: Entry[] = []\n const modifiers = new Set<Modifier>()\n const codes = new Set<string>()\n\n for (const part of normalized.split('+')) {\n const rawToken = part.trim()\n\n if (rawToken === '') {\n throw new TypeError(`Invalid hotkey: \"${input}\"`)\n }\n\n if (/^[A-Z]$/.test(rawToken)) {\n throw new TypeError(\n `Uppercase key \"${rawToken}\" is ambiguous because it may imply Shift. Please use the lowercase version: \"${rawToken.toLowerCase()}\".`,\n )\n }\n\n const token = rawToken.toLowerCase()\n\n if (token in modifierAliases) {\n const modifier = modifierAliases[token as keyof typeof modifierAliases]\n\n if (modifiers.has(modifier)) {\n throw new TypeError(`Duplicate key in hotkey: \"${token}\"`)\n }\n\n modifiers.add(modifier)\n entries.push({ type: 'modifier', modifier })\n continue\n }\n\n const code = /^[0-9]$/.test(token)\n ? `Digit${token}`\n : codeByToken.get(token)\n\n if (code === undefined) {\n throw new TypeError(`Unknown key in hotkey: \"${token}\"`)\n }\n\n if (codes.has(code)) {\n throw new TypeError(`Duplicate key in hotkey: \"${token}\"`)\n }\n\n codes.add(code)\n entries.push({ type: 'key', code })\n }\n\n if (codes.size === 0) {\n throw new TypeError('Hotkey must contain at least one non-modifier key')\n }\n\n return entries\n}\n"],"names":[],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./__compiled__/cjs/src/index.js";
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./__compiled__/cjs/src/index.js");
|
package/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./__compiled__/esm/src/index.mjs";
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xavescor/reatom-hotkey",
|
|
3
|
+
"type": "commonjs",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"types": "./__compiled__/cjs/src/index.d.ts",
|
|
6
|
+
"module": "./__compiled__/esm/src/index.mjs",
|
|
7
|
+
"main": "./__compiled__/cjs/src/index.js",
|
|
8
|
+
"description": "",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./__compiled__/esm/src/index.d.mts",
|
|
13
|
+
"default": "./__compiled__/esm/src/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./__compiled__/cjs/src/index.d.ts",
|
|
17
|
+
"default": "./__compiled__/cjs/src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"types": "./__compiled__/cjs/src/index.d.ts",
|
|
20
|
+
"default": "./__compiled__/cjs/src/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@reatom/core": "^1001.1.0"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@vitest/browser": "^3.2.7",
|
|
30
|
+
"oxlint": "^1.73.0",
|
|
31
|
+
"playwright": "^1.61.1",
|
|
32
|
+
"smartbundle": "^0.14.2",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"vitest": "^3.2.7"
|
|
35
|
+
}
|
|
36
|
+
}
|