@peter.naydenov/shortcuts 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/Changelog.md +24 -0
  2. package/How..to.make.plugins.md +41 -0
  3. package/Migration.guide.md +77 -0
  4. package/README-v.2.x.x.md +375 -0
  5. package/README.md +108 -58
  6. package/cypress/fixtures/example.json +5 -0
  7. package/cypress/support/commands.js +25 -0
  8. package/cypress/support/component-index.html +14 -0
  9. package/cypress/support/component.js +27 -0
  10. package/cypress/support/e2e.js +20 -0
  11. package/dist/shortcuts.cjs +1 -0
  12. package/dist/shortcuts.esm.mjs +1 -0
  13. package/dist/shortcuts.umd.js +1 -0
  14. package/package.json +32 -19
  15. package/rollup.config.js +40 -0
  16. package/src/main.js +81 -30
  17. package/src/methods/_normalizeWithPlugins.js +25 -0
  18. package/src/methods/_readShortcutWithPlugins.js +24 -0
  19. package/src/methods/_systemAction.js +25 -0
  20. package/src/methods/changeContext.js +20 -26
  21. package/src/methods/index.js +7 -13
  22. package/src/methods/load.js +21 -14
  23. package/src/plugins/click/_findTarget.js +20 -0
  24. package/src/plugins/click/_listenDOM.js +117 -0
  25. package/src/plugins/click/_normalizeShortcutName.js +44 -0
  26. package/src/plugins/click/_readClickEvent.js +24 -0
  27. package/src/plugins/click/_registerShortcutEvents.js +30 -0
  28. package/src/plugins/click/index.js +74 -0
  29. package/src/plugins/key/_listenDOM.js +138 -0
  30. package/src/plugins/key/_normalizeShortcutName.js +31 -0
  31. package/src/{methods → plugins/key}/_readKeyEvent.js +2 -3
  32. package/src/plugins/key/_registerShortcutEvents.js +28 -0
  33. package/src/plugins/key/index.js +76 -0
  34. package/test/01-general.cy.js +189 -154
  35. package/src/methods/_findTarget.js +0 -19
  36. package/src/methods/_listen.js +0 -210
  37. package/src/methods/_readMouseEvent.js +0 -24
  38. package/src/methods/_readShortcut.js +0 -17
  39. /package/src/{methods → plugins/key}/_specialChars.js +0 -0
package/Changelog.md CHANGED
@@ -1,6 +1,30 @@
1
1
  ## Release History
2
2
 
3
3
 
4
+
5
+ ### 3.0.0 ( 2024-03-05 )
6
+ - [x] Mouse events don't have `preventDefault` by default anymore. Handle it in the action function if needed. Take a look on `Mouse Action Function` argument description. Object `event` is available;
7
+ - [x] Plugin system where plugins role is to convert DOM events to shortcut strings, then the core part will trigger the action functions related to the shortcut.
8
+ - [x] Plugin can be enable/disable;
9
+ - [x] Plugin can be mute/unmute;
10
+ - [x] Plugin prefix in shortcut description is required. Keyboard plugin will take care for events started with ‘key:’ for example. Required because we should know the plugin that will handle the event;
11
+ - [x] Start a plugin: enablePlugin ( pluginCode, pluginOptions );
12
+ - [x] Function to destroy(remove) a plugin - disablePlugin();
13
+ - [x] Mute/unmute a plugin. It’s like disable all events for a specific plugin;
14
+ - [x] Plugin system documentation;
15
+ - [x] Plugin interface: getPrefix, shortcutName, contextChange, mute, unmute, destroy;
16
+ - [x] Plugin options - specific for each plugin;
17
+
18
+
19
+
20
+
21
+ ### 2.2.0 ( 2024-02-10 )
22
+ - [x] Folder 'dist' was added to the project. Includes commonjs, umd and esm versions of the library;
23
+ - [x] Package.json: "exports" section was added. Allows you to use package as commonjs or es6 module without additional configuration;
24
+ - [x] Rollup was added to the project. Used to build the library versions;
25
+
26
+
27
+
4
28
  ### 2.1.0 ( 2023-10-17 )
5
29
  - [x] Method `setDependencies` to add more external objects available in all action functions;
