@peter.naydenov/shortcuts 1.1.1 → 2.1.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 CHANGED
@@ -1,6 +1,25 @@
1
1
  ## Release History
2
2
 
3
3
 
4
+ ### 2.1.0 ( 2023-10-17 )
5
+ - [x] Method `setDependencies` to add more external objects available in all action functions;
6
+ - [x] Method `getDependencies` to look at existing `dependencies` list;
7
+
8
+
9
+
10
+ ### 2.0.0 ( 2023-10-16 )
11
+ - [x] HTML attribute `data-quick-click` is available to speed up single click response;
12
+ - [x] Documentation on methods `pause` and `resume`;
13
+ - [x] Method `listShortcuts` to get names of all shortcuts defined;
14
+ - [x] Arguments for `onShortcut` and `streamKeys` converted to named arguments;
15
+ - [x] Method `setDependencies` to add objects to the "**dependencies**" object available as named argument in all action functions;
16
+ - [x] Method `getDependencies` to look at existing `dependencies` list;
17
+ - [x] Methods `onShortcut` and `streamKeys` have a new argument `dependencies` to pass dependencies to the callback;
18
+ - [x] Dependencies are aviailable for `emit` method as well as first argument;
19
+ - [x] JSdoc type description for public methods;
20
+ - [x] Tag <a> is always a target, regardless of argument 'data-click'. Tag <a> is always a `data-quick-click` target also;
21
+
22
+
4
23
 
5
24
  ### 1.1.1 (2023-09-30)
6
25
  - [x] Mouse faster response when maxClicks achived. (maxClicks is automatically calculated according shortcut definitions);
@@ -0,0 +1,51 @@
1
+ # Migration Guides
2
+
3
+ ## From version 1.x.x to version 2.x.x
4
+ There are 2 breaking changes: in action functions and in emit method.
5
+ ### Action functions
6
+ Version 1.x.x:
7
+ ```js
8
+ function ( shortcut, contextName, contextNote ) {
9
+ // ...
10
+ }
11
+ ```
12
+ In version 2.x.x:
13
+ ```js
14
+ function ({ shortcut, context, note, dependencies } ) {
15
+ // Change:
16
+ // - all arguments are named(properties of single object);
17
+ // - argument order doesn't matter;
18
+ // - added argument `dependencies` to pass dependencies to all action function;
19
+ }
20
+ ```
21
+
22
+ ### Emit method
23
+
24
+ Version 1.x.x:
25
+ ```js
26
+ let result = null;
27
+ const myAllContext = {
28
+ myAll: {
29
+ 'yo' : r => result = r
30
+ }}
31
+ short.load ( myAllContext )
32
+ short.changeContext ( 'myAll' )
33
+ short.emit ( 'yo', 'hello' )
34
+ // result == 'hello'
35
+ ```
36
+
37
+ In version 2.x.x:
38
+ ```js
39
+ let result = null;
40
+ const myAllContext = {
41
+ myAll: {
42
+ 'yo' : ( dependencies, r ) => result = r
43
+ // Change:
44
+ // - first argument is always dependencies object;
45
+ // - event-data is available from position 2 of arguments list;
46
+ }}
47
+ short.load ( myAllContext )
48
+ short.changeContext ( 'myAll' )
49
+ short.emit ( 'yo', 'hello' )
50
+ // result == 'hello'
51
+ ```
package/README.md CHANGED
@@ -11,8 +11,11 @@ Define a context based keyboard-shortcuts and describe a mouse clicks. Switch am
11
11
 
12
12
 
13
13
  ## What's new?
14
- Version 1.1.0 is coming with new method `emit` that make possible to trigger context functions also programmatically. In `shortcuts` you can mix keyboard, mouse and programmatical events that is prity everything that can happen in a web page.
15
14
 
15
+ - 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;
16
+ - 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;
17
+ - 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;
18
+ - 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;
16
19
 
17
20
 
18
21
  ## Shortcut Description Rules
@@ -131,6 +134,43 @@ Order of describing mouse event and modifier keys is not important.
131
134
  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`.
132
135
 
133
136
 
137
+ ## Define a mouse targets
138
+ Target HTML elements for `shortcuts` are defined by `data-click` attribute. The value of the attribute is the name of the target. Example:
139
+
140
+ ```html
141
+ <button data-click="id">Click me</button>
142
+ <!-- target name is 'id' -->
143
+ ```
144
+
145
+ Attribute is customizable by setting `clickTarget` option. Read more in section `Options`.
146
+
147
+ 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:
148
+
149
+ ```html
150
+ <button data-click="id" data-quick-click>Click me</button>
151
+ <!-- target name is 'id' and will not wait for more then 1 click -->
152
+ ```
153
+ Using a <a> tag is a special case. It's always recognized as a target, and always with attribute `data-quick-click`. No need to set it manually. Example:
154
+
155
+ ```html
156
+ <a href="#">Click me</a>
157
+ <!-- Recognized as a target and will not wait for more then 1 click -->
158
+ <!-- Take care for the action from shortcut `mouse-click-left-1`. -->
159
+ ```
160
+
161
+ Clicking on <a> tag will not execute anything. All events are blocked by default. In your `mouse-click-left-1` action function you can write a code to execute the default action. Example:
162
+
163
+ ```js
164
+ {
165
+ contextName : {
166
+ 'mouse-click-left-1' : function ( {target, event} ) {
167
+ if ( target.tagName === 'A' ) { // All targets that are <a> tags will execute the default action
168
+ window.location.href = target.href // Go to the link
169
+ }
170
+ }
171
+ }
172
+ }
173
+ ```
134
174
 
135
175
 
136
176
 
@@ -201,6 +241,7 @@ Description of keyboard action functions is:
201
241
  function myKeyHandler ({
202
242
  context // (string) Name of the current context;
203
243
  , note // (string) Name of the note or null if note isn't set;
244
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
204
245
  , wait // (function). Call it to stop a sequence timer and write shortcut sequence without a timer.
205
246
  , end // (function). Recover the sequence timer;
206
247
  , ignore // (function). Call it to ignore the current shortcut from the sequence;
@@ -220,6 +261,7 @@ Mouse action functions can be described like:
220
261
  function myMouseHandler ({
221
262
  context // (string) Name of the current context;
222
263
  , note // (string) Name of the note or null if note isn't set;
264
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
223
265
  , target // (DOM element). Target element of the mouse event;
224
266
  , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
225
267
  , x // (number). X coordinate of the target element;
@@ -239,18 +281,30 @@ function myMouseHandler ({
239
281
  Description of the methods of shortcut instance:
240
282
 
241
283
  ```js
