├── .editorconfig
├── .gitignore
├── demo
├── index.html
└── index.js
├── license
├── package.json
├── readme.md
├── src
├── constants.ts
├── index.ts
├── maps.ts
├── types.ts
└── utils.ts
├── test
└── index.js
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 |
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.err
3 | *.log
4 | ._*
5 | .cache
6 | .fseventsd
7 | .DocumentRevisions*
8 | .DS_Store
9 | .TemporaryItems
10 | .Trashes
11 | Thumbs.db
12 |
13 | dist
14 | node_modules
15 | package-lock.json
16 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ShoSho
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 |
2 | /* IMPORT */
3 |
4 | import ShoSho from '../src';
5 |
6 | /* HELPERS */
7 |
8 | const yep = message => {
9 | return () => {
10 | console.log ( `✅ ${message}` );
11 | return true;
12 | };
13 | };
14 |
15 | const nope = message => {
16 | return () => {
17 | console.log ( `❌ ${message}` );
18 | return false;
19 | };
20 | };
21 |
22 | /* MAIN - LISTENING */
23 |
24 | const shortcuts = new ShoSho ();
25 |
26 | shortcuts.register ( '0', yep ( '0' ) );
27 | shortcuts.register ( '1', yep ( '1' ) );
28 | shortcuts.register ( '2', yep ( '2' ) );
29 | shortcuts.register ( '3', yep ( '3' ) );
30 | shortcuts.register ( '4', yep ( '4' ) );
31 | shortcuts.register ( '5', yep ( '5' ) );
32 | shortcuts.register ( '6', yep ( '6' ) );
33 | shortcuts.register ( '7', yep ( '7' ) );
34 | shortcuts.register ( '8', yep ( '8' ) );
35 | shortcuts.register ( '9', yep ( '9' ) );
36 |
37 | shortcuts.register ( 'A', yep ( 'A' ) );
38 | shortcuts.register ( 'B', yep ( 'B' ) );
39 | shortcuts.register ( 'C', yep ( 'C' ) );
40 | shortcuts.register ( 'D', yep ( 'D' ) );
41 | shortcuts.register ( 'E', yep ( 'E' ) );
42 | shortcuts.register ( 'F', yep ( 'F' ) );
43 | shortcuts.register ( 'G', yep ( 'G' ) );
44 | shortcuts.register ( 'H', yep ( 'H' ) );
45 | shortcuts.register ( 'I', yep ( 'I' ) );
46 | shortcuts.register ( 'J', yep ( 'J' ) );
47 | shortcuts.register ( 'K', yep ( 'K' ) );
48 | shortcuts.register ( 'L', yep ( 'L' ) );
49 | shortcuts.register ( 'M', yep ( 'M' ) );
50 | shortcuts.register ( 'N', yep ( 'N' ) );
51 | shortcuts.register ( 'O', yep ( 'O' ) );
52 | shortcuts.register ( 'P', yep ( 'P' ) );
53 | shortcuts.register ( 'Q', yep ( 'Q' ) );
54 | shortcuts.register ( 'R', yep ( 'R' ) );
55 | shortcuts.register ( 'S', yep ( 'S' ) );
56 | shortcuts.register ( 'T', yep ( 'T' ) );
57 | shortcuts.register ( 'U', yep ( 'U' ) );
58 | shortcuts.register ( 'V', yep ( 'V' ) );
59 | shortcuts.register ( 'W', yep ( 'W' ) );
60 | shortcuts.register ( 'X', yep ( 'X' ) );
61 | shortcuts.register ( 'Y', yep ( 'Y' ) );
62 | shortcuts.register ( 'Z', yep ( 'Z' ) );
63 |
64 | shortcuts.register ( 'F1', yep ( 'F1' ) );
65 | shortcuts.register ( 'F2', yep ( 'F2' ) );
66 | shortcuts.register ( 'F3', yep ( 'F3' ) );
67 | shortcuts.register ( 'F4', yep ( 'F4' ) );
68 | shortcuts.register ( 'F5', yep ( 'F5' ) );
69 | shortcuts.register ( 'F6', yep ( 'F6' ) );
70 | shortcuts.register ( 'F7', yep ( 'F7' ) );
71 | shortcuts.register ( 'F8', yep ( 'F8' ) );
72 | shortcuts.register ( 'F9', yep ( 'F9' ) );
73 | shortcuts.register ( 'F10', yep ( 'F10' ) );
74 | shortcuts.register ( 'F11', yep ( 'F11' ) );
75 | shortcuts.register ( 'F12', yep ( 'F12' ) );
76 | shortcuts.register ( 'F13', yep ( 'F13' ) );
77 | shortcuts.register ( 'F14', yep ( 'F14' ) );
78 | shortcuts.register ( 'F15', yep ( 'F15' ) );
79 | shortcuts.register ( 'F16', yep ( 'F16' ) );
80 | shortcuts.register ( 'F17', yep ( 'F17' ) );
81 | shortcuts.register ( 'F18', yep ( 'F18' ) );
82 | shortcuts.register ( 'F19', yep ( 'F19' ) );
83 | shortcuts.register ( 'F20', yep ( 'F20' ) );
84 | shortcuts.register ( 'F21', yep ( 'F21' ) );
85 | shortcuts.register ( 'F22', yep ( 'F22' ) );
86 | shortcuts.register ( 'F23', yep ( 'F23' ) );
87 | shortcuts.register ( 'F24', yep ( 'F24' ) );
88 |
89 | shortcuts.register ( 'Numpad0', yep ( 'Numpad0' ) );
90 | shortcuts.register ( 'Numpad1', yep ( 'Numpad1' ) );
91 | shortcuts.register ( 'Numpad2', yep ( 'Numpad2' ) );
92 | shortcuts.register ( 'Numpad3', yep ( 'Numpad3' ) );
93 | shortcuts.register ( 'Numpad4', yep ( 'Numpad4' ) );
94 | shortcuts.register ( 'Numpad5', yep ( 'Numpad5' ) );
95 | shortcuts.register ( 'Numpad6', yep ( 'Numpad6' ) );
96 | shortcuts.register ( 'Numpad7', yep ( 'Numpad7' ) );
97 | shortcuts.register ( 'Numpad8', yep ( 'Numpad8' ) );
98 | shortcuts.register ( 'Numpad9', yep ( 'Numpad9' ) );
99 |
100 | shortcuts.register ( 'NumpadAdd', yep ( 'NumpadAdd' ) );
101 | shortcuts.register ( 'NumpadComma', yep ( 'NumpadComma' ) );
102 | shortcuts.register ( 'NumpadDecimal', yep ( 'NumpadDecimal' ) );
103 | shortcuts.register ( 'NumpadDivide', yep ( 'NumpadDivide' ) );
104 | shortcuts.register ( 'NumpadEnter', yep ( 'NumpadEnter' ) );
105 | shortcuts.register ( 'NumpadEqual', yep ( 'NumpadEqual' ) );
106 | shortcuts.register ( 'NumpadMultiply', yep ( 'NumpadMultiply' ) );
107 | shortcuts.register ( 'NumpadSubtract', yep ( 'NumpadSubtract' ) );
108 |
109 | shortcuts.register ( 'Left+Up', nope ( 'Left+Up' ) );
110 | shortcuts.register ( 'Right+Up', nope ( 'Right+Up' ) );
111 | shortcuts.register ( 'Left+Down', nope ( 'Left+Down' ) );
112 | shortcuts.register ( 'Right+Down', nope ( 'Right+Down' ) );
113 | shortcuts.register ( 'Left+Right', nope ( 'Left+Right' ) );
114 | shortcuts.register ( 'Up+Down', nope ( 'Up+Down' ) );
115 |
116 | shortcuts.register ( 'Down', yep ( 'Down' ) );
117 | shortcuts.register ( 'Left', yep ( 'Left' ) );
118 | shortcuts.register ( 'Right', yep ( 'Right' ) );
119 | shortcuts.register ( 'Up', yep ( 'Up' ) );
120 |
121 | shortcuts.register ( 'Backspace', yep ( 'Backspace' ) );
122 | shortcuts.register ( 'CapsLock', yep ( 'CapsLock' ) );
123 | shortcuts.register ( 'Delete', yep ( 'Delete' ) );
124 | shortcuts.register ( 'End', yep ( 'End' ) );
125 | shortcuts.register ( 'Enter', yep ( 'Enter' ) );
126 | shortcuts.register ( 'Escape', yep ( 'Escape' ) );
127 | shortcuts.register ( 'Home', yep ( 'Home' ) );
128 | shortcuts.register ( 'Insert', yep ( 'Insert' ) );
129 | shortcuts.register ( 'PageDown', yep ( 'PageDown' ) );
130 | shortcuts.register ( 'PageUp', yep ( 'PageUp' ) );
131 | shortcuts.register ( 'Space', yep ( 'Space' ) );
132 | shortcuts.register ( 'Tab', yep ( 'Tab' ) );
133 |
134 | shortcuts.register ( 'NumLock', yep ( 'NumLock' ) );
135 | shortcuts.register ( 'ScrollLock', yep ( 'ScrollLock' ) );
136 |
137 | // shortcuts.register ( 'AltLeft', nope ( 'AltLeft' ) );
138 | // shortcuts.register ( 'AltRight', nope ( 'AltRight' ) );
139 | // shortcuts.register ( 'Alt', nope ( 'Alt' ) );
140 | // shortcuts.register ( 'OptionLeft', nope ( 'OptionLeft' ) );
141 | // shortcuts.register ( 'OptionRight', nope ( 'OptionRight' ) );
142 | // shortcuts.register ( 'Option', nope ( 'Option' ) );
143 | // shortcuts.register ( 'CmdLeft', nope ( 'CmdLeft' ) );
144 | // shortcuts.register ( 'CmdRight', nope ( 'CmdRight' ) );
145 | // shortcuts.register ( 'Cmd', nope ( 'Cmd' ) );
146 | // shortcuts.register ( 'CommandLeft', nope ( 'CommandLeft' ) );
147 | // shortcuts.register ( 'CommandRight', nope ( 'CommandRight' ) );
148 | // shortcuts.register ( 'Command', nope ( 'Command' ) );
149 | // shortcuts.register ( 'MetaLeft', nope ( 'MetaLeft' ) );
150 | // shortcuts.register ( 'MetaRight', nope ( 'MetaRight' ) );
151 | // shortcuts.register ( 'Meta', nope ( 'Meta' ) );
152 | // shortcuts.register ( 'CtrlLeft', nope ( 'CtrlLeft' ) );
153 | // shortcuts.register ( 'CtrlRight', nope ( 'CtrlRight' ) );
154 | // shortcuts.register ( 'Ctrl', nope ( 'Ctrl' ) );
155 | // shortcuts.register ( 'ControlLeft', nope ( 'ControlLeft' ) );
156 | // shortcuts.register ( 'ControlRight', nope ( 'ControlRight' ) );
157 | // shortcuts.register ( 'Control', nope ( 'Control' ) );
158 | // shortcuts.register ( 'ShiftLeft', nope ( 'ShiftLeft' ) );
159 | // shortcuts.register ( 'ShiftRight', nope ( 'ShiftRight' ) );
160 | // shortcuts.register ( 'Shift', nope ( 'Shift' ) );
161 | // shortcuts.register ( 'CmdLeftOrCtrlLeft', nope ( 'CmdLeftOrCtrlLeft' ) );
162 | // shortcuts.register ( 'CmdRightOrCtrlRight', nope ( 'CmdRightOrCtrlRight' ) );
163 | // shortcuts.register ( 'CmdOrCtrl', nope ( 'CmdOrCtrl' ) );
164 | // shortcuts.register ( 'CommandLeftOrControlLeft', nope ( 'CommandLeftOrControlLeft' ) );
165 | // shortcuts.register ( 'CommandRightOrControlRight', nope ( 'CommandRightOrControlRight' ) );
166 | // shortcuts.register ( 'CommandOrControl', nope ( 'CommandOrControl' ) );
167 |
168 | shortcuts.register ( '!', nope ( 'ExclationMark' ) );
169 | shortcuts.register ( '"', nope ( 'DoubleQuote' ) );
170 | shortcuts.register ( '#', nope ( 'Hash' ) );
171 | shortcuts.register ( '$', nope ( 'Dollar' ) );
172 | shortcuts.register ( '%', nope ( 'Percent' ) );
173 | shortcuts.register ( '&', nope ( 'Ampersand' ) );
174 | shortcuts.register ( '\'', nope ( 'Quote' ) );
175 | shortcuts.register ( '(', nope ( 'ParenthesisLeft' ) );
176 | shortcuts.register ( ')', nope ( 'ParenthesisRight' ) );
177 | shortcuts.register ( '*', nope ( 'Asterisk' ) );
178 | shortcuts.register ( '+', nope ( 'Plus' ) );
179 | shortcuts.register ( ',', nope ( 'Comma' ) );
180 | shortcuts.register ( '-', nope ( 'Minus' ) );
181 | shortcuts.register ( '.', nope ( 'Period' ) );
182 | shortcuts.register ( '/', nope ( 'Slash' ) );
183 | shortcuts.register ( ':', nope ( 'Colon' ) );
184 | shortcuts.register ( ';', nope ( 'Semicolon' ) );
185 | shortcuts.register ( '<', nope ( 'LessThan' ) );
186 | shortcuts.register ( '=', nope ( 'Equal' ) );
187 | shortcuts.register ( '>', nope ( 'GreaterThan' ) );
188 | shortcuts.register ( '?', nope ( 'QuestionMark' ) );
189 | shortcuts.register ( '@', nope ( 'At' ) );
190 | shortcuts.register ( '[', nope ( 'BracketLeft' ) );
191 | shortcuts.register ( '\\', nope ( 'Backslash' ) );
192 | shortcuts.register ( ']', nope ( 'BracketRight' ) );
193 | shortcuts.register ( '^', nope ( 'Caret' ) );
194 | shortcuts.register ( '_', nope ( 'Underscore' ) );
195 | shortcuts.register ( '`', nope ( 'Backquote' ) );
196 | shortcuts.register ( '{', nope ( 'BraceLeft' ) );
197 | shortcuts.register ( '|', nope ( 'Pipe' ) );
198 | shortcuts.register ( '}', nope ( 'BraceRight' ) );
199 | shortcuts.register ( '~', nope ( 'Tilde' ) );
200 |
201 | shortcuts.register ( 'Shift+Plus', nope ( 'Shift+Plus (Alias)' ) );
202 | shortcuts.register ( 'Shift+!', nope ( 'Shift+ExclationMark' ) );
203 | shortcuts.register ( 'Shift+"', nope ( 'Shift+DoubleQuote' ) );
204 | shortcuts.register ( 'Shift+#', nope ( 'Shift+Hash' ) );
205 | shortcuts.register ( 'Shift+$', nope ( 'Shift+Dollar' ) );
206 | shortcuts.register ( 'Shift+%', nope ( 'Shift+Percent' ) );
207 | shortcuts.register ( 'Shift+&', nope ( 'Shift+Ampersand' ) );
208 | shortcuts.register ( 'Shift+\'', nope ( 'Shift+Quote' ) );
209 | shortcuts.register ( 'Shift+(', nope ( 'Shift+ParenthesisLeft' ) );
210 | shortcuts.register ( 'Shift+)', nope ( 'Shift+ParenthesisRight' ) );
211 | shortcuts.register ( 'Shift+*', nope ( 'Shift+Asterisk' ) );
212 | shortcuts.register ( 'Shift++', nope ( 'Shift+Plus' ) );
213 | shortcuts.register ( 'Shift+,', nope ( 'Shift+Comma' ) );
214 | shortcuts.register ( 'Shift+-', nope ( 'Shift+Minus' ) );
215 | shortcuts.register ( 'Shift+.', nope ( 'Shift+Period' ) );
216 | shortcuts.register ( 'Shift+/', nope ( 'Shift+Slash' ) );
217 | shortcuts.register ( 'Shift+:', nope ( 'Shift+Colon' ) );
218 | shortcuts.register ( 'Shift+;', nope ( 'Shift+Semicolon' ) );
219 | shortcuts.register ( 'Shift+<', nope ( 'Shift+LessThan' ) );
220 | shortcuts.register ( 'Shift+=', nope ( 'Shift+Equal' ) );
221 | shortcuts.register ( 'Shift+>', nope ( 'Shift+GreaterThan' ) );
222 | shortcuts.register ( 'Shift+?', nope ( 'Shift+QuestionMark' ) );
223 | shortcuts.register ( 'Shift+@', nope ( 'Shift+At' ) );
224 | shortcuts.register ( 'Shift+[', nope ( 'Shift+BracketLeft' ) );
225 | shortcuts.register ( 'Shift+\\', nope ( 'Shift+Backslash' ) );
226 | shortcuts.register ( 'Shift+]', nope ( 'Shift+BracketRight' ) );
227 | shortcuts.register ( 'Shift+^', nope ( 'Shift+Caret' ) );
228 | shortcuts.register ( 'Shift+_', nope ( 'Shift+Underscore' ) );
229 | shortcuts.register ( 'Shift+`', nope ( 'Shift+Backquote' ) );
230 | shortcuts.register ( 'Shift+{', nope ( 'Shift+BraceLeft' ) );
231 | shortcuts.register ( 'Shift+|', nope ( 'Shift+Pipe' ) );
232 | shortcuts.register ( 'Shift+}', nope ( 'Shift+BraceRight' ) );
233 | shortcuts.register ( 'Shift+~', nope ( 'Shift+Tilde' ) );
234 |
235 | shortcuts.register ( 'Shift+1', nope ( 'Shift+1' ) );
236 | shortcuts.register ( 'Shift+2', nope ( 'Shift+2' ) );
237 | shortcuts.register ( 'Shift+3', nope ( 'Shift+3' ) );
238 | shortcuts.register ( 'Shift+4', nope ( 'Shift+4' ) );
239 | shortcuts.register ( 'Shift+5', nope ( 'Shift+5' ) );
240 | shortcuts.register ( 'Shift+6', nope ( 'Shift+6' ) );
241 | shortcuts.register ( 'Shift+7', nope ( 'Shift+7' ) );
242 | shortcuts.register ( 'Shift+8', nope ( 'Shift+8' ) );
243 | shortcuts.register ( 'Shift+9', nope ( 'Shift+9' ) );
244 | shortcuts.register ( 'Shift+0', nope ( 'Shift+0' ) );
245 |
246 | shortcuts.register ( 'Shift+A', nope ( 'Shift+A' ) );
247 | shortcuts.register ( 'Shift+B', nope ( 'Shift+B' ) );
248 | shortcuts.register ( 'Shift+C', nope ( 'Shift+C' ) );
249 | shortcuts.register ( 'Shift+Z', nope ( 'Shift+Z' ) );
250 | shortcuts.register ( 'Shift+Y', nope ( 'Shift+Y' ) );
251 | shortcuts.register ( 'Shift+X', nope ( 'Shift+X' ) );
252 |
253 | shortcuts.register ( 'ClickLeft', nope ( 'ClickLeft' ) );
254 | shortcuts.register ( 'ClickRight', nope ( 'ClickRight' ) );
255 | shortcuts.register ( 'ClickMiddle', nope ( 'ClickMiddle' ) );
256 | shortcuts.register ( 'MouseLeft', nope ( 'MouseLeft' ) );
257 | shortcuts.register ( 'MouseRight', nope ( 'MouseRight' ) );
258 | shortcuts.register ( 'MouseMiddle', nope ( 'MouseMiddle' ) );
259 | shortcuts.register ( 'Mouse0', nope ( 'Mouse0' ) );
260 | shortcuts.register ( 'Mouse1', nope ( 'Mouse1' ) );
261 | shortcuts.register ( 'Mouse2', nope ( 'Mouse2' ) );
262 | shortcuts.register ( 'Mouse3', nope ( 'Mouse3' ) );
263 | shortcuts.register ( 'Mouse4', nope ( 'Mouse4' ) );
264 | shortcuts.register ( 'Mouse5', nope ( 'Mouse5' ) );
265 | shortcuts.register ( 'Mouse6', nope ( 'Mouse6' ) );
266 | shortcuts.register ( 'Mouse7', nope ( 'Mouse7' ) );
267 | shortcuts.register ( 'Mouse8', nope ( 'Mouse8' ) );
268 | shortcuts.register ( 'Mouse9', nope ( 'Mouse9' ) );
269 |
270 | shortcuts.register ( 'Alt+F', yep ( 'Alt+F' ) );
271 | shortcuts.register ( 'Cmd+K Cmd+A', yep ( 'Cmd+K Cmd+A' ) );
272 | shortcuts.register ( 'Cmd+K B', yep ( 'Cmd+K B' ) );
273 | shortcuts.register ( 'Cmd+K Alt+C', yep ( 'Cmd+K Alt+C' ) );
274 | shortcuts.register ( 'Shift+ClickLeft', yep ( 'Shift+ClickLeft' ) );
275 | shortcuts.register ( 'Cmd+ClickMiddle', yep ( 'Cmd+ClickMiddle' ) );
276 | shortcuts.register ( 'Alt+ClickLeft Cmd+ClickMiddle', yep ( 'Alt+ClickLeft Cmd+ClickMiddle' ) );
277 |
278 | shortcuts.register ( 'CmdLeft+CmdRight', yep ( 'CmdLeft+CmdRight' ) );
279 | // shortcuts.register ( 'CmdLeft+CmdRight AltLeft+AltRight', yep ( 'CmdLeft+CmdRight AltLeft+AltRight' ) ); //TODO It'd be cool to support this too
280 |
281 | shortcuts.register ( 'Up Up Down Down Left Right Left Right B A', yep ( 'Up Up Down Down Left Right Left Right B A 🚀' ), { konami: true } );
282 |
283 | // shortcuts.register ( 'Left+Up', nope ( 'Left+Up' ) );
284 | // shortcuts.register ( 'Left+Down', nope ( 'Left+Down' ) );
285 | // shortcuts.register ( 'Left+Right', nope ( 'Left+Right' ) );
286 | // shortcuts.register ( 'Up+Down', nope ( 'Up+Down' ) );
287 | // shortcuts.register ( 'Up+Right', nope ( 'Up+Right' ) );
288 | // shortcuts.register ( 'Down+Right', nope ( 'Down+Right' ) );
289 |
290 | shortcuts.register ( 'Ctrl+Cmd+Right', yep ( 'Ctrl+Cmd+Right' ) );
291 |
292 | shortcuts.register ( '§', yep ( '§' ) );
293 | shortcuts.register ( '±', yep ( '±' ) );
294 | shortcuts.register ( 'Shift+§', nope ( 'Shift+§' ) );
295 | shortcuts.register ( 'Shift+±', nope ( 'Shift+±' ) );
296 |
297 | shortcuts.register ( 'Ctrl+Cmd+A', nope ( 'NOT DISPOSED!' ) )();
298 |
299 | shortcuts.start ();
300 |
301 | // setTimeout ( () => {
302 | // shosho.stop ();
303 | // shosho.reset ();
304 | // }, 5000 );
305 |
306 | /* MAIN - RECORDING */
307 |
308 | shortcuts.register ( 'Ctrl+Cmd+R', () => {
309 |
310 | const dispose = ShoSho.record ( shortcut => {
311 | console.log ( '---' );
312 | console.log ( shortcut );
313 | console.log ( `"${shortcut}"` );
314 | const trimmed = shortcut.replace ( /(^.*?)((?:Command(?:Left|Right)\+K )*\S+$)/, '$2' );
315 | console.log ( trimmed );
316 | const formatted = ShoSho.format ( trimmed, 'long-inflexible-nondirectional' );
317 | console.log ( formatted );
318 | }, {
319 | passthrough: false
320 | });
321 |
322 | setTimeout ( dispose, 5000 );
323 |
324 | return true;
325 |
326 | });
327 |
328 | /* EXPORT */
329 |
330 | globalThis.shosho = shortcuts;
331 | globalThis.S = shortcuts;
332 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023-present Fabio Spampinato
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shosho",
3 | "repository": "github:fabiospampinato/shosho",
4 | "description": "A modern and powerful shortcuts management library.",
5 | "version": "1.4.3",
6 | "type": "module",
7 | "main": "dist/index.js",
8 | "exports": "./dist/index.js",
9 | "types": "./dist/index.d.ts",
10 | "scripts": {
11 | "clean": "tsex clean",
12 | "compile": "tsex compile",
13 | "compile:watch": "tsex compile --watch",
14 | "demo": "vite demo",
15 | "test": "tsex test",
16 | "test:watch": "tsex test --watch",
17 | "prepublishOnly": "tsex prepare"
18 | },
19 | "keywords": [
20 | "modern",
21 | "shortcuts",
22 | "management",
23 | "library"
24 | ],
25 | "devDependencies": {
26 | "fava": "^0.2.1",
27 | "tsex": "^3.0.1",
28 | "typescript": "^5.1.6",
29 | "vite": "^4.4.9"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # ShoSho
2 |
3 | A modern and powerful shortcuts management library.
4 |
5 | ## Features
6 |
7 | - It is largely built on top of [`KeyboardEvent#code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) and [`KeyboardEvent#key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key), for great compatibility across browsers, platforms, languages and layouts.
8 | - It is _fast_. Almost always this library will do `O(1)` registrations, `O(1)` disposals, `O(1)` lookups and `O(1)` resetting.
9 | - It supports ~100 keyboard keys, including function keys and numpad keys.
10 | - It supports mouse buttons too, which can be mixed in shortcts with keyboard keys, like `Ctrl+ClickLeft`.
11 | - It supports simple shortcuts, like `Ctrl+F`, and shortcuts sequences, like `Ctrl+K Ctrl+F`.
12 | - It supports detecting which exact modifier key was pressed, like `ControlLeft` or `ControlRight`.
13 | - It supports common aliases for keys, like `Alt`/`Option`, or `Esc`/`Escape`.
14 | - It supports some character-based shortcuts, like `Shift+#`, rather than the equivalent key-based shortcut, which might be `Shift+3`.
15 | - It supports automatically using `Command` under macOS and `Control` elsewhere, by writing for example `CommandOrControl+F`.
16 | - It supports multi-trigger shortcuts, like `Up+Right` or `Down+Left`, except when the `Meta` key is pressed, which is buggy in browsers.
17 | - It supports Konami codes, a.k.a. cheatcode shortcuts, like `Up Up Down Down Left Right Left Right B A`.
18 | - It supports formatting shortcuts in 10 different styles, including formatting for [Electron](https://www.electronjs.org) and as symbols for hints/tooltips.
19 | - It supports recording a shortcut, for customizing shortcuts easily.
20 |
21 | ## Shortcut Syntax
22 |
23 | The following keys can be used when defining a shortcut:
24 |
25 | - **Modifiers**: Alt/Option, Cmd/Command/Meta, Ctrl/Control, Shift, CmdOrCtrl/CommandOrControl.
26 | - **Left modifiers**: AltLeft/OptionLeft, CmdLeft/CommandLeft/MetaLeft, CtrlLeft/ControLeft, CmdLeftOrCtrlLeft/CommandLeftOrControlLeft.
27 | - **Right modifiers**: AltRight/OptionRight, CmdRight/CommandRight/MetaRight, CtrlRight/ControRight, CmdRightOrCtrlRight/CommandRightOrControlRight.
28 | - **Digits**: 0-9.
29 | - **Alphabet letters**: A-Z.
30 | - **Function keys**: F1-F24.
31 | - **Numpad digits**: Numpad0-Numpad9.
32 | - **Numpad operators**: NumpadAdd, NumpadComma, NumpadDecimal, NumpadDivide, NumpadEnter, NumpadEqual, NumpadMultiply, NumpadSubtract.
33 | - **Special keys**: Backspace, Capslock, Del/Delete, Down, End, Enter/Return, Esc/Escape, Home, Insert, Left, PageDown, PageUp, Right, Space/Spacebar, Tab, Up, NumLock, ScrollLock.
34 | - **Punctuation keys**: !, @, #, $, %, ^, &, *, ( , ), _, {, }, |, :, ", <, >, ?, ~.
35 | - **Mouse buttons**: ClickLeft/MouseLeft, ClickMiddle/MouseMiddle, ClickRight/MouseRight, Mouse0-Mouse9.
36 |
37 | Please note that:
38 |
39 | - ℹ️ Other keys are not supported.
40 | - ℹ️ Shortcuts are case insensitive.
41 | - ℹ️ Keys in a single shortcut must be joined by a plus sign (e.g. Ctrl+A).
42 | - ℹ️ Sequences of shortcuts must be joined by a space (e.g. Ctrl+K Ctrl+B).
43 | - ⚠️ Punctuation keys should be avoided when possible, especially when used in combination with Shift/Alt, as they may lead to somewhat different results across different layouts.
44 |
45 | ## Install
46 |
47 | ```sh
48 | npm install --save shosho
49 | ```
50 |
51 | ## Usage
52 |
53 | ```ts
54 | import ShoSho from 'shosho';
55 |
56 | // Let's create a shortcuts manager
57 |
58 | const shortcuts = new ShoSho ({
59 | capture: true,
60 | target: document,
61 | shouldHandleEvent ( event ) {
62 | // Return "true" if this event should be handled
63 | return true;
64 | }
65 | });
66 |
67 | // Let's register some shortcuts. Shortcuts handlers must return "true" if they actually handled the shortcut
68 |
69 | shortcuts.register ( 'A', event => { // Single-key shortcut
70 | console.log ( 'Handling A' );
71 | return true;
72 | });
73 |
74 | shortcuts.register ( 'Ctrl+F', event => { // Simple modifier+trigger shortcut
75 | console.log ( 'Handling Ctrl+F' );
76 | return true;
77 | });
78 |
79 | shortcuts.register ( 'Ctrl+K Ctrl+F', event => { // Advanced sequence shortcut
80 | console.log ( 'Handling Ctrl+K Ctrl+F' );
81 | return true;
82 | });
83 |
84 | shortcuts.register ( 'ClickMiddle', event => { // Single-mouse-button shortcut
85 | console.log ( 'Skipping ClickMiddle actually' );
86 | return false; // Returning "false" tells the library to try other potential handlers for this shortcut
87 | });
88 |
89 | shortcuts.register ( 'CmdOrCtrl+ClickRight', event => { // Advanced mixed keyboard/mouse shortcut
90 | console.log ( 'Handling CmdOrCtrl+ClickRight' );
91 | return true;
92 | });
93 |
94 | // Let's register a Konami code, which basically is not affected by other registered shortcuts
95 |
96 | shortcuts.register ( 'Up Up Down Down Left Right Left Right B A', event => { // A Konami code
97 | console.log ( 'Secret shortcut triggered 🚀' );
98 | }, { konami: true } ); // Registering it as a Konami code
99 |
100 | // Let's register a shortcut but then dispose of it
101 |
102 | const dispose = shortcuts.register ( 'Shift+1', event => {
103 | console.log ( 'Handling Shift+1' );
104 | return true;
105 | });
106 |
107 | dispose (); // Unregistering that shortcut with that handler
108 |
109 | // Let's trigger a shortcut programmatically, perhaps for debugging purposes
110 |
111 | const handled = shortcuts.trigger ( 'Ctrl+F' );
112 |
113 | console.log ( handled ); // => true
114 |
115 | // Let's actually start listening for shortcuts
116 |
117 | shortcuts.start ();
118 |
119 | // Let's stop listening for shortcuts
120 |
121 | shortcuts.stop ();
122 |
123 | // Let's dispose of all registered shortcuts
124 |
125 | shortcuts.reset ();
126 |
127 | // Let's format a shortcut, with every supported format
128 |
129 | ShoSho.format ( 'ControlLeft+A', 'electron' ); // => 'Ctrl+A'
130 | ShoSho.format ( 'ControlLeft+A', 'symbols' ); // => '⌃A'
131 | ShoSho.format ( 'ControlLeft+A', 'long-flexible-directional' ); // => 'CommandOrControlLeft+A'
132 | ShoSho.format ( 'ControlLeft+A', 'long-flexible-nondirectional' ); // => 'CommandOrControl+A'
133 | ShoSho.format ( 'ControlLeft+A', 'long-inflexible-directional' ); // => 'ControlLeft+A'
134 | ShoSho.format ( 'ControlLeft+A', 'long-inflexible-nondirectional' ); // => 'Control+A'
135 | ShoSho.format ( 'ControlLeft+A', 'short-flexible-directional' ); // => 'CmdOrCtrlLeft+A'
136 | ShoSho.format ( 'ControlLeft+A', 'short-flexible-nondirectional' ); // => 'CmdOrCtrl+A'
137 | ShoSho.format ( 'ControlLeft+A', 'short-inflexible-directional' ); // => 'CtrlLeft+A'
138 | ShoSho.format ( 'ControlLeft+A', 'short-inflexible-nondirectional' ); // => 'Ctrl+A'
139 |
140 | // Let's check if something is a valid shortcut
141 |
142 | ShoSho.isShortcut ( 'Control+A' ); // => true
143 | ShoSho.isShortcut ( 'ControlA' ); // => false
144 |
145 | // Let's record a shortcut, which will require manual trimming and formatting
146 |
147 | const dispose = ShoSho.record ( shortcut => {
148 |
149 | console.log ( shortcut ); // => It could be 'A', 'CtrlLeft+A', 'CtrlLeft+A CtrlRight', 'CtrlLeft+A CtrlRight+B' and so on...
150 |
151 | const trimmed = shortcut.replace ( /(^.*?)((?:Control(?:Left|Right)\+K )*\S+$)/, '$2' ); // Allowing only 'ControlLeft+K' and 'ControlRight+K' to not be the last shortcut in the sequence
152 | const formatted = ShoSho.format ( trimmed, 'long-inflexible-nondirectional' ); // Ensuring the final shortcut is formatted exactly how we want it
153 |
154 | console.log ( formatted ); // => It could be 'K', 'CtrlLeft+K', 'CtrlLeft+K CtrlRight', 'Ctrlleft+K CtrlRight+A' and so on...
155 |
156 | });
157 |
158 | // Let's stop recording
159 |
160 | dispose ();
161 | ```
162 |
163 | ## License
164 |
165 | MIT © Fabio Spampinato
166 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 |
2 | /* MAIN */
3 |
4 | const DEFAULT_FORMAT_FORMAT = 'long-inflexible-nondirectional';
5 | const DEFAULT_RECORD_FORMAT = 'long-inflexible-directional';
6 |
7 | const MODIFIER_BITMASK = ( 1n << 15n ) - 1n; // Bitmask that includes all modifier keys and none of the triggers
8 | const TRIGGER_BITMASK = ( ( 1n << 160n ) - 1n ) ^ MODIFIER_BITMASK; // Bitmask that includes all trigger keys and none of the modifiers
9 | const UNSUPPORTED = TRIGGER_BITMASK;
10 |
11 | const PLUS_JOINER_RE = /(\S)\+/g;
12 | const WHITESPACE_JOINER_RE = /(\S)\s/g;
13 | const PLUSES_RE = /\+{2,}/gi;
14 | const WHITESPACE_RE = /\s+/gi;
15 |
16 | /* EXPORT */
17 |
18 | export {DEFAULT_FORMAT_FORMAT, DEFAULT_RECORD_FORMAT};
19 | export {MODIFIER_BITMASK, TRIGGER_BITMASK, UNSUPPORTED};
20 | export {PLUS_JOINER_RE, WHITESPACE_JOINER_RE, PLUSES_RE, WHITESPACE_RE};
21 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | /* IMPORT */
3 |
4 | import {DEFAULT_FORMAT_FORMAT, DEFAULT_RECORD_FORMAT} from './constants';
5 | import {MODIFIER_BITMASK, TRIGGER_BITMASK, UNSUPPORTED} from './constants';
6 | import {PLUS_JOINER_RE, PLUSES_RE, WHITESPACE_RE} from './constants';
7 | import {CODE2ID, CODE_RISKY2ID, ID2FORMAT_TITLE, ID2FORMAT_ELECTRON, ID2FORMAT_SYMBOL, KEY_UNSUPPORTED2ID, KEY2ID, MOUSE2ID, NAME2ID, NAME_FORMATTING2ID, WHICH2ID} from './maps';
8 | import {attempt, castArray, comparator, decompose, enumerate, first, isKeyboardEvent, isMac, isMouseEvent, isString, memoize, nope, or, orWith, takeRight, uniq, without, yep} from './utils';
9 | import type {Checker, Disposer, Format, Handler, ChordNode, HandlerNode, HandlerOptions, Options, RecordHandler, RecordOptions} from './types';
10 |
11 | /* HELPERS */ //TODO: Maybe move these elsewhere
12 |
13 | const has = ( id: bigint, key: bigint ): boolean => {
14 | return !!( id & key );
15 | };
16 |
17 | const id2trigger = ( id: bigint ): bigint => {
18 | return ( id & TRIGGER_BITMASK );
19 | };
20 |
21 | const shortcut2keys = memoize ( ( shortcut: string ): string[] => {
22 | const keys = shortcut.trim ().replace ( PLUSES_RE, '+Plus' ).replace ( PLUS_JOINER_RE, '$1 ' ).toLowerCase ().split ( WHITESPACE_RE ).filter ( Boolean );
23 | return keys;
24 | });
25 |
26 | const shortcut2ids = memoize ( ( shortcut: string ): bigint[][] => {
27 | const chords = shortcut.trim ().split ( WHITESPACE_RE );
28 | const parts = chords.map ( chord2ids );
29 | const ids = enumerate ( parts );
30 | return ids;
31 | });
32 |
33 | const shortcut2id_decomposed = memoize ( ( shortcut: string ): bigint[][] => {
34 | const chords = shortcut.trim ().split ( WHITESPACE_RE );
35 | const keys = chords.map ( shortcut2keys );
36 | const ids = keys.map ( keys => keys.map ( key => NAME_FORMATTING2ID[key] || UNSUPPORTED ) );
37 | return ids;
38 | });
39 |
40 | const chord2ids = memoize ( ( chord: string ): bigint[] => {
41 | const keys = shortcut2keys ( chord );
42 | const parts = keys.map ( key => NAME2ID[key] || UNSUPPORTED );
43 | const ids = enumerate ( parts ).map ( or );
44 | return ids;
45 | });
46 |
47 | const event2ids = ( event: Event ): bigint[] => { // Returning all possible detected ids, to support every scenario
48 | if ( isKeyboardEvent ( event ) ) {
49 | const codeId = CODE2ID[event.code] || 0n;
50 | if ( codeId ) return [codeId];
51 | const keyId = KEY2ID[event.key] || 0n;
52 | const whichId = WHICH2ID[event.which] || 0n;
53 | const codeRiskyId = KEY_UNSUPPORTED2ID[event.key] ? 0n : CODE_RISKY2ID[event.code] || 0n;
54 | return [keyId, whichId, codeRiskyId];
55 | } else if ( isMouseEvent ( event ) ) {
56 | return [MOUSE2ID[event.button] || 0n];
57 | } else {
58 | return [0n];
59 | }
60 | };
61 |
62 | /* MAIN */
63 |
64 | //TODO: Support character-based shortcuts (like Shift+#), by forking the current chords, i.e. properly
65 | //TODO: Support character-based shortcut triggering
66 | //TODO: Support deleting shortcuts with a filter, without the disposer function
67 | //TODO: Make sure there's always an Event object passed to handlers
68 | //TODO: Check if the event got stopped
69 |
70 | class ShoSho {
71 |
72 | /* VARIABLES */
73 |
74 | private active: boolean;
75 | private chords: bigint[];
76 | private chordsKonami: bigint[];
77 | private depth: number;
78 | private depthKonami: number;
79 | private depthRecorder: number;
80 | private shouldHandle: Checker;
81 | private options: Options;
82 | private recorder?: RecordHandler;
83 |
84 | private tree: ChordNode = {
85 | children: {},
86 | handlers: {
87 | parent: undefined,
88 | prev: undefined,
89 | next: undefined,
90 | handler: nope
91 | }
92 | };
93 |
94 | private treeKonami: ChordNode = {
95 | children: {},
96 | handlers: {
97 | parent: undefined,
98 | prev: undefined,
99 | next: undefined,
100 | handler: nope
101 | }
102 | };
103 |
104 | /* CONSTRUCTOR */
105 |
106 | constructor ( options: Options = {} ) {
107 |
108 | this.active = false;
109 | this.chords = [0n];
110 | this.chordsKonami = [0n];
111 | this.depth = 0;
112 | this.depthKonami = 0;
113 | this.depthRecorder = 0;
114 | this.shouldHandle = options.shouldHandleEvent || yep;
115 | this.options = options;
116 |
117 | }
118 |
119 | /* PRIVATE API */
120 |
121 | private onBlur = (): void => { // If the tab loses focus we might not receive keyup/mouseup events
122 |
123 | const index = this.chords.length - 1;
124 | const indexKonami = this.chordsKonami.length - 1;
125 |
126 | this.chords[index] = 0n;
127 | this.chordsKonami[indexKonami] = 0n;
128 |
129 | };
130 |
131 | private onNormalize = ( event: Event ): void => { // We might be missing some keydown/keyup events, safer to check again what the state of modifiers is
132 |
133 | if ( !isKeyboardEvent ( event ) && !isMouseEvent ( event ) ) return;
134 |
135 | const {altKey, ctrlKey, metaKey, shiftKey} = event;
136 | const code = isKeyboardEvent ( event ) ? event.code : '';
137 |
138 | const index = this.chords.length - 1;
139 | const indexKonami = this.chordsKonami.length - 1;
140 | const chord = this.chords[index];
141 |
142 | /* MISSING KEYDOWNS */ // We can't tell whether the left one or the right one was pressed, so we're taking a bit of a gamble on the left one //TODO: Maybe listen always to events to have that bit of data always available
143 |
144 | let addId = 0n;
145 |
146 | const hasAlt = has ( chord, CODE2ID.AltLeft ) || has ( chord, CODE2ID.AltRight );
147 | const hasCtrl = has ( chord, CODE2ID.ControlLeft ) || has ( chord, CODE2ID.ControlRight );
148 | const hasMeta = has ( chord, CODE2ID.MetaLeft ) || has ( chord, CODE2ID.MetaRight );
149 | const hasShift = has ( chord, CODE2ID.ShiftLeft ) || has ( chord, CODE2ID.ShiftRight );
150 |
151 | const isAlt = code === 'AltLeft' || code === 'AltRight';
152 | const isCtrl = code === 'ControlLeft' || code === 'ControlRight';
153 | const isMeta = code === 'MetaLeft' || code === 'MetaRight' || code === 'OSLeft' || code === 'OSRight';
154 | const isShift = code === 'ShiftLeft' || code === 'ShiftRight';
155 |
156 | if ( altKey && !hasAlt && !isAlt ) addId |= CODE2ID.AltLeft;
157 | if ( ctrlKey && !hasCtrl && !isCtrl ) addId |= CODE2ID.ControlLeft;
158 | if ( metaKey && !hasMeta && !isMeta ) addId |= CODE2ID.MetaLeft;
159 | if ( shiftKey && !hasShift && !isShift ) addId |= CODE2ID.ShiftLeft;
160 |
161 | if ( addId ) {
162 |
163 | this.chords[index] |= addId;
164 | this.chordsKonami[indexKonami] |= addId;
165 |
166 | }
167 |
168 | /* MISSING KEYUPS */
169 |
170 | let deleteId = 0n;
171 |
172 | if ( !altKey ) deleteId |= CODE2ID.AltLeft | CODE2ID.AltRight;
173 | if ( !ctrlKey ) deleteId |= CODE2ID.ControlLeft | CODE2ID.ControlRight;
174 | if ( !metaKey ) deleteId |= CODE2ID.MetaLeft | CODE2ID.MetaRight;
175 | if ( !shiftKey ) deleteId |= CODE2ID.ShiftLeft | CODE2ID.ShiftRight;
176 |
177 | if ( deleteId ) {
178 |
179 | this.chords[index] &=~ deleteId;
180 | this.chordsKonami[indexKonami] &=~ deleteId;
181 |
182 | }
183 |
184 | };
185 |
186 | private onRecord = ( ids: bigint[] ): void => {
187 |
188 | if ( !this.recorder ) return;
189 |
190 | const shortcut = first ( orWith ( this.chords, ids ).reverse ().map ( shortcut => ShoSho.format ( shortcut, DEFAULT_RECORD_FORMAT ) ) );
191 |
192 | if ( !shortcut ) return;
193 |
194 | this.recorder ( shortcut );
195 |
196 | };
197 |
198 | private onDown = ( event: Event ): void => {
199 |
200 | if ( !this.shouldHandle ( event ) ) return;
201 |
202 | this.onNormalize ( event );
203 |
204 | const index = this.chords.length - 1;
205 | const indexKonami = this.chordsKonami.length - 1;
206 | const chord = this.chords[index];
207 | const chordKonami = this.chordsKonami[indexKonami];
208 | const chordNoTrigger = chord & MODIFIER_BITMASK;
209 | const chordKonamiNoTrigger = chordKonami & MODIFIER_BITMASK;
210 | const chords = ( chord === chordNoTrigger ) ? [[chord, chordKonami]] : [[chord, chordKonami], [chordNoTrigger, chordKonamiNoTrigger]]; // Not being super strict about multi-key shortcuts here
211 | const ids = without ( uniq ( event2ids ( event ) ), 0n );
212 |
213 | this.onRecord ( ids );
214 |
215 | for ( let i = 0, l = ids.length; i < l; i++ ) {
216 |
217 | const id = ids[i];
218 | const isLastId = ( i === l - 1 );
219 |
220 | for ( let ci = 0, cl = chords.length; ci < cl; ci++ ) {
221 |
222 | const [chord, chordKonami] = chords[ci];
223 | const isLastChord = ( ci === cl - 1 );
224 |
225 | this.chords[index] = chord | id;
226 | this.chordsKonami[indexKonami] = chordKonami | id;
227 |
228 | const handled = !this.recorder && attempt ( () => this.trigger ( this.chords, event ), false );
229 | const triggered = !!id2trigger ( this.chords[index] );
230 | const isLast = handled || ( isLastId && isLastChord );
231 |
232 | if ( !isLast ) continue;
233 |
234 | if ( handled ) {
235 |
236 | if ( !triggered || isMouseEvent ( event ) || chord & CODE2ID.MetaLeft || chord & CODE2ID.MetaRight ) { // Meta keys are buggy, they will swallow keyup events for the trigger
237 |
238 | this.chords = triggered ? [this.chords[index] &~ id] : [0n];
239 |
240 | }
241 |
242 | event.preventDefault ();
243 | event.stopImmediatePropagation ();
244 |
245 | } else if ( triggered ) {
246 |
247 | this.chords.push ( this.chords[index] &~ id );
248 |
249 | }
250 |
251 | if ( triggered ) {
252 |
253 | this.chordsKonami.push ( this.chordsKonami[indexKonami] &~ id );
254 |
255 | }
256 |
257 | if ( this.recorder ) {
258 |
259 | if ( this.chords.length > this.depthRecorder ) {
260 |
261 | this.chords = takeRight ( this.chords, Math.max ( 1, this.depthRecorder ) );
262 |
263 | }
264 |
265 | this.chordsKonami = [0n];
266 |
267 | } else {
268 |
269 | if ( this.chords.length > this.depth ) {
270 |
271 | this.chords = takeRight ( this.chords, Math.max ( 1, this.depth ) );
272 |
273 | }
274 |
275 | if ( this.chordsKonami.length > this.depthKonami ) {
276 |
277 | this.chordsKonami = takeRight ( this.chordsKonami, Math.max ( 1, this.depthKonami ) );
278 |
279 | }
280 |
281 | }
282 |
283 | break;
284 |
285 | }
286 |
287 | }
288 |
289 | };
290 |
291 | private onUp = ( event: Event ): void => {
292 |
293 | if ( !this.shouldHandle ( event ) ) return;
294 |
295 | const index = this.chords.length - 1;
296 | const indexKonami = this.chordsKonami.length - 1;
297 | const id = or ( event2ids ( event ) );
298 |
299 | this.chords[index] &=~ id;
300 | this.chordsKonami[indexKonami] &=~ id;
301 |
302 | };
303 |
304 | /* PUBLIC API */
305 |
306 | record = ( handler: RecordHandler ): Disposer => {
307 |
308 | const isStartedAlready = this.active;
309 |
310 | this.chords = [0n];
311 | this.chordsKonami = [0n];
312 | this.recorder = handler;
313 |
314 | if ( !isStartedAlready ) {
315 |
316 | this.start ();
317 |
318 | }
319 |
320 | return (): void => {
321 |
322 | this.chords = [0n];
323 | this.chordsKonami = [0n];
324 | this.recorder = undefined;
325 |
326 | if ( !isStartedAlready ) {
327 |
328 | this.stop ();
329 |
330 | }
331 |
332 | };
333 |
334 | };
335 |
336 | register = ( shortcut: string | string[], handler: Handler, options: HandlerOptions = {} ): Disposer => {
337 |
338 | const chordseses = castArray ( shortcut ).map ( shortcut2ids );
339 | const nodes: HandlerNode[] = [];
340 | const konami = !!options.konami;
341 |
342 | /* REGISTER */
343 |
344 | for ( const chordses of chordseses ) {
345 |
346 | for ( const chords of chordses ) {
347 |
348 | let node = konami ? this.treeKonami : this.tree;
349 | let depth = 0;
350 |
351 | for ( const chord of chords ) {
352 |
353 | node = node.children[String ( chord )] ||= {
354 | children: {},
355 | handlers: {
356 | parent: undefined,
357 | prev: undefined,
358 | next: undefined,
359 | handler: nope
360 | }
361 | };
362 |
363 | depth += 1;
364 |
365 | }
366 |
367 | if ( konami ) {
368 |
369 | this.depthKonami = Math.max ( this.depthKonami, depth );
370 |
371 | } else {
372 |
373 | this.depth = Math.max ( this.depth, depth );
374 |
375 | }
376 |
377 | const handlers = node.handlers;
378 |
379 | node.handlers = handlers.next = {
380 | parent: node,
381 | prev: handlers,
382 | next: undefined,
383 | handler
384 | };
385 |
386 | nodes.push ( node.handlers );
387 |
388 | }
389 |
390 | }
391 |
392 | /* UNREGISTER */
393 |
394 | return (): void => {
395 |
396 | for ( const node of nodes ) {
397 |
398 | const {prev, next} = node;
399 |
400 | if ( prev ) prev.next = next;
401 | if ( next ) next.prev = prev;
402 | if ( !next && node.parent ) node.parent.handlers = prev!; //TSC //UGLY: Write this better
403 |
404 | }
405 |
406 | };
407 |
408 | };
409 |
410 | reset = (): void => {
411 |
412 | this.tree.children = {};
413 | this.treeKonami.children = {};
414 |
415 | };
416 |
417 | trigger = ( shortcut: string | bigint | bigint[], event?: Event ) : boolean => {
418 |
419 | const chords = isString ( shortcut ) ? first ( shortcut2ids ( shortcut ) ) : castArray ( shortcut );
420 | const chordsKonami = this.chordsKonami; //TODO: Ugly, maybe there should just be two trigger functions, write this better
421 |
422 | if ( !chords ) return false;
423 |
424 | if ( this.depthKonami ) { // Triggering konami shortcuts, which are not stopped by, and don't stop, propagation
425 |
426 | for ( let i = 0, l = chordsKonami.length; i < l; i++ ) { // Trying all possible sequences
427 |
428 | const slice = i ? chordsKonami.slice ( i ) : chordsKonami;
429 |
430 | let node: ChordNode | undefined = this.treeKonami;
431 |
432 | for ( const chord of slice ) {
433 |
434 | node = node?.children[String ( chord )];
435 |
436 | }
437 |
438 | let handler = node?.handlers;
439 |
440 | while ( handler ) {
441 |
442 | handler.handler ( event );
443 | handler = handler.prev;
444 |
445 | }
446 |
447 | }
448 |
449 | }
450 |
451 | if ( this.depth ) { // Triggering regular shortcuts, which are stopped by, and stop, propagation
452 |
453 | for ( let i = 0, l = chords.length; i < l; i++ ) { // Trying all possible sequences
454 |
455 | const slice = i ? chords.slice ( i ) : chords;
456 |
457 | let node: ChordNode | undefined = this.tree;
458 |
459 | for ( const chord of slice ) {
460 |
461 | node = node?.children[String ( chord )];
462 |
463 | }
464 |
465 | let handler = node?.handlers;
466 |
467 | while ( handler ) {
468 |
469 | if ( handler.handler ( event ) !== true ) {
470 |
471 | handler = handler.prev;
472 |
473 | } else {
474 |
475 | return true;
476 |
477 | }
478 |
479 | }
480 |
481 | }
482 |
483 | }
484 |
485 | return false;
486 |
487 | };
488 |
489 | start = (): void => {
490 |
491 | if ( this.active ) return;
492 |
493 | this.active = true;
494 |
495 | const capture = this.options.capture ?? false;
496 | const root = this.options.target || document;
497 |
498 | root.addEventListener ( 'keydown', this.onDown, { capture } );
499 | root.addEventListener ( 'keyup', this.onUp, { capture } );
500 | root.addEventListener ( 'click', this.onDown, { capture } );
501 | root.addEventListener ( 'auxclick', this.onDown, { capture } );
502 | root.addEventListener ( 'contextmenu', this.onDown, { capture } );
503 |
504 | window.addEventListener ( 'blur', this.onBlur );
505 |
506 | };
507 |
508 | stop = (): void => {
509 |
510 | if ( !this.active ) return;
511 |
512 | this.active = false;
513 |
514 | const capture = this.options.capture ?? false;
515 | const root = this.options.target || document;
516 |
517 | root.removeEventListener ( 'keydown', this.onDown, { capture } );
518 | root.removeEventListener ( 'keyup', this.onUp, { capture } );
519 | root.removeEventListener ( 'click', this.onDown, { capture } );
520 | root.removeEventListener ( 'auxclick', this.onDown, { capture } );
521 | root.removeEventListener ( 'contextmenu', this.onDown, { capture } );
522 |
523 | window.removeEventListener ( 'blur', this.onBlur );
524 |
525 | };
526 |
527 | /* STATIC API */
528 |
529 | static format = ( shortcut: string | bigint | bigint[], format: Format = DEFAULT_FORMAT_FORMAT ): string => {
530 |
531 | const formatMap = ( format === 'electron' ) ? ID2FORMAT_ELECTRON : ( format === 'symbols' ) ? ID2FORMAT_SYMBOL : ID2FORMAT_TITLE;
532 | const formatKeysJoiner = ( format === 'symbols' ) ? '' : '+';
533 | const formatShortcutsJoiner = ' ';
534 |
535 | let outputIds = isString ( shortcut ) ? shortcut2id_decomposed ( shortcut ) : castArray ( shortcut ).map ( chord => ( chord === UNSUPPORTED ) ? [] : decompose ( chord ) );
536 | let output = outputIds.map ( keys => keys.sort ( comparator ).map ( key => formatMap[key] || '§' ).join ( formatKeysJoiner ) ).join ( formatShortcutsJoiner );
537 |
538 | if ( format.includes ( '-nondirectional' ) ) {
539 |
540 | /* SPARING LEFT+RIGHT MODIFIERS IN A SHORTCUT */
541 |
542 | output = output.replace ( /(CommandOrControl|Control|Command|Alt|Shift)Left(\S+)\1Right/g, '$1§eft$2$1§ight' );
543 | output = output.replace ( /(CommandOrControl|Control|Command|Alt|Shift)Right(\S+)\1Left/g, '$1§ight$2$1§eft' );
544 |
545 | /* REMOVING LEFT/RIGHT SUFFIXES FROM MODIFIERS */
546 |
547 | output = output.replace ( /(Control|Command|Alt|Shift)(Left|Right)/g, '$1' );
548 |
549 | /* RESTORING LEFT+RIGHT MODIFIERS IN A SHORTCUT */
550 |
551 | output = output.replaceAll ( '§eft', 'Left' );
552 | output = output.replaceAll ( '§ight', 'Right' );
553 |
554 | }
555 |
556 | if ( format.includes ( '-inflexible' ) ) {
557 |
558 | /* RESOLVING FLEXIBLE MODIFIERS */
559 |
560 | const replacement = isMac () ? 'Command' : 'Control';
561 |
562 | output = output.replace ( /Command(Left|Right)?OrControl/g, replacement );
563 |
564 | }
565 |
566 | if ( format.includes ( '-flexible' ) ) {
567 |
568 | /* SPARING CONTROL+COMMAND IN A SHORTCUT */
569 |
570 | output = output.replace ( /Control(\S+)Command/g, '§ontrol$1§ommand' );
571 | output = output.replace ( /Command(\S+)Control/g, '§ommand$1§ontrol' );
572 |
573 | /* MAKING OTHERS CONTROLS/COMMANDS FLEXIBLE */
574 |
575 | output = output.replace ( /(^|\W)Control/g, '$1CommandOrControl' );
576 | output = output.replace ( /Command(?!Or|Left|Right)/g, 'CommandOrControl' );
577 | output = output.replace ( /CommandLeft(?!Or)/g, 'CommandOrControlLeft' );
578 | output = output.replace ( /CommandRight(?!Or)/g, 'CommandOrControlRight' );
579 |
580 | /* RESTORING CONTROL+COMMAND IN A SHORTCUT */
581 |
582 | output = output.replaceAll ( '§ontrol', 'Control' );
583 | output = output.replaceAll ( '§ommand', 'Command' );
584 |
585 | }
586 |
587 | if ( format.includes ( 'short-' ) ) {
588 |
589 | /* SHORTHENING MODIFIERS */
590 |
591 | output = output.replaceAll ( 'Command', 'Cmd' );
592 | output = output.replaceAll ( 'Control', 'Ctrl' );
593 |
594 | }
595 |
596 | return output;
597 |
598 | };
599 |
600 | static isShortcut = memoize ( ( shortcut: string ): boolean => {
601 |
602 | const keys = shortcut2keys ( shortcut );
603 | const isValid = keys.every ( key => key in NAME2ID );
604 |
605 | return isValid;
606 |
607 | });
608 |
609 | static record = ( handler: RecordHandler, options: RecordOptions = {} ): Disposer => {
610 |
611 | const shouldHandle = options.shouldHandleEvent || yep;
612 |
613 | const shortcuts = new ShoSho ({
614 | capture: true,
615 | target: window,
616 | shouldHandleEvent: event => {
617 | if ( !shouldHandle ( event ) ) return false;
618 | if ( !options.passthrough ) {
619 | event.preventDefault ();
620 | event.stopImmediatePropagation ();
621 | }
622 | return true;
623 | }
624 | });
625 |
626 | shortcuts.depthRecorder = options.limit ?? 10;
627 |
628 | return shortcuts.record ( handler );
629 |
630 | };
631 |
632 | }
633 |
634 | /* EXPORT */
635 |
636 | export default ShoSho;
637 | export type {Format, Handler, HandlerOptions, Options, RecordHandler, RecordOptions};
638 |
--------------------------------------------------------------------------------
/src/maps.ts:
--------------------------------------------------------------------------------
1 |
2 | /* IMPORT */
3 |
4 | import {isMac} from './utils';
5 |
6 | /* MODIFIERS */
7 |
8 | const ControlLeft = 1n << 0n;
9 | const ControlRight = 1n << 1n;
10 | const MetaLeft = 1n << 2n;
11 | const MetaRight = 1n << 3n;
12 | const OSLeft = 1n << 2n;
13 | const OSRight = 1n << 3n;
14 | const AltLeft = 1n << 4n;
15 | const AltRight = 1n << 5n;
16 | const ShiftLeft = 1n << 6n;
17 | const ShiftRight = 1n << 7n;
18 |
19 | /* VIRTUAL MODIFIERS */
20 |
21 | const Control = 1n << 8n;
22 | const Command = 1n << 9n;
23 | const Alt = 1n << 10n;
24 | const Shift = 1n << 11n;
25 | const CommandOrControl = 1n << 12n;
26 | const CommandOrControlLeft = 1n << 13n;
27 | const CommandOrControlRight = 1n << 14n;
28 |
29 | /* DIGITS */
30 |
31 | const Digit0 = 1n << 15n;
32 | const Digit1 = 1n << 16n;
33 | const Digit2 = 1n << 17n;
34 | const Digit3 = 1n << 18n;
35 | const Digit4 = 1n << 19n;
36 | const Digit5 = 1n << 20n;
37 | const Digit6 = 1n << 21n;
38 | const Digit7 = 1n << 22n;
39 | const Digit8 = 1n << 23n;
40 | const Digit9 = 1n << 24n;
41 |
42 | /* ALPHABET */
43 |
44 | const KeyA = 1n << 25n;
45 | const KeyB = 1n << 26n;
46 | const KeyC = 1n << 27n;
47 | const KeyD = 1n << 28n;
48 | const KeyE = 1n << 29n;
49 | const KeyF = 1n << 30n;
50 | const KeyG = 1n << 31n;
51 | const KeyH = 1n << 32n;
52 | const KeyI = 1n << 33n;
53 | const KeyJ = 1n << 34n;
54 | const KeyK = 1n << 35n;
55 | const KeyL = 1n << 36n;
56 | const KeyM = 1n << 37n;
57 | const KeyN = 1n << 38n;
58 | const KeyO = 1n << 39n;
59 | const KeyP = 1n << 40n;
60 | const KeyQ = 1n << 41n;
61 | const KeyR = 1n << 42n;
62 | const KeyS = 1n << 43n;
63 | const KeyT = 1n << 44n;
64 | const KeyU = 1n << 45n;
65 | const KeyV = 1n << 46n;
66 | const KeyW = 1n << 47n;
67 | const KeyX = 1n << 48n;
68 | const KeyY = 1n << 49n;
69 | const KeyZ = 1n << 50n;
70 |
71 | /* FUNCTION KEYS */
72 |
73 | const F1 = 1n << 51n;
74 | const F2 = 1n << 52n;
75 | const F3 = 1n << 53n;
76 | const F4 = 1n << 54n;
77 | const F5 = 1n << 55n;
78 | const F6 = 1n << 56n;
79 | const F7 = 1n << 57n;
80 | const F8 = 1n << 58n;
81 | const F9 = 1n << 59n;
82 | const F10 = 1n << 60n;
83 | const F11 = 1n << 61n;
84 | const F12 = 1n << 62n;
85 | const F13 = 1n << 63n;
86 | const F14 = 1n << 64n;
87 | const F15 = 1n << 65n;
88 | const F16 = 1n << 66n;
89 | const F17 = 1n << 67n;
90 | const F18 = 1n << 68n;
91 | const F19 = 1n << 69n;
92 | const F20 = 1n << 70n;
93 | const F21 = 1n << 71n;
94 | const F22 = 1n << 72n;
95 | const F23 = 1n << 73n;
96 | const F24 = 1n << 74n;
97 |
98 | /* NUMPAD DIGITS */
99 |
100 | const Numpad0 = 1n << 75n;
101 | const Numpad1 = 1n << 76n;
102 | const Numpad2 = 1n << 77n;
103 | const Numpad3 = 1n << 78n;
104 | const Numpad4 = 1n << 79n;
105 | const Numpad5 = 1n << 80n;
106 | const Numpad6 = 1n << 81n;
107 | const Numpad7 = 1n << 82n;
108 | const Numpad8 = 1n << 83n;
109 | const Numpad9 = 1n << 84n;
110 |
111 | /* NUMPAD OPERATORS */
112 |
113 | const NumpadAdd = 1n << 85n;
114 | const NumpadComma = 1n << 86n;
115 | const NumpadDecimal = 1n << 87n;
116 | const NumpadDivide = 1n << 88n;
117 | const NumpadEnter = 1n << 89n;
118 | const NumpadEqual = 1n << 90n;
119 | const NumpadMultiply = 1n << 91n;
120 | const NumpadSubtract = 1n << 92n;
121 |
122 | /* SPECIAL KEYS */
123 |
124 | const ArrowDown = 1n << 93n;
125 | const ArrowLeft = 1n << 94n;
126 | const ArrowRight = 1n << 95n;
127 | const ArrowUp = 1n << 96n;
128 | const Backspace = 1n << 97n;
129 | const CapsLock = 1n << 98n;
130 | const Delete = 1n << 99n;
131 | const End = 1n << 100n;
132 | const Enter = 1n << 101n;
133 | const Escape = 1n << 102n;
134 | const Home = 1n << 103n;
135 | const Insert = 1n << 104n;
136 | const PageDown = 1n << 105n;
137 | const PageUp = 1n << 106n;
138 | const Space = 1n << 107n;
139 | const Tab = 1n << 108n;
140 |
141 | /* PUNCTUATION KEYS */
142 |
143 | const Backquote = 1n << 109n;
144 | const Backslash = 1n << 110n;
145 | const BracketLeft = 1n << 111n;
146 | const BracketRight = 1n << 112n;
147 | const Comma = 1n << 113n;
148 | const Equal = 1n << 114n;
149 | const IntlBackslash = 1n << 115n;
150 | const Minus = 1n << 116n;
151 | const Period = 1n << 117n;
152 | const Quote = 1n << 118n;
153 | const Semicolon = 1n << 119n;
154 | const Slash = 1n << 120n;
155 |
156 | /* OTHER KEYS */
157 |
158 | const NumLock = 1n << 121n;
159 | const ScrollLock = 1n << 122n;
160 |
161 | /* CUSTOM KEYBOARD KEYS */
162 |
163 | const Ampersand = 1n << 123n;
164 | const Asterisk = 1n << 124n;
165 | const At = 1n << 125n;
166 | const BraceLeft = 1n << 126n;
167 | const BraceRight = 1n << 127n;
168 | const Caret = 1n << 128n;
169 | const Colon = 1n << 129n;
170 | const Dollar = 1n << 130n;
171 | const DoubleQuote = 1n << 131n;
172 | const ExclamationMark = 1n << 132n;
173 | const GreaterThan = 1n << 133n;
174 | const Hash = 1n << 134n;
175 | const LessThan = 1n << 135n;
176 | const ParenthesisLeft = 1n << 136n;
177 | const ParenthesisRight = 1n << 137n;
178 | const Percent = 1n << 138n;
179 | const Pipe = 1n << 139n;
180 | const Plus = 1n << 140n;
181 | const QuestionMark = 1n << 141n;
182 | const Tilde = 1n << 142n;
183 | const Underscore = 1n << 143n;
184 |
185 | /* CUSTOM MOUSE KEYS */
186 |
187 | const Mouse0 = 1n << 144n;
188 | const Mouse1 = 1n << 145n;
189 | const Mouse2 = 1n << 146n;
190 | const Mouse3 = 1n << 147n;
191 | const Mouse4 = 1n << 148n;
192 | const Mouse5 = 1n << 149n;
193 | const Mouse6 = 1n << 150n;
194 | const Mouse7 = 1n << 151n;
195 | const Mouse8 = 1n << 152n;
196 | const Mouse9 = 1n << 153n;
197 |
198 | /* UNSUPPORTED KEYS */
199 |
200 | const Section = 1n << 154n;
201 | const PlusMinus = 1n << 155n;
202 |
203 | /* MAIN */
204 |
205 | const CODE2ID = {
206 | /* MODIFIERS */
207 | ControlLeft,
208 | ControlRight,
209 | MetaLeft,
210 | MetaRight,
211 | OSLeft,
212 | OSRight,
213 | AltLeft,
214 | AltRight,
215 | ShiftLeft,
216 | ShiftRight,
217 | /* FUNCTION KEYS */
218 | F1,
219 | F2,
220 | F3,
221 | F4,
222 | F5,
223 | F6,
224 | F7,
225 | F8,
226 | F9,
227 | F10,
228 | F11,
229 | F12,
230 | F13,
231 | F14,
232 | F15,
233 | F16,
234 | F17,
235 | F18,
236 | F19,
237 | F20,
238 | F21,
239 | F22,
240 | F23,
241 | F24,
242 | /* NUMPAD DIGITS */
243 | Numpad0,
244 | Numpad1,
245 | Numpad2,
246 | Numpad3,
247 | Numpad4,
248 | Numpad5,
249 | Numpad6,
250 | Numpad7,
251 | Numpad8,
252 | Numpad9,
253 | /* NUMPAD OPERATORS */
254 | NumpadAdd,
255 | NumpadComma,
256 | NumpadDecimal,
257 | NumpadDivide,
258 | NumpadEnter,
259 | NumpadEqual,
260 | NumpadMultiply,
261 | NumpadSubtract,
262 | /* SPECIAL KEYS */
263 | ArrowDown,
264 | ArrowLeft,
265 | ArrowRight,
266 | ArrowUp,
267 | Backspace,
268 | CapsLock,
269 | Delete,
270 | End,
271 | Enter,
272 | Escape,
273 | Home,
274 | Insert,
275 | PageDown,
276 | PageUp,
277 | Space,
278 | Tab,
279 | /* OTHER KEYS */
280 | NumLock,
281 | ScrollLock
282 | };
283 |
284 | const CODE_RISKY2ID = {
285 | /* PUNCTUATION */
286 | Backquote,
287 | Backslash,
288 | BracketLeft,
289 | BracketRight,
290 | Comma,
291 | Equal,
292 | IntlBackslash,
293 | Minus,
294 | Period,
295 | Quote,
296 | Semicolon,
297 | Slash
298 | };
299 |
300 | const ID2FORMAT_TITLE = {
301 | /* MODIFIERS */
302 | [ControlLeft]: 'ControlLeft',
303 | [ControlRight]: 'ControlRight',
304 | [MetaLeft]: 'CommandLeft',
305 | [MetaRight]: 'CommandRight',
306 | [OSLeft]: 'CommandLeft',
307 | [OSRight]: 'CommandRight',
308 | [AltLeft]: 'AltLeft',
309 | [AltRight]: 'AltRight',
310 | [ShiftLeft]: 'ShiftLeft',
311 | [ShiftRight]: 'ShiftRight',
312 | /* VIRTUAL MODIFIERS */
313 | [Control]: 'Control',
314 | [Command]: 'Command',
315 | [Alt]: 'Alt',
316 | [Shift]: 'Shift',
317 | [CommandOrControl]: 'CommandOrControl',
318 | [CommandOrControlLeft]: 'CommandOrControlLeft',
319 | [CommandOrControlRight]: 'CommandOrControlRight',
320 | /* DIGITS */
321 | [Digit0]: '0',
322 | [Digit1]: '1',
323 | [Digit2]: '2',
324 | [Digit3]: '3',
325 | [Digit4]: '4',
326 | [Digit5]: '5',
327 | [Digit6]: '6',
328 | [Digit7]: '7',
329 | [Digit8]: '8',
330 | [Digit9]: '9',
331 | /* ALPHABET */
332 | [KeyA]: 'A',
333 | [KeyB]: 'B',
334 | [KeyC]: 'C',
335 | [KeyD]: 'D',
336 | [KeyE]: 'E',
337 | [KeyF]: 'F',
338 | [KeyG]: 'G',
339 | [KeyH]: 'H',
340 | [KeyI]: 'I',
341 | [KeyJ]: 'J',
342 | [KeyK]: 'K',
343 | [KeyL]: 'L',
344 | [KeyM]: 'M',
345 | [KeyN]: 'N',
346 | [KeyO]: 'O',
347 | [KeyP]: 'P',
348 | [KeyQ]: 'Q',
349 | [KeyR]: 'R',
350 | [KeyS]: 'S',
351 | [KeyT]: 'T',
352 | [KeyU]: 'U',
353 | [KeyV]: 'V',
354 | [KeyW]: 'W',
355 | [KeyX]: 'X',
356 | [KeyY]: 'Y',
357 | [KeyZ]: 'Z',
358 | /* FUNCTION KEYS */
359 | [F1]: 'F1',
360 | [F2]: 'F2',
361 | [F3]: 'F3',
362 | [F4]: 'F4',
363 | [F5]: 'F5',
364 | [F6]: 'F6',
365 | [F7]: 'F7',
366 | [F8]: 'F8',
367 | [F9]: 'F9',
368 | [F10]: 'F10',
369 | [F11]: 'F11',
370 | [F12]: 'F12',
371 | [F13]: 'F13',
372 | [F14]: 'F14',
373 | [F15]: 'F15',
374 | [F16]: 'F16',
375 | [F17]: 'F17',
376 | [F18]: 'F18',
377 | [F19]: 'F19',
378 | [F20]: 'F20',
379 | [F21]: 'F21',
380 | [F22]: 'F22',
381 | [F23]: 'F23',
382 | [F24]: 'F24',
383 | /* NUMPAD DIGITS */
384 | [Numpad0]: 'Numpad0',
385 | [Numpad1]: 'Numpad1',
386 | [Numpad2]: 'Numpad2',
387 | [Numpad3]: 'Numpad3',
388 | [Numpad4]: 'Numpad4',
389 | [Numpad5]: 'Numpad5',
390 | [Numpad6]: 'Numpad6',
391 | [Numpad7]: 'Numpad7',
392 | [Numpad8]: 'Numpad8',
393 | [Numpad9]: 'Numpad9',
394 | /* NUMPAD OPERATORS */
395 | [NumpadAdd]: 'NumpadAdd',
396 | [NumpadComma]: 'NumpadComma',
397 | [NumpadDecimal]: 'NumpadDecimal',
398 | [NumpadDivide]: 'NumpadDivide',
399 | [NumpadEnter]: 'NumpadEnter',
400 | [NumpadEqual]: 'NumpadEqual',
401 | [NumpadMultiply]: 'NumpadMultiply',
402 | [NumpadSubtract]: 'NumpadSubtract',
403 | /* SPECIAL KEYS */
404 | [ArrowDown]: 'Down',
405 | [ArrowLeft]: 'Left',
406 | [ArrowRight]: 'Right',
407 | [ArrowUp]: 'Up',
408 | [Backspace]: 'Backspace',
409 | [CapsLock]: 'CapsLock',
410 | [Delete]: 'Delete',
411 | [End]: 'End',
412 | [Enter]: 'Enter',
413 | [Escape]: 'Esc',
414 | [Home]: 'Home',
415 | [Insert]: 'Insert',
416 | [PageDown]: 'PageDown',
417 | [PageUp]: 'PageUp',
418 | [Space]: 'Space',
419 | [Tab]: 'Tab',
420 | /* PUNCTUATION KEYS */
421 | [Backquote]: '`',
422 | [Backslash]: '\\',
423 | [BracketLeft]: '[',
424 | [BracketRight]: ']',
425 | [Comma]: ',',
426 | [Equal]: '=',
427 | [Minus]: '-',
428 | [Period]: '.',
429 | [Quote]: '\'',
430 | [Semicolon]: ';',
431 | [Slash]: '/',
432 | /* OTHER KEYS */
433 | [NumLock]: 'NumLock',
434 | [ScrollLock]: 'ScrollLock',
435 | /* CUSTOM KEYBOARD KEYS */
436 | [Ampersand]: '&',
437 | [Asterisk]: '*',
438 | [At]: '@',
439 | [BraceLeft]: '{',
440 | [BraceRight]: '}',
441 | [Caret]: '^',
442 | [Colon]: ':',
443 | [Dollar]: '$',
444 | [DoubleQuote]: '"',
445 | [ExclamationMark]: '!',
446 | [GreaterThan]: '>',
447 | [Hash]: '#',
448 | [LessThan]: '<',
449 | [ParenthesisLeft]: '(',
450 | [ParenthesisRight]: ')',
451 | [Percent]: '%',
452 | [Pipe]: '|',
453 | [Plus]: '+',
454 | [QuestionMark]: '?',
455 | [Tilde]: '~',
456 | [Underscore]: '_',
457 | /* CUSTOM MOUSE KEYS */
458 | [Mouse0]: 'LeftClick',
459 | [Mouse1]: 'MiddleClick',
460 | [Mouse2]: 'RightClick',
461 | [Mouse3]: 'Mouse3',
462 | [Mouse4]: 'Mouse4',
463 | [Mouse5]: 'Mouse5',
464 | [Mouse6]: 'Mouse6',
465 | [Mouse7]: 'Mouse7',
466 | [Mouse8]: 'Mouse8',
467 | [Mouse9]: 'Mouse9',
468 | /* UNSUPPORTED KEYS */
469 | [Section]: '§',
470 | [PlusMinus]: '±'
471 | };
472 |
473 | const ID2FORMAT_ELECTRON = {
474 | /* MODIFIERS */
475 | [ControlLeft]: 'Ctrl',
476 | [ControlRight]: 'Ctrl',
477 | [MetaLeft]: 'Cmd',
478 | [MetaRight]: 'Cmd',
479 | [OSLeft]: 'Cmd',
480 | [OSRight]: 'Cmd',
481 | [AltLeft]: 'Alt',
482 | [AltRight]: 'Alt',
483 | [ShiftLeft]: 'Shift',
484 | [ShiftRight]: 'Shift',
485 | /* VIRTUAL MODIFIERS */
486 | [Control]: 'Ctrl',
487 | [Command]: 'Cmd',
488 | [Alt]: 'Alt',
489 | [Shift]: 'Shift',
490 | [CommandOrControl]: 'CmdOrCtrl',
491 | [CommandOrControlLeft]: 'CmdOrCtrl',
492 | [CommandOrControlRight]: 'CmdOrCtrl',
493 | /* DIGITS */
494 | [Digit0]: '0',
495 | [Digit1]: '1',
496 | [Digit2]: '2',
497 | [Digit3]: '3',
498 | [Digit4]: '4',
499 | [Digit5]: '5',
500 | [Digit6]: '6',
501 | [Digit7]: '7',
502 | [Digit8]: '8',
503 | [Digit9]: '9',
504 | /* ALPHABET */
505 | [KeyA]: 'A',
506 | [KeyB]: 'B',
507 | [KeyC]: 'C',
508 | [KeyD]: 'D',
509 | [KeyE]: 'E',
510 | [KeyF]: 'F',
511 | [KeyG]: 'G',
512 | [KeyH]: 'H',
513 | [KeyI]: 'I',
514 | [KeyJ]: 'J',
515 | [KeyK]: 'K',
516 | [KeyL]: 'L',
517 | [KeyM]: 'M',
518 | [KeyN]: 'N',
519 | [KeyO]: 'O',
520 | [KeyP]: 'P',
521 | [KeyQ]: 'Q',
522 | [KeyR]: 'R',
523 | [KeyS]: 'S',
524 | [KeyT]: 'T',
525 | [KeyU]: 'U',
526 | [KeyV]: 'V',
527 | [KeyW]: 'W',
528 | [KeyX]: 'X',
529 | [KeyY]: 'Y',
530 | [KeyZ]: 'Z',
531 | /* FUNCTION KEYS */
532 | [F1]: 'F1',
533 | [F2]: 'F2',
534 | [F3]: 'F3',
535 | [F4]: 'F4',
536 | [F5]: 'F5',
537 | [F6]: 'F6',
538 | [F7]: 'F7',
539 | [F8]: 'F8',
540 | [F9]: 'F9',
541 | [F10]: 'F10',
542 | [F11]: 'F11',
543 | [F12]: 'F12',
544 | [F13]: 'F13',
545 | [F14]: 'F14',
546 | [F15]: 'F15',
547 | [F16]: 'F16',
548 | [F17]: 'F17',
549 | [F18]: 'F18',
550 | [F19]: 'F19',
551 | [F20]: 'F20',
552 | [F21]: 'F21',
553 | [F22]: 'F22',
554 | [F23]: 'F23',
555 | [F24]: 'F24',
556 | /* NUMPAD DIGITS */
557 | [Numpad0]: 'Num0',
558 | [Numpad1]: 'Num1',
559 | [Numpad2]: 'Num2',
560 | [Numpad3]: 'Num3',
561 | [Numpad4]: 'Num4',
562 | [Numpad5]: 'Num5',
563 | [Numpad6]: 'Num6',
564 | [Numpad7]: 'Num7',
565 | [Numpad8]: 'Num8',
566 | [Numpad9]: 'Num9',
567 | /* NUMPAD OPERATORS */
568 | [NumpadAdd]: 'NumAdd',
569 | [NumpadComma]: '§',
570 | [NumpadDecimal]: 'NumDec',
571 | [NumpadDivide]: 'NumDiv',
572 | [NumpadEnter]: '§',
573 | [NumpadEqual]: '§',
574 | [NumpadMultiply]: 'NumMult',
575 | [NumpadSubtract]: 'NumSub',
576 | /* SPECIAL KEYS */
577 | [ArrowDown]: 'Down',
578 | [ArrowLeft]: 'Left',
579 | [ArrowRight]: 'Right',
580 | [ArrowUp]: 'Up',
581 | [Backspace]: 'Backspace',
582 | [CapsLock]: 'CapsLock',
583 | [Delete]: 'Delete',
584 | [End]: 'End',
585 | [Enter]: 'Enter',
586 | [Escape]: 'Esc',
587 | [Home]: 'Home',
588 | [Insert]: 'Insert',
589 | [PageDown]: 'PageDown',
590 | [PageUp]: 'PageUp',
591 | [Space]: 'Space',
592 | [Tab]: 'Tab',
593 | /* PUNCTUATION KEYS */
594 | [Backquote]: '`',
595 | [Backslash]: '\\',
596 | [BracketLeft]: '[',
597 | [BracketRight]: ']',
598 | [Comma]: ',',
599 | [Equal]: '=',
600 | [Minus]: '-',
601 | [Period]: '.',
602 | [Quote]: '\'',
603 | [Semicolon]: ';',
604 | [Slash]: '/',
605 | /* OTHER KEYS */
606 | [NumLock]: 'NumLock',
607 | [ScrollLock]: 'ScrollLock',
608 | /* CUSTOM KEYBOARD KEYS */
609 | [Ampersand]: '&',
610 | [Asterisk]: '*',
611 | [At]: '@',
612 | [BraceLeft]: '{',
613 | [BraceRight]: '}',
614 | [Caret]: '^',
615 | [Colon]: ':',
616 | [Dollar]: '$',
617 | [DoubleQuote]: '"',
618 | [ExclamationMark]: '!',
619 | [GreaterThan]: '>',
620 | [Hash]: '#',
621 | [LessThan]: '<',
622 | [ParenthesisLeft]: '(',
623 | [ParenthesisRight]: ')',
624 | [Percent]: '%',
625 | [Pipe]: '|',
626 | [Plus]: '+',
627 | [QuestionMark]: '?',
628 | [Tilde]: '~',
629 | [Underscore]: '_',
630 | /* CUSTOM MOUSE KEYS */
631 | [Mouse0]: '§',
632 | [Mouse1]: '§',
633 | [Mouse2]: '§',
634 | [Mouse3]: '§',
635 | [Mouse4]: '§',
636 | [Mouse5]: '§',
637 | [Mouse6]: '§',
638 | [Mouse7]: '§',
639 | [Mouse8]: '§',
640 | [Mouse9]: '§',
641 | /* UNSUPPORTED KEYS */
642 | [Section]: '§',
643 | [PlusMinus]: '±'
644 | };
645 |
646 | const ID2FORMAT_SYMBOL = {
647 | /* MODIFIERS */
648 | [ControlLeft]: '⌃',
649 | [ControlRight]: '⌃',
650 | [MetaLeft]: '⌘',
651 | [MetaRight]: '⌘',
652 | [OSLeft]: '⌘',
653 | [OSRight]: '⌘',
654 | [AltLeft]: '⌥',
655 | [AltRight]: '⌥',
656 | [ShiftLeft]: '⇧',
657 | [ShiftRight]: '⇧',
658 | /* VIRTUAL MODIFIERS */
659 | [Control]: '⌃',
660 | [Command]: '⌘',
661 | [Alt]: '⌥',
662 | [Shift]: '⇧',
663 | [CommandOrControl]: isMac () ? '⌘' : '⌃',
664 | [CommandOrControlLeft]: isMac () ? '⌘' : '⌃',
665 | [CommandOrControlRight]: isMac () ? '⌘' : '⌃',
666 | /* DIGITS */
667 | [Digit0]: '0',
668 | [Digit1]: '1',
669 | [Digit2]: '2',
670 | [Digit3]: '3',
671 | [Digit4]: '4',
672 | [Digit5]: '5',
673 | [Digit6]: '6',
674 | [Digit7]: '7',
675 | [Digit8]: '8',
676 | [Digit9]: '9',
677 | /* ALPHABET */
678 | [KeyA]: 'A',
679 | [KeyB]: 'B',
680 | [KeyC]: 'C',
681 | [KeyD]: 'D',
682 | [KeyE]: 'E',
683 | [KeyF]: 'F',
684 | [KeyG]: 'G',
685 | [KeyH]: 'H',
686 | [KeyI]: 'I',
687 | [KeyJ]: 'J',
688 | [KeyK]: 'K',
689 | [KeyL]: 'L',
690 | [KeyM]: 'M',
691 | [KeyN]: 'N',
692 | [KeyO]: 'O',
693 | [KeyP]: 'P',
694 | [KeyQ]: 'Q',
695 | [KeyR]: 'R',
696 | [KeyS]: 'S',
697 | [KeyT]: 'T',
698 | [KeyU]: 'U',
699 | [KeyV]: 'V',
700 | [KeyW]: 'W',
701 | [KeyX]: 'X',
702 | [KeyY]: 'Y',
703 | [KeyZ]: 'Z',
704 | /* FUNCTION KEYS */
705 | [F1]: 'F1',
706 | [F2]: 'F2',
707 | [F3]: 'F3',
708 | [F4]: 'F4',
709 | [F5]: 'F5',
710 | [F6]: 'F6',
711 | [F7]: 'F7',
712 | [F8]: 'F8',
713 | [F9]: 'F9',
714 | [F10]: 'F10',
715 | [F11]: 'F11',
716 | [F12]: 'F12',
717 | [F13]: 'F13',
718 | [F14]: 'F14',
719 | [F15]: 'F15',
720 | [F16]: 'F16',
721 | [F17]: 'F17',
722 | [F18]: 'F18',
723 | [F19]: 'F19',
724 | [F20]: 'F20',
725 | [F21]: 'F21',
726 | [F22]: 'F22',
727 | [F23]: 'F23',
728 | [F24]: 'F24',
729 | /* NUMPAD DIGITS */
730 | [Numpad0]: 'Numpad0',
731 | [Numpad1]: 'Numpad1',
732 | [Numpad2]: 'Numpad2',
733 | [Numpad3]: 'Numpad3',
734 | [Numpad4]: 'Numpad4',
735 | [Numpad5]: 'Numpad5',
736 | [Numpad6]: 'Numpad6',
737 | [Numpad7]: 'Numpad7',
738 | [Numpad8]: 'Numpad8',
739 | [Numpad9]: 'Numpad9',
740 | /* NUMPAD OPERATORS */
741 | [NumpadAdd]: 'NumpadAdd',
742 | [NumpadComma]: 'NumpadComma',
743 | [NumpadDecimal]: 'NumpadDecimal',
744 | [NumpadDivide]: 'NumpadDivide',
745 | [NumpadEnter]: 'NumpadEnter',
746 | [NumpadEqual]: 'NumpadEqual',
747 | [NumpadMultiply]: 'NumpadMultiply',
748 | [NumpadSubtract]: 'NumpadSubtract',
749 | /* SPECIAL KEYS */
750 | [ArrowDown]: '↓',
751 | [ArrowLeft]: '←',
752 | [ArrowRight]: '→',
753 | [ArrowUp]: '↑',
754 | [Backspace]: '⌫',
755 | [CapsLock]: '⇪',
756 | [Delete]: '⌦',
757 | [End]: '↘',
758 | [Enter]: '⏎',
759 | [Escape]: '⎋',
760 | [Home]: '↖',
761 | [Insert]: '⎀',
762 | [PageDown]: '⇟',
763 | [PageUp]: '⇞',
764 | [Space]: '␣',
765 | [Tab]: '⇥',
766 | /* PUNCTUATION KEYS */
767 | [Backquote]: '`',
768 | [Backslash]: '\\',
769 | [BracketLeft]: '[',
770 | [BracketRight]: ']',
771 | [Comma]: ',',
772 | [Equal]: '=',
773 | [Minus]: '-',
774 | [Period]: '.',
775 | [Quote]: '\'',
776 | [Semicolon]: ';',
777 | [Slash]: '/',
778 | /* OTHER KEYS */
779 | [NumLock]: 'NumLock',
780 | [ScrollLock]: 'ScrollLock',
781 | /* CUSTOM KEYBOARD KEYS */
782 | [Ampersand]: '&',
783 | [Asterisk]: '*',
784 | [At]: '@',
785 | [BraceLeft]: '{',
786 | [BraceRight]: '}',
787 | [Caret]: '^',
788 | [Colon]: ':',
789 | [Dollar]: '$',
790 | [DoubleQuote]: '"',
791 | [ExclamationMark]: '!',
792 | [GreaterThan]: '>',
793 | [Hash]: '#',
794 | [LessThan]: '<',
795 | [ParenthesisLeft]: '(',
796 | [ParenthesisRight]: ')',
797 | [Percent]: '%',
798 | [Pipe]: '|',
799 | [Plus]: '+',
800 | [QuestionMark]: '?',
801 | [Tilde]: '~',
802 | [Underscore]: '_',
803 | /* CUSTOM MOUSE KEYS */
804 | [Mouse0]: 'LeftClick',
805 | [Mouse1]: 'MiddleClick',
806 | [Mouse2]: 'RightClick',
807 | [Mouse3]: 'Mouse3',
808 | [Mouse4]: 'Mouse4',
809 | [Mouse5]: 'Mouse5',
810 | [Mouse6]: 'Mouse6',
811 | [Mouse7]: 'Mouse7',
812 | [Mouse8]: 'Mouse8',
813 | [Mouse9]: 'Mouse9',
814 | /* UNSUPPORTED KEYS */
815 | [Section]: '§',
816 | [PlusMinus]: '±'
817 | };
818 |
819 | const KEY_DIGITS2ID = {
820 | /* DIGITS */
821 | '0': Digit0,
822 | '1': Digit1,
823 | '2': Digit2,
824 | '3': Digit3,
825 | '4': Digit4,
826 | '5': Digit5,
827 | '6': Digit6,
828 | '7': Digit7,
829 | '8': Digit8,
830 | '9': Digit9
831 | };
832 |
833 | const KEY_ALPHABET2ID = {
834 | /* ALPHABET UPPERCASE */
835 | 'A': KeyA,
836 | 'B': KeyB,
837 | 'C': KeyC,
838 | 'D': KeyD,
839 | 'E': KeyE,
840 | 'F': KeyF,
841 | 'G': KeyG,
842 | 'H': KeyH,
843 | 'I': KeyI,
844 | 'J': KeyJ,
845 | 'K': KeyK,
846 | 'L': KeyL,
847 | 'M': KeyM,
848 | 'N': KeyN,
849 | 'O': KeyO,
850 | 'P': KeyP,
851 | 'Q': KeyQ,
852 | 'R': KeyR,
853 | 'S': KeyS,
854 | 'T': KeyT,
855 | 'U': KeyU,
856 | 'V': KeyV,
857 | 'W': KeyW,
858 | 'X': KeyX,
859 | 'Y': KeyY,
860 | 'Z': KeyZ,
861 | /* ALPHABET LOWERCASE */
862 | 'a': KeyA,
863 | 'b': KeyB,
864 | 'c': KeyC,
865 | 'd': KeyD,
866 | 'e': KeyE,
867 | 'f': KeyF,
868 | 'g': KeyG,
869 | 'h': KeyH,
870 | 'i': KeyI,
871 | 'j': KeyJ,
872 | 'k': KeyK,
873 | 'l': KeyL,
874 | 'm': KeyM,
875 | 'n': KeyN,
876 | 'o': KeyO,
877 | 'p': KeyP,
878 | 'q': KeyQ,
879 | 'r': KeyR,
880 | 's': KeyS,
881 | 't': KeyT,
882 | 'u': KeyU,
883 | 'v': KeyV,
884 | 'w': KeyW,
885 | 'x': KeyX,
886 | 'y': KeyY,
887 | 'z': KeyZ
888 | };
889 |
890 | const KEY_PUNCTUATION2ID = {
891 | /* PUNCTUATION KEYS */
892 | '`': Backquote,
893 | '\\': Backslash,
894 | '[': BracketLeft,
895 | ']': BracketRight,
896 | ',': Comma,
897 | '=': Equal,
898 | '-': Minus,
899 | '.': Period,
900 | '\'': Quote,
901 | ';': Semicolon,
902 | '/': Slash,
903 | /* OTHER PUNCTUATION KEYS */
904 | '&': Ampersand,
905 | '*': Asterisk,
906 | '@': At,
907 | '{': BraceLeft,
908 | '}': BraceRight,
909 | '^': Caret,
910 | ':': Colon,
911 | '$': Dollar,
912 | '"': DoubleQuote,
913 | '!': ExclamationMark,
914 | '>': GreaterThan,
915 | '#': Hash,
916 | '<': LessThan,
917 | '(': ParenthesisLeft,
918 | ')': ParenthesisRight,
919 | '%': Percent,
920 | '|': Pipe,
921 | '+': Plus,
922 | '?': QuestionMark,
923 | '~': Tilde,
924 | '_': Underscore
925 | };
926 |
927 | const KEY_UNSUPPORTED2ID = {
928 | '§': Section,
929 | '±': PlusMinus
930 | };
931 |
932 | const KEY2ID = {
933 | ...KEY_DIGITS2ID,
934 | ...KEY_ALPHABET2ID,
935 | ...KEY_PUNCTUATION2ID,
936 | ...KEY_UNSUPPORTED2ID
937 | };
938 |
939 | const MOUSE2ID = {
940 | 0: Mouse0,
941 | 1: Mouse1,
942 | 2: Mouse2,
943 | 3: Mouse3,
944 | 4: Mouse4,
945 | 5: Mouse5,
946 | 6: Mouse6,
947 | 7: Mouse7,
948 | 8: Mouse8,
949 | 9: Mouse9
950 | };
951 |
952 | const NAME2ID = {
953 | /* MODIFIERS */
954 | altleft: AltLeft,
955 | altright: AltRight,
956 | alt: [AltLeft, AltRight],
957 | optionleft: AltLeft,
958 | optionright: AltRight,
959 | option: [AltLeft, AltRight],
960 | cmdleft: MetaLeft,
961 | cmdright: MetaRight,
962 | cmd: [MetaLeft, MetaRight],
963 | commandleft: MetaLeft,
964 | commandright: MetaRight,
965 | command: [MetaLeft, MetaRight],
966 | metaleft: MetaLeft,
967 | metaright: MetaRight,
968 | meta: [MetaLeft, MetaRight],
969 | ctrlleft: ControlLeft,
970 | ctrlright: ControlRight,
971 | ctrl: [ControlLeft, ControlRight],
972 | controlleft: ControlLeft,
973 | controlright: ControlRight,
974 | control: [ControlLeft, ControlRight],
975 | shiftleft: ShiftLeft,
976 | shiftright: ShiftRight,
977 | shift: [ShiftLeft, ShiftRight],
978 | /* VIRTUAL MODIFIERS */
979 | cmdleftorctrlleft: isMac () ? MetaLeft : ControlLeft,
980 | cmdrightorctrlright: isMac () ? MetaRight : ControlRight,
981 | cmdorctrlleft: isMac () ? MetaLeft : ControlLeft,
982 | cmdorctrlright: isMac () ? MetaRight : ControlRight,
983 | cmdorctrl: isMac () ? [MetaLeft, MetaRight] : [ControlLeft, ControlRight],
984 | commandleftorcontrolleft: isMac () ? MetaLeft : ControlLeft,
985 | commandrightorcontrolright: isMac () ? MetaRight : ControlRight,
986 | commandorcontrolleft: isMac () ? MetaLeft : ControlLeft,
987 | commandorcontrolright: isMac () ? MetaRight : ControlRight,
988 | commandorcontrol: isMac () ? [MetaLeft, MetaRight] : [ControlLeft, ControlRight],
989 | /* MOUSE */
990 | clickleft: Mouse0,
991 | clickmiddle: Mouse1,
992 | clickright: Mouse2,
993 | leftclick: Mouse0,
994 | middleclick: Mouse1,
995 | rightclick: Mouse2,
996 | leftmouse: Mouse0,
997 | middlemouse: Mouse1,
998 | rightmouse: Mouse2,
999 | mouseleft: Mouse0,
1000 | mousemiddle: Mouse1,
1001 | mouseright: Mouse2,
1002 | mouse0: Mouse0,
1003 | mouse1: Mouse1,
1004 | mouse2: Mouse2,
1005 | mouse3: Mouse3,
1006 | mouse4: Mouse4,
1007 | mouse5: Mouse5,
1008 | mouse6: Mouse6,
1009 | mouse7: Mouse7,
1010 | mouse8: Mouse8,
1011 | mouse9: Mouse9,
1012 | /* SPECIAL CHARACTERS */
1013 | backspace: Backspace,
1014 | capslock: CapsLock,
1015 | del: Delete,
1016 | delete: Delete,
1017 | down: ArrowDown,
1018 | end: End,
1019 | enter: Enter,
1020 | return: Enter,
1021 | esc: Escape,
1022 | escape: Escape,
1023 | home: Home,
1024 | insert: Insert,
1025 | left: ArrowLeft,
1026 | pagedown: PageDown,
1027 | pageup: PageUp,
1028 | right: ArrowRight,
1029 | space: Space,
1030 | spacebar: Space,
1031 | tab: Tab,
1032 | up: ArrowUp,
1033 | /* DIGITS & ALPHABET & PUNCTUATION KEYS */
1034 | ...KEY_DIGITS2ID,
1035 | ...KEY_ALPHABET2ID,
1036 | ...KEY_PUNCTUATION2ID,
1037 | plus: Plus,
1038 | /* FUNCTION KEYS */
1039 | f1: F1,
1040 | f2: F2,
1041 | f3: F3,
1042 | f4: F4,
1043 | f5: F5,
1044 | f6: F6,
1045 | f7: F7,
1046 | f8: F8,
1047 | f9: F9,
1048 | f10: F10,
1049 | f11: F11,
1050 | f12: F12,
1051 | f13: F13,
1052 | f14: F14,
1053 | f15: F15,
1054 | f16: F16,
1055 | f17: F17,
1056 | f18: F18,
1057 | f19: F19,
1058 | f20: F20,
1059 | f21: F21,
1060 | f22: F22,
1061 | f23: F23,
1062 | f24: F24,
1063 | /* NUMPAD DIGITS */
1064 | numpad0: Numpad0,
1065 | numpad1: Numpad1,
1066 | numpad2: Numpad2,
1067 | numpad3: Numpad3,
1068 | numpad4: Numpad4,
1069 | numpad5: Numpad5,
1070 | numpad6: Numpad6,
1071 | numpad7: Numpad7,
1072 | numpad8: Numpad8,
1073 | numpad9: Numpad9,
1074 | /* NUMPAD OPERATORS */
1075 | numpadadd: NumpadAdd,
1076 | numpadcomma: NumpadComma,
1077 | numpaddecimal: NumpadDecimal,
1078 | numpaddivide: NumpadDivide,
1079 | numpadenter: NumpadEnter,
1080 | numpadequal: NumpadEqual,
1081 | numpadmultiply: NumpadMultiply,
1082 | numpadsubtract: NumpadSubtract,
1083 | /* OTHERS */
1084 | numlock: NumLock,
1085 | scrolllock: ScrollLock
1086 | };
1087 |
1088 | const NAME_FORMATTING2ID = {
1089 | /* REGULARS */
1090 | ...NAME2ID,
1091 | /* VIRTUAL MODIFIERS */
1092 | ctrl: Control,
1093 | control: Control,
1094 | cmd: Command,
1095 | command: Command,
1096 | alt: Alt,
1097 | shift: Shift,
1098 | cmdleftorctrlleft: CommandOrControlLeft,
1099 | cmdrightorctrlright: CommandOrControlRight,
1100 | cmdorctrlleft: CommandOrControlLeft,
1101 | cmdorctrlright: CommandOrControlRight,
1102 | cmdorctrl: CommandOrControl,
1103 | commandleftorcontrolleft: CommandOrControlLeft,
1104 | commandrightorcontrolright: CommandOrControlRight,
1105 | commandorcontrolleft: CommandOrControlLeft,
1106 | commandorcontrolright: CommandOrControlRight,
1107 | commandorcontrol: CommandOrControl
1108 | };
1109 |
1110 | const WHICH2ID = {
1111 | /* DIGITS */
1112 | 48: Digit0,
1113 | 49: Digit1,
1114 | 50: Digit2,
1115 | 51: Digit3,
1116 | 52: Digit4,
1117 | 53: Digit5,
1118 | 54: Digit6,
1119 | 55: Digit7,
1120 | 56: Digit8,
1121 | 57: Digit9,
1122 | /* ALPHABET UPPERCASE */
1123 | 65: KeyA,
1124 | 66: KeyB,
1125 | 67: KeyC,
1126 | 68: KeyD,
1127 | 69: KeyE,
1128 | 70: KeyF,
1129 | 71: KeyG,
1130 | 72: KeyH,
1131 | 73: KeyI,
1132 | 74: KeyJ,
1133 | 75: KeyK,
1134 | 76: KeyL,
1135 | 77: KeyM,
1136 | 78: KeyN,
1137 | 79: KeyO,
1138 | 80: KeyP,
1139 | 81: KeyQ,
1140 | 82: KeyR,
1141 | 83: KeyS,
1142 | 84: KeyT,
1143 | 85: KeyU,
1144 | 86: KeyV,
1145 | 87: KeyW,
1146 | 88: KeyX,
1147 | 89: KeyY,
1148 | 90: KeyZ,
1149 | /* ALPHABET LOWERCASE */
1150 | 97: KeyA,
1151 | 98: KeyB,
1152 | 99: KeyC,
1153 | 100: KeyD,
1154 | 101: KeyE,
1155 | 102: KeyF,
1156 | 103: KeyG,
1157 | 104: KeyH,
1158 | 105: KeyI,
1159 | 106: KeyJ,
1160 | 107: KeyK,
1161 | 108: KeyL,
1162 | 109: KeyM,
1163 | 110: KeyN,
1164 | 111: KeyO,
1165 | 112: KeyP,
1166 | 113: KeyQ,
1167 | 114: KeyR,
1168 | 115: KeyS,
1169 | 116: KeyT,
1170 | 117: KeyU,
1171 | 118: KeyV,
1172 | 119: KeyW,
1173 | 120: KeyX,
1174 | 121: KeyY,
1175 | 122: KeyZ,
1176 | /* PUNCTUATION KEYS */
1177 | 96: Backquote,
1178 | 92: Backslash,
1179 | 91: BracketLeft,
1180 | 93: BracketRight,
1181 | 44: Comma,
1182 | 61: Equal,
1183 | 45: Minus,
1184 | 46: Period,
1185 | 39: Quote,
1186 | 59: Semicolon,
1187 | 47: Slash,
1188 | /* OTHER PUNCTUATION KEYS */
1189 | 38: Ampersand,
1190 | 42: Asterisk,
1191 | 64: At,
1192 | 123: BraceLeft,
1193 | 125: BraceRight,
1194 | 94: Caret,
1195 | 58: Colon,
1196 | 36: Dollar,
1197 | 34: DoubleQuote,
1198 | 33: ExclamationMark,
1199 | 62: GreaterThan,
1200 | 35: Hash,
1201 | 60: LessThan,
1202 | 40: ParenthesisLeft,
1203 | 41: ParenthesisRight,
1204 | 37: Percent,
1205 | 124: Pipe,
1206 | 43: Plus,
1207 | 63: QuestionMark,
1208 | 126: Tilde,
1209 | 95: Underscore
1210 | };
1211 |
1212 | /* EXPORT */
1213 |
1214 | export {CODE2ID, CODE_RISKY2ID, ID2FORMAT_TITLE, ID2FORMAT_ELECTRON, ID2FORMAT_SYMBOL, KEY_DIGITS2ID, KEY_ALPHABET2ID, KEY_PUNCTUATION2ID, KEY_UNSUPPORTED2ID, KEY2ID, MOUSE2ID, NAME2ID, NAME_FORMATTING2ID, WHICH2ID};
1215 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 |
2 | /* MAIN */
3 |
4 | type Checker = {
5 | ( event: Event ): boolean
6 | };
7 |
8 | type Disposer = {
9 | (): void
10 | };
11 |
12 | type Format = (
13 | 'electron' |
14 | 'symbols' |
15 | 'long-flexible-directional' |
16 | 'long-flexible-nondirectional' |
17 | 'long-inflexible-directional' |
18 | 'long-inflexible-nondirectional' |
19 | 'short-flexible-directional' |
20 | 'short-flexible-nondirectional' |
21 | 'short-inflexible-directional' |
22 | 'short-inflexible-nondirectional'
23 | );
24 |
25 | type Handler = {
26 | ( event?: Event ): boolean | void
27 | };
28 |
29 | type ChordNode = {
30 | children: Partial>,
31 | handlers: HandlerNode // Pointing to the last item in the linked list
32 | };
33 |
34 | type HandlerNode = {
35 | parent: ChordNode | undefined,
36 | prev: HandlerNode | undefined,
37 | next: HandlerNode | undefined,
38 | handler: Handler
39 | };
40 |
41 | type HandlerOptions = {
42 | konami?: boolean
43 | };
44 |
45 | type Options = {
46 | capture?: boolean,
47 | target?: Window | Document | HTMLElement | SVGElement | MathMLElement,
48 | shouldHandleEvent?: ( event: Event ) => boolean
49 | };
50 |
51 | type RecordHandler = {
52 | ( shortcut: string ): void
53 | };
54 |
55 | type RecordOptions = {
56 | limit?: number,
57 | passthrough?: boolean,
58 | shouldHandleEvent?: ( event: Event ) => boolean
59 | };
60 |
61 | /* EXPORT */
62 |
63 | export type {Checker, Disposer, Format, Handler, ChordNode, HandlerNode, HandlerOptions, Options, RecordHandler, RecordOptions};
64 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | /* MAIN */
3 |
4 | const attempt = ( fn: () => T, fallback: T ): T => {
5 |
6 | try {
7 |
8 | return fn ();
9 |
10 | } catch {
11 |
12 | return fallback;
13 |
14 | }
15 |
16 | };
17 |
18 | const castArray = ( value: T | T[] ): T[] => {
19 |
20 | return isArray ( value ) ? value : [value];
21 |
22 | };
23 |
24 | const comparator = ( a: T, b: T ): -1 | 0 | 1 => {
25 |
26 | if ( a < b ) return -1;
27 |
28 | if ( a > b ) return 1;
29 |
30 | return 0;
31 |
32 | };
33 |
34 | const decompose = ( value: bigint ): bigint[] => { // Returns all the primitive components of a binary bigint
35 |
36 | const components: bigint[] = [];
37 |
38 | for ( let i = 0n; value > 0n; i += 1n, value >>= 1n ) {
39 |
40 | if ( value & 1n ) {
41 |
42 | components.push ( 1n << i );
43 |
44 | }
45 |
46 | }
47 |
48 | return components;
49 |
50 | };
51 |
52 | const enumerate = ( parts: (bigint | bigint[])[] ): bigint[][] => { // Exhaustively listing all possible paths from the start to the end
53 |
54 | if ( parts.length === 0 ) {
55 |
56 | return [];
57 |
58 | } else if ( parts.length === 1 ) {
59 |
60 | const part = parts[0];
61 |
62 | if ( isArray ( part ) ) {
63 |
64 | if ( part.length <= 1 ) {
65 |
66 | return [part];
67 |
68 | } else {
69 |
70 | return part.map ( value => [value] );
71 |
72 | }
73 |
74 | } else {
75 |
76 | return [[part]];
77 |
78 | }
79 |
80 | }
81 |
82 | const normalized = parts.map ( castArray );
83 | const heads = normalized[0];
84 | const tails = enumerate ( normalized.slice ( 1 ) );
85 | const values: bigint[][] = [];
86 |
87 | for ( const head of heads ) {
88 |
89 | if ( tails.length ) {
90 |
91 | for ( const tail of tails ) {
92 |
93 | values.push ([ head, ...tail ]);
94 |
95 | }
96 |
97 | } else {
98 |
99 | values.push ([ head ]);
100 |
101 | }
102 |
103 | }
104 |
105 | return values;
106 |
107 | };
108 |
109 | const first = ( values: T[] ): T | undefined => {
110 |
111 | return values[0];
112 |
113 | };
114 |
115 | const isArray = ( value: unknown ): value is unknown[] => {
116 |
117 | return Array.isArray ( value );
118 |
119 | };
120 |
121 | const isKeyboardEvent = ( value: unknown ): value is KeyboardEvent => {
122 |
123 | return value instanceof KeyboardEvent;
124 |
125 | };
126 |
127 | const isMac = ((): () => boolean => {
128 |
129 | const isMac = ((): boolean => {
130 |
131 | if ( typeof navigator === 'object' ) {
132 |
133 | return /mac|ipod|iphone|ipad/i.test ( navigator.platform );
134 |
135 | }
136 |
137 | if ( typeof globalThis['process'] === 'object' ) {
138 |
139 | return globalThis['process']['platform'] === 'darwin';
140 |
141 | }
142 |
143 | return false;
144 |
145 | })();
146 |
147 | return () => isMac;
148 |
149 | })();
150 |
151 | const isMouseEvent = ( value: unknown ): value is MouseEvent => {
152 |
153 | return value instanceof MouseEvent;
154 |
155 | };
156 |
157 | const isString = ( value: unknown ): value is string => {
158 |
159 | return typeof value === 'string';
160 |
161 | };
162 |
163 | const isUndefined = ( value: unknown ): value is undefined => {
164 |
165 | return value === undefined;
166 |
167 | };
168 |
169 | const last = ( values: T[] ): T | undefined => {
170 |
171 | return values[values.length - 1];
172 |
173 | };
174 |
175 | const memoize = ( fn: ( arg: T ) => R ): (( arg: T ) => R) => {
176 |
177 | const cache = new Map ();
178 |
179 | return ( arg: T ): R => {
180 |
181 | const resultCached = cache.get ( arg );
182 |
183 | if ( !isUndefined ( resultCached ) || cache.has ( arg ) ) return resultCached;
184 |
185 | const result = fn ( arg );
186 |
187 | cache.set ( arg, result );
188 |
189 | return result;
190 |
191 | };
192 |
193 | };
194 |
195 | const nope = (): false => {
196 |
197 | return false;
198 |
199 | };
200 |
201 | const or = ( values: bigint[] ): bigint => {
202 |
203 | return values.reduce ( ( acc, value ) => acc | value, 0n );
204 |
205 | };
206 |
207 | const orWith = ( values: bigint[], otherValues: bigint[] ): bigint[][] => {
208 |
209 | return otherValues.map ( other => {
210 |
211 | const head = values.slice ( 0, -1 );
212 | const tail = last ( values ) || 0n;
213 |
214 | return [...head, tail | other];
215 |
216 | });
217 |
218 | };
219 |
220 | const take = ( arr: T[], nr: number ): T[] => {
221 |
222 | return arr.slice ( 0, Math.max ( 0, nr ) );
223 |
224 | };
225 |
226 | const takeRight = ( arr: T[], nr: number ): T[] => {
227 |
228 | return arr.slice ( arr.length - Math.max ( 0, nr ), arr.length );
229 |
230 | };
231 |
232 | const uniq = ( values: T[] ): T[] => {
233 |
234 | if ( values.length <= 1 ) return values;
235 |
236 | return Array.from ( new Set ( values ) );
237 |
238 | };
239 |
240 | const without = ( values: T[], except: T ): T[] => {
241 |
242 | if ( !values.includes ( except ) ) return values;
243 |
244 | return values.filter ( value => value !== except );
245 |
246 | };
247 |
248 | const yep = (): true => {
249 |
250 | return true;
251 |
252 | };
253 |
254 | /* EXPORT */
255 |
256 | export {attempt, castArray, comparator, decompose, enumerate, first, isArray, isKeyboardEvent, isMac, isMouseEvent, isString, last, memoize, nope, or, orWith, take, takeRight, uniq, without, yep};
257 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 |
2 | /* IMPORT */
3 |
4 | import {describe} from 'fava';
5 | import {NAME2ID} from '../dist/maps.js';
6 | import ShoSho from '../dist/index.js';
7 |
8 | /* MAIN */
9 |
10 | describe ( 'ShoSho', () => {
11 |
12 | describe ( 'format', it => {
13 |
14 | it ( 'supports formatting shortcuts in different formats', t => {
15 |
16 | const tests = [
17 | // Empty
18 | ['electron', '', ''],
19 | ['symbols', '', ''],
20 | ['long-flexible-directional', '', ''],
21 | ['long-flexible-nondirectional', '', ''],
22 | ['long-inflexible-directional', '', ''],
23 | ['long-inflexible-nondirectional', '', ''],
24 | ['short-flexible-directional', '', ''],
25 | ['short-flexible-nondirectional', '', ''],
26 | ['short-inflexible-directional', '', ''],
27 | ['short-inflexible-nondirectional', '', ''],
28 | // Control+A
29 | ['electron', 'Control+A', 'Ctrl+A'],
30 | ['symbols', 'Control+A', '⌃A'],
31 | ['long-flexible-directional', 'Control+A', 'CommandOrControl+A'],
32 | ['long-flexible-nondirectional', 'Control+A', 'CommandOrControl+A'],
33 | ['long-inflexible-directional', 'Control+A', 'Control+A'],
34 | ['long-inflexible-nondirectional', 'Control+A', 'Control+A'],
35 | ['short-flexible-directional', 'Control+A', 'CmdOrCtrl+A'],
36 | ['short-flexible-nondirectional', 'Control+A', 'CmdOrCtrl+A'],
37 | ['short-inflexible-directional', 'Control+A', 'Ctrl+A'],
38 | ['short-inflexible-nondirectional', 'Control+A', 'Ctrl+A'],
39 | // ControlLeft+A
40 | ['electron', 'ControlLeft+A', 'Ctrl+A'],
41 | ['symbols', 'ControlLeft+A', '⌃A'],
42 | ['long-flexible-directional', 'ControlLeft+A', 'CommandOrControlLeft+A'],
43 | ['long-flexible-nondirectional', 'ControlLeft+A', 'CommandOrControl+A'],
44 | ['long-inflexible-directional', 'ControlLeft+A', 'ControlLeft+A'],
45 | ['long-inflexible-nondirectional', 'ControlLeft+A', 'Control+A'],
46 | ['short-flexible-directional', 'ControlLeft+A', 'CmdOrCtrlLeft+A'],
47 | ['short-flexible-nondirectional', 'ControlLeft+A', 'CmdOrCtrl+A'],
48 | ['short-inflexible-directional', 'ControlLeft+A', 'CtrlLeft+A'],
49 | ['short-inflexible-nondirectional', 'ControlLeft+A', 'Ctrl+A'],
50 | // ControlRight+A
51 | ['electron', 'ControlRight+A', 'Ctrl+A'],
52 | ['symbols', 'ControlRight+A', '⌃A'],
53 | ['long-flexible-directional', 'ControlRight+A', 'CommandOrControlRight+A'],
54 | ['long-flexible-nondirectional', 'ControlRight+A', 'CommandOrControl+A'],
55 | ['long-inflexible-directional', 'ControlRight+A', 'ControlRight+A'],
56 | ['long-inflexible-nondirectional', 'ControlRight+A', 'Control+A'],
57 | ['short-flexible-directional', 'ControlRight+A', 'CmdOrCtrlRight+A'],
58 | ['short-flexible-nondirectional', 'ControlRight+A', 'CmdOrCtrl+A'],
59 | ['short-inflexible-directional', 'ControlRight+A', 'CtrlRight+A'],
60 | ['short-inflexible-nondirectional', 'ControlRight+A', 'Ctrl+A'],
61 | // CommandLeft+A
62 | ['electron', 'CommandLeft+A', 'Cmd+A'],
63 | ['symbols', 'CommandLeft+A', '⌘A'],
64 | ['long-flexible-directional', 'CommandLeft+A', 'CommandOrControlLeft+A'],
65 | ['long-flexible-nondirectional', 'CommandLeft+A', 'CommandOrControl+A'],
66 | ['long-inflexible-directional', 'CommandLeft+A', 'CommandLeft+A'],
67 | ['long-inflexible-nondirectional', 'CommandLeft+A', 'Command+A'],
68 | ['short-flexible-directional', 'CommandLeft+A', 'CmdOrCtrlLeft+A'],
69 | ['short-flexible-nondirectional', 'CommandLeft+A', 'CmdOrCtrl+A'],
70 | ['short-inflexible-directional', 'CommandLeft+A', 'CmdLeft+A'],
71 | ['short-inflexible-nondirectional', 'CommandLeft+A', 'Cmd+A'],
72 | // CommandRight+A
73 | ['electron', 'CommandRight+A', 'Cmd+A'],
74 | ['symbols', 'CommandRight+A', '⌘A'],
75 | ['long-flexible-directional', 'CommandRight+A', 'CommandOrControlRight+A'],
76 | ['long-flexible-nondirectional', 'CommandRight+A', 'CommandOrControl+A'],
77 | ['long-inflexible-directional', 'CommandRight+A', 'CommandRight+A'],
78 | ['long-inflexible-nondirectional', 'CommandRight+A', 'Command+A'],
79 | ['short-flexible-directional', 'CommandRight+A', 'CmdOrCtrlRight+A'],
80 | ['short-flexible-nondirectional', 'CommandRight+A', 'CmdOrCtrl+A'],
81 | ['short-inflexible-directional', 'CommandRight+A', 'CmdRight+A'],
82 | ['short-inflexible-nondirectional', 'CommandRight+A', 'Cmd+A'],
83 | // CommandOrControlLeft+A
84 | ['electron', 'CommandOrControlLeft+A', 'CmdOrCtrl+A'],
85 | ['symbols', 'CommandOrControlLeft+A', '⌘A'],
86 | ['long-flexible-directional', 'CommandOrControlLeft+A', 'CommandOrControlLeft+A'],
87 | ['long-flexible-nondirectional', 'CommandOrControlLeft+A', 'CommandOrControl+A'],
88 | ['long-inflexible-directional', 'CommandOrControlLeft+A', 'CommandLeft+A'],
89 | ['long-inflexible-nondirectional', 'CommandOrControlLeft+A', 'Command+A'],
90 | ['short-flexible-directional', 'CommandOrControlLeft+A', 'CmdOrCtrlLeft+A'],
91 | ['short-flexible-nondirectional', 'CommandOrControlLeft+A', 'CmdOrCtrl+A'],
92 | ['short-inflexible-directional', 'CommandOrControlLeft+A', 'CmdLeft+A'],
93 | ['short-inflexible-nondirectional', 'CommandOrControlLeft+A', 'Cmd+A'],
94 | // CommandRightOrControlRight+A
95 | ['electron', 'CmdRightOrCtrlRight+A', 'CmdOrCtrl+A'],
96 | ['symbols', 'CmdRightOrCtrlRight+A', '⌘A'],
97 | ['long-flexible-directional', 'CmdRightOrCtrlRight+A', 'CommandOrControlRight+A'],
98 | ['long-flexible-nondirectional', 'CmdRightOrCtrlRight+A', 'CommandOrControl+A'],
99 | ['long-inflexible-directional', 'CmdRightOrCtrlRight+A', 'CommandRight+A'],
100 | ['long-inflexible-nondirectional', 'CmdRightOrCtrlRight+A', 'Command+A'],
101 | ['short-flexible-directional', 'CmdRightOrCtrlRight+A', 'CmdOrCtrlRight+A'],
102 | ['short-flexible-nondirectional', 'CmdRightOrCtrlRight+A', 'CmdOrCtrl+A'],
103 | ['short-inflexible-directional', 'CmdRightOrCtrlRight+A', 'CmdRight+A'],
104 | ['short-inflexible-nondirectional', 'CmdRightOrCtrlRight+A', 'Cmd+A'],
105 | // Ctrl+Cmd+A Ctrl
106 | ['electron', 'Ctrl+Cmd+A Ctrl', 'Ctrl+Cmd+A Ctrl'],
107 | ['symbols', 'Ctrl+Cmd+A Ctrl', '⌃⌘A ⌃'],
108 | ['long-flexible-directional', 'Ctrl+Cmd+A Ctrl', 'Control+Command+A CommandOrControl'],
109 | ['long-flexible-nondirectional', 'Ctrl+Cmd+A Ctrl', 'Control+Command+A CommandOrControl'],
110 | ['long-inflexible-directional', 'Ctrl+Cmd+A Ctrl', 'Control+Command+A Control'],
111 | ['long-inflexible-nondirectional', 'Ctrl+Cmd+A Ctrl', 'Control+Command+A Control'],
112 | ['short-flexible-directional', 'Ctrl+Cmd+A Ctrl', 'Ctrl+Cmd+A CmdOrCtrl'],
113 | ['short-flexible-nondirectional', 'Ctrl+Cmd+A Ctrl', 'Ctrl+Cmd+A CmdOrCtrl'],
114 | ['short-inflexible-directional', 'Ctrl+Cmd+A Ctrl', 'Ctrl+Cmd+A Ctrl'],
115 | ['short-inflexible-nondirectional', 'Ctrl+Cmd+A Ctrl', 'Ctrl+Cmd+A Ctrl'],
116 | // Cmd+Ctrl+A Cmd
117 | ['electron', 'Cmd+Ctrl+A Cmd', 'Ctrl+Cmd+A Cmd'],
118 | ['symbols', 'Cmd+Ctrl+A Cmd', '⌃⌘A ⌘'],
119 | ['long-flexible-directional', 'Cmd+Ctrl+A Cmd', 'Control+Command+A CommandOrControl'],
120 | ['long-flexible-nondirectional', 'Cmd+Ctrl+A Cmd', 'Control+Command+A CommandOrControl'],
121 | ['long-inflexible-directional', 'Cmd+Ctrl+A Cmd', 'Control+Command+A Command'],
122 | ['long-inflexible-nondirectional', 'Cmd+Ctrl+A Cmd', 'Control+Command+A Command'],
123 | ['short-flexible-directional', 'Cmd+Ctrl+A Cmd', 'Ctrl+Cmd+A CmdOrCtrl'],
124 | ['short-flexible-nondirectional', 'Cmd+Ctrl+A Cmd', 'Ctrl+Cmd+A CmdOrCtrl'],
125 | ['short-inflexible-directional', 'Cmd+Ctrl+A Cmd', 'Ctrl+Cmd+A Cmd'],
126 | ['short-inflexible-nondirectional', 'Cmd+Ctrl+A Cmd', 'Ctrl+Cmd+A Cmd'],
127 | // CmdLeft+CmdRight
128 | ['electron', 'CmdLeft+CmdRight', 'Cmd+Cmd'],
129 | ['symbols', 'CmdLeft+CmdRight', '⌘⌘'],
130 | ['long-flexible-directional', 'CmdLeft+CmdRight', 'CommandOrControlLeft+CommandOrControlRight'],
131 | ['long-flexible-nondirectional', 'CmdLeft+CmdRight', 'CommandOrControlLeft+CommandOrControlRight'],
132 | ['long-inflexible-directional', 'CmdLeft+CmdRight', 'CommandLeft+CommandRight'],
133 | ['long-inflexible-nondirectional', 'CmdLeft+CmdRight', 'CommandLeft+CommandRight'],
134 | ['short-flexible-directional', 'CmdLeft+CmdRight', 'CmdOrCtrlLeft+CmdOrCtrlRight'],
135 | ['short-flexible-nondirectional', 'CmdLeft+CmdRight', 'CmdOrCtrlLeft+CmdOrCtrlRight'],
136 | ['short-inflexible-directional', 'CmdLeft+CmdRight', 'CmdLeft+CmdRight'],
137 | ['short-inflexible-nondirectional', 'CmdLeft+CmdRight', 'CmdLeft+CmdRight'],
138 | // CmdLeft+ShiftRight
139 | ['electron', 'CmdLeft+ShiftRight', 'Cmd+Shift'],
140 | ['symbols', 'CmdLeft+ShiftRight', '⌘⇧'],
141 | ['long-flexible-directional', 'CmdLeft+ShiftRight', 'CommandOrControlLeft+ShiftRight'],
142 | ['long-flexible-nondirectional', 'CmdLeft+ShiftRight', 'CommandOrControl+Shift'],
143 | ['long-inflexible-directional', 'CmdLeft+ShiftRight', 'CommandLeft+ShiftRight'],
144 | ['long-inflexible-nondirectional', 'CmdLeft+ShiftRight', 'Command+Shift'],
145 | ['short-flexible-directional', 'CmdLeft+ShiftRight', 'CmdOrCtrlLeft+ShiftRight'],
146 | ['short-flexible-nondirectional', 'CmdLeft+ShiftRight', 'CmdOrCtrl+Shift'],
147 | ['short-inflexible-directional', 'CmdLeft+ShiftRight', 'CmdLeft+ShiftRight'],
148 | ['short-inflexible-nondirectional', 'CmdLeft+ShiftRight', 'Cmd+Shift'],
149 | // Left+Right modifiers
150 | ['short-inflexible-nondirectional', 'CtrlLeft+CtrlRight', 'CtrlLeft+CtrlRight'],
151 | ['short-inflexible-nondirectional', 'CmdLeft+CtrlRight', 'Ctrl+Cmd'],
152 | ['short-inflexible-nondirectional', 'AltLeft+CtrlRight', 'Ctrl+Alt'],
153 | ['short-inflexible-nondirectional', 'ShiftLeft+CtrlRight', 'Ctrl+Shift'],
154 | ['short-inflexible-nondirectional', 'CtrlLeft+CmdRight', 'Ctrl+Cmd'],
155 | ['short-inflexible-nondirectional', 'CmdLeft+CmdRight', 'CmdLeft+CmdRight'],
156 | ['short-inflexible-nondirectional', 'AltLeft+CmdRight', 'Cmd+Alt'],
157 | ['short-inflexible-nondirectional', 'ShiftLeft+CmdRight', 'Cmd+Shift'],
158 | ['short-inflexible-nondirectional', 'CtrlLeft+AltRight', 'Ctrl+Alt'],
159 | ['short-inflexible-nondirectional', 'CmdLeft+AltRight', 'Cmd+Alt'],
160 | ['short-inflexible-nondirectional', 'AltLeft+AltRight', 'AltLeft+AltRight'],
161 | ['short-inflexible-nondirectional', 'ShiftLeft+AltRight', 'Alt+Shift'],
162 | ['short-inflexible-nondirectional', 'CtrlLeft+ShiftRight', 'Ctrl+Shift'],
163 | ['short-inflexible-nondirectional', 'CmdLeft+ShiftRight', 'Cmd+Shift'],
164 | ['short-inflexible-nondirectional', 'AltLeft+ShiftRight', 'Alt+Shift'],
165 | ['short-inflexible-nondirectional', 'ShiftLeft+ShiftRight', 'ShiftLeft+ShiftRight'],
166 | // Right+Left modifiers
167 | ['short-inflexible-nondirectional', 'CtrlRight+CtrlLeft', 'CtrlLeft+CtrlRight'],
168 | ['short-inflexible-nondirectional', 'CmdRight+CtrlLeft', 'Ctrl+Cmd'],
169 | ['short-inflexible-nondirectional', 'AltRight+CtrlLeft', 'Ctrl+Alt'],
170 | ['short-inflexible-nondirectional', 'ShiftRight+CtrlLeft', 'Ctrl+Shift'],
171 | ['short-inflexible-nondirectional', 'CtrlRight+CmdLeft', 'Ctrl+Cmd'],
172 | ['short-inflexible-nondirectional', 'CmdRight+CmdLeft', 'CmdLeft+CmdRight'],
173 | ['short-inflexible-nondirectional', 'AltRight+CmdLeft', 'Cmd+Alt'],
174 | ['short-inflexible-nondirectional', 'ShiftRight+CmdLeft', 'Cmd+Shift'],
175 | ['short-inflexible-nondirectional', 'CtrlRight+AltLeft', 'Ctrl+Alt'],
176 | ['short-inflexible-nondirectional', 'CmdRight+AltLeft', 'Cmd+Alt'],
177 | ['short-inflexible-nondirectional', 'AltRight+AltLeft', 'AltLeft+AltRight'],
178 | ['short-inflexible-nondirectional', 'ShiftRight+AltLeft', 'Alt+Shift'],
179 | ['short-inflexible-nondirectional', 'CtrlRight+ShiftLeft', 'Ctrl+Shift'],
180 | ['short-inflexible-nondirectional', 'CmdRight+ShiftLeft', 'Cmd+Shift'],
181 | ['short-inflexible-nondirectional', 'AltRight+ShiftLeft', 'Alt+Shift'],
182 | ['short-inflexible-nondirectional', 'ShiftRight+ShiftLeft', 'ShiftLeft+ShiftRight'],
183 | // Others...
184 | ['electron', 'Alt+Cmd+Ctrl+Shift', 'Ctrl+Cmd+Alt+Shift'],
185 | ['electron', 'Shift+Ctrl+Cmd+Alt', 'Ctrl+Cmd+Alt+Shift'],
186 | ['short-inflexible-nondirectional', 'CommandOrControlLeft+CommandOrControlRight', 'CmdLeft+CmdRight'],
187 | ['electron', '+', '+']
188 | ];
189 |
190 | for ( const [format, input, output] of tests ) {
191 |
192 | t.is ( ShoSho.format ( input, format ), output );
193 |
194 | }
195 |
196 | });
197 |
198 | });
199 |
200 | describe ( 'isShortcut', it => {
201 |
202 | it ( 'supports detecting valid shortcuts', t => {
203 |
204 | for ( const key in NAME2ID ) {
205 |
206 | t.true ( ShoSho.isShortcut ( key ) );
207 | t.true ( ShoSho.isShortcut ( key.toUpperCase () ) );
208 | t.true ( ShoSho.isShortcut ( key.toLowerCase () ) );
209 |
210 | }
211 |
212 | for ( const key in NAME2ID ) {
213 |
214 | t.false ( ShoSho.isShortcut ( `${key}${key}` ) );
215 |
216 | }
217 |
218 | t.true ( ShoSho.isShortcut ( 'Cmd' ) );
219 | t.true ( ShoSho.isShortcut ( 'Command' ) );
220 | t.true ( ShoSho.isShortcut ( 'Command+A' ) );
221 | t.true ( ShoSho.isShortcut ( 'Command A' ) );
222 | t.true ( ShoSho.isShortcut ( 'Command+A B' ) );
223 | t.true ( ShoSho.isShortcut ( 'CommandLeft+A' ) );
224 | t.true ( ShoSho.isShortcut ( 'CommandRight+A' ) );
225 | t.true ( ShoSho.isShortcut ( '+' ) );
226 |
227 | t.false ( ShoSho.isShortcut ( 'Comma' ) );
228 | t.false ( ShoSho.isShortcut ( 'AB' ) );
229 | t.false ( ShoSho.isShortcut ( 'CommandA' ) );
230 |
231 | });
232 |
233 | });
234 |
235 | });
236 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsex/tsconfig.json",
3 | "compilerOptions": {
4 | "noImplicitAny": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------