6
30
  - [x] Method `getDependencies` to look at existing `dependencies` list;
@@ -0,0 +1,41 @@
1
+ # Shortcut plugins
2
+
3
+ Shortcut plugin should be a function that receives 3 arguments: dependencies, state and options. Shoud return an shortcut plugin API object.
4
+
5
+ ```js
6
+ function plugin ( dependencies, state, options ) {
7
+ // Normalize all shortcuts related to this plugin
8
+ // Setup some internal state
9
+ // Start listening to DOM events if current context has shortcuts related to this plugin
10
+ // ...
11
+ return {
12
+ // API
13
+ getPrefix: function () {
14
+ // return a plugin prefix
15
+ },
16
+ shortcutName : function ( shortcutName ) {
17
+ // normalize shortcut name
18
+ // return normalized shortcut name
19
+ },
20
+ contextChange: function ( context ) {
21
+ // How plugin should react to context change
22
+ // return void
23
+ },
24
+ mute: function () {
25
+ // Function that will stop DOM events from being triggered
26
+ },
27
+ unmute: function () {
28
+ // Function that will resume DOM events
29
+ },
30
+ destroy: function () {
31
+ // Destroy plugin instance
32
+ }
33
+ }
34
+ } // plugin
35
+ ```
36
+
37
+ State and dependencies are objects coming from the main library. **Dependencies** contains the library event emitter ( dependencies.ev). From state object you have access to the current context object state and loaded shortcuts contextes. Object **options** contains a plugin options provided by the user.
38
+
39
+ If you need see an example of a shortcut plugin, check the available plugins in the library: key and click.
40
+
41
+
@@ -1,5 +1,82 @@
1
1
  # Migration Guides
2
2
 
3
+
4
+ ## From version 2.x.x to version 3.x.x
5
+
6
+ Reason for significant refactoring of the code was my desire to make the library extensible with a plugins. Plugins role is to convert DOM events to shortcut strings, then the core part will trigger the action functions related to the shortcut. Now we have a core, plugin interface and plugins. All listener for `keyboard` and `mouse clicks` are moved to plugins. Mouse events are tracked by `click` plugin and keyboard events are tracked by `key` plugin.
7
+
8
+ We starting with only two plugins that cover the existing functionality of the library but plans are to extend the library with more plugins. For example we can add `'scroll`, `drag-drop`, `input events`, etc. Whould be great to make possible to describe all possible page events as shortcuts.
9
+
10
+ ### Package structure
11
+ Package contains the library and these two plugins.
12
+
13
+ ```js
14
+ // Import the library and plugins
15
+ // Version 2.x.x
16
+ import shortcuts from 'shortcuts';
17
+ const short2 = shortcuts({listenFor: ['mouse', 'keyboard']}) // Start listening for mouse and keyboard events
18
+ // Version 3.x.x
19
+ // Package provides access to the library and plugins. No default import anymore.
20
+ import { shortcuts, click, key } from 'shortcuts';
21
+ const short3 = shortcuts () // Start library without any plugins
22
+ // Enable plugins if you need them:
23
+ shorts3.enablePlugin(click)
24
+ shorts3.enablePlugin(key)
25
+ ```
26
+
27
+ ### Global Options
28
+ Part of the options are moved to the plugin options.
29
+ ```js
30
+ // Version 2 options:
31
+ mouseWait : '-> Moved to click plugin options'
32
+ , keyWait : '-> Moved to key plugin options'
33
+ , clickTarget : '-> Moved to click plugin options'
34
+ , listenFor : `x Removed. Use enablePlugin() method to start listening for a plugin`
35
+ , onShortcut : 'No changes. Available as global option'
36
+ , streamKeys : '-> Moved to key plugin options'
37
+ ```
38
+ The method `shortcuts.getDefaults()` was removed. No reasons to exist anymore.
39
+
40
+ ### Shortcut names
41
+ Because we have plugins now, we need to address somehow the plugin that will take care about the shortcut. Introducing plugin prefix.
42
+ ```js
43
+ // Description of the shortcut
44
+ // Version 2.x.x
45
+ const description2 = {
46
+ contextName : {
47
+ 'shortcutName' : 'actionFunction'
48
+ }
49
+ }
50
+ // Version 3.x.x
51
+ const description3 ={
52
+ contextName : {
53
+ 'pluginPrefix:shortcutName' : 'actionFunction'
54
+ }
55
+ }
56
+
57
+ // Here is an example:
58
+ //v.2.x.x
59
+ const desc2 = {
60
+ myContext : {
61
+ 'shift+f' : r => console.log(r)
62
+ }
63
+ }
64
+ //v.3.x.x
65
+ const desc3 = {
66
+ myContext : {
67
+ 'key:shift+f' : r => console.log(r)
68
+ }
69
+ }
70
+ ```
71
+
72
+
73
+ ### Mouse events
74
+ Prevent default is not called by default anymore. Handle it manually in the action function if needed. Object `event` is available in arguments object of action function. That will allow you to use default browser behavior without writing additional code.
75
+
76
+
77
+
78
+
79
+
3
80
  ## From version 1.x.x to version 2.x.x