242
- load : 'Load and extend a shortcut definition.'
243
- , unload : 'Remove a shortcut context with all its shortcuts.'
244
- , changeContext : 'Switch to existing shortcut context.'
245
- , emit : 'Trigger a shortcut or custom event programmatically.'
246
- , pause : 'Stop listening for shortcuts.'
247
- , resume : 'Resume listening for shortcuts.'
248
- , listContexts : 'Return list of available contexts.'
249
- , getContext : 'Return a name of current context or null if there is no context selected'
250
- , getNote : `Return a name of current note or null if note isn't set`
251
- , setNote : 'Set a note to current context.'
284
+ load : 'Load and extend a shortcut definition.'
285
+ , unload : 'Remove a shortcut context with all its shortcuts.'
286
+ , changeContext : 'Switch to existing shortcut context.'
287
+ , emit : 'Trigger a shortcut or custom event programmatically.'
288
+ , pause : 'Stop listening for shortcuts.'
289
+ , resume : 'Resume listening for shortcuts.'
290
+ , listContexts : 'Return list of available contexts.'
291
+ , getContext : 'Return a name of current context or null if there is no context selected'
292
+ , getNote : `Return a name of current note or null if note isn't set`
293
+ , setNote : 'Set a note to current context.'
294
+ , setDependencies : 'Set dependencies that will be available in action functions.'
295
+ , getDependencies : 'Return dependencies object.'
252
296
  ```
253
297
 
298
+ ### How to 'pause' and 'resume'?
299
+ When you want to stop execution of shortcuts, call `short.pause()`. It's equal to `short.pause('*')`. Will stop all shortcuts in the active context. Stop for single shortcut is by calling `short.pause('shortcutName')`. To resume shortcuts execution call `short.resume()`. It's equal to `short.resume('*')`. Will resume all shortcuts in the active context. Resume for single shortcut is by calling `short.resume('shortcutName')`.
300
+
301
+ ```js
302
+ // pause all shortcuts in the active context
303
+ short.pause () // will stop all shortcuts in the active context
304
+ short.resume ( 'shift+a' ) // will resume only 'shift+a' shortcut
305
+
306
+ short.resume ('*') // will resume all shortcuts
307
+ ```
254
308
 
255
309
 
256
310
 
@@ -283,10 +337,11 @@ const short = shortcuts ( shortcuts.getDefaults () ) // same as above
283
337
 
284
338
  ### onShortcut option
285
339
  ```js
