@peter.naydenov/shortcuts 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changelog.md +13 -2
- package/How..to.make.plugins.md +41 -0
- package/Migration.guide.md +77 -0
- package/README-v.2.x.x.md +375 -0
- package/README.md +106 -58
- package/dist/shortcuts.cjs +1 -1
- package/dist/shortcuts.esm.mjs +1 -1
- package/dist/shortcuts.umd.js +1 -1
- package/package.json +5 -5
- package/src/main.js +81 -30
- package/src/methods/_normalizeWithPlugins.js +25 -0
- package/src/methods/_readShortcutWithPlugins.js +24 -0
- package/src/methods/_systemAction.js +25 -0
- package/src/methods/changeContext.js +20 -26
- package/src/methods/index.js +7 -13
- package/src/methods/load.js +21 -14
- package/src/plugins/click/_findTarget.js +20 -0
- package/src/plugins/click/_listenDOM.js +117 -0
- package/src/plugins/click/_normalizeShortcutName.js +44 -0
- package/src/plugins/click/_readClickEvent.js +24 -0
- package/src/plugins/click/_registerShortcutEvents.js +30 -0
- package/src/plugins/click/index.js +74 -0
- package/src/plugins/key/_listenDOM.js +138 -0
- package/src/plugins/key/_normalizeShortcutName.js +31 -0
- package/src/{methods → plugins/key}/_readKeyEvent.js +2 -3
- package/src/plugins/key/_registerShortcutEvents.js +28 -0
- package/src/plugins/key/index.js +76 -0
- package/test/01-general.cy.js +189 -154
- package/src/methods/_findTarget.js +0 -19
- package/src/methods/_listen.js +0 -210
- package/src/methods/_readMouseEvent.js +0 -24
- package/src/methods/_readShortcut.js +0 -17
- /package/src/{methods → plugins/key}/_specialChars.js +0 -0
package/README.md
CHANGED
|
@@ -6,20 +6,16 @@
|
|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
Describe all page activities as list of shortcuts wrapped in contexts. Switch among available contexts.
|
|
11
|
+
Library has a plugin system to extend the possible `shortcut names`/`event coverage`. Plugins role is to convert DOM events to shortcut strings, then the core part will trigger the action functions related to the shortcut. At the moment we have 2 plugins:
|
|
12
|
+
- `key` - converts keyboard events to shortcut names;
|
|
13
|
+
- `click` - converts mouse events to shortcut names;
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
Planned work on the plugins for `scroll`, `drag-drop`, `input-change`, etc...
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
## What's new?
|
|
16
|
-
|
|
17
|
-
- Version 1.1.x and above are coming with method `emit` that make possible to trigger shortcuts programmatically. In `shortcuts` you can mix keyboard, mouse and programmatical events that is prity everything that can happen in a web page;
|
|
18
|
-
- Method `setDependencies` comes after version 2.0.0. You can insert external libraries and modules that you will need to access from action functions. They will come as **dependencies** object. In function `emit` will come as first argument. Other arguments are attached behind the dependencies object;
|
|
19
|
-
- Another html data attribute is added - `data-quick-click`. Example: `<button data-click="id" data-quick-click>Click me</button>`. This attribute is saying to `shortcuts` that target element don't need to wait for more then 1 click and shortcut can be executed immediately after the click. Attribute is available in version 2.0.0 and above;
|
|
20
|
-
- Method `listShortcuts` is added in version 2.0.0. It returns a list of shortcuts for requested context. If context is not set, will return a list of all shortcuts;
|
|
21
|
-
|
|
22
|
-
|
|
23
19
|
## Shortcut Description Rules
|
|
24
20
|
The shortcuts definition includes a context name and a set of rules(object). The rules are a set of key-value pairs. The key is a shortcut name and the value is a function or array of functions, to be executed when the shortcut is triggered (action function).
|
|
25
21
|
|
|
@@ -44,14 +40,28 @@ The shortcuts definition includes a context name and a set of rules(object). The
|
|
|
44
40
|
]
|
|
45
41
|
}
|
|
46
42
|
}
|
|
43
|
+
// shortcutName after v.3.0.0 have a plugin prefix. - 'pluginPrefix:shortcutName'.
|
|
44
|
+
// For example: 'key:s+alt' - for 's+alt' shortcut that is handled by 'key' plugin.
|
|
47
45
|
```
|
|
48
46
|
|
|
49
47
|
Load a shortcut definition by calling `load` method.
|
|
50
48
|
|
|
51
49
|
```js
|
|
52
|
-
|
|
50
|
+
// for es6 module projects:
|
|
51
|
+
include { shortcuts, pluginKey, pluginClick } from '@peter.naydenov/shortcuts'
|
|
52
|
+
// for commonjs projects:
|
|
53
|
+
const { shortcuts, pluginKey, pluginClick } = require('@peter.naydenov/shortcuts')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
53
57
|
const short = shortcuts ();
|
|
58
|
+
// Load a needed plugins
|
|
59
|
+
// short.enablePlugin ( pluginCode, ?pluginOptions )
|
|
60
|
+
short.enablePlugin ( pluginKey )
|
|
61
|
+
short.enablePlugin ( pluginClick )
|
|
62
|
+
// Load a shortcut definition
|
|
54
63
|
short.load ( shortcutDefinition )
|
|
64
|
+
|
|
55
65
|
```
|
|
56
66
|
|
|
57
67
|
Shortcuts are working only if contex is active. To activate a context call `changeContext` method.
|
|
@@ -104,39 +114,45 @@ short.getNote ()
|
|
|
104
114
|
|
|
105
115
|
|
|
106
116
|
|
|
107
|
-
##
|
|
117
|
+
## Plugin 'click' Shortcut Descriptions
|
|
108
118
|
Mouse event name is build from the following parts:
|
|
109
119
|
```js
|
|
110
|
-
//
|
|
120
|
+
// click:<mouse button>-<number of clicks>-<modifier key>-<modifier key>-<modifier key>
|
|
111
121
|
// example:
|
|
112
|
-
//
|
|
113
|
-
//
|
|
122
|
+
// click: left-2 -> for double click with left mouse button
|
|
123
|
+
// click: right-3 -> for triple click with right mouse button
|
|
114
124
|
|
|
115
125
|
// mouse button options: left, right, middle
|
|
116
126
|
```
|
|
117
127
|
|
|
118
|
-
The modifier keys `ctrl`, `alt`, and `shift` are supported. They are added to the mouse event by sign
|
|
128
|
+
The modifier keys `ctrl`, `alt`, and `shift` are supported. They are added to the mouse event by sign `-`:
|
|
119
129
|
|
|
120
130
|
```js
|
|
121
131
|
// example:
|
|
122
|
-
//
|
|
132
|
+
// click: left-1-ctrl -> for single click with left mouse button and ctrl key pressed
|
|
123
133
|
```
|
|
124
|
-
|
|
134
|
+
|
|
135
|
+
Order of describing click event and modifier keys is not important.
|
|
125
136
|
|
|
126
137
|
```js
|
|
127
138
|
// example:
|
|
128
|
-
//
|
|
139
|
+
// click: ctrl-left-1 -> same as above
|
|
129
140
|
|
|
130
141
|
// These 3 descriptions are equal:
|
|
131
|
-
//
|
|
132
|
-
// alt
|
|
133
|
-
//
|
|
142
|
+
// click: left-1-ctrl-alt-shift
|
|
143
|
+
// click: alt-shift-left-1-ctrl
|
|
144
|
+
// click: left-1-shift-ctrl-alt
|
|
134
145
|
```
|
|
135
146
|
|
|
136
|
-
Multiple clicks are detected automatically by time interval between clicks. The default interval is 320ms but you can change it by setting `mouseWait` option.
|
|
147
|
+
Multiple clicks are detected automatically by time interval between clicks. The default interval is 320ms but you can change it by setting `mouseWait` click plugin option.
|
|
148
|
+
```js
|
|
149
|
+
short.enablePlugin ( pluginClick, { mouseWait: 500 }) // set the interval to 500ms
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Read more in section `Options`.
|
|
137
153
|
|
|
138
154
|
|
|
139
|
-
## Define a
|
|
155
|
+
## Define a Click Targets
|
|
140
156
|
Target HTML elements for `shortcuts` are defined by `data-click` attribute. The value of the attribute is the name of the target. Example:
|
|
141
157
|
|
|
142
158
|
```html
|
|
@@ -144,7 +160,7 @@ Target HTML elements for `shortcuts` are defined by `data-click` attribute. The
|
|
|
144
160
|
<!-- target name is 'id' -->
|
|
145
161
|
```
|
|
146
162
|
|
|
147
|
-
Attribute is customizable by setting `clickTarget` option. Read more in section `Options`.
|
|
163
|
+
Attribute is customizable by setting `clickTarget` click plugin option. Read more in section `Options`.
|
|
148
164
|
|
|
149
165
|
If current shortcuts context contain definition for 2 or more clicks, this may slow down the execution of single shortcuts because `shortcuts` will wait for the time interval to detect multiple clicks. To avoid this for specific targets, you can set `data-quick-click` attribute to the target element. Example:
|
|
150
166
|
|
|
@@ -157,17 +173,18 @@ Using a <a> tag is a special case. It's always recognized as a target, and alway
|
|
|
157
173
|
```html
|
|
158
174
|
<a href="#">Click me</a>
|
|
159
175
|
<!-- Recognized as a target and will not wait for more then 1 click -->
|
|
160
|
-
<!-- Take care for the action from shortcut `
|
|
176
|
+
<!-- Take care for the action from shortcut `click: left-1`. -->
|
|
161
177
|
```
|
|
162
178
|
|
|
163
|
-
Clicking on <a> tag will
|
|
179
|
+
Clicking on <a> tag will execute default browser behaviour. In your `click:left-1` action function you can take the control. Example:
|
|
164
180
|
|
|
165
181
|
```js
|
|
166
182
|
{
|
|
167
183
|
contextName : {
|
|
168
|
-
'
|
|
169
|
-
if ( target.tagName === 'A' ) { //
|
|
170
|
-
|
|
184
|
+
'click:left-1' : function ( {target, event} ) {
|
|
185
|
+
if ( target.tagName === 'A' ) { // To prevent default action on <a> tag
|
|
186
|
+
event.preventDefault ()
|
|
187
|
+
// do something...
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
190
|
}
|
|
@@ -176,35 +193,35 @@ Clicking on <a> tag will not execute anything. All events are blocked by default
|
|
|
176
193
|
|
|
177
194
|
|
|
178
195
|
|
|
179
|
-
##
|
|
196
|
+
## Plugin 'key' Event Descriptions
|
|
180
197
|
Keyboard event description contains a key name and a modifier keys if they are used. The modifier keys `ctrl`, `alt`, and `shift` are supported. They are added to the keyboard event by sign `+`:
|
|
181
198
|
|
|
182
199
|
```js
|
|
183
200
|
// example:
|
|
184
|
-
// ctrl+alt+shift+a -> for key 'a' with ctrl, alt and shift keys pressed
|
|
201
|
+
// key: ctrl+alt+shift+a -> for key 'a' with ctrl, alt and shift keys pressed
|
|
185
202
|
```
|
|
186
203
|
|
|
187
204
|
Keyboard event description support a shortcut sequenses. These means that you can press a sequence of keys to trigger a shortcut. The sequence elements are separated by sign "," ( coma ):
|
|
188
205
|
|
|
189
206
|
```js
|
|
190
207
|
// example:
|
|
191
|
-
// a,b,c -> for key 'a' then key 'b' then key 'c'
|
|
208
|
+
// key: a,b,c -> for key 'a' then key 'b' then key 'c'
|
|
192
209
|
|
|
193
|
-
// g+shift,o,t,o -> for key 'g' with shift, then key 'o', then key 't' then key 'o'
|
|
210
|
+
// key: g+shift,o,t,o -> for key 'g' with shift, then key 'o', then key 't' then key 'o'
|
|
194
211
|
```
|
|
195
212
|
|
|
196
213
|
Order of describing keyboard event and modifier keys is not important, but sequence elements are:
|
|
197
214
|
|
|
198
215
|
```js
|
|
199
216
|
// example:
|
|
200
|
-
// a+ctrl,l,o,t -> a with ctrl, then l, then o, then t
|
|
217
|
+
// key: a+ctrl,l,o,t -> a with ctrl, then l, then o, then t
|
|
201
218
|
// this is equal to:
|
|
202
|
-
// ctrl+a,l,o,t
|
|
219
|
+
// key: ctrl+a,l,o,t
|
|
203
220
|
// but not equal to:
|
|
204
|
-
// ctrl+a,o,t,l
|
|
221
|
+
// key: ctrl+a,o,t,l
|
|
205
222
|
```
|
|
206
223
|
|
|
207
|
-
Keyboard sequence is detected automatically by time interval between key presses. The default interval is 480ms but you can change it by setting `keyWait` option. Read more in section `Options`.
|
|
224
|
+
Keyboard sequence is detected automatically by time interval between key presses. The default interval is 480ms but you can change it by setting `keyWait` key plugin option. Read more in section `Options`.
|
|
208
225
|
|
|
209
226
|
There is a way to disable automatic sequence detection and mark the begining and the end of the sequense by using a keyboard action functions. Read more in section `Keyboard Action Functions`.
|
|
210
227
|
|
|
@@ -226,19 +243,19 @@ Special characters that are available for your shortcut descriptions:
|
|
|
226
243
|
- ']' - close square bracket key
|
|
227
244
|
- '`' - backtick key
|
|
228
245
|
|
|
229
|
-
**Warning**: For keys with two symbols, in shortcut description use the lower one. Examples: Use '=' instead of '+', use '/' instead of '?', etc. Modifier keys are available for special characters too.
|
|
246
|
+
**Warning**: For keys with two symbols(look at the keyboard), in shortcut description use the lower one. Examples: Use '=' instead of '+', use '/' instead of '?', etc. Modifier keys are available for special characters too.
|
|
230
247
|
|
|
231
248
|
**Warining**: Some of the shortcuts are used by OS and the browswer, so they are not available.
|
|
232
249
|
|
|
233
250
|
|
|
234
251
|
|
|
235
252
|
## Action Functions
|
|
236
|
-
Action functions are called when a shortcut is triggered.
|
|
253
|
+
Action functions are called when a shortcut is triggered. There is a difference among plugin action functions. Arguments are slightly different.
|
|
237
254
|
|
|
238
255
|
|
|
239
256
|
|
|
240
257
|
### Keyboard Action Functions
|
|
241
|
-
Description of
|
|
258
|
+
Description of `key` plugin action functions is:
|
|
242
259
|
```js
|
|
243
260
|
function myKeyHandler ({
|
|
244
261
|
context // (string) Name of the current context;
|
|
@@ -257,7 +274,7 @@ function myKeyHandler ({
|
|
|
257
274
|
|
|
258
275
|
|
|
259
276
|
### Mouse Action Functions
|
|
260
|
-
|
|
277
|
+
Click plugin action functions can be described like:
|
|
261
278
|
|
|
262
279
|
```js
|
|
263
280
|
function myMouseHandler ({
|
|
@@ -285,16 +302,23 @@ Description of the methods of shortcut instance:
|
|
|
285
302
|
```js
|
|
286
303
|
load : 'Load and extend a shortcut definition.'
|
|
287
304
|
, unload : 'Remove a shortcut context with all its shortcuts.'
|
|
288
|
-
|
|
305
|
+
|
|
306
|
+
, enablePlugin : 'Enable a plugin.'
|
|
307
|
+
, disablePlugin : 'Disable a plugin.'
|
|
308
|
+
, mutePlugin : 'Mute a plugin. All events for the plugin will be ignored.'
|
|
309
|
+
, unmutePlugin : 'Unmute a plugin. All events for the plugin will be listened again.'
|
|
310
|
+
|
|
311
|
+
, changeContext : 'Switch to existing shortcut context or shitch off the context(if no argument).'
|
|
312
|
+
, getContext : 'Return a name of current context or null if there is no context selected'
|
|
289
313
|
, emit : 'Trigger a shortcut or custom event programmatically.'
|
|
290
314
|
, pause : 'Stop listening for shortcuts.'
|
|
291
315
|
, resume : 'Resume listening for shortcuts.'
|
|
292
316
|
, listContexts : 'Return list of available contexts.'
|
|
293
|
-
,
|
|
317
|
+
, listShortcuts : 'Return list of shortcuts per context.'
|
|
294
318
|
, getNote : `Return a name of current note or null if note isn't set`
|
|
295
319
|
, setNote : 'Set a note to current context.'
|
|
296
320
|
, setDependencies : 'Set dependencies that will be available in action functions.'
|
|
297
|
-
, getDependencies : 'Return dependencies object.'
|
|
321
|
+
, getDependencies : 'Return a dependencies object.'
|
|
298
322
|
```
|
|
299
323
|
|
|
300
324
|
### How to 'pause' and 'resume'?
|
|
@@ -312,27 +336,44 @@ short.resume ('*') // will resume all shortcuts
|
|
|
312
336
|
|
|
313
337
|
## Options
|
|
314
338
|
|
|
315
|
-
|
|
339
|
+
Shortcut receives `options` during the start. Here is the list of available options:
|
|
316
340
|
|
|
317
341
|
```js
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
342
|
+
onShortcut : 'Callback function that is called when a shortcut is triggered. Default value - false'
|
|
343
|
+
```
|
|
344
|
+
```js
|
|
345
|
+
const short = shortcut ({onShortcut: (shortcut) => console.log(shortcut) }) // Log in console each triggered shortcut
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
### Plugin 'key' options
|
|
351
|
+
```js
|
|
352
|
+
keyWait : 'Timeout for entering shortcut sequence in ms. Default value - 480'
|
|
323
353
|
, streamKeys : 'False or a callback function that is called when a key is pressed. Default value - false'
|
|
324
354
|
```
|
|
325
355
|
|
|
326
|
-
You can request default list of options with their default values:
|
|
327
356
|
|
|
357
|
+
|
|
358
|
+
### Plugin 'click' options
|
|
328
359
|
```js
|
|
329
|
-
|
|
330
|
-
|
|
360
|
+
mouseWait : 'Timeout for entering multiple mouse events. Default value - 320.'
|
|
361
|
+
, clickTarget : 'Data attribute name to recognize click items in HTML. Default value - click' // data attribute 'click' means attribute ( data-click='someName' )
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Plugin options are provided as a second argument during the plugin enabling. It's look like this:
|
|
365
|
+
|
|
366
|
+
```js
|
|
367
|
+
short.enablePlugin ( pluginKey, {
|
|
368
|
+
keyWait: 500 // set the interval to 500ms
|
|
369
|
+
, streamKeys: (key) => console.log(key) // Log in console each pressed key
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
short.enablePlugin ( pluginClick, {
|
|
373
|
+
mouseWait: 200 // set the interval between multiple clicks to 200ms
|
|
374
|
+
, clickTarget: 'puk' // data attribute 'puk' means attribute ( data-puk='someName' )
|
|
375
|
+
})
|
|
331
376
|
|
|
332
|
-
// start a shortcuts with default options
|
|
333
|
-
const short = shortcuts ()
|
|
334
|
-
const short = shortcuts ( shortcuts.getDefaults () ) // same as above
|
|
335
|
-
// The idea behind getDefaults is to see what options are available and what are their default values.
|
|
336
377
|
```
|
|
337
378
|
|
|
338
379
|
|
|
@@ -361,6 +402,13 @@ const short = shortcuts ( shortcuts.getDefaults () ) // same as above
|
|
|
361
402
|
|
|
362
403
|
|
|
363
404
|
|
|
405
|
+
## Links
|
|
406
|
+
|
|
407
|
+
- [History of changes](https://github.com/PeterNaydenov/shortcuts/blob/main/Changelog.md)
|
|
408
|
+
- [Migration guide](https://github.com/PeterNaydenov/shortcuts/blob/main/Migration.guide.md)
|
|
409
|
+
- [How to make a plugin](https://github.com/PeterNaydenov/shortcuts/blob/main/How.to.make.plugins.md)
|
|
410
|
+
|
|
411
|
+
|
|
364
412
|
|
|
365
413
|
## Credits
|
|
366
414
|
'@peter.naydenov/shortcuts' was created and supported by Peter Naydenov.
|
package/dist/shortcuts.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var t=require("@peter.naydenov/notice");var e={_normalizeWithPlugins:function(t,e){return function(t){const n=e.shortcuts;Object.keys(n).forEach((e=>{Object.entries(n[e]).forEach((([o,r])=>{const i=t(o);i!==o&&(delete n[e][o],n[e][i]=r)}))}))}},_readShortcutWithPlugins:function(t,e){return function(n){const{inAPI:o}=t,r=n.split(":")[0],i=o._systemAction(r,"none");let s=n;return-1!==i&&(s=e.plugins[i].shortcutName(n)),s}},_systemAction:function(t,e){return function(t,n,o=null){return e.plugins.findIndex((e=>e.getPrefix()===t&&(e[n]&&e[n](o),!0)))}},changeContext:function(t,e){const{shortcuts:n,currentContext:o}=e,{ev:r}=t;return function(t=!1){const i=o.name;if(!t)return r.reset(),void(o.name=null);i!==t&&(n[t]?(n[i]&&r.reset(),o.name=t,e.plugins.forEach((e=>e.contextChange(t))),Object.entries(n[t]).forEach((([t,e])=>{e.forEach((e=>r.on(t,e)))})),r.on("*",((...t)=>{e.exposeShortcut&&e.exposeShortcut(...t)}))):r.emit("@shortcuts-error",`Context '${t}' does not exist`))}},listShortcuts:function(t,e){const n=e.shortcuts;return function(t=null){if(null!=t){let e=n[t];return null==e?null:Object.entries(e).map((([t,e])=>t))}return Object.keys(n).map((t=>{let e={};return e.context=t,e.shortcuts=Object.entries(n[t]).map((([t,e])=>t)),e}))}},load:function(t,e){const{shortcuts:n,plugins:o}=e,{API:{changeContext:r,getContext:i}}=t;return function(t){const e=i(),s=o.map((t=>t.getPrefix().toUpperCase()));let c=!1;Object.entries(t).forEach((([t,r])=>{t===e&&(c=!0),n[t]={},Object.entries(r).forEach((([e,r])=>{let i=e,c=e.toUpperCase().trim(),u=s.map(((t,e)=>c.startsWith(t)?e:null)).filter((t=>null!==t));if(u.length){let t=u[0];i=o[t].shortcutName(e)}r instanceof Function&&(r=[r]),n[t][i]=r}))})),c&&(r(),r(e))}},unload:function(t,e){const{currentContext:n,shortcuts:o}=e,{ev:r}=t;return function(t){n.name!==t?o[t]?delete o[t]:r.emit("shortcuts-error",`Context '${t}' does not exist`):r.emit("shortcuts-error",`Context '${t}' can't be removed during is current active context. Change the context first`)}}};function n(t){const e=t.toUpperCase(),n=/KEY\s*\:/i.test(e),o=e.indexOf(":");if(!n)return t;return`KEY:${e.slice(o+1).split(",").map((t=>t.trim())).map((t=>t.split("+").map((t=>t.trim())).sort().join("+"))).join(",")}`}function o(t,e){let{shiftKey:n,altKey:o,ctrlKey:r}=t,i=t.code.replace("Key","").replace("Digit",""),s=[];return r&&s.push("CTRL"),n&&s.push("SHIFT"),o&&s.push("ALT"),e.hasOwnProperty(i)?s.push(e[i].toUpperCase()):["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"].includes(i)||s.push(i.toUpperCase()),s.sort()}function r(t,e){let n=0;const{regex:o}=t,{listenOptions:r,currentContext:{name:i},shortcuts:s}=e;return null==i?0:(Object.entries(s[i]).forEach((([t,e])=>{if(!o.test(t))return;n++;let i=t.slice(4).split(",").length;r.maxSequence<i&&(r.maxSequence=i)})),n)}function i(){return{ArrowLeft:"LEFT",ArrowUp:"UP",ArrowRight:"RIGHT",ArrowDown:"DOWN",Enter:"ENTER",NumpadEnter:"ENTER",Escape:"ESC",Backspace:"BACKSPACE",Space:"SPACE",Tab:"TAB",Backquote:"`",BracketLeft:"[",BracketRight:"]",Equal:"=",Slash:"/",Backslash:"\\",IntlBackslash:"`",F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12"}}function s(t,e,n){const{listenOptions:{clickTarget:o}}=e;let r=n;return r===document||r===document.body?null:r.dataset[o]||"A"===r.nodeName?r:s(t,e,r.parentNode)}function c(t){const e=t.toUpperCase(),n=/CLICK\s*\:/i.test(e),o=["LEFT","MIDDLE","RIGHT"],r=["ALT","SHIFT","CTRL"];let i=null,s=[],c=0,u=e.indexOf(":");return n?(e.slice(u+1).trim().split("-").map((t=>t.trim())).forEach((t=>{o.includes(t)?i=t:r.includes(t)?s.push(t):isNaN(t)||(c=t)})),`CLICK:${i}-${c}${s.length>0?"-":""}${s.sort().join("-")}`):t}function u(t,e){let{shiftKey:n,altKey:o,ctrlKey:r,key:i,button:s}=t,c=`CLICK:${["LEFT","MIDDLE","RIGHT"][s]}-${e}`,u=[];return r&&u.push("CTRL"),n&&u.push("SHIFT"),o&&u.push("ALT"),u.length>0?`${c}${u.length>0?"-":""}${u.sort().join("-")}`:`${c}`}function l(t,e){let n=0;const{regex:o}=t,{listenOptions:r,currentContext:{name:i},shortcuts:s}=e;return null==i?0:(Object.entries(s[i]).forEach((([t,e])=>{if(!o.test(t))return;n++;let[,i]=t.slice(6).split("-");r.maxClicks<i&&(r.maxClicks=i)})),n)}exports.pluginClick=function(t,e,n){let{currentContext:o,shortcuts:r}=e,{inAPI:i}=t,a={ev:t.ev,_findTarget:s,_readClickEvent:u,mainDependencies:t,regex:/CLICK\s*\:/i},m={currentContext:o,shortcuts:r,listenOptions:{mouseWait:n.mouseWait?n.mouseWait:320,maxClicks:1,clickTarget:n.clickTarget?n.clickTarget:"click"}};i._normalizeWithPlugins(c);let p=function(t,e){const{ev:n,_findTarget:o,_readClickEvent:r,mainDependencies:i}=t,{listenOptions:s,currentContext:c}=e,{mouseWait:u}=s;let l=null,a=null,m=null,p=null,h=0;function f(){const t=r(a,h),e={target:l,targetProps:l?l.getBoundingClientRect():null,x:a.clientX,y:a.clientY,context:c.name,note:c.note,event:a,dependencies:i.extra,type:"click"};n.emit(t,e),m=null,p=null,l=null,a=null,h=0}function d(n){let r=s.maxClicks;return clearTimeout(m),p?(clearTimeout(p),void(p=setTimeout((()=>p=null),u))):(l=o(t,e,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,h++,h>=r?(f(),void(r>1&&(p=setTimeout((()=>p=null),u)))):void(m=setTimeout(f,u)))}function g(n){let r=s.maxClicks;return clearTimeout(m),p?(clearTimeout(p),void(p=setTimeout((()=>p=null),u))):(l=o(t,e,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,h++,h>=r?(f(),void(r>1&&(p=setTimeout((()=>p=null),u)))):void(m=setTimeout(f,u)))}return{start:function(){e.active||(window.addEventListener("contextmenu",g),document.addEventListener("click",d),e.active=!0)},stop:function(){e.active&&(window.removeEventListener("contextmenu",g),document.removeEventListener("click",d),e.active=!1)}}}(a,m),h=l(a,m);h>0&&p.start();const f={getPrefix:()=>"click",shortcutName:t=>c(t),contextChange:()=>{h=l(a,m),h<1&&p.stop(),h>0&&p.start()},mute:()=>p.stop(),unmute:()=>p.start(),destroy:()=>{p.stop(),m=null,f=null}};return Object.freeze(f),f},exports.pluginKey=function(t,e,s={}){let{currentContext:c,shortcuts:u,exposeShortcut:l}=e,{inAPI:a}=t,m={ev:t.ev,_specialChars:i,_readKeyEvent:o,mainDependencies:t,regex:/KEY\s*\:/i},p={currentContext:c,shortcuts:u,active:!1,listenOptions:{keyWait:s.keyWait?s.keyWait:480,maxSequence:1,keyIgnore:null},streamKeys:!(!s.streamKeys||"function"!=typeof s.streamKeys)&&s.streamKeys,exposeShortcut:l};a._normalizeWithPlugins(n);let h=function(t,e){const{ev:n,_specialChars:o,_readKeyEvent:r,mainDependencies:i}=t,{currentContext:s,streamKeys:c,listenOptions:u}=e,{keyWait:l}=u;let a=[],m=null,p=!0,h=!1;const f=()=>p=!1,d=()=>p=!0,g=()=>h=!0,x=()=>!1===p;function y(){let t=a.map((t=>[t.join("+")]));const e={wait:f,end:d,ignore:g,isWaiting:x,note:s.note,context:s.name,dependencies:i.extra,type:"key"};if(!p){let o=t.at(-1);n.emit(o,e),h&&(t=t.slice(0,-1),h=!1)}if(p){const o=`KEY:${t.join(",")}`;n.emit(o,e),a=[],m=null}}function C(e){if(clearTimeout(m),o.hasOwnProperty(e.code))return a.push(r(e,o)),c&&c({key:e.key,context:s.name,note:s.note,dependencies:t.extra}),u.keyIgnore?(clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l))):p&&a.length===u.maxSequence?(y(),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l))):void(p?m=setTimeout(y,l):y())}function k(e){if(!o.hasOwnProperty(e.code)){if(clearTimeout(m),c&&c({key:e.key,context:s.name,note:s.note,dependencies:t.extra}),u.keyIgnore)return clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l));if(a.push(r(e,o)),p&&a.length===u.maxSequence)return y(),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l));p?m=setTimeout(y,l):y()}}return{start:function(){e.active||(document.addEventListener("keydown",C),document.addEventListener("keypress",k),e.active=!0)},stop:function(){e.active&&(document.removeEventListener("keydown",C),document.removeEventListener("keypress",k),e.active=!1)}}}(m,p),f=r(m,p);f>0&&h.start();const d={getPrefix:()=>"key",shortcutName:t=>n(t),contextChange:t=>{f=r(m,p),f<1&&h.stop(),f>0&&h.start()},mute:()=>h.stop(),unmute:()=>h.start(),destroy:()=>{h.stop(),p=null,d=null}};return Object.freeze(d),d},exports.shortcuts=function(n={}){const o=t(),r={},i={},s={currentContext:{name:null,note:null},shortcuts:{},plugins:[],exposeShortcut:!(!n.onShortcut||"function"!=typeof n.onShortcut)&&n.onShortcut},c={ev:o,inAPI:r,API:i,extra:{}};return i.enablePlugin=(t,e={})=>{const n=t.name;if(-1===r._systemAction(n,"none")){let n;n=t(c,s,e),s.plugins.push(n)}},i.disablePlugin=t=>{const e=r._systemAction(t,"destroy");-1!==e&&(s.plugins=s.plugins.filter(((t,n)=>n!==e)))},i.mutePlugin=t=>r._systemAction(t,"mute"),i.unmutePlugin=t=>r._systemAction(t,"unmute"),i.getContext=()=>s.currentContext.name,i.getNote=()=>s.currentContext.note,i.setNote=(t=null)=>{"string"!=typeof t&&null!=t||(s.currentContext.note=t)},i.pause=(t="*")=>{let e=r._readShortcutWithPlugins(t);o.stop(e)},i.resume=(t="*")=>{const e=r._readShortcutWithPlugins(t);o.start(e)},i.emit=(t,...e)=>o.emit(r._readShortcutWithPlugins(t),...e),i.listContexts=()=>Object.keys(s.shortcuts),i.setDependencies=t=>c.extra={...c.extra,...t},i.getDependencies=()=>c.extra,Object.entries(e).forEach((([t,e])=>{t.startsWith("_")?r[t]=e(c,s):i[t]=e(c,s)})),i};
|
package/dist/shortcuts.esm.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import t from"@peter.naydenov/notice";var e={_normalizeWithPlugins:function(t,e){return function(t){const n=e.shortcuts;Object.keys(n).forEach((e=>{Object.entries(n[e]).forEach((([o,r])=>{const i=t(o);i!==o&&(delete n[e][o],n[e][i]=r)}))}))}},_readShortcutWithPlugins:function(t,e){return function(n){const{inAPI:o}=t,r=n.split(":")[0],i=o._systemAction(r,"none");let s=n;return-1!==i&&(s=e.plugins[i].shortcutName(n)),s}},_systemAction:function(t,e){return function(t,n,o=null){return e.plugins.findIndex((e=>e.getPrefix()===t&&(e[n]&&e[n](o),!0)))}},changeContext:function(t,e){const{shortcuts:n,currentContext:o}=e,{ev:r}=t;return function(t=!1){const i=o.name;if(!t)return r.reset(),void(o.name=null);i!==t&&(n[t]?(n[i]&&r.reset(),o.name=t,e.plugins.forEach((e=>e.contextChange(t))),Object.entries(n[t]).forEach((([t,e])=>{e.forEach((e=>r.on(t,e)))})),r.on("*",((...t)=>{e.exposeShortcut&&e.exposeShortcut(...t)}))):r.emit("@shortcuts-error",`Context '${t}' does not exist`))}},listShortcuts:function(t,e){const n=e.shortcuts;return function(t=null){if(null!=t){let e=n[t];return null==e?null:Object.entries(e).map((([t,e])=>t))}return Object.keys(n).map((t=>{let e={};return e.context=t,e.shortcuts=Object.entries(n[t]).map((([t,e])=>t)),e}))}},load:function(t,e){const{shortcuts:n,plugins:o}=e,{API:{changeContext:r,getContext:i}}=t;return function(t){const e=i(),s=o.map((t=>t.getPrefix().toUpperCase()));let c=!1;Object.entries(t).forEach((([t,r])=>{t===e&&(c=!0),n[t]={},Object.entries(r).forEach((([e,r])=>{let i=e,c=e.toUpperCase().trim(),u=s.map(((t,e)=>c.startsWith(t)?e:null)).filter((t=>null!==t));if(u.length){let t=u[0];i=o[t].shortcutName(e)}r instanceof Function&&(r=[r]),n[t][i]=r}))})),c&&(r(),r(e))}},unload:function(t,e){const{currentContext:n,shortcuts:o}=e,{ev:r}=t;return function(t){n.name!==t?o[t]?delete o[t]:r.emit("shortcuts-error",`Context '${t}' does not exist`):r.emit("shortcuts-error",`Context '${t}' can't be removed during is current active context. Change the context first`)}}};function n(t){const e=t.toUpperCase(),n=/KEY\s*\:/i.test(e),o=e.indexOf(":");if(!n)return t;return`KEY:${e.slice(o+1).split(",").map((t=>t.trim())).map((t=>t.split("+").map((t=>t.trim())).sort().join("+"))).join(",")}`}function o(t,e){let{shiftKey:n,altKey:o,ctrlKey:r}=t,i=t.code.replace("Key","").replace("Digit",""),s=[];return r&&s.push("CTRL"),n&&s.push("SHIFT"),o&&s.push("ALT"),e.hasOwnProperty(i)?s.push(e[i].toUpperCase()):["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"].includes(i)||s.push(i.toUpperCase()),s.sort()}function r(t,e){let n=0;const{regex:o}=t,{listenOptions:r,currentContext:{name:i},shortcuts:s}=e;return null==i?0:(Object.entries(s[i]).forEach((([t,e])=>{if(!o.test(t))return;n++;let i=t.slice(4).split(",").length;r.maxSequence<i&&(r.maxSequence=i)})),n)}function i(){return{ArrowLeft:"LEFT",ArrowUp:"UP",ArrowRight:"RIGHT",ArrowDown:"DOWN",Enter:"ENTER",NumpadEnter:"ENTER",Escape:"ESC",Backspace:"BACKSPACE",Space:"SPACE",Tab:"TAB",Backquote:"`",BracketLeft:"[",BracketRight:"]",Equal:"=",Slash:"/",Backslash:"\\",IntlBackslash:"`",F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12"}}function s(t,e,s={}){let{currentContext:c,shortcuts:u,exposeShortcut:l}=e,{inAPI:a}=t,m={ev:t.ev,_specialChars:i,_readKeyEvent:o,mainDependencies:t,regex:/KEY\s*\:/i},p={currentContext:c,shortcuts:u,active:!1,listenOptions:{keyWait:s.keyWait?s.keyWait:480,maxSequence:1,keyIgnore:null},streamKeys:!(!s.streamKeys||"function"!=typeof s.streamKeys)&&s.streamKeys,exposeShortcut:l};a._normalizeWithPlugins(n);let h=function(t,e){const{ev:n,_specialChars:o,_readKeyEvent:r,mainDependencies:i}=t,{currentContext:s,streamKeys:c,listenOptions:u}=e,{keyWait:l}=u;let a=[],m=null,p=!0,h=!1;const f=()=>p=!1,d=()=>p=!0,g=()=>h=!0,x=()=>!1===p;function y(){let t=a.map((t=>[t.join("+")]));const e={wait:f,end:d,ignore:g,isWaiting:x,note:s.note,context:s.name,dependencies:i.extra,type:"key"};if(!p){let o=t.at(-1);n.emit(o,e),h&&(t=t.slice(0,-1),h=!1)}if(p){const o=`KEY:${t.join(",")}`;n.emit(o,e),a=[],m=null}}function C(e){if(clearTimeout(m),o.hasOwnProperty(e.code))return a.push(r(e,o)),c&&c({key:e.key,context:s.name,note:s.note,dependencies:t.extra}),u.keyIgnore?(clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l))):p&&a.length===u.maxSequence?(y(),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l))):void(p?m=setTimeout(y,l):y())}function k(e){if(!o.hasOwnProperty(e.code)){if(clearTimeout(m),c&&c({key:e.key,context:s.name,note:s.note,dependencies:t.extra}),u.keyIgnore)return clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l));if(a.push(r(e,o)),p&&a.length===u.maxSequence)return y(),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l));p?m=setTimeout(y,l):y()}}return{start:function(){e.active||(document.addEventListener("keydown",C),document.addEventListener("keypress",k),e.active=!0)},stop:function(){e.active&&(document.removeEventListener("keydown",C),document.removeEventListener("keypress",k),e.active=!1)}}}(m,p),f=r(m,p);f>0&&h.start();const d={getPrefix:()=>"key",shortcutName:t=>n(t),contextChange:t=>{f=r(m,p),f<1&&h.stop(),f>0&&h.start()},mute:()=>h.stop(),unmute:()=>h.start(),destroy:()=>{h.stop(),p=null,d=null}};return Object.freeze(d),d}function c(t,e,n){const{listenOptions:{clickTarget:o}}=e;let r=n;return r===document||r===document.body?null:r.dataset[o]||"A"===r.nodeName?r:c(t,e,r.parentNode)}function u(t){const e=t.toUpperCase(),n=/CLICK\s*\:/i.test(e),o=["LEFT","MIDDLE","RIGHT"],r=["ALT","SHIFT","CTRL"];let i=null,s=[],c=0,u=e.indexOf(":");return n?(e.slice(u+1).trim().split("-").map((t=>t.trim())).forEach((t=>{o.includes(t)?i=t:r.includes(t)?s.push(t):isNaN(t)||(c=t)})),`CLICK:${i}-${c}${s.length>0?"-":""}${s.sort().join("-")}`):t}function l(t,e){let{shiftKey:n,altKey:o,ctrlKey:r,key:i,button:s}=t,c=`CLICK:${["LEFT","MIDDLE","RIGHT"][s]}-${e}`,u=[];return r&&u.push("CTRL"),n&&u.push("SHIFT"),o&&u.push("ALT"),u.length>0?`${c}${u.length>0?"-":""}${u.sort().join("-")}`:`${c}`}function a(t,e){let n=0;const{regex:o}=t,{listenOptions:r,currentContext:{name:i},shortcuts:s}=e;return null==i?0:(Object.entries(s[i]).forEach((([t,e])=>{if(!o.test(t))return;n++;let[,i]=t.slice(6).split("-");r.maxClicks<i&&(r.maxClicks=i)})),n)}function m(t,e,n){let{currentContext:o,shortcuts:r}=e,{inAPI:i}=t,s={ev:t.ev,_findTarget:c,_readClickEvent:l,mainDependencies:t,regex:/CLICK\s*\:/i},m={currentContext:o,shortcuts:r,listenOptions:{mouseWait:n.mouseWait?n.mouseWait:320,maxClicks:1,clickTarget:n.clickTarget?n.clickTarget:"click"}};i._normalizeWithPlugins(u);let p=function(t,e){const{ev:n,_findTarget:o,_readClickEvent:r,mainDependencies:i}=t,{listenOptions:s,currentContext:c}=e,{mouseWait:u}=s;let l=null,a=null,m=null,p=null,h=0;function f(){const t=r(a,h),e={target:l,targetProps:l?l.getBoundingClientRect():null,x:a.clientX,y:a.clientY,context:c.name,note:c.note,event:a,dependencies:i.extra,type:"click"};n.emit(t,e),m=null,p=null,l=null,a=null,h=0}function d(n){let r=s.maxClicks;return clearTimeout(m),p?(clearTimeout(p),void(p=setTimeout((()=>p=null),u))):(l=o(t,e,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,h++,h>=r?(f(),void(r>1&&(p=setTimeout((()=>p=null),u)))):void(m=setTimeout(f,u)))}function g(n){let r=s.maxClicks;return clearTimeout(m),p?(clearTimeout(p),void(p=setTimeout((()=>p=null),u))):(l=o(t,e,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,h++,h>=r?(f(),void(r>1&&(p=setTimeout((()=>p=null),u)))):void(m=setTimeout(f,u)))}return{start:function(){e.active||(window.addEventListener("contextmenu",g),document.addEventListener("click",d),e.active=!0)},stop:function(){e.active&&(window.removeEventListener("contextmenu",g),document.removeEventListener("click",d),e.active=!1)}}}(s,m),h=a(s,m);h>0&&p.start();const f={getPrefix:()=>"click",shortcutName:t=>u(t),contextChange:()=>{h=a(s,m),h<1&&p.stop(),h>0&&p.start()},mute:()=>p.stop(),unmute:()=>p.start(),destroy:()=>{p.stop(),m=null,f=null}};return Object.freeze(f),f}function p(n={}){const o=t(),r={},i={},s={currentContext:{name:null,note:null},shortcuts:{},plugins:[],exposeShortcut:!(!n.onShortcut||"function"!=typeof n.onShortcut)&&n.onShortcut},c={ev:o,inAPI:r,API:i,extra:{}};return i.enablePlugin=(t,e={})=>{const n=t.name;if(-1===r._systemAction(n,"none")){let n;n=t(c,s,e),s.plugins.push(n)}},i.disablePlugin=t=>{const e=r._systemAction(t,"destroy");-1!==e&&(s.plugins=s.plugins.filter(((t,n)=>n!==e)))},i.mutePlugin=t=>r._systemAction(t,"mute"),i.unmutePlugin=t=>r._systemAction(t,"unmute"),i.getContext=()=>s.currentContext.name,i.getNote=()=>s.currentContext.note,i.setNote=(t=null)=>{"string"!=typeof t&&null!=t||(s.currentContext.note=t)},i.pause=(t="*")=>{let e=r._readShortcutWithPlugins(t);o.stop(e)},i.resume=(t="*")=>{const e=r._readShortcutWithPlugins(t);o.start(e)},i.emit=(t,...e)=>o.emit(r._readShortcutWithPlugins(t),...e),i.listContexts=()=>Object.keys(s.shortcuts),i.setDependencies=t=>c.extra={...c.extra,...t},i.getDependencies=()=>c.extra,Object.entries(e).forEach((([t,e])=>{t.startsWith("_")?r[t]=e(c,s):i[t]=e(c,s)})),i}export{m as pluginClick,s as pluginKey,p as shortcuts};
|
package/dist/shortcuts.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).shortcuts=t()}(this,(function(){"use strict";var e={_findTarget:function(e,t){const{listenOptions:{clickTarget:n}}=t;return function e(t){const o=t;return o===document||o===document.body?null:o.dataset[n]||"A"===o.nodeName?o:e(o.parentNode)}},_listen:function(e,t){return function(){const{ev:n,inAPI:{_findTarget:o,_specialChars:r,_readKeyEvent:i,_readMouseEvent:s}}=e,{exposeShortcut:c,currentContext:u,streamKeys:l,listenOptions:a}=t,{mouseWait:d,keyWait:f,clickTarget:m,listenFor:h}=a;let p=[],g=null,y=null,k=null,x=null,T=null,C=!0,F=!1,E=0;const v=()=>C=!1,I=()=>C=!0,S=()=>F=!0,A=()=>!1===C;function O(){let t=p.map((e=>[e.join("+")]));if(!C){let e=t.at(-1);n.emit(e,{wait:v,end:I,ignore:S,isWaiting:A,note:u.note,context:u.name}),F&&(t=t.slice(0,-1),F=!1)}const o={wait:v,end:I,ignore:S,isWaiting:A,note:u.note,context:u.name,dependencies:e.extra};C&&(n.emit(t.join(","),o),c&&c({shortcut:t.join(","),context:u.name,note:u.note,dependencies:e.extra}),p=[],k=null)}function b(){const t=s(y,E),o={target:g,targetProps:g?g.getBoundingClientRect():null,x:y.clientX,y:y.clientY,context:u.name,note:u.note,event:y,dependencies:e.extra};n.emit(t.join("+"),o),c&&c({shortcut:t.join("+"),context:u.name,note:u.note,dependencies:e.extra}),x=null,T=null,g=null,y=null,E=0}h.includes("mouse")&&(window.addEventListener("contextmenu",(e=>{let t=a.maxClicks;return e.preventDefault(),clearTimeout(x),T?(clearTimeout(T),void(T=setTimeout((()=>T=null),d))):(g=o(e.target),g&&g.dataset.hasOwnProperty("quickClick")&&(t=1),g&&"A"===g.tagName&&(t=1),y=e,E++,E>=t?(b(),void(t>1&&(T=setTimeout((()=>T=null),d)))):void(x=setTimeout(b,d)))})),document.addEventListener("click",(e=>{let t=a.maxClicks;return e.preventDefault(),clearTimeout(x),T?(clearTimeout(T),void(T=setTimeout((()=>T=null),d))):(g=o(e.target),g&&g.dataset.hasOwnProperty("quickClick")&&(t=1),g&&"A"===g.tagName&&(t=1),y=e,E++,E>=t?(b(),void(t>1&&(T=setTimeout((()=>T=null),d)))):void(x=setTimeout(b,d)))}))),h.includes("keyboard")&&(document.addEventListener("keydown",(t=>{if(clearTimeout(k),r.hasOwnProperty(t.code))return p.push(i(t,r)),l&&l({key:t.key,context:u.name,note:u.note,dependencies:e.extra}),a.keyIgnore?(clearTimeout(a.keyIgnore),void(a.keyIgnore=setTimeout((()=>a.keyIgnore=null),f))):C&&p.length===a.maxSequence?(O(),void(a.keyIgnore=setTimeout((()=>a.keyIgnore=null),f))):void(C?k=setTimeout(O,f):O())})),document.addEventListener("keypress",(t=>{if(!r.hasOwnProperty(t.code)){if(clearTimeout(k),l&&l({key:t.key,context:u.name,note:u.note,dependencies:e.extra}),a.keyIgnore)return clearTimeout(a.keyIgnore),void(a.keyIgnore=setTimeout((()=>a.keyIgnore=null),f));if(p.push(i(t,r)),C&&p.length===a.maxSequence)return O(),void(a.keyIgnore=setTimeout((()=>a.keyIgnore=null),f));C?k=setTimeout(O,f):O()}})))}},_readKeyEvent:function(){return function(e,t){let{shiftKey:n,altKey:o,ctrlKey:r}=e,i=e.code.replace("Key","").replace("Digit",""),s=[];return r&&s.push("CTRL"),n&&s.push("SHIFT"),o&&s.push("ALT"),t.hasOwnProperty(i)?s.push(t[i].toUpperCase()):["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"].includes(i)||s.push(i.toUpperCase()),s.sort()}},_readMouseEvent:function(){return function(e,t){let{shiftKey:n,altKey:o,ctrlKey:r,key:i,button:s}=e,c=`MOUSE-CLICK-${["LEFT","MIDDLE","RIGHT"][s]}-${t}`,u=[];return u.push(c),r&&u.push("CTRL"),n&&u.push("SHIFT"),o&&u.push("ALT"),u.sort()}},_readShortcut:function(){return function(e){return e.split(",").map((e=>e.trim())).map((e=>e.split("+").map((e=>e.toUpperCase())).sort().join("+"))).join(",")}},_specialChars:function(){return{ArrowLeft:"LEFT",ArrowUp:"UP",ArrowRight:"RIGHT",ArrowDown:"DOWN",Enter:"ENTER",NumpadEnter:"ENTER",Escape:"ESC",Backspace:"BACKSPACE",Space:"SPACE",Tab:"TAB",Backquote:"`",BracketLeft:"[",BracketRight:"]",Equal:"=",Slash:"/",Backslash:"\\",IntlBackslash:"`",F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12"}},changeContext:function(e,t){const{shortcuts:n,listenOptions:o,currentContext:r}=t,{ev:i}=e;return function(e=!1){const t=r.name;if(o.maxSequence=1,o.maxClicks=1,o.keyIgnore>=0&&(clearTimeout(o.keyIgnore),o.keyIgnore=null),!e)return i.reset(),void(r.name=null);t!==e&&(n[e]?(n[t]&&i.reset(),Object.entries(n[e]).forEach((([e,t])=>{if(e.includes("MOUSE-CLICK-")){let[,,,t]=e.split("-"),n=parseInt(t);o.maxClicks<n&&(o.maxClicks=n)}else{let t=e.split(",").length;o.maxSequence<t&&(o.maxSequence=t)}t.forEach((t=>i.on(e,t)))})),r.name=e):i.emit("shortcuts-error",`Context '${e}' does not exist`))}},listShortcuts:function(e,t){const n=t.shortcuts;return function(e=null){if(null!=e){let t=n[e];return null==t?null:Object.entries(t).map((([e,t])=>e))}return Object.keys(n).map((e=>{let t={};return t.context=e,t.shortcuts=Object.entries(n[e]).map((([e,t])=>e)),t}))}},load:function(e,t){const{shortcuts:n}=t,{inAPI:{_readShortcut:o},API:{changeContext:r,getContext:i}}=e;return function(e){const t=i(),s=Object.keys(e);let c=!1;s.forEach((r=>{const i=Object.entries(e[r]);r===t&&(c=!0),n[r]={},i.forEach((([e,t])=>{const i=o(e);t instanceof Function&&(t=[t]),n[r][i]=t}))})),c&&(r(),r(t))}},unload:function(e,t){const{currentContext:n,shortcuts:o}=t,{ev:r}=e;return function(e){n.name!==e?o[e]?delete o[e]:r.emit("shortcuts-error",`Context '${e}' does not exist`):r.emit("shortcuts-error",`Context '${e}' can't be removed during is current active context. Change the context first`)}}};function t(t={}){const n=new function(){let e={"*":[]},t={},n=[],o=!1,r="";return{on:function(t,n){e[t]||(e[t]=[]),e[t].push(n)},once:function(e,n){"*"!==e&&(t[e]||(t[e]=[]),t[e].push(n))},off:function(n,o){if(o)return e[n]&&(e[n]=e[n].filter((e=>e!==o))),t[n]&&(t[n]=t[n].filter((e=>e!==o))),e[n]&&0===e[n].length&&delete e[n],void(t[n]&&0===t[n].length&&delete e[n]);t[n]&&delete t[n],e[n]&&delete e[n]},reset:function(){e={"*":[]},t={},n=[]},emit:function(){const[i,...s]=arguments;function c(t){"*"!==t&&(n.includes(t)||(e[t].forEach((e=>e(...s))),e["*"].forEach((e=>e(i,...s)))))}if(o&&(console.log(`${r} Event "${i}" was triggered.`),s.length>0&&(console.log("Arguments:"),console.log(...s),console.log("^----"))),"*"!==i){if(t[i]){if(n.includes(i))return;t[i].forEach((e=>e(...s))),delete t[i]}e[i]&&c(i)}else Object.keys(e).forEach((e=>c(e)))},stop:function(o){if("*"!==o)n.push(o);else{const o=Object.keys(e),r=Object.keys(t);n=[...r,...o]}},start:function(e){n="*"!==e?n.filter((t=>e!=t)):[]},debug:function(e,t){o=!!e,t&&"string"==typeof t&&(r=t)}}},o={},r={},i={currentContext:{name:null,note:null},shortcuts:{},listenOptions:{mouseWait:t.mouseWait?t.mouseWait:320,maxClicks:1,keyWait:t.keyWait?t.keyWait:480,maxSequence:1,clickTarget:t.clickTarget?t.clickTarget:"click",listenFor:t.listenFor&&Array.isArray(t.listenFor)?t.listenFor:["mouse","keyboard"],keyIgnore:null},exposeShortcut:!(!t.onShortcut||"function"!=typeof t.onShortcut)&&t.onShortcut,streamKeys:!(!t.streamKeys||"function"!=typeof t.streamKeys)&&t.streamKeys},s={ev:n,inAPI:o,API:r,extra:{}};return r.getContext=()=>i.currentContext.name,r.getNote=()=>i.currentContext.note,r.setNote=(e=null)=>{"string"!=typeof e&&null!=e||(i.currentContext.note=e)},r.pause=(e="*")=>n.stop(o._readShortcut(e)),r.resume=(e="*")=>n.start(o._readShortcut(e)),r.emit=(e,...t)=>n.emit(o._readShortcut(e),s.extra,...t),r.listContexts=()=>Object.keys(shortcuts),r.setDependencies=e=>s.extra={...s.extra,...e},r.getDependencies=()=>s.extra,Object.entries(e).forEach((([e,t])=>{e.startsWith("_")?o[e]=t(s,i):r[e]=t(s,i)})),o._listen(),r}return t.getDefaults=()=>({mouseWait:320,keyWait:480,clickTarget:"click",listenFor:["mouse","keyboard"],onShortcut:!1,streamKeys:!1}),t}));
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).shortcuts={})}(this,(function(e){"use strict";var t={_normalizeWithPlugins:function(e,t){return function(e){const n=t.shortcuts;Object.keys(n).forEach((t=>{Object.entries(n[t]).forEach((([o,r])=>{const i=e(o);i!==o&&(delete n[t][o],n[t][i]=r)}))}))}},_readShortcutWithPlugins:function(e,t){return function(n){const{inAPI:o}=e,r=n.split(":")[0],i=o._systemAction(r,"none");let s=n;return-1!==i&&(s=t.plugins[i].shortcutName(n)),s}},_systemAction:function(e,t){return function(e,n,o=null){return t.plugins.findIndex((t=>t.getPrefix()===e&&(t[n]&&t[n](o),!0)))}},changeContext:function(e,t){const{shortcuts:n,currentContext:o}=t,{ev:r}=e;return function(e=!1){const i=o.name;if(!e)return r.reset(),void(o.name=null);i!==e&&(n[e]?(n[i]&&r.reset(),o.name=e,t.plugins.forEach((t=>t.contextChange(e))),Object.entries(n[e]).forEach((([e,t])=>{t.forEach((t=>r.on(e,t)))})),r.on("*",((...e)=>{t.exposeShortcut&&t.exposeShortcut(...e)}))):r.emit("@shortcuts-error",`Context '${e}' does not exist`))}},listShortcuts:function(e,t){const n=t.shortcuts;return function(e=null){if(null!=e){let t=n[e];return null==t?null:Object.entries(t).map((([e,t])=>e))}return Object.keys(n).map((e=>{let t={};return t.context=e,t.shortcuts=Object.entries(n[e]).map((([e,t])=>e)),t}))}},load:function(e,t){const{shortcuts:n,plugins:o}=t,{API:{changeContext:r,getContext:i}}=e;return function(e){const t=i(),s=o.map((e=>e.getPrefix().toUpperCase()));let c=!1;Object.entries(e).forEach((([e,r])=>{e===t&&(c=!0),n[e]={},Object.entries(r).forEach((([t,r])=>{let i=t,c=t.toUpperCase().trim(),u=s.map(((e,t)=>c.startsWith(e)?t:null)).filter((e=>null!==e));if(u.length){let e=u[0];i=o[e].shortcutName(t)}r instanceof Function&&(r=[r]),n[e][i]=r}))})),c&&(r(),r(t))}},unload:function(e,t){const{currentContext:n,shortcuts:o}=t,{ev:r}=e;return function(e){n.name!==e?o[e]?delete o[e]:r.emit("shortcuts-error",`Context '${e}' does not exist`):r.emit("shortcuts-error",`Context '${e}' can't be removed during is current active context. Change the context first`)}}};function n(e){const t=e.toUpperCase(),n=/KEY\s*\:/i.test(t),o=t.indexOf(":");if(!n)return e;return`KEY:${t.slice(o+1).split(",").map((e=>e.trim())).map((e=>e.split("+").map((e=>e.trim())).sort().join("+"))).join(",")}`}function o(e,t){let{shiftKey:n,altKey:o,ctrlKey:r}=e,i=e.code.replace("Key","").replace("Digit",""),s=[];return r&&s.push("CTRL"),n&&s.push("SHIFT"),o&&s.push("ALT"),t.hasOwnProperty(i)?s.push(t[i].toUpperCase()):["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"].includes(i)||s.push(i.toUpperCase()),s.sort()}function r(e,t){let n=0;const{regex:o}=e,{listenOptions:r,currentContext:{name:i},shortcuts:s}=t;return null==i?0:(Object.entries(s[i]).forEach((([e,t])=>{if(!o.test(e))return;n++;let i=e.slice(4).split(",").length;r.maxSequence<i&&(r.maxSequence=i)})),n)}function i(){return{ArrowLeft:"LEFT",ArrowUp:"UP",ArrowRight:"RIGHT",ArrowDown:"DOWN",Enter:"ENTER",NumpadEnter:"ENTER",Escape:"ESC",Backspace:"BACKSPACE",Space:"SPACE",Tab:"TAB",Backquote:"`",BracketLeft:"[",BracketRight:"]",Equal:"=",Slash:"/",Backslash:"\\",IntlBackslash:"`",F1:"F1",F2:"F2",F3:"F3",F4:"F4",F5:"F5",F6:"F6",F7:"F7",F8:"F8",F9:"F9",F10:"F10",F11:"F11",F12:"F12"}}function s(e,t,n){const{listenOptions:{clickTarget:o}}=t;let r=n;return r===document||r===document.body?null:r.dataset[o]||"A"===r.nodeName?r:s(e,t,r.parentNode)}function c(e){const t=e.toUpperCase(),n=/CLICK\s*\:/i.test(t),o=["LEFT","MIDDLE","RIGHT"],r=["ALT","SHIFT","CTRL"];let i=null,s=[],c=0,u=t.indexOf(":");return n?(t.slice(u+1).trim().split("-").map((e=>e.trim())).forEach((e=>{o.includes(e)?i=e:r.includes(e)?s.push(e):isNaN(e)||(c=e)})),`CLICK:${i}-${c}${s.length>0?"-":""}${s.sort().join("-")}`):e}function u(e,t){let{shiftKey:n,altKey:o,ctrlKey:r,key:i,button:s}=e,c=`CLICK:${["LEFT","MIDDLE","RIGHT"][s]}-${t}`,u=[];return r&&u.push("CTRL"),n&&u.push("SHIFT"),o&&u.push("ALT"),u.length>0?`${c}${u.length>0?"-":""}${u.sort().join("-")}`:`${c}`}function l(e,t){let n=0;const{regex:o}=e,{listenOptions:r,currentContext:{name:i},shortcuts:s}=t;return null==i?0:(Object.entries(s[i]).forEach((([e,t])=>{if(!o.test(e))return;n++;let[,i]=e.slice(6).split("-");r.maxClicks<i&&(r.maxClicks=i)})),n)}e.pluginClick=function(e,t,n){let{currentContext:o,shortcuts:r}=t,{inAPI:i}=e,a={ev:e.ev,_findTarget:s,_readClickEvent:u,mainDependencies:e,regex:/CLICK\s*\:/i},f={currentContext:o,shortcuts:r,listenOptions:{mouseWait:n.mouseWait?n.mouseWait:320,maxClicks:1,clickTarget:n.clickTarget?n.clickTarget:"click"}};i._normalizeWithPlugins(c);let m=function(e,t){const{ev:n,_findTarget:o,_readClickEvent:r,mainDependencies:i}=e,{listenOptions:s,currentContext:c}=t,{mouseWait:u}=s;let l=null,a=null,f=null,m=null,p=0;function h(){const e=r(a,p),t={target:l,targetProps:l?l.getBoundingClientRect():null,x:a.clientX,y:a.clientY,context:c.name,note:c.note,event:a,dependencies:i.extra,type:"click"};n.emit(e,t),f=null,m=null,l=null,a=null,p=0}function d(n){let r=s.maxClicks;return clearTimeout(f),m?(clearTimeout(m),void(m=setTimeout((()=>m=null),u))):(l=o(e,t,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,p++,p>=r?(h(),void(r>1&&(m=setTimeout((()=>m=null),u)))):void(f=setTimeout(h,u)))}function g(n){let r=s.maxClicks;return clearTimeout(f),m?(clearTimeout(m),void(m=setTimeout((()=>m=null),u))):(l=o(e,t,n.target),l&&l.dataset.hasOwnProperty("quickClick")&&(r=1),l&&"A"===l.tagName&&(r=1),a=n,p++,p>=r?(h(),void(r>1&&(m=setTimeout((()=>m=null),u)))):void(f=setTimeout(h,u)))}return{start:function(){t.active||(window.addEventListener("contextmenu",g),document.addEventListener("click",d),t.active=!0)},stop:function(){t.active&&(window.removeEventListener("contextmenu",g),document.removeEventListener("click",d),t.active=!1)}}}(a,f),p=l(a,f);p>0&&m.start();const h={getPrefix:()=>"click",shortcutName:e=>c(e),contextChange:()=>{p=l(a,f),p<1&&m.stop(),p>0&&m.start()},mute:()=>m.stop(),unmute:()=>m.start(),destroy:()=>{m.stop(),f=null,h=null}};return Object.freeze(h),h},e.pluginKey=function(e,t,s={}){let{currentContext:c,shortcuts:u,exposeShortcut:l}=t,{inAPI:a}=e,f={ev:e.ev,_specialChars:i,_readKeyEvent:o,mainDependencies:e,regex:/KEY\s*\:/i},m={currentContext:c,shortcuts:u,active:!1,listenOptions:{keyWait:s.keyWait?s.keyWait:480,maxSequence:1,keyIgnore:null},streamKeys:!(!s.streamKeys||"function"!=typeof s.streamKeys)&&s.streamKeys,exposeShortcut:l};a._normalizeWithPlugins(n);let p=function(e,t){const{ev:n,_specialChars:o,_readKeyEvent:r,mainDependencies:i}=e,{currentContext:s,streamKeys:c,listenOptions:u}=t,{keyWait:l}=u;let a=[],f=null,m=!0,p=!1;const h=()=>m=!1,d=()=>m=!0,g=()=>p=!0,y=()=>!1===m;function x(){let e=a.map((e=>[e.join("+")]));const t={wait:h,end:d,ignore:g,isWaiting:y,note:s.note,context:s.name,dependencies:i.extra,type:"key"};if(!m){let o=e.at(-1);n.emit(o,t),p&&(e=e.slice(0,-1),p=!1)}if(m){const o=`KEY:${e.join(",")}`;n.emit(o,t),a=[],f=null}}function C(t){if(clearTimeout(f),o.hasOwnProperty(t.code))return a.push(r(t,o)),c&&c({key:t.key,context:s.name,note:s.note,dependencies:e.extra}),u.keyIgnore?(clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l))):m&&a.length===u.maxSequence?(x(),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l))):void(m?f=setTimeout(x,l):x())}function k(t){if(!o.hasOwnProperty(t.code)){if(clearTimeout(f),c&&c({key:t.key,context:s.name,note:s.note,dependencies:e.extra}),u.keyIgnore)return clearTimeout(u.keyIgnore),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l));if(a.push(r(t,o)),m&&a.length===u.maxSequence)return x(),void(u.keyIgnore=setTimeout((()=>u.keyIgnore=null),l));m?f=setTimeout(x,l):x()}}return{start:function(){t.active||(document.addEventListener("keydown",C),document.addEventListener("keypress",k),t.active=!0)},stop:function(){t.active&&(document.removeEventListener("keydown",C),document.removeEventListener("keypress",k),t.active=!1)}}}(f,m),h=r(f,m);h>0&&p.start();const d={getPrefix:()=>"key",shortcutName:e=>n(e),contextChange:e=>{h=r(f,m),h<1&&p.stop(),h>0&&p.start()},mute:()=>p.stop(),unmute:()=>p.start(),destroy:()=>{p.stop(),m=null,d=null}};return Object.freeze(d),d},e.shortcuts=function(e={}){const n=new function(){let e={"*":[]},t={},n=[],o=!1,r="";return{on:function(t,n){e[t]||(e[t]=[]),e[t].push(n)},once:function(e,n){"*"!==e&&(t[e]||(t[e]=[]),t[e].push(n))},off:function(n,o){if(o)return e[n]&&(e[n]=e[n].filter((e=>e!==o))),t[n]&&(t[n]=t[n].filter((e=>e!==o))),e[n]&&0===e[n].length&&delete e[n],void(t[n]&&0===t[n].length&&delete e[n]);t[n]&&delete t[n],e[n]&&delete e[n]},reset:function(){e={"*":[]},t={},n=[]},emit:function(){const[i,...s]=arguments;function c(t){"*"!==t&&(n.includes(t)||(e[t].forEach((e=>e(...s))),e["*"].forEach((e=>e(i,...s)))))}if(o&&(console.log(`${r} Event "${i}" was triggered.`),s.length>0&&(console.log("Arguments:"),console.log(...s),console.log("^----"))),"*"!==i){if(t[i]){if(n.includes(i))return;t[i].forEach((e=>e(...s))),delete t[i]}e[i]&&c(i)}else Object.keys(e).forEach((e=>c(e)))},stop:function(o){if("*"!==o)n.push(o);else{const o=Object.keys(e),r=Object.keys(t);n=[...r,...o]}},start:function(e){n="*"!==e?n.filter((t=>e!=t)):[]},debug:function(e,t){o=!!e,t&&"string"==typeof t&&(r=t)}}},o={},r={},i={currentContext:{name:null,note:null},shortcuts:{},plugins:[],exposeShortcut:!(!e.onShortcut||"function"!=typeof e.onShortcut)&&e.onShortcut},s={ev:n,inAPI:o,API:r,extra:{}};return r.enablePlugin=(e,t={})=>{const n=e.name;if(-1===o._systemAction(n,"none")){let n;n=e(s,i,t),i.plugins.push(n)}},r.disablePlugin=e=>{const t=o._systemAction(e,"destroy");-1!==t&&(i.plugins=i.plugins.filter(((e,n)=>n!==t)))},r.mutePlugin=e=>o._systemAction(e,"mute"),r.unmutePlugin=e=>o._systemAction(e,"unmute"),r.getContext=()=>i.currentContext.name,r.getNote=()=>i.currentContext.note,r.setNote=(e=null)=>{"string"!=typeof e&&null!=e||(i.currentContext.note=e)},r.pause=(e="*")=>{let t=o._readShortcutWithPlugins(e);n.stop(t)},r.resume=(e="*")=>{const t=o._readShortcutWithPlugins(e);n.start(t)},r.emit=(e,...t)=>n.emit(o._readShortcutWithPlugins(e),...t),r.listContexts=()=>Object.keys(i.shortcuts),r.setDependencies=e=>s.extra={...s.extra,...e},r.getDependencies=()=>s.extra,Object.entries(t).forEach((([e,t])=>{e.startsWith("_")?o[e]=t(s,i):r[e]=t(s,i)})),r}}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peter.naydenov/shortcuts",
|
|
3
3
|
"description": "Context control of shortcuts based on keyboard and mouse events",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Peter Naydenov",
|
|
7
7
|
"main": "./dist/shortcuts.umd.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"dev": "vite",
|
|
21
21
|
"build": "rollup -c",
|
|
22
|
-
"test": "cypress
|
|
22
|
+
"test": "cypress open --component --browser chrome test"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"@rollup/plugin-terser": "^0.4.4",
|
|
35
35
|
"@vitejs/plugin-react": "^4.2.1",
|
|
36
36
|
"ask-for-promise": "^2.0.3",
|
|
37
|
-
"chai": "^5.0
|
|
38
|
-
"cypress": "^13.6.
|
|
37
|
+
"chai": "^5.1.0",
|
|
38
|
+
"cypress": "^13.6.6",
|
|
39
39
|
"mocha": "^10.3.0",
|
|
40
40
|
"react": "^18.2.0",
|
|
41
41
|
"react-dom": "^18.2.0",
|
|
42
|
-
"vite": "^5.1.
|
|
42
|
+
"vite": "^5.1.5"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"shortcut",
|