4
81
  There are 2 breaking changes: in action functions and in emit method.
5
82
  ### Action functions
@@ -0,0 +1,375 @@
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
+ ![GitHub issues](https://img.shields.io/github/issues/peterNaydenov/shortcuts)
6
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40peter.naydenov%2Fshortcuts)
7
+
8
+
9
+
10
+ Define a context based keyboard-shortcuts and describe a mouse clicks. Switch among contexts.
11
+
12
+
13
+
14
+ ## Shortcut Description Rules
15
+ 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).
16
+
17
+ ```js
18
+ // { context: { shortcutName: actionFunction } }
19
+ // or
20
+ // { context: { shortcutName: [ actionFunction1, actionFunction2 ] }}
21
+
22
+ // Shortcut definition object:
23
+ {
24
+ contextName : {
25
+ shortcutName : function () {
26
+ // do something
27
+ }
28
+ , shortcutName : [
29
+ function action1() {
30
+ // do something
31
+ }
32
+ , function action2() {
33
+ // do something
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ```
39
+
40
+ Load a shortcut definition by calling `load` method.
41
+
42
+ ```js
43
+ // for es6 module projects:
44
+ include shortcuts from '@peter.naydenov/shortcuts'
45
+ // for commonjs projects:
46
+ const shortcuts = require('@peter.naydenov/shortcuts')
47
+
48
+
49
+
50
+ const short = shortcuts ();
51
+ short.load ( shortcutDefinition )
52
+ ```
53
+
54
+ Shortcuts are working only if contex is active. To activate a context call `changeContext` method.
55
+
56
+ ```js
57
+ short.changeContext ( contextName )
58
+ ```
59
+
60
+ To deactivate a context without starting other context, call `changeContext` method without arguments.
61
+
62
+ ```js
63
+ short.changeContext ()
64
+ ```
65
+
66
+ 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.
67
+
68
+ ```js
69
+ short.setNote ( 'special' ) // set note to 'special'
70
+ short.setNote () // remove the note
71
+ ```
72
+
73
+ 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`.
74
+
75
+ ```js
76
+ {
77
+ contextName : {
78
+ shortcutName : function ( {context, note} ) {
79
+ if ( note === 'special' ) {
80
+ // do something
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ Context and notes are available inside action functions but you can check them from outside too.
88
+ Check current context by calling `getContext` method.
89
+
90
+ ```js
91
+ short.getContext ()
92
+ ```
93
+
94
+ Check notes by calling `getNote` method.
95
+
96
+ ```js
97
+ short.getNote ()
98
+ ```
99
+
100
+
101
+
102
+
103
+
104
+ ## Mouse Event Descriptions
105
+ Mouse event name is build from the following parts:
106
+ ```js
107
+ // mouse-click-<mouse button>-<number of clicks>
108
+ // example:
109
+ // mouse-click-left-2 -> for double click with left mouse button
110
+ // mouse-click-right-3 -> for triple click with right mouse button
111
+
112
+ // mouse button options: left, right, middle
113
+ ```
114
+
115
+ The modifier keys `ctrl`, `alt`, and `shift` are supported. They are added to the mouse event by sign `+`:
116
+
117
+ ```js
118
+ // example:
119
+ // ctrl+mouse-click-left-1 -> for single click with left mouse button and ctrl key pressed
120
+ ```
121
+ Order of describing mouse event and modifier keys is not important.
122
+
123
+ ```js
124
+ // example:
125
+ // mouse-click-left-1+ctrl -> same as above
126
+
127
+ // These 3 descriptions are equal:
128
+ // mouse-click-left-1+ctrl+alt+shift
129
+ // alt+shift+mouse-click-left-1+ctrl
130
+ // mouse-click-left-1+shift+ctrl+alt
131
+ ```
132
+
133
+ 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`.
134
+
135
+
136
+ ## Define a mouse targets
137
+ Target HTML elements for `shortcuts` are defined by `data-click` attribute. The value of the attribute is the name of the target. Example:
138
+
139
+ ```html
140
+ <button data-click="id">Click me</button>
141
+ <!-- target name is 'id' -->
142
+ ```
143
+
144
+ Attribute is customizable by setting `clickTarget` option. Read more in section `Options`.
145
+
146
+ 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:
147
+
148
+ ```html
149
+ <button data-click="id" data-quick-click>Click me</button>
150
+ <!-- target name is 'id' and will not wait for more then 1 click -->
151
+ ```
152
+ 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:
153
+
154
+ ```html
155
+ <a href="#">Click me</a>
156
+ <!-- Recognized as a target and will not wait for more then 1 click -->
157
+ <!-- Take care for the action from shortcut `mouse-click-left-1`. -->
158
+ ```
159
+
160
+ 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:
161
+
162
+ ```js
163
+ {
164
+ contextName : {
165
+ 'mouse-click-left-1' : function ( {target, event} ) {
166
+ if ( target.tagName === 'A' ) { // All targets that are <a> tags will execute the default action
167
+ window.location.href = target.href // Go to the link
168
+ }
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+
175
+
176
+ ## Keyboard Event Descriptions
177
+ 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 `+`:
178
+
179
+ ```js
180
+ // example:
181
+ // ctrl+alt+shift+a -> for key 'a' with ctrl, alt and shift keys pressed
182
+ ```
183
+
184
+ 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 ):
185
+
186
+ ```js
187
+ // example:
188
+ // a,b,c -> for key 'a' then key 'b' then key 'c'
189
+
190
+ // g+shift,o,t,o -> for key 'g' with shift, then key 'o', then key 't' then key 'o'
191
+ ```
192
+
193
+ Order of describing keyboard event and modifier keys is not important, but sequence elements are:
194
+
195
+ ```js
196
+ // example:
197
+ // a+ctrl,l,o,t -> a with ctrl, then l, then o, then t
198
+ // this is equal to:
199
+ // ctrl+a,l,o,t
200
+ // but not equal to:
201
+ // ctrl+a,o,t,l
202
+ ```
203
+
204
+ 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`.
205
+
206
+ 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`.
207
+
208
+ Special characters that are available for your shortcut descriptions:
209
+ - 'left' - left arrow key
210
+ - 'right' - right arrow key
211
+ - 'up' - up arrow key
212
+ - 'down' - down arrow key
213
+ - 'enter' - enter key
214
+ - 'space' - space key
215
+ - 'esc' - escape key
216
+ - 'tab' - tab key
217
+ - 'backspace' - backspace key
218
+ - '=' - equal key
219
+ - F1 - F12 - function keys
220
+ - '/' - slash key
221
+ - '\\' - backslash key
222
+ - '[' - open square bracket key
223
+ - ']' - close square bracket key
224
+ - '`' - backtick key
225
+
226
+ **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.
227
+
228
+ **Warining**: Some of the shortcuts are used by OS and the browswer, so they are not available.
229
+
230
+
231
+
232
+ ## Action Functions
233
+ Action functions are called when a shortcut is triggered. They is a difference between keyboard and mouse action functions. Arguments are slightly different.
234
+
235
+
236
+
237
+ ### Keyboard Action Functions
238
+ Description of keyboard action functions is:
239
+ ```js
240
+ function myKeyHandler ({
241
+ context // (string) Name of the current context;
242
+ , note // (string) Name of the note or null if note isn't set;
243
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
244
+ , wait // (function). Call it to stop a sequence timer and write shortcut sequence without a timer.
245
+ , end // (function). Recover the sequence timer;
246
+ , ignore // (function). Call it to ignore the current shortcut from the sequence;
247
+ , isWaiting // (boolean). True if the sequence timer is active;
248
+ }) {
249
+ // Body of the handler. Do something...
250
+ }
251
+ ```
252
+
253
+
254
+
255
+
256
+ ### Mouse Action Functions
257
+ Mouse action functions can be described like:
258
+
259
+ ```js
260
+ function myMouseHandler ({
261
+ context // (string) Name of the current context;
262
+ , note // (string) Name of the note or null if note isn't set;
263
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
264
+ , target // (DOM element). Target element of the mouse event;
265
+ , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
266
+ , x // (number). X coordinate of the target element;
267
+ , y // (number). Y coordinate of the target element;
268
+ , event // (object). Original mouse event object;
269
+ }) {
270
+ // Body of the handler. Do something...
271
+ }
272
+ ```
273
+
274
+
275
+
276
+
277
+
278
+ ## Methods
279
+
280
+ Description of the methods of shortcut instance:
281
+
282
+ ```js
283
+ load : 'Load and extend a shortcut definition.'
284
+ , unload : 'Remove a shortcut context with all its shortcuts.'
285
+ , changeContext : 'Switch to existing shortcut context.'
286
+ , emit : 'Trigger a shortcut or custom event programmatically.'
287
+ , pause : 'Stop listening for shortcuts.'
288
+ , resume : 'Resume listening for shortcuts.'
289
+ , listContexts : 'Return list of available contexts.'
290
+ , listShortcuts : 'Return list of shortcuts per context.'
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.'
296
+ ```
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
+ ```
308
+
309
+
310
+
311
+ ## Options
312
+
313
+ By `options` you can customize the behavior of the shortcuts. Here is the list of available options:
314
+
315
+ ```js
316
+ mouseWait : 'Timeout for entering multiple mouse events. Default value - 320.'
317
+ , keyWait : 'Timeout for entering shortcut sequence in ms. Default value - 480'
318
+ , clickTarget : 'Data attribute name to recognize click items in HTML. Default value - click' // data attribute 'click' means attribute ( data-click='someName' )
319
+ , listenFor : `List input signal sources. Default value - [ 'mouse', 'keyboard' ]`
320
+ , onShortcut : 'False or a callback function that is called when a shortcut is triggered. Default value - false'
321
+ , streamKeys : 'False or a callback function that is called when a key is pressed. Default value - false'
322
+ ```
323
+
324
+ You can request default list of options with their default values:
325
+
326
+ ```js
327
+ shortcuts.getDefaults ()
328
+ // Note: This method is availalble on the original shortcuts object, not on the shortcuts instance.
329
+
330
+ // start a shortcuts with default options
331
+ const short = shortcuts ()
332
+ const short = shortcuts ( shortcuts.getDefaults () ) // same as above
333
+ // The idea behind getDefaults is to see what options are available and what are their default values.
334
+ ```
335
+
336
+
337
+
338
+ ### onShortcut option
339
+ ```js
340
+ function onShortcut ({ shortcut, context, note, dependencies }) {
341
+ // shortcut - (string) Triggered shortcut name
342
+ // context - (string) Name of the current context
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
345
+ }
346
+ ```
347
+
348
+
349
+
350
+ ### streamKeys option
351
+ ```js
352
+ function streamKeys ({ key, context, note, dependencies }) {
353
+ // key - (string) Pressed key name
354
+ // context - (string) Name of the current context
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
357
+ }
358
+ ```
359
+
360
+
361
+
362
+ ## Links
363
+
364
+ - [History of changes](https://github.com/PeterNaydenov/shortcuts/blob/main/Changelog.md)
365
+ - [Migration guide](https://github.com/PeterNaydenov/shortcuts/blob/main/Migration.guide.md)
366
+
367
+
368
+
369
+ ## Credits
370
+ '@peter.naydenov/shortcuts' was created and supported by Peter Naydenov.
371
+
372
+
373
+
374
+ ## License
375
+ '@peter.naydenov/shortcuts' is released under the MIT License.