286
- function onShortcut ( shortcut, context, note ) {
340
+ function onShortcut ({ shortcut, context, note, dependencies }) {
287
341
  // shortcut - (string) Triggered shortcut name
288
342
  // context - (string) Name of the current context
289
343
  // note - (string) Name of the note or null if note isn't set
344
+ // dependencies - (object) Object with dependencies that you have set by calling `setDependencies` method
290
345
  }
291
346
  ```
292
347
 
@@ -294,10 +349,11 @@ const short = shortcuts ( shortcuts.getDefaults () ) // same as above
294
349
 
295
350
  ### streamKeys option
296
351
  ```js
297
- function streamKeys ( key, context, note ) {
352
+ function streamKeys ({ key, context, note, dependencies }) {
298
353
  // key - (string) Pressed key name
299
354
  // context - (string) Name of the current context
300
355
  // note - (string) Name of the note or null if note isn't set
356
+ // dependencies - (object) Object with dependencies that you have set by calling `setDependencies` method
301
357
  }
302
358
  ```
303
359
 
package/index.html CHANGED
@@ -25,7 +25,7 @@
25
25
  import sc from '/src/main.js'
26
26
 
27
27
  const options = {
28
- onShortcut ( shortcut, context, note ) {
28
+ onShortcut ({ shortcut, context, note }) {
29
29
  console.log ( '-- RESULTS --->' )
30
30
  console.log ( 'Shortcut', shortcut )
31
31
  console.log ( 'Context:', context )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peter.naydenov/shortcuts",
3
- "version": "1.1.1",
3
+ "version": "2.1.0",
4
4
  "description": "Context control of shortcuts based on keyboard and mouse events",
5
5
  "keywords": [
6
6
  "shortcut",
@@ -25,13 +25,13 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "@vitejs/plugin-react": "^4.1.0",
28
- "ask-for-promise": "^1.3.1",
28
+ "ask-for-promise": "^1.4.0",
29
29
  "chai": "^4.3.10",
30
- "cypress": "^13.3.0",
30
+ "cypress": "^13.3.1",
31
31
  "mocha": "^10.2.0",
32
32
  "react": "^18.2.0",
33
33
  "react-dom": "^18.2.0",
34
- "vite": "^4.4.9"
34
+ "vite": "^4.4.11"
35
35
  },
36
36
  "author": "Peter Naydenov",
37
37
  "license": "MIT"
package/src/main.js CHANGED
@@ -10,21 +10,13 @@
10
10
  * - Development was started on June 21st, 2023
11
11
  * - First version was published on August 14th, 2023
12
12
  * - Method 'emit' was added on September 30st, 2023
13
+ * - Version 2.0.0 was published on October 16th, 2023
13
14
  */
14
15
 
15
16
 
16
17
 
17
- import notice from '@peter.naydenov/notice' // Docs: https://github.com/PeterNaydenov/notice
18
-
19
- import listen from './listen.js'
20
- import readShortcut from './readShortcut.js'
21
- import readKeyEvent from './readKeyEvent.js'
22
- import readMouseEvent from './readMouseEvent.js'
23
- import findTarget from './findTarget.js'
24
- import specialChars from './specialChars.js'
25
- import load from './load.js'
26
- import unload from './unload.js'
27
- import changeContext from './changeContext.js'
18
+ import notice from '@peter.naydenov/notice' // Docs: https://github.com/PeterNaydenov/notice
19
+ import methods from './methods/index.js'
28
20
 
29
21
 
30
22
 
@@ -33,53 +25,115 @@ import changeContext from './changeContext.js'
33
25
  function main ( options = {} ) {
34
26
  const
35
27
  ev = notice () // Event emitter instance
36
- , currentContext = { name: null, note: null } // Context data container
37
- , exposeShortcut = (options.onShortcut && ( typeof options.onShortcut === 'function')) ? options.onShortcut : false
38
- , streamKeys = (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false
39
- , listenOptions = {
40
- mouseWait : options.mouseWait ? options.mouseWait : 320 // 320 ms
41
- , maxClicks : 1 // The maximum number of clicks in a sequence. Controlled automatically by 'changeContext' function.
42
- , keyWait : options.keyWait ? options.keyWait : 480 // 480 ms
43
- , maxSequence : 1 // How many keys can be pressed in a sequence. Controlled automatically by 'changeContext' function.
44
- , clickTarget : options.clickTarget ? options.clickTarget : 'click' // Data-attribute name for click target ( data-click )
45
- , listenFor : (options.listenFor && Array.isArray(options.listenFor)) ? options.listenFor : [ 'mouse', 'keyboard' ] // What to listen for: ['mouse'], ['keyboard'], ['mouse', 'keyboard']
46
- , keyIgnore : null // Timer for ignoring key presses after max sequence or null. Not a public option.
47
- }
48
- , shortcuts = {} // shortcuts = { contextName : { shortcut : callback[] } }
49
- , getContext = () => currentContext.name
50
- , getNote = () => currentContext.note
51
- , setNote = (note=null) => { if (typeof note === 'string' || note == null ) currentContext.note = note }
28
+ , inAPI = {} // API for internal methods
29
+ , API = {} // API for public methods
30
+ , state = {
31
+ currentContext : { name: null, note: null } // Context data container
32
+ , shortcuts : {} // shortcuts = { contextName : { shortcut : callback[] } }
33
+ , listenOptions : {
34
+ mouseWait : options.mouseWait ? options.mouseWait : 320 // 320 ms
35
+ , maxClicks : 1 // The maximum number of clicks in a sequence. Controlled automatically by 'changeContext' function.
36
+ , keyWait : options.keyWait ? options.keyWait : 480 // 480 ms
37
+ , maxSequence : 1 // How many keys can be pressed in a sequence. Controlled automatically by 'changeContext' function.
38
+ , clickTarget : options.clickTarget ? options.clickTarget : 'click' // Data-attribute name for click target ( data-click )
39
+ , listenFor : (options.listenFor && Array.isArray(options.listenFor)) ? options.listenFor : [ 'mouse', 'keyboard' ] // What to listen for: ['mouse'], ['keyboard'], ['mouse', 'keyboard']
40
+ , keyIgnore : null // Timer for ignoring key presses after max sequence or null. Not a public option.
41
+ }
42
+ , exposeShortcut : (options.onShortcut && ( typeof options.onShortcut === 'function')) ? options.onShortcut : false
43
+ , streamKeys : (options.streamKeys && ( typeof options.streamKeys === 'function')) ? options.streamKeys : false
44
+ } // state
52
45
  , dependencies = {
53
- specialChars
54
- , readKeyEvent
55
- , readMouseEvent
56
- , findTarget
57
- , ev
58
- , exposeShortcut
59
- , streamKeys
46
+ ev
47
+ , inAPI
48
+ , API
49
+ , extra : {}
60
50
  }
61
51
  ;
62
-
63
- listen ( dependencies, listenOptions, currentContext )
64
-
65
- return { // shortcuts API
66
- load : load ( shortcuts, readShortcut, changeContext( shortcuts, listenOptions, ev, currentContext ), getContext )
67
- , unload : unload ( shortcuts, ev, currentContext )
68
- , changeContext : changeContext ( shortcuts, listenOptions, ev, currentContext )
69
- , pause : () => ev.stop ()
70
- , resume : () => ev.start ()
71
- , emit : (x,...args) => ev.emit ( readShortcut(x), ...args )
72
- , listContexts : () => Object.keys ( shortcuts )
73
- , getContext
74
- , getNote
75
- , setNote
76
- }
52
+
53
+ /**
54
+ * @function getContext
55
+ * @description Get current context name
56
+ * @returns {string} - Current context name
57
+ */
58
+ API.getContext = () => state.currentContext.name
59
+
60
+ /**
61
+ * @function getNote
62
+ * @description Get current context note
63
+ * @returns {string} - Current context note
64
+ */
65
+ API.getNote = () => state.currentContext.note
66
+
67
+ /**
68
+ * @function setNote
69
+ * @description Set current context note
70
+ * @param {string} note - Context note
71
+ * @returns {void}
72
+ */
73
+ API.setNote = (note=null) => { if (typeof note === 'string' || note == null ) state.currentContext.note = note }
74
+
75
+ /**
76
+ * @function pause
77
+ * @description Pause shortcut(s) in current context
78
+ * @param {string} [name='*' ] - Shortcut name that should be paused. Default is '*' - all shortcuts in current context.
79
+ * @returns {void}
80
+ */
81
+ API.pause = (name='*') => ev.stop ( inAPI._readShortcut(name) )
82
+
83
+ /**
84
+ * @function resume
85
+ * @description Resume shortcut(s) in current context
86
+ * @param {string} [name='*' ] - Shortcut name that should be resumed. Default is '*' - all shortcuts in current context.
87
+ * @returns {void}
88
+ */
89
+ API.resume = (name='*') => ev.start ( inAPI._readShortcut(name) )
90
+
91
+ /**
92
+ * @function emit
93
+ * @description Emit event for shortcut in current context
94
+ * @param {string} name - Shortcut name
95
+ * @param {any} [args] - Arguments for callback function
96
+ * @returns {void}
97
+ **/
98
+ API.emit = (name,...args) => ev.emit ( inAPI._readShortcut(name), dependencies.extra, ...args )
99
+
100
+ /**
101
+ * @function listContexts
102
+ * @description List all context names
103
+ * @returns {string[]} - Array of context names
104
+ */
105
+ API.listContexts = () => Object.keys ( shortcuts )
106
+
107
+ /**
108
+ * @function setDependencies
109
+ * @description Set a dependency package that will be provided to each action function
110
+ * @param {object} deps - Enumerate external dependencies
111
+ * @returns {void}
112
+ */
113
+ API.setDependencies = (deps) => dependencies.extra = { ...dependencies.extra, ...deps }
114
+
115
+ /**
116
+ * @function getDependencies
117
+ * @description Get a dependency package that will be provided to each action function
118
+ * @returns {object} - Enumerate external dependencies
119
+ **/
120
+ API.getDependencies = () => dependencies.extra
121
+
122
+
123
+
124
+ Object.entries ( methods ).forEach ( ([ name, method ]) => {
125
+ if ( name.startsWith('_') ) inAPI [ name ] = method ( dependencies, state )
126
+ else API [ name ] = method ( dependencies, state )
127
+ })
128
+
129
+ inAPI._listen ()
130
+ return API
77
131
  } // main func.
78
132
 
79
133
 
80
134
 
81
135
  main.getDefaults = () => ({
82
- mouseWait : 320 // 320 ms // TODO: Slow down. It's too fast at the moment.
136
+ mouseWait : 320 // 320 ms
83
137
  , keyWait : 480 // 480 ms
84
138
  , clickTarget : 'click' // Data-attribute name for click target ( data-click )
85
139
  , listenFor : [ 'mouse', 'keyboard' ]
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ function _findTarget ( dependencies, state ) {
4
+ const { listenOptions : {clickTarget}} = state;
5
+ return function _findTarget ( target ) {
6
+ const t = target;
7
+ if ( t === document ) return null
8
+ if ( t === document.body ) return null
9
+
10
+ if ( t.dataset[clickTarget] ) return t
11
+ if ( t.nodeName === 'A' ) return t
12
+ return _findTarget ( t.parentNode )
13
+ }} // _findTarget func.
14
+
15
+
16
+
17
+ export default _findTarget
18
+
19
+
@@ -2,22 +2,30 @@
2
2
 
3
3
 
4
4
 
5
- function listen ( dependencies, options, currentContext ) { // Listen for input signals and generate event titles
5
+ function _listen ( dependencies, state ) {
6
+ // Listen for input signals and generate event titles
7
+ return function _listen () {
6
8
  const {
7
- specialChars
8
- , readKeyEvent
9
- , readMouseEvent
10
- , findTarget
11
- , ev
12
- , exposeShortcut
13
- , streamKeys
14
- } = dependencies
15
- , {
9
+ ev
10
+ , inAPI : {
11
+ _findTarget
12
+ , _specialChars
13
+ , _readKeyEvent
14
+ , _readMouseEvent
15
+ }
16
+ } = dependencies
17
+ , {
18
+ exposeShortcut
19
+ , currentContext
20
+ , streamKeys
21
+ , listenOptions
22
+ } = state
23
+ , {
16
24
  mouseWait
17
25
  , keyWait
18
26
  , clickTarget
19
- , listenFor
20
- } = options
27
+ , listenFor
28
+ } = listenOptions
21
29
  ;
22
30
 
23
31
  let
@@ -54,10 +62,18 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
54
62
  ignore = false
55
63
  }
56
64
  }
57
-
65
+ const data = {
66
+ wait: waitKeys
67
+ , end:endKeys
68
+ , ignore:ignoreKeys
69
+ , isWaiting:waitingKeys
70
+ , note: currentContext.note
71
+ , context: currentContext.name
72
+ , dependencies : dependencies.extra
73
+ };
58
74
  if ( sequence ) {
59
- ev.emit ( res.join(','), { wait: waitKeys, end:endKeys, ignore:ignoreKeys, isWaiting:waitingKeys, note: currentContext.note, context: currentContext.name })
60
- if ( exposeShortcut ) exposeShortcut ( res.join(','), currentContext.name, currentContext.note ) // TODO: Add a context information...?
75
+ ev.emit ( res.join(','), data )
76
+ if ( exposeShortcut ) exposeShortcut ({ shortcut:res.join(','), context: currentContext.name, note:currentContext.note, dependencies:dependencies.extra })
61
77
  // Reset:
62
78
  r = []
63
79
  keyTimer = null
@@ -68,7 +84,7 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
68
84
 
69
85
  function mouseSequenceEnd () { // Execute when mouse sequence ends
70
86
  const
71
- mouseEvent = readMouseEvent ( mouseDomEvent, count )
87
+ mouseEvent = _readMouseEvent ( mouseDomEvent, count )
72
88
  , data = {
73
89
  target : mouseTarget
74
90
  , targetProps : mouseTarget ? mouseTarget.getBoundingClientRect() : null
@@ -77,10 +93,11 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
77
93
  , context : currentContext.name
78
94
  , note : currentContext.note
79
95
  , event : mouseDomEvent
96
+ , dependencies : dependencies.extra
80
97
  }
81
98
  ;
82
99
  ev.emit ( mouseEvent.join('+'), data )
83
- if ( exposeShortcut ) exposeShortcut ( mouseEvent.join('+'), currentContext.name, currentContext.note )
100
+ if ( exposeShortcut ) exposeShortcut ({ shortcut: mouseEvent.join('+'), context:currentContext.name, note:currentContext.note, dependencies:dependencies.extra })
84
101
  // Reset:
85
102
  mouseTimer = null
86
103
  mouseIgnore = null
@@ -93,6 +110,7 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
93
110
 
94
111
  function listenMouse () {
95
112
  window.addEventListener ( 'contextmenu', event => { // Listen for right mouse clicks
113
+ let targetMax = listenOptions.maxClicks; // Maximum number of clicks per target
96
114
  event.preventDefault ()
97
115
  clearTimeout ( mouseTimer )
98
116
  if ( mouseIgnore ) {
@@ -100,18 +118,21 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
100
118
  mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
101
119
  return
102
120
  }
103
- mouseTarget = findTarget (event.target, clickTarget )
121
+ mouseTarget = _findTarget ( event.target )
122
+ if ( mouseTarget && mouseTarget.dataset.hasOwnProperty('quickClick')) targetMax = 1
123
+ if ( mouseTarget && mouseTarget.tagName === 'A' ) targetMax = 1
104
124
  mouseDomEvent = event
105
125
  count++
106
- if ( count === options.maxClicks ) {
126
+ if ( count >= targetMax ) {
107
127
  mouseSequenceEnd ()
108
- mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
128
+ if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
109
129
  return
110
130
  }
111
131
  mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
112
132
  })
113
133
 
114
134
  document.addEventListener ( 'click', event => { // Listen for left and middle mouse clicks
135
+ let targetMax = listenOptions.maxClicks; // Maximum number of clicks per target
115
136
  event.preventDefault ()
116
137
  clearTimeout ( mouseTimer )
117
138
  if ( mouseIgnore ) {
@@ -119,12 +140,14 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
119
140
  mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
120
141
  return
121
142
  }
122
- mouseTarget = findTarget ( event.target, clickTarget )
143
+ mouseTarget = _findTarget ( event.target )
144
+ if ( mouseTarget && mouseTarget.dataset.hasOwnProperty('quickClick')) targetMax = 1
145
+ if ( mouseTarget && mouseTarget.tagName === 'A' ) targetMax = 1
123
146
  mouseDomEvent = event
124
147
  count++
125
- if ( count >= options.maxClicks ) {
148
+ if ( count >= targetMax ) {
126
149
  mouseSequenceEnd ()
127
- mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
150
+ if ( targetMax > 1 ) mouseIgnore = setTimeout ( () => mouseIgnore=null, mouseWait )
128
151
  return
129
152
  }
130
153
  mouseTimer = setTimeout ( mouseSequenceEnd, mouseWait )
@@ -136,17 +159,17 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
136
159
  function listenKeyboard () {
137
160
  document.addEventListener ( 'keydown', event => { // Listen for special keyboard keys
138
161
  clearTimeout ( keyTimer )
139
- if ( specialChars.hasOwnProperty(event.code) ) r.push ( readKeyEvent ( event, specialChars ))
162
+ if ( _specialChars.hasOwnProperty(event.code) ) r.push ( _readKeyEvent ( event, _specialChars ))
140
163
  else return
141
- if ( streamKeys ) streamKeys ( event.key, currentContext.name, currentContext.note )
142
- if ( options.keyIgnore ) {
143
- clearTimeout ( options.keyIgnore )
144
- options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
164
+ if ( streamKeys ) streamKeys ({ key:event.key, context:currentContext.name, note:currentContext.note, dependencies:dependencies.extra })
165
+ if ( listenOptions.keyIgnore ) {
166
+ clearTimeout ( listenOptions.keyIgnore )
167
+ listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
145
168
  return
146
169
  }
147
- if ( sequence && r.length === options.maxSequence ) {
170
+ if ( sequence && r.length === listenOptions.maxSequence ) {
148
171
  keySequenceEnd ()
149
- options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
172
+ listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
150
173
  return
151
174
  }
152
175
  if ( sequence ) keyTimer = setTimeout ( keySequenceEnd, keyWait )
@@ -154,18 +177,18 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
154
177
  })
155
178
 
156
179
  document.addEventListener ( 'keypress', event => { // Listen for regular keyboard keys
157
- if ( specialChars.hasOwnProperty(event.code) ) return
180
+ if ( _specialChars.hasOwnProperty(event.code) ) return
158
181
  clearTimeout ( keyTimer )
159
- if ( streamKeys ) streamKeys ( event.key, currentContext.name, currentContext.note )
160
- if ( options.keyIgnore ) {
161
- clearTimeout ( options.keyIgnore )
162
- options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
182
+ if ( streamKeys ) streamKeys ({ key:event.key, context:currentContext.name, note:currentContext.note, dependencies:dependencies.extra })
183
+ if ( listenOptions.keyIgnore ) {
184
+ clearTimeout ( listenOptions.keyIgnore )
185
+ listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
163
186
  return
164
187
  }
165
- r.push ( readKeyEvent ( event, specialChars ))
166
- if ( sequence && r.length === options.maxSequence ) {
188
+ r.push ( _readKeyEvent ( event, _specialChars ))
189
+ if ( sequence && r.length === listenOptions.maxSequence ) {
167
190
  keySequenceEnd ()
168
- options.keyIgnore = setTimeout ( () => options.keyIgnore=null, keyWait )
191
+ listenOptions.keyIgnore = setTimeout ( () => listenOptions.keyIgnore=null, keyWait )
169
192
  return
170
193
  }
171
194
  if ( sequence ) keyTimer = setTimeout ( keySequenceEnd, keyWait )
@@ -178,10 +201,10 @@ function listen ( dependencies, options, currentContext ) { // Listen for inpu
178
201
  if ( listenFor.includes('mouse') ) listenMouse ()
179
202
  if ( listenFor.includes('keyboard') ) listenKeyboard ()
180
203
 
181
- } // listen func.
204
+ }} // _listen func.
182
205
 
183
206
 
184
207
 
185
- export default listen
208
+ export default _listen
186
209
 
187
210
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
-
4
- function readKeyEvent ( event, specialChars ) {
3
+ function _readKeyEvent () {
4
+ return function _readKeyEvent ( event, _specialChars ) {
5
5
  let
6
6
  { shiftKey, altKey, ctrlKey } = event
7
7
  , falseKeys = [ 'ControlLeft','ControlRight', 'ShiftLeft', 'ShiftRight', 'AltLeft', 'AltRight', 'Meta' ]
@@ -15,13 +15,13 @@ function readKeyEvent ( event, specialChars ) {
15
15
  if ( shiftKey ) res.push ( 'SHIFT' )
16
16
  if ( altKey ) res.push ( 'ALT' )
17
17
 
18
- if ( specialChars.hasOwnProperty ( key ) ) res.push ( specialChars[key].toUpperCase () )
19
- else if ( !falseKeys.includes(key) ) res.push ( key.toUpperCase () )
18
+ if ( _specialChars.hasOwnProperty ( key ) ) res.push ( _specialChars[key].toUpperCase () )
19
+ else if ( !falseKeys.includes(key) ) res.push ( key.toUpperCase () )
20
20
  return res.sort ()
21
- } // readKeyEvent func.
21
+ }} // _readKeyEvent func.
22
22
 
23
23
 
24
24
 
25
- export default readKeyEvent
25
+ export default _readKeyEvent
26
26
 
27
27
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
-
4
- function readMouseEvent ( event, count ) {
3
+ function _readMouseEvent () {
4
+ return function _readMouseEvent ( event, count ) {
5
5
  let
6
6
  { shiftKey, altKey, ctrlKey, key, button } = event
7
7
  , mouseNames = [ 'LEFT', 'MIDDLE', 'RIGHT' ]
@@ -15,10 +15,10 @@ function readMouseEvent ( event, count ) {
15
15
  if ( altKey ) res.push ( 'ALT' )
16
16
 
17
17
  return res.sort ()
18
- } // readMouseEvent func.
18
+ }} // _readMouseEvent func.
19
19
 
20
20
 
21
21
 
22
- export default readMouseEvent
22
+ export default _readMouseEvent
23
23
 
24
24
 
@@ -1,16 +1,17 @@
1
1
  'use strict'
2
2
  // [ crtl+s, shift+alt+o]
3
- function readShortcut ( txt ) {
3
+ function _readShortcut () {
4
+ return function _readShortcut ( txt ) {
4
5
  const r = txt
5
6
  .split ( ',' )
6
7
  .map ( (x) => x.trim() )
7
8
  .map ( (x) => x.split ( '+' ).map(x => x.toUpperCase()).sort().join('+') )
8
9
  .join ( ',' );
9
10
  return r
10
- } // readShortcut func.
11
+ }} // _readShortcut func.
11
12
 
12
13
 
13
14
 
14
- export default readShortcut
15
+ export default _readShortcut
15
16
 
16
17
 
@@ -0,0 +1,38 @@
1
+ function _specialChars () {
2
+ return {
3
+ 'ArrowLeft' : 'LEFT'
4
+ , 'ArrowUp' : 'UP'
5
+ , 'ArrowRight' : 'RIGHT'
6
+ , 'ArrowDown' : 'DOWN'
7
+ , 'Enter' : 'ENTER'
8
+ , 'NumpadEnter' : 'ENTER'
9
+ , 'Escape' : 'ESC'
10
+ , 'Backspace' : 'BACKSPACE'
11
+ , 'Space' : 'SPACE'
12
+ , 'Tab' : 'TAB'
13
+ , 'Backquote' : '`'
14
+ , 'BracketLeft' : '['
15
+ , 'BracketRight': ']'
16
+ , 'Equal' : '='
17
+ , 'Slash' : '/'
18
+ , 'Backslash' : '\\'
19
+ , 'IntlBackslash' : '`'
20
+ , 'F1' : 'F1'
21
+ , 'F2' : 'F2'
22
+ , 'F3' : 'F3'
23
+ , 'F4' : 'F4'
24
+ , 'F5' : 'F5'
25
+ , 'F6' : 'F6'
26
+ , 'F7' : 'F7'
27
+ , 'F8' : 'F8'
28
+ , 'F9' : 'F9'
29
+ , 'F10' : 'F10'
30
+ , 'F11' : 'F11'
31
+ , 'F12' : 'F12'
32
+ }}
33
+
34
+
35
+
36
+ export default _specialChars
37
+
38
+
@@ -1,8 +1,24 @@
1
1
  'use strict'
