@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 ADDED
@@ -0,0 +1,11 @@
1
+ ## Release History
2
+
3
+
4
+
5
+ ### 1.0.0 (2023-08-14)
6
+
7
+ - [x] Initial code;
8
+ - [x] Test package;
9
+ - [x] Documentation;
10
+
11
+
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
+ ![version](https://img.shields.io/github/package-json/v/peterNaydenov/shortcuts)
4
+ ![license](https://img.shields.io/github/license/peterNaydenov/shortcuts)
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,10 @@
1
+ import { defineConfig } from "cypress";
2
+
3
+ export default defineConfig({
4
+ component: {
5
+ devServer: {
6
+ framework: "react",
7
+ bundler: "vite",
8
+ },
9
+ },
10
+ });
@@ -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");
@@ -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
+ }
@@ -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
+