@peter.naydenov/shortcuts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changelog.md +11 -0
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/blueprint-docs.md +73 -0
- package/cypress.config.js +10 -0
- package/dist/assets/index-3c4377c6.js +1 -0
- package/dist/index.html +32 -0
- package/dist/vite.svg +1 -0
- package/index.html +73 -0
- package/javascript.svg +1 -0
- package/package.json +33 -0
- package/public/vite.svg +1 -0
- package/src/changeContext.js +51 -0
- package/src/findTarget.js +18 -0
- package/src/listen.js +186 -0
- package/src/load.js +32 -0
- package/src/main.js +92 -0
- package/src/readKeyEvent.js +27 -0
- package/src/readMouseEvent.js +24 -0
- package/src/readShortcut.js +16 -0
- package/src/specialChars.js +31 -0
- package/src/unload.js +23 -0
- package/style.css +97 -0
- package/test/01-general.cy.js +111 -0
- package/test-components/Block.jsx +11 -0
- package/test-components/style.css +21 -0
- package/vite.config.js +7 -0
package/Changelog.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Peter Naydenov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
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 FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Shortcuts (@peter.naydenov/shortcuts)
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Build a keyboard shortcuts maps and describe a mouse clicks. Control them on context.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Shortcut Description Rules
|
|
11
|
+
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).
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// Shortcut definition object:
|
|
15
|
+
{
|
|
16
|
+
contextName : {
|
|
17
|
+
shortcutName : function () {
|
|
18
|
+
// do something
|
|
19
|
+
}
|
|
20
|
+
, shortcutName : [
|
|
21
|
+
function action1() {
|
|
22
|
+
// do something
|
|
23
|
+
}
|
|
24
|
+
, function action2() {
|
|
25
|
+
// do something
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
Load a shortcut definition by calling `load` method.
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
include shortcuts from '@peter.naydenov/shortcuts'
|
|
35
|
+
const short = shortcuts ();
|
|
36
|
+
short.load ( shortcutDefinition )
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Shortcuts are working only if contex is active. To activate a context call `changeContext` method.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
short.changeContext ( contextName )
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To deactivate a context without starting other context, call `changeContext` method without arguments.
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
short.changeContext ()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Shortcuts context has `note` that works like sub-contexts. Every shortcut function receives a context and note as arguments, so you can have fine control over the context.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
short.setNote ( 'special' ) // set note to 'special'
|
|
55
|
+
short.setNote () // remove the note
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The idea of `note` is to minimize the number of contexts if they are very simular. You can use same context but change the `note` and control the shortcut execution from inside of the action function by checking the `note`.
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
{
|
|
62
|
+
contextName : {
|
|
63
|
+
shortcutName : function ( {context, note} ) {
|
|
64
|
+
if ( note === 'special' ) {
|
|
65
|
+
// do something
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Context and notes are available inside action functions but you can check them from outside too.
|
|
73
|
+
Check current context by calling `getContext` method.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
short.getContext ()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Check notes by calling `getNote` method.
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
short.getNote ()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Mouse Event Descriptions
|
|
90
|
+
Mouse event name is build from the following parts:
|
|
91
|
+
```js
|
|
92
|
+
// mouse-click-<mouse button>-<number of clicks>
|
|
93
|
+
// example:
|
|
94
|
+
// mouse-click-left-2 -> for double click with left mouse button
|
|
95
|
+
// mouse-click-right-3 -> for triple click with right mouse button
|
|
96
|
+
|
|
97
|
+
// mouse button options: left, right, middle
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The modifier keys `ctrl`, `alt`, and `shift` are supported. They are added to the mouse event by sign `+`:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// example:
|
|
104
|
+
// ctrl+mouse-click-left-1 -> for single click with left mouse button and ctrl key pressed
|
|
105
|
+
```
|
|
106
|
+
Order of describing mouse event and modifier keys is not important.
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
// example:
|
|
110
|
+
// mouse-click-left-1+ctrl -> same as above
|
|
111
|
+
|
|
112
|
+
// These 3 descriptions are equal:
|
|
113
|
+
// mouse-click-left-1+ctrl+alt+shift
|
|
114
|
+
// alt+shift+mouse-click-left-1+ctrl
|
|
115
|
+
// mouse-click-left-1+shift+ctrl+alt
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Multiple clicks are detected automatically by time interval between clicks. The default interval is 320ms but you can change it by setting `mouseWait` option. Read more in section `Options`.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Keyboard Event Descriptions
|
|
125
|
+
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 `+`:
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
// example:
|
|
129
|
+
// ctrl+alt+shift+a -> for key 'a' with ctrl, alt and shift keys pressed
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
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 ):
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
// example:
|
|
136
|
+
// a,b,c -> for key 'a' then key 'b' then key 'c'
|
|
137
|
+
|
|
138
|
+
// g+shift,o,t,o -> for key 'g' with shift, then key 'o', then key 't' then key 'o'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Order of describing keyboard event and modifier keys is not important, but sequence elements are:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
// example:
|
|
145
|
+
// a+ctrl,l,o,t -> a with ctrl, then l, then o, then t
|
|
146
|
+
// this is equal to:
|
|
147
|
+
// ctrl+a,l,o,t
|
|
148
|
+
// but not equal to:
|
|
149
|
+
// ctrl+a,o,t,l
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
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`.
|
|
153
|
+
|
|
154
|
+
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`.
|
|
155
|
+
|
|
156
|
+
Special characters that are available for your shortcut descriptions:
|
|
157
|
+
- 'left' - left arrow key
|
|
158
|
+
- 'right' - right arrow key
|
|
159
|
+
- 'up' - up arrow key
|
|
160
|
+
- 'down' - down arrow key
|
|
161
|
+
- 'enter' - enter key
|
|
162
|
+
- 'space' - space key
|
|
163
|
+
- 'esc' - escape key
|
|
164
|
+
- 'tab' - tab key
|
|
165
|
+
- 'backspace' - backspace key
|
|
166
|
+
- '=' - equal key
|
|
167
|
+
- F1 - F12 - function keys
|
|
168
|
+
- '/' - slash key
|
|
169
|
+
- '\\' - backslash key
|
|
170
|
+
- '[' - open square bracket key
|
|
171
|
+
- ']' - close square bracket key
|
|
172
|
+
- '`' - backtick key
|
|
173
|
+
|
|
174
|
+
**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.
|
|
175
|
+
|
|
176
|
+
**Warining**: Some of the shortcuts are used by OS and the browswer, so they are not available.
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Action Functions
|
|
181
|
+
Action functions are called when a shortcut is triggered. They is a difference between keyboard and mouse action functions. Arguments are slightly different.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
### Keyboard Action Functions
|
|
186
|
+
Description of keyboard action functions is:
|
|
187
|
+
```js
|
|
188
|
+
function myKeyHandler ({
|
|
189
|
+
context // (string) Name of the current context;
|
|
190
|
+
, note // (string) Name of the note or null if note isn't set;
|
|
191
|
+
, wait // (function). Call it to stop a sequence timer and write shortcut sequence without a timer.
|
|
192
|
+
, end // (function). Recover the sequence timer;
|
|
193
|
+
, ignore // (function). Call it to ignore the current shortcut from the sequence;
|
|
194
|
+
, isWaiting // (boolean). True if the sequence timer is active;
|
|
195
|
+
}) {
|
|
196
|
+
// Body of the handler. Do something...
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
### Mouse Action Functions
|
|
204
|
+
Mouse action functions can be described like:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
function myMouseHandler ({
|
|
208
|
+
context // (string) Name of the current context;
|
|
209
|
+
, note // (string) Name of the note or null if note isn't set;
|
|
210
|
+
, target // (DOM element). Target element of the mouse event;
|
|
211
|
+
, targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
|
|
212
|
+
, x // (number). X coordinate of the target element;
|
|
213
|
+
, y // (number). Y coordinate of the target element;
|
|
214
|
+
, event // (object). Original mouse event object;
|
|
215
|
+
}) {
|
|
216
|
+
// Body of the handler. Do something...
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## Methods
|
|
225
|
+
|
|
226
|
+
Description of the methods of shortcut instance:
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
load : 'Load and extend a shortcut definition.'
|
|
230
|
+
, unload : 'Remove a shortcut context with all its shortcuts.'
|
|
231
|
+
, changeContext : 'Switch to existing shortcut context.'
|
|
232
|
+
, pause : 'Stop listening for shortcuts.'
|
|
233
|
+
, resume : 'Resume listening for shortcuts.'
|
|
234
|
+
, listContexts : 'Return list of available contexts.'
|
|
235
|
+
, getContext : 'Return a name of current context or null if there is no context selected'
|
|
236
|
+
, getNote : `Return a name of current note or null if note isn't set`
|
|
237
|
+
, setNote : 'Set a note to current context.'
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
## Options
|
|
244
|
+
|
|
245
|
+
By `options` you can customize the behavior of the shortcuts. Here is the list of available options:
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
mouseWait : 'Timeout for entering multiple mouse events. Default value - 320.'
|
|
249
|
+
, keyWait : 'Timeout for entering shortcut sequence in ms. Default value - 480'
|
|
250
|
+
, clickTarget : 'Data attribute name to recognize click items in HTML. Default value - click' // data attribute 'click' means attribute ( data-click='someName' )
|
|
251
|
+
, listenFor : `List input signal sources. Default value - [ 'mouse', 'keyboard' ]`
|
|
252
|
+
, onShortcut : 'False or a callback function that is called when a shortcut is triggered. Default value - false'
|
|
253
|
+
, streamKeys : 'False or a callback function that is called when a key is pressed. Default value - false'
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
You can request default list of options with their default values:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
shortcuts.getDefaults ()
|
|
260
|
+
// Note: This method is availalble on the original shortcuts object, not on the shortcuts instance.
|
|
261
|
+
|
|
262
|
+
// start a shortcuts with default options
|
|
263
|
+
const short = shortcuts ()
|
|
264
|
+
const short = shortcuts ( shortcuts.getDefaults () ) // same as above
|
|
265
|
+
// The idea behind getDefaults is to see what options are available and what are their default values.
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
### onShortcut option
|
|
271
|
+
```js
|
|
272
|
+
function onShortcut ( shortcut, context, note ) {
|
|
273
|
+
// shortcut - (string) Triggered shortcut name
|
|
274
|
+
// context - (string) Name of the current context
|
|
275
|
+
// note - (string) Name of the note or null if note isn't set
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
### streamKeys option
|
|
282
|
+
```js
|
|
283
|
+
function streamKeys ( key, context, note ) {
|
|
284
|
+
// key - (string) Pressed key name
|
|
285
|
+
// context - (string) Name of the current context
|
|
286
|
+
// note - (string) Name of the note or null if note isn't set
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
## Credits
|
|
294
|
+
'@peter.naydenov/shortcuts' was created and supported by Peter Naydenov.
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
'@peter.naydenov/shortcuts' is released under the MIT License.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Shortcuts (@peter.naydenov/shortcuts)
|
|
2
|
+
*UNDER HEAVY DEVELOPMENT - early experimental stage*
|
|
3
|
+
|
|
4
|
+
Build a keyboard shortcuts maps and describe a mouse clicks. Control them on context.
|
|
5
|
+
|
|
6
|
+
Tread it as a "**draft**" during `HEAVY DEVELOPMENT` stage. the API will change frequently and version of the package will not be updated. Will stay at `0.0.1` until the API get usable.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Create shortcuts for your web application based on keyboard and mouse events.
|
|
13
|
+
|
|
14
|
+
Listen for keyboard, mouse and touch events and converts them into event-emmiter events.
|
|
15
|
+
Define a shortcut library with shortcut names and callback functions. The callback functions will be called when the shortcut is pressed. Library `shortcuts` supports a context switching and that allows
|
|
16
|
+
to add, remove and change multiple shortcuts by changing the current context.
|
|
17
|
+
|
|
18
|
+
- Context switching;
|
|
19
|
+
- Listen for keyboard, mouse and touch events;
|
|
20
|
+
- Read a shortcut sequences;
|
|
21
|
+
- Multiple mouse clicks events for left and right mouse buttons;
|
|
22
|
+
- The delay between keypresses and mouse clicks in shortcut sequences are controlled by parameters;
|
|
23
|
+
- Centralized control over mouse click event. All clicks on the page can be described by a single callback function;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Define and conrol shortcuts in your application. Setup a stage, create a shortcut name and associate a list of callback function with it. The callback function will be called when the shortcut is pressed. Changing the stage will stop all shortcuts associated with it old stage and start all shortcuts associated with the new stage.
|
|
27
|
+
|
|
28
|
+
Event sources: keyboard, mouse, touch;
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm i @peter.naydenov/shortcuts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
From the project file:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import shortcuts from '@peter.naydenov/shortcuts'
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Methods
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
'load' : 'Provide context objects, shortcuts related to the context and list of callback functions'
|
|
48
|
+
, 'unload' : 'Remove context objects and shortcuts'
|
|
49
|
+
, 'changeContext' : 'Change the current context. Change active list of shortcuts'
|
|
50
|
+
, 'pause' : 'Disable all shortcuts. No change of context'
|
|
51
|
+
, 'resume' : 'Resume shortcuts after pause'
|
|
52
|
+
, 'getContext' : 'Return the current context name'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Keyboard Action functions
|
|
57
|
+
Keypress will create an event with name of pressed keys. If `shortcut` has a callback function associated with this name, the callback function will be called.
|
|
58
|
+
|
|
59
|
+
The callback function will receive an object with the following properties:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
function keyCallback (key) {
|
|
63
|
+
console.log(key)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
Every shortcut is defined by a callback function. The callback function will be called when the shortcut is pressed. The callback function will receive an object with the following properties:
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
function keyCallback (key) {
|
|
71
|
+
console.log(key)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const l of o)if(l.type==="childList")for(const u of l.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&i(u)}).observe(document,{childList:!0,subtree:!0});function t(o){const l={};return o.integrity&&(l.integrity=o.integrity),o.referrerPolicy&&(l.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?l.credentials="include":o.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function i(o){if(o.ep)return;o.ep=!0;const l=t(o);fetch(o.href,l)}})();function M(){function r(){let e={},t={},i=[],o=!1,l="";function u(n,s){e[n]||(e[n]=[]),e[n].push(s)}function c(n,s){t[n]||(t[n]=[]),t[n].push(s)}function y(n,s){if(s){e[n]&&(e[n]=e[n].filter(g=>g!==s)),t[n]&&(t[n]=t[n].filter(g=>g!==s)),e[n]&&e[n].length===0&&delete e[n],t[n]&&t[n].length===0&&delete e[n];return}t[n]&&delete t[n],e[n]&&delete e[n]}function f(){e={},t={},i=[]}function m(n,s){o=!!n,s&&typeof s=="string"&&(l=s)}function d(){const[n,...s]=arguments;if(o&&(console.log(`${l} Event "${n}" was triggered.`),s.length>0&&(console.log("Arguments:"),console.log(...s),console.log("^----"))),n=="*"&&Object.keys(e).forEach(E=>{i.includes(E)||e[E].forEach(F=>F(...s))}),t[n]){if(i.includes(n))return;t[n].forEach(g=>g(...s)),delete t[n]}if(e[n]){if(i.includes(n))return;e[n].forEach(g=>g(...s))}}function k(n){if(n==="*"){i=[];return}i=i.filter(s=>n!=s)}function p(n){if(n==="*"){const s=Object.keys(e);i=[...Object.keys(t),...s];return}i.push(n)}return{on:u,once:c,off:y,reset:f,emit:d,stop:p,start:k,debug:m}}return new r}function P(r,e,t){const{specialChars:i,readKeyEvent:o,readMouseEvent:l,findTarget:u,ev:c,exposeShortcut:y,streamKeys:f}=r,{mouseWait:m,keyWait:d,clickTarget:k,listenFor:p}=e;let n=[],s=null,g=null,E=null,F=null,h=null,T=!0,L=!1,S=0;const A=()=>T=!1,b=()=>T=!0,O=()=>L=!0,v=()=>T===!1;function I(){let a=n.map(K=>[K.join("+")]);if(!T){let K=a.at(-1);c.emit(K,{wait:A,end:b,ignore:O,isWaiting:v,note:t.note,context:t.name}),L&&(a=a.slice(0,-1),L=!1)}T&&(c.emit(a.join(","),{wait:A,end:b,ignore:O,isWaiting:v,note:t.note,context:t.name}),y&&y(a.join(","),t.name,t.note),n=[],E=null)}function w(){const a=l(g,S),K={target:s,targetProps:s?s.getBoundingClientRect():null,x:g.clientX,y:g.clientY,context:t.name,note:t.note,event:g};c.emit(a.join("+"),K),y&&y(a.join("+"),t.name,t.note),F=null,h=null,s=null,g=null,S=0}function q(){window.addEventListener("contextmenu",a=>{if(clearTimeout(F),h){clearTimeout(h),h=setTimeout(()=>h=null,m);return}if(S===e.maxClicks){w(),h=setTimeout(()=>h=null,m);return}a.preventDefault(),s=u(a.target,k),g=a,S++,F=setTimeout(w,m)}),document.addEventListener("click",a=>{if(clearTimeout(F),h){clearTimeout(h),h=setTimeout(()=>h=null,m);return}if(S===e.maxClicks){w(),h=setTimeout(()=>h=null,m);return}s=u(a.target,k),g=a,S++,F=setTimeout(w,m)})}function j(){document.addEventListener("keydown",a=>{if(clearTimeout(E),i.hasOwnProperty(a.code))n.push(o(a,i));else return;if(f&&f(a.key,t.name,t.note),e.keyIgnore){clearTimeout(e.keyIgnore),e.keyIgnore=setTimeout(()=>e.keyIgnore=null,d);return}if(T&&n.length===e.maxSequence){I(),e.keyIgnore=setTimeout(()=>e.keyIgnore=null,d);return}T?E=setTimeout(I,d):I()}),document.addEventListener("keypress",a=>{if(!i.hasOwnProperty(a.code)){if(clearTimeout(E),f&&f(a.key,t.name,t.note),e.keyIgnore){clearTimeout(e.keyIgnore),e.keyIgnore=setTimeout(()=>e.keyIgnore=null,d);return}if(n.push(o(a,i)),T&&n.length===e.maxSequence){I(),e.keyIgnore=setTimeout(()=>e.keyIgnore=null,d);return}T?E=setTimeout(I,d):I()}})}p.includes("mouse")&&q(),p.includes("keyboard")&&j()}function B(r){return r.split(",").map(t=>t.trim()).map(t=>t.split("+").map(i=>i.toUpperCase()).sort().join("+")).join(",")}function D(r,e){let{shiftKey:t,altKey:i,ctrlKey:o}=r,l=["ControlLeft","ControlRight","ShiftLeft","ShiftRight","AltLeft","AltRight","Meta"],u=r.code.replace("Key","").replace("Digit",""),c=[];return o&&c.push("CTRL"),t&&c.push("SHIFT"),i&&c.push("ALT"),e.hasOwnProperty(u)?c.push(e[u].toUpperCase()):l.includes(u)||c.push(u.toUpperCase()),c.sort()}function U(r,e){let{shiftKey:t,altKey:i,ctrlKey:o,key:l,button:u}=r,c=["LEFT","MIDDLE","RIGHT"],y=`MOUSE-CLICK-${c[u]}-${e}`,f=[];return f.push(y),o&&f.push("CTRL"),t&&f.push("SHIFT"),i&&f.push("ALT"),f.sort()}function $(r,e){let t=r;for(;t&&!t.dataset[e];)if(t=t.parentNode,t===document||t===document.body)return null;return t}const x={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 N(r,e,t,i){return function(l){const u=i(),c=Object.keys(l);let y=!1;c.forEach(f=>{const m=Object.entries(l[f]);f===u&&(y=!0),r[f]={},m.forEach(([d,k])=>{const p=e(d);k instanceof Function&&(k=[k]),r[f][p]=k})}),y&&(t(),t(u))}}function H(r,e,t){return function(o){if(t.name===o){e.emit("shortcuts-error",`Context '${o}' can't be removed during is current active context. Change the context first`);return}if(!r[o]){e.emit("shortcuts-error",`Context '${o}' does not exist`);return}delete r[o]}}function R(r,e,t,i){return function(l=!1){const u=i.name;if(e.maxSequence=1,e.maxClicks=1,e.keyIgnore>=0&&(clearTimeout(e.keyIgnore),e.keyIgnore=null),!l){t.reset(),i.name=null;return}if(u!==l){if(!r[l]){t.emit("shortcuts-error",`Context '${l}' does not exist`);return}r[u]&&t.reset(),Object.entries(r[l]).forEach(([c,y])=>{if(c.includes("MOUSE-CLICK-")){let[,,,m]=c.split("-"),d=parseInt(m);e.maxClicks<d&&(e.maxClicks=d)}else{let m=c.split(",").length;e.maxSequence<m&&(e.maxSequence=m)}y.forEach(m=>t.on(c,m))}),i.name=l}}}function W(r={}){const e=M(),t={name:null,note:null},i=r.onShortcut&&typeof r.onShortcut=="function"?r.onShortcut:!1,o=r.streamKeys&&typeof r.streamKeys=="function"?r.streamKeys:!1,l={mouseWait:r.mouseWait?r.mouseWait:320,maxClicks:1,keyWait:r.keyWait?r.keyWait:480,maxSequence:1,clickTarget:r.clickTarget?r.clickTarget:"click",listenFor:r.listenFor&&Array.isArray(r.listenFor)?r.listenFor:["mouse","keyboard"],keyIgnore:null},u={},c=()=>t.name,y=()=>t.note,f=(d=null)=>{(typeof d=="string"||d==null)&&(t.note=d)};return P({specialChars:x,readKeyEvent:D,readMouseEvent:U,findTarget:$,ev:e,exposeShortcut:i,streamKeys:o},l,t),{load:N(u,B,R(u,l,e,t),c),unload:H(u,e,t),changeContext:R(u,l,e,t),pause:()=>e.stop(),resume:()=>e.start(),listContexts:()=>Object.keys(u),getContext:c,getNote:y,setNote:f}}W.getDefaults=()=>({mouseWait:320,keyWait:480,clickTarget:"click",listenFor:["mouse","keyboard"],onShortcut:!1,streamKeys:!1});const G={onShortcut(r,e,t){console.log("-- RESULTS --->"),console.log("Shortcut",r),console.log("Context:",e),t&&console.log("Note:",t)}},C=W(G);C.load({general:{q:[()=>console.log("Q was clicked")],"r,o,s":()=>console.log("ROS was clicked"),"mouse-click-left-2":()=>console.log("Mouse left click 2"),r:[({isWaiting:r})=>{r()||console.log("R was clicked")}],"shift+w":[({wait:r,end:e,ignore:t,isWaiting:i,note:o})=>{t(),i()?(console.log("END"),e()):(console.log("WAIT"),r())},()=>console.log("W")]}});C.changeContext("general");C.setNote("some");
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite App</title>
|
|
8
|
+
<style>
|
|
9
|
+
.block {
|
|
10
|
+
width: 100px;
|
|
11
|
+
height: 100px;
|
|
12
|
+
background-color: red;
|
|
13
|
+
}
|
|
14
|
+
.big-btn {
|
|
15
|
+
width: 200px;
|
|
16
|
+
height: 200px;
|
|
17
|
+
margin-left: 320px;
|
|
18
|
+
background-color: skyblue;
|
|
19
|
+
border-radius: 10px;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-3c4377c6.js"></script>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
|
|
26
|
+
<div class="block" data-click="red-block">
|
|
27
|
+
<span>hello</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<button class="big-btn" data-click="mega-button" draggable>Mega button</button>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
package/dist/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/index.html
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite App</title>
|
|
8
|
+
<style>
|
|
9
|
+
.block {
|
|
10
|
+
width: 100px;
|
|
11
|
+
height: 100px;
|
|
12
|
+
background-color: red;
|
|
13
|
+
}
|
|
14
|
+
.big-btn {
|
|
15
|
+
width: 200px;
|
|
16
|
+
height: 200px;
|
|
17
|
+
margin-left: 320px;
|
|
18
|
+
background-color: skyblue;
|
|
19
|
+
border-radius: 10px;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<script type="module">
|
|
25
|
+
import sc from '/src/main.js'
|
|
26
|
+
|
|
27
|
+
const options = {
|
|
28
|
+
onShortcut ( shortcut, context, note ) {
|
|
29
|
+
console.log ( '-- RESULTS --->' )
|
|
30
|
+
console.log ( 'Shortcut', shortcut )
|
|
31
|
+
console.log ( 'Context:', context )
|
|
32
|
+
if ( note ) console.log ( 'Note:', note )
|
|
33
|
+
} // onShortcut func.
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const short = sc ( options );
|
|
37
|
+
short.load ( { general : {
|
|
38
|
+
'q' : [ () => console.log('Q was clicked') ]
|
|
39
|
+
, 'r,o,s' : () => console.log ( 'ROS was clicked' )
|
|
40
|
+
, 'mouse-click-left-2' : () => console.log ( 'Mouse left click 2' )
|
|
41
|
+
, 'r' : [
|
|
42
|
+
({isWaiting}) => {
|
|
43
|
+
if ( !isWaiting() ) {
|
|
44
|
+
console.log ( 'R was clicked' )
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
, 'shift+w' : [
|
|
49
|
+
({wait, end, ignore, isWaiting, note }) => {
|
|
50
|
+
ignore ()
|
|
51
|
+
if ( isWaiting () ) {
|
|
52
|
+
console.log('END')
|
|
53
|
+
end ()
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log('WAIT')
|
|
57
|
+
wait ()
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
() => console.log ( 'W' )
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
short.changeContext ( 'general' )
|
|
65
|
+
short.setNote ( 'some' )
|
|
66
|
+
</script>
|
|
67
|
+
<div class="block" data-click="red-block">
|
|
68
|
+
<span>hello</span>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<button class="big-btn" data-click="mega-button" draggable>Mega button</button>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
package/javascript.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#F7DF1E" d="M0 0h256v256H0V0Z"></path><path d="m67.312 213.932l19.59-11.856c3.78 6.701 7.218 12.371 15.465 12.371c7.905 0 12.89-3.092 12.89-15.12v-81.798h24.057v82.138c0 24.917-14.606 36.259-35.916 36.259c-19.245 0-30.416-9.967-36.087-21.996m85.07-2.576l19.588-11.341c5.157 8.421 11.859 14.607 23.715 14.607c9.969 0 16.325-4.984 16.325-11.858c0-8.248-6.53-11.17-17.528-15.98l-6.013-2.58c-17.357-7.387-28.87-16.667-28.87-36.257c0-18.044 13.747-31.792 35.228-31.792c15.294 0 26.292 5.328 34.196 19.247l-18.732 12.03c-4.125-7.389-8.591-10.31-15.465-10.31c-7.046 0-11.514 4.468-11.514 10.31c0 7.217 4.468 10.14 14.778 14.608l6.014 2.577c20.45 8.765 31.963 17.7 31.963 37.804c0 21.654-17.012 33.51-39.867 33.51c-22.339 0-36.774-10.654-43.819-24.574"></path></svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peter.naydenov/shortcuts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create shortcuts for your web application based on keyboard and mouse events",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"shortcut",
|
|
7
|
+
"key",
|
|
8
|
+
"keyboard",
|
|
9
|
+
"mouse",
|
|
10
|
+
"click"
|
|
11
|
+
],
|
|
12
|
+
"main": "src/main.js",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "vite",
|
|
16
|
+
"build": "vite build",
|
|
17
|
+
"test": "cypress open --component --browser chrome test"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@peter.naydenov/notice": "^2.1.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@vitejs/plugin-react": "^4.0.4",
|
|
24
|
+
"chai": "^4.3.7",
|
|
25
|
+
"cypress": "^12.17.3",
|
|
26
|
+
"mocha": "^10.2.0",
|
|
27
|
+
"react": "^18.2.0",
|
|
28
|
+
"react-dom": "^18.2.0",
|
|
29
|
+
"vite": "^4.4.9"
|
|
30
|
+
},
|
|
31
|
+
"author": "Peter Naydenov",
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|
package/public/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import listen from "./listen";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function changeContext ( shortcuts, listenOptions, ev, currentContext ) {
|
|
8
|
+
return function changeContext ( contextName = false ) {
|
|
9
|
+
const current = currentContext.name;
|
|
10
|
+
listenOptions.maxSequence = 1
|
|
11
|
+
listenOptions.maxClicks = 1
|
|
12
|
+
|
|
13
|
+
if ( listenOptions.keyIgnore >= 0 ) {
|
|
14
|
+
clearTimeout ( listenOptions.keyIgnore )
|
|
15
|
+
listenOptions.keyIgnore = null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if ( !contextName ) { // Switch off all shortcuts if contextName is not defined
|
|
19
|
+
ev.reset ()
|
|
20
|
+
currentContext.name = null
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
if ( current === contextName ) return // Do nothing if contextName is the same as current
|
|
24
|
+
if ( !shortcuts [ contextName ] ) { // If contextName is not defined
|
|
25
|
+
ev.emit ( 'shortcuts-error', `Context '${ contextName }' does not exist` )
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
if ( shortcuts[current] ) {
|
|
29
|
+
ev.reset () // Disable all shortcuts from current context
|
|
30
|
+
}
|
|
31
|
+
Object.entries ( shortcuts [ contextName ]).forEach ( ([shortcutName, list ]) => { // Enable new context shortcuts and set a listenOptions 'maxSequence' and 'maxClicks'
|
|
32
|
+
let isMouseEv = shortcutName.includes ( 'MOUSE-CLICK-' );
|
|
33
|
+
if ( isMouseEv ) { // Set mouse max clicks
|
|
34
|
+
let [ , , , count ] = shortcutName.split('-')
|
|
35
|
+
let c = parseInt ( count ); // Number of clicks
|
|
36
|
+
if ( listenOptions.maxClicks < c ) listenOptions.maxClicks = c
|
|
37
|
+
}
|
|
38
|
+
else { // Set key max sequence
|
|
39
|
+
let sequenceArraySize = shortcutName.split(',').length;
|
|
40
|
+
if ( listenOptions.maxSequence < sequenceArraySize ) listenOptions.maxSequence = sequenceArraySize
|
|
41
|
+
}
|
|
42
|
+
list.forEach ( fn => ev.on ( shortcutName, fn ) ) // Enable new context shortcuts
|
|
43
|
+
})
|
|
44
|
+
currentContext.name = contextName
|
|
45
|
+
}} // changeContext func.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export default changeContext
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function findTarget ( target, dataName ) {
|
|
5
|
+
let t = target
|
|
6
|
+
while ( t && !t.dataset[dataName] ) {
|
|
7
|
+
t = t.parentNode;
|
|
8
|
+
if ( t === document ) return null
|
|
9
|
+
if ( t === document.body ) return null
|
|
10
|
+
}
|
|
11
|
+
return t
|
|
12
|
+
} // findTarget func.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export default findTarget
|
|
17
|
+
|
|
18
|
+
|