2
2
 
3
- function changeContext ( shortcuts, listenOptions, ev, currentContext ) {
3
+ function changeContext ( dependencies, state ) {
4
+ const
5
+ {
6
+ shortcuts
7
+ , listenOptions
8
+ , currentContext
9
+ } = state
10
+ , { ev } = dependencies
11
+ ;
12
+
13
+ /**
14
+ * @function changeContext
15
+ * @description Change current context with shortcuts belonging to it.
16
+ * @param {string} [contextName=false] - Name of context to change to. Default 'false' will switch off all shortcuts.
17
+ * @returns {void}
18
+ */
4
19
  return function changeContext ( contextName = false ) {
5
20
  const current = currentContext.name;
21
+
6
22
  listenOptions.maxSequence = 1
7
23
  listenOptions.maxClicks = 1
8
24
 
@@ -37,7 +53,7 @@ return function changeContext ( contextName = false ) {
37
53
  }
38
54
  list.forEach ( fn => ev.on ( shortcutName, fn ) ) // Enable new context shortcuts
39
55
  })
40
- currentContext.name = contextName
56
+ currentContext.name = contextName
41
57
  }} // changeContext func.
42
58
 
43
59
 
@@ -0,0 +1,29 @@
1
+ import _findTarget from './_findTarget.js'
2
+ import _listen from './_listen.js'
3
+ import _readKeyEvent from './_readKeyEvent.js'
4
+ import _readMouseEvent from './_readMouseEvent.js'
5
+ import _readShortcut from './_readShortcut.js'
6
+ import _specialChars from './_specialChars.js'
7
+
8
+ import load from './load.js'
9
+ import unload from './unload.js'
10
+ import changeContext from './changeContext.js'
11
+ import listShortcuts from './listShortcuts.js'
12
+
13
+
14
+
15
+ export default {
16
+ // Internal methods
17
+ _findTarget
18
+ , _listen
19
+ , _readKeyEvent
20
+ , _readMouseEvent
21
+ , _readShortcut
22
+ , _specialChars
23
+
24
+ // Public methods
25
+ , changeContext
26
+ , listShortcuts
27
+ , load
28
+ , unload
29
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+ /**
3
+ * @typedef {object} contextShortcuts
4
+ * @property {string} context - Context name
5
+ * @property {string[]} shortcuts - List of shortcuts in a context
6
+ */
7
+
8
+
9
+
10
+ function listShortcuts ( dependencies, state ) {
11
+ const shortcuts = state.shortcuts;
12
+ /**
13
+ * List all shortcuts in all contexts or in a specific context.
14
+ * @param {string}[ contextName=null] - List of shortcuts for provided context name. (optional)
15
+ * @returns {string[]|contextShortcuts[]} - List of shortcuts for a specified context or list of contextShortcuts for all contexts.
16
+ */
17
+ return function listShortcuts ( contextName=null ) {
18
+
19
+ // Create a list of shortcuts for a specific context:
20
+ if ( contextName != null ) {
21
+ let context = shortcuts[contextName];
22
+ if ( context == null ) return null
23
+ return Object.entries ( context ).map ( ([shortcut, callbacks]) => shortcut )
24
+ }
25
+
26
+ // Create a list of allShortcuts for each context:
27
+ return Object.keys ( shortcuts ).map ( contextName => {
28
+ let result = {};
29
+ result['context'] = contextName
30
+ result['shortcuts'] = Object.entries ( shortcuts[contextName] ).map ( ([shortcut, callbacks]) => shortcut )
31
+ return result
32
+ }) // map
33
+
34
+ }} // listShortcuts func.
35
+
36
+
37
+
38
+ export default listShortcuts
39
+
40
+
@@ -1,8 +1,17 @@
1
1
  'use strict'
2
2
 
3
-
4
-
5
- function load ( shortcuts, readShortcut, changeContext, getContext ) {
3
+ function load ( dependencies, state ) {
4
+ const
5
+ { shortcuts } = state
6
+ , {
7
+ inAPI : { _readShortcut }
8
+ , API : { changeContext, getContext }
9
+ } = dependencies;
10
+ /**
11
+ * Load a context with shortcuts object
12
+ * @param {Object} obj - Context description object: { contextName : { shortcut : callback[] }
13
+ * @returns {void}
14
+ */
6
15
  return function load ( obj ) {
7
16
  const
8
17
  currentContextName = getContext ()
@@ -14,7 +23,7 @@ return function load ( obj ) {
14
23
  if ( contextName === currentContextName ) hasChanges = true;
15
24
  shortcuts [ contextName ] = {}
16
25
  description.forEach ( ([ title, callbackList ]) => {
17
- const name = readShortcut ( title );
26
+ const name = _readShortcut ( title );
18
27
  if ( callbackList instanceof Function ) callbackList = [ callbackList ]
19
28
  shortcuts [contextName][ name ] = callbackList
20
29
  }) // shortcuts.forEach
@@ -1,8 +1,16 @@
1
1
  'use strict'
2
2
 
3
-
4
-
5
- function unload ( shortcuts, ev, currentContext ) {
3
+ function unload ( dependencies, state ) {
4
+ const
5
+ { currentContext, shortcuts } = state
6
+ , { ev } = dependencies
7
+ ;
8
+ /**
9
+ * @function unload
10
+ * @description Unload a non-active context with shortcuts.
11
+ * @param {string} contextName - Context name to unload
12
+ * @returns {void}
13
+ */
6
14
  return function unload ( contextName ) {
7
15
  const current = currentContext.name;
8
16
  if ( current === contextName ) {
@@ -4,11 +4,13 @@ import '../test-components/style.css'
4
4
  import shortcuts from '../src/main.js'
5
5
  import { expect } from 'chai'
6
6
 
7
+ import askForPromise from 'ask-for-promise'
8
+
7
9
  let
8
10
  a = false
9
11
  , b = false
10
12
  ;
11
- const short = shortcuts ({onShortcut : ( shortcut, context, note ) => console.log (shortcut, context, note)});
13
+ const short = shortcuts ({onShortcut : ({shortcut, context, note}) => console.log (shortcut, context, note)});
12
14
  short.load ({
13
15
  general : {
14
16
  'shift+a': [ () => a = true ]
@@ -116,12 +118,94 @@ it ( 'Emit custom event', () => {
116
118
  const myAllContext = {
117
119
  myAll: {
118
120
  'mouse-click-leff-1' : () => console.log ( 'nothing' )
119
- , 'yo' : r => result = r
121
+ , 'yo' : (dependencies,r) => result = r
120
122
  }}
121
123
  short.load ( myAllContext )
122
124
  short.changeContext ( 'myAll' )
123
125
  short.emit ( 'yo', 'hello' )
124
126
  expect ( result ).to.be.equal ( 'hello' )
127
+ short.changeContext ( 'general' )
128
+ short.unload ( 'myAll' )
125
129
  }) // it emit custom event
126
130
 
131
+
132
+
133
+ it ( 'Dependencies on shortcuts', done => {
134
+ const task = askForPromise ();
135
+ expect ( a ).to.be.false
136
+ expect ( b ).to.be.false
137
+
138
+ short.setDependencies ({ task })
139
+
140
+ short.load ({
141
+ 'extra' : { // load will overwrite existing 'extra' context definition
142
+ 'mouse-click-left-1' : ({dependencies}) => {
143
+ const { task } = dependencies;
144
+ expect ( task ).to.have.property ( 'done' )
145
+ expect ( task ).to.have.property ( 'promise' )
146
+ a = true
147
+ }
148
+ }
149
+ }) // load will restart the selected context
150
+
151
+ short.changeContext ( 'extra' )
152
+ cy.get('#rspan').click ()
153
+ cy.wait ( 350 ) // Default wait mouse timeout is 320 ms
154
+ .then ( () => {
155
+ expect ( a ).to.be.true
156
+ done ()
157
+ })
158
+ }) // it dependencies on shortcuts
159
+
160
+
161
+
162
+ it ( 'List shortcuts', () => {
163
+ let general = short.listShortcuts ('general');
164
+ expect ( general ).to.be.an('array')
165
+ expect ( general ).to.have.lengthOf ( 1 )
166
+ expect ( general[0] ).to.be.equal ( 'A+SHIFT' )
167
+
168
+ let fail = short.listShortcuts ('somethingNotExisting');
169
+ expect ( fail ).to.be.null
170
+
171
+ let all = short.listShortcuts ();
172
+ expect ( all ).to.be.an('array')
173
+
174
+ expect ( all ).to.have.lengthOf ( 2 )
175
+ expect ( all[0] ).to.have.property ( 'context' )
176
+ expect ( all[0] ).to.have.property ( 'shortcuts' )
177
+ expect ( all[0].shortcuts ).to.be.an('array')
178
+ expect ( all[0].shortcuts ).to.have.lengthOf ( 1 )
179
+ expect ( all[0].shortcuts[0] ).to.be.equal ( 'A+SHIFT' )
180
+ expect ( all[0].context ).to.be.equal ( 'general' )
181
+
182
+ }) // it list shortcuts
183
+
184
+
185
+
186
+ it ( 'Click on anchor', done => {
187
+ // Click on anchor that don't have click-data attribute.
188
+ let result = 'none';
189
+ short.load ({ 'extra' : {
190
+ 'mouse-click-left-1' : ({target, context }) => {
191
+ expect ( context ).to.be.equal ( 'extra' )
192
+ expect ( target.nodeName ).to.be.equal ( 'A' )
193
+ result = target.nodeName
194
+ }
195
+ }
196
+ })
197
+ short.changeContext ( 'extra' )
198
+ cy.get ( '#anchor' ).click ()
199
+ cy.wait ( 3 ) // Consider mouse click has some latency
200
+ // According Cypress documentation:
201
+ // It is unsafe to chain further commands that rely on the subject after .click().
202
+ // source docs: https://docs.cypress.io/api/commands/click
203
+ .then ( () => {
204
+ short.changeContext ( 'general' )
205
+ expect ( result ).to.be.equal ( 'A' )
206
+ done ()
207
+ })
208
+ }) // it click on anchor
209
+
210
+
127
211
  }) // describe
@@ -2,6 +2,7 @@ function Block () {
2
2
  return <>
3
3
  <div className="block" data-click="red"><span id='rspan'>Red</span> </div>
4
4
  <button className="big-btn" data-click="mega">Mega button</button>
5
+ <p>Some text with <a href="#" target="_blank">link <span id="anchor">in</span></a> it</p>
5
6
  </>
6
7
  }
7
8
 
package/blueprint-docs.md DELETED
@@ -1,73 +0,0 @@
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
- ```
@@ -1 +0,0 @@
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 DELETED
@@ -1,32 +0,0 @@
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 DELETED
@@ -1 +0,0 @@
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/src/findTarget.js DELETED
@@ -1,18 +0,0 @@
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
-
@@ -1,31 +0,0 @@
1
- export default {
2
- 'ArrowLeft' : 'LEFT'
3
- , 'ArrowUp' : 'UP'
4
- , 'ArrowRight' : 'RIGHT'
5
- , 'ArrowDown' : 'DOWN'
6
- , 'Enter' : 'ENTER'
7
- , 'NumpadEnter' : 'ENTER'
8
- , 'Escape' : 'ESC'
9
- , 'Backspace' : 'BACKSPACE'
10
- , 'Space' : 'SPACE'
11
- , 'Tab' : 'TAB'
12
- , 'Backquote' : '`'
13
- , 'BracketLeft' : '['
14
- , 'BracketRight': ']'
15
- , 'Equal' : '='
16
- , 'Slash' : '/'
17
- , 'Backslash' : '\\'
18
- , 'IntlBackslash' : '`'
19
- , 'F1' : 'F1'
20
- , 'F2' : 'F2'
21
- , 'F3' : 'F3'
22
- , 'F4' : 'F4'
23
- , 'F5' : 'F5'
24
- , 'F6' : 'F6'
25
- , 'F7' : 'F7'
26
- , 'F8' : 'F8'
27
- , 'F9' : 'F9'
28
- , 'F10' : 'F10'
29
- , 'F11' : 'F11'
30
- , 'F12' : 'F12'
31
- }