@peter.naydenov/shortcuts 4.0.1 → 4.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.
Files changed (89) hide show
  1. package/AGENTS.md +54 -0
  2. package/Changelog.md +14 -0
  3. package/README.md +935 -426
  4. package/dist/plugins/click/_listenDOM.d.ts +2 -2
  5. package/dist/plugins/form/_listenDOM.d.ts +4 -4
  6. package/dist/plugins/hover/_listenDOM.d.ts +2 -2
  7. package/dist/plugins/key/_listenDOM.d.ts +2 -2
  8. package/dist/plugins/scroll/_listenDOM.d.ts +2 -2
  9. package/dist/shortcuts.cjs +1 -1
  10. package/dist/shortcuts.esm.mjs +1 -1
  11. package/dist/shortcuts.umd.js +1 -1
  12. package/graphify-out/.graphify_benchmark.json +35 -0
  13. package/graphify-out/.graphify_python +1 -0
  14. package/graphify-out/GRAPH_REPORT.md +121 -0
  15. package/graphify-out/cache/024633ee076562ce0dbc0bf3b5315906f4e5d48c57bc96856b98cd5d42a69009.json +1 -0
  16. package/graphify-out/cache/037d2ba5552034c06beca677b1914ac7a053f696dfeaf12ca0de06eb42659f18.json +1 -0
  17. package/graphify-out/cache/055bfc2a3bfb8747f7ee0b7335704d2dff1c071d25a9bdd4f954e4cc2d630772.json +1 -0
  18. package/graphify-out/cache/0b2f626892ed04f158e39593dbf5f266c2a5982a21b2dddb144edc79af81e2d1.json +1 -0
  19. package/graphify-out/cache/0c17fe866170c4ab4023ac0680d1e05c5dc232a17ac0f08dfa1e76c2eaf75c44.json +1 -0
  20. package/graphify-out/cache/1392dca26291b5396829f1a996aac2c1d34a03d134a5fcf54e4f15824ee74e2b.json +1 -0
  21. package/graphify-out/cache/18113cd08ebffb11ed91ffab9c6d34795e22bfb5993941db07a52eed6eba45b8.json +1 -0
  22. package/graphify-out/cache/1a7a71b157cadd117435818e1a6561157c2930c4066d3a207fe04e318f76b296.json +1 -0
  23. package/graphify-out/cache/2448db4b822a94d6f3512ce8788077f35dfb567aef8628a846fad841b40575e7.json +1 -0
  24. package/graphify-out/cache/2592868b7b9d2de3f2cb575b7bd68ca2f252d34f71c12f2e8721d789cfbfbf88.json +1 -0
  25. package/graphify-out/cache/2aaf58292523f67421e6f728fd97740c5bf07dd903cf267cf557a4383759ba5e.json +1 -0
  26. package/graphify-out/cache/349a418954e66e5ef45370dca740ebff559a72d11f5810f6d40c0af14ef768e0.json +1 -0
  27. package/graphify-out/cache/361eacb4abb14862b75257bdf673a353826bf5764fb187ccde94ae21454bcf99.json +1 -0
  28. package/graphify-out/cache/40b4c82a11a2a31b279563c143d91d7894eb3f3d0c386f8323cb8062bcdfadd5.json +1 -0
  29. package/graphify-out/cache/40db98ff036ad694953cb13628795fabc0c5892ff093dfdcc18a309ac0d10846.json +1 -0
  30. package/graphify-out/cache/43a590ac22f05be183ea1a4655922185595c969f79dd3df3d44d1c7e49355785.json +1 -0
  31. package/graphify-out/cache/4993a6e98dcf33bb6fa78341b4eeac4776e2322f10522eecdc5195aa7969f35a.json +1 -0
  32. package/graphify-out/cache/5095c6e52a24f4ecec9acf63835761ad508df88d56b1799faec47672fbd4e348.json +1 -0
  33. package/graphify-out/cache/5cba1d38ffea01d8c62e5a0b0d8b164cf9b115ff1b6f1acc606f78877712d5de.json +1 -0
  34. package/graphify-out/cache/67aadf0b8f90224cb725e903e6fedbf6828f203467a633f98031b0740930cb21.json +1 -0
  35. package/graphify-out/cache/68dffbffbd811942d85ef2600ca31e423d3c3c343de98cbba4c954c94dd11470.json +1 -0
  36. package/graphify-out/cache/69c684e506b9ce22e95aff044c1da4b0f88ef72c510da02a739edbe551372a8e.json +1 -0
  37. package/graphify-out/cache/6e336587fcfe2b298f23326e881f048d37f1bb062d00806c338da58a8fd281ca.json +1 -0
  38. package/graphify-out/cache/78d88737e6db913f091c4c48e96953df065f6808e2b60dd828fdee64067dac91.json +1 -0
  39. package/graphify-out/cache/7e5340b989299a0b7217d1fd0d2b919cba65142f3e468b0aca5f4ffd5c0594d8.json +1 -0
  40. package/graphify-out/cache/813d6bac52066ed8733781c35710ecb7995e6cabbe0d9abb9854e3c67610b974.json +1 -0
  41. package/graphify-out/cache/8180f34832546e874bc5f1931eee545d97300be49faac5c9b6d515653a763324.json +1 -0
  42. package/graphify-out/cache/885894ca90af6a3724182762bc4fc7ff7d22727a931d46fe7593d1eea10c0c71.json +1 -0
  43. package/graphify-out/cache/8a304ae8f6bf02bfa40923cdbea99e4bea943db52c185f22caa43ba7c34f94d3.json +1 -0
  44. package/graphify-out/cache/903a7dea28112a27dcc1b9ece66514f4d5dd6ca264f5ee70835aca069a8df2ad.json +1 -0
  45. package/graphify-out/cache/9831a7833c5bfeb9a0611e416f7038bd37884b42a9a720f9b4c0a01f860a4f54.json +1 -0
  46. package/graphify-out/cache/98dbdcdd1b19bc942850f50b1ebdeb1865c72ba724990217464efd28a3732b32.json +1 -0
  47. package/graphify-out/cache/a2459f621d588f0166ae6a4204bb6b89f9d669b3ad0c54a88afac6c7abb134b4.json +1 -0
  48. package/graphify-out/cache/a25d47ecf087fa6888d641f89f08cefd35c68b5823c8c55b3baa0243ab110110.json +1 -0
  49. package/graphify-out/cache/a3bd22d8493943a3195c3ef1254a7240624a962edf2baa2c30eb0ae60564fbe7.json +1 -0
  50. package/graphify-out/cache/a4d4fb674183a3b348f542b1b9fb9c0d7b176c43636afb2554af088a9613a1c0.json +1 -0
  51. package/graphify-out/cache/a87a705106773b14c5a25697d30c743cdab01df551cdd9892d6ec46f98ad1659.json +1 -0
  52. package/graphify-out/cache/a9416d0397b5fb994b8c3847aea2599a9d33940e6f0652accc5ba1de478349ee.json +1 -0
  53. package/graphify-out/cache/ab03b9df0e9b8a74db3782c96fee833d800d93838fc0c056306ac2ef9a3e0c09.json +1 -0
  54. package/graphify-out/cache/ad3a99182567225cc19374c28d33097f146547bd945967c723b66d1065134ce9.json +1 -0
  55. package/graphify-out/cache/aece91cfc3a5181bbb77a1758921dfb6a323ab04cc402ce42f2832446d04f420.json +1 -0
  56. package/graphify-out/cache/bd65fd515423e8964058f6aa997c05e3e0fb9e6d39209d4a1d76a079c6af46e8.json +1 -0
  57. package/graphify-out/cache/c2a85071784f9516ab2dea976eeb3a514a53b15701bbf60b4d8be6cd3385cd6c.json +1 -0
  58. package/graphify-out/cache/c9a8c9342926031f308af0eb0a8d60cf0b443e84bae839da42998956465e47e6.json +1 -0
  59. package/graphify-out/cache/d05c0aa647a624e0c696f53c027d066c35d0893695e9a23fd820235ee86b4a70.json +1 -0
  60. package/graphify-out/cache/d3d9832015ab51f52ae88375cea2cbeabecd4a000578e28e899ce23e74245733.json +1 -0
  61. package/graphify-out/cache/d449ad503a40840d41cbf24ed57f408bf5fdf891f830990f836cf52da5c605eb.json +1 -0
  62. package/graphify-out/cache/d92b22194973f3c39ac53d85a29f5d4837d07b0f9f0d375e3ddce8da158777fb.json +1 -0
  63. package/graphify-out/cache/dda8f89f688d8a4db8b7279031ad26a0d8d4accc0aa049abda5fa19eac4bd5ef.json +1 -0
  64. package/graphify-out/cache/e1d80dbca10b7e2ba65339eff0649699c6091d30b836a1e9d5d094bb95aacc48.json +1 -0
  65. package/graphify-out/cache/e207108277cbe1af0501688b0268fea879d0414424386fbaa93a5861f306bdba.json +1 -0
  66. package/graphify-out/cache/e6032dad287da859a517d6b59105595db90e81833dbd850b37653bbd0f3acef7.json +1 -0
  67. package/graphify-out/cache/f49d7295a833de68579e0e265832bc78d21e901764e31705423e621a703124dc.json +1 -0
  68. package/graphify-out/cache/fcf90a1251a332948a773c6aaaad4ce7f6de8d2f2333687cb2fe94e0d860a6c9.json +1 -0
  69. package/graphify-out/cache/fe06fcb623d36858b89c8741696482530a084f599d48bea88de7943fae0f9bea.json +1 -0
  70. package/graphify-out/cache/ffa0f819e023809d17aac1af75cf0f6fbf08500615aee27341b658f24357105a.json +1 -0
  71. package/graphify-out/cost.json +12 -0
  72. package/graphify-out/graph.html +266 -0
  73. package/graphify-out/graph.json +634 -0
  74. package/graphify-out/manifest.json +124 -0
  75. package/how-to-create-a-plugin.md +573 -0
  76. package/package.json +23 -21
  77. package/src/main.js +5 -5
  78. package/src/plugins/click/_listenDOM.js +3 -3
  79. package/src/plugins/form/_listenDOM.js +7 -6
  80. package/src/plugins/hover/_listenDOM.js +2 -2
  81. package/src/plugins/key/_listenDOM.js +4 -4
  82. package/src/plugins/scroll/_listenDOM.js +1 -1
  83. package/test/01-general.test.js +1 -1
  84. package/test/02-key.test.js +45 -1
  85. package/test/03-click.test.js +51 -2
  86. package/test/04-form.test.js +26 -1
  87. package/test/05-hover.test.js +50 -2
  88. package/test/06-scroll.test.js +21 -0
  89. package/How.to.create.plugins.md +0 -929
package/README.md CHANGED
@@ -22,37 +22,167 @@ Currently existing plugins:
22
22
 
23
23
 
24
24
 
25
- ## Shortcut Description Rules
26
- The shortcuts definition includes a context name and a set of rules(object). The rules are a set of key-value pairs. The key contains a plugin name and a shortcut name and the value is a function or array of functions, to be executed when the shortcut is triggered (action function).
25
+ ## Table of Contents
26
+ 1. [Quick Start](#quick-start)
27
+ 2. [Concepts](#concepts)
28
+ 3. [Constructor Options](#constructor-options)
29
+ 4. [Per-Plugin Options](#per-plugin-options)
30
+ 5. [Shortcut Description Rules](#shortcut-description-rules)
31
+ 6. [Per-Context Setup vs `enablePlugin` Options](#per-context-setup-vs-enableplugin-options)
32
+ 7. [Plugin Reference](#plugin-reference)
33
+ - [Plugin 'key'](#plugin-key-shortcut-descriptions)
34
+ - [Plugin 'click'](#plugin-click-shortcut-descriptions)
35
+ - [Plugin 'hover'](#plugin-hover-shortcut-descriptions)
36
+ - [Plugin 'scroll'](#plugin-scroll-shortcut-descriptions)
37
+ - [Plugin 'form'](#plugin-form-shortcut-descriptions)
38
+ 8. [Action Function Signatures](#action-functions)
39
+ 9. [Methods](#methods)
40
+ 10. [Contexts, Notes, and Dependencies](#contexts-notes-and-dependencies)
41
+ 11. [Errors and the `errorEventName` Channel](#errors-and-the-erroreventname-channel)
42
+ 12. [Custom Events & Workflows](#custom-events--workflows)
43
+ 13. [TypeScript Support](#typescript-support)
44
+ 14. [Links](#links)
45
+
46
+
47
+
48
+ ## Quick Start
27
49
 
28
- ### Per-Context Plugin Setup (Preferred Method)
29
- Every plugin supports a `setup` event (e.g., `key:setup`, `click:setup`, `hover:setup`, `scroll:setup`) that allows you to configure plugin settings specifically for that context. This is the **preferred method** for customizing plugins as it provides:
50
+ ```js
51
+ // for es6 module projects:
52
+ import { shortcuts, pluginKey, pluginClick, pluginForm, pluginHover, pluginScroll } from '@peter.naydenov/shortcuts'
53
+ // for commonjs projects:
54
+ const { shortcuts, pluginKey, pluginClick, pluginForm, pluginHover, pluginScroll } = require('@peter.naydenov/shortcuts')
30
55
 
31
- - **Context-specific configuration** - Different settings for different contexts
32
- - **Cleaner code** - No global plugin options needed
33
- - **Better maintainability** - Settings are defined alongside the shortcuts they affect
34
56
 
35
- The setup function receives:
36
- - `dependencies` - External dependencies set via `setDependencies()`
37
- - `defaults` - Default plugin options as a starting point
57
+ const short = shortcuts ();
58
+ // Load needed plugins. Second argument is per-plugin options.
59
+ short.enablePlugin ( pluginKey )
60
+ short.enablePlugin ( pluginClick )
61
+ // Load a shortcut definition
62
+ short.load ( shortcutDefinition )
63
+ // Activate a context to start listening.
64
+ short.changeContext ( 'editor' )
65
+ ```
66
+
67
+ To stop all shortcuts without activating another context:
38
68
 
39
- Example pattern:
40
69
  ```js
41
- const shortcutDefinition = {
42
- context1: {
43
- 'plugin:setup': ({ dependencies, defaults }) => {
44
- return {
45
- // Override specific options for this context
46
- option1: 'customValue1',
47
- option2: 123
48
- };
49
- },
50
- 'plugin:event': () => { /* your action */ }
51
- }
52
- };
70
+ short.changeContext () // deactivates the current context
53
71
  ```
54
72
 
55
- See individual plugin sections for specific setup examples.
73
+ See [Shortcut Description Rules](#shortcut-description-rules) for the shape of `shortcutDefinition`, and [Methods](#methods) for the full API.
74
+
75
+
76
+
77
+ ## Concepts
78
+
79
+ - **Shortcut** — a single rule like `'key: ctrl+s'` or `'click: left-1'` that is triggered by a physical event.
80
+ - **Action function** — the function (or array of functions) executed when a shortcut fires. Each plugin passes a different `data` object to it (see [Action Functions](#action-functions)).
81
+ - **Context** — a named bucket of shortcuts. Only one context is active at a time; `changeContext(name)` switches between them.
82
+ - **Note** — a sub-context tag that lives inside the current context. Lets a single context react differently to the same shortcut under different notes. Set with `setNote('foo')`, read inside the action as `data.note`.
83
+ - **Plugin** — the layer that converts physical input into shortcut events (`key`, `click`, `hover`, `scroll`, `form`).
84
+ - **Custom event** — any string (typically namespaced with `@` or `app:`) that you can `emit` and `listen` to. See [Custom Events & Workflows](#custom-events--workflows).
85
+
86
+
87
+
88
+ ## Constructor Options
89
+
90
+ `shortcuts(options)` accepts:
91
+
92
+ | Option | Type | Default | Description |
93
+ |---|---|---|---|
94
+ | `onShortcut` | `function` | `false` | Called for every triggered shortcut in the current context (including programmatically emitted events). |
95
+ | `errorEventName` | `string` | `'@shortcuts-error'` | Name of the event on which the library publishes error messages. See [Errors and the `errorEventName` Channel](#errors-and-the-erroreventname-channel). |
96
+ | `streamKeys` | `function` | `false` | Called on every key press while the `key` plugin is active. Equivalent to passing `streamKeys` to `enablePlugin(pluginKey, …)`. |
97
+
98
+ ```js
99
+ const short = shortcuts ({
100
+ onShortcut : ({ shortcut, context }) => console.log ( shortcut, 'in', context )
101
+ , errorEventName : '@my-app-shortcut-errors'
102
+ });
103
+ ```
104
+
105
+ ### `onShortcut({ shortcut, context, note, dependencies })`
106
+ ```js
107
+ function onShortcut ({ shortcut, context, note, dependencies }) {
108
+ // shortcut - (string) Triggered shortcut name (e.g. 'KEY:CTRL+S')
109
+ // context - (string) Name of the current context
110
+ // note - (string) Name of the note or null if note isn't set
111
+ // dependencies - (object) Object with dependencies that you have set by calling `setDependencies` method
112
+ }
113
+ ```
114
+
115
+ ### `streamKeys({ key, context, note, dependencies })`
116
+ ```js
117
+ function streamKeys ({ key, context, note, dependencies }) {
118
+ // key - (string) Pressed key name
119
+ // context - (string) Name of the current context
120
+ // note - (string) Name of the note or null if note isn't set
121
+ // dependencies - (object) Object with dependencies that you have set by calling `setDependencies` method
122
+ }
123
+ ```
124
+
125
+
126
+
127
+ ## Per-Plugin Options
128
+
129
+ These are passed as the second argument of `enablePlugin(plugin, options)`. To override per-context, use the `plugin:setup` event — see [Per-Context Setup vs `enablePlugin` Options](#per-context-setup-vs-enableplugin-options).
130
+
131
+ ### Plugin 'key' options
132
+ ```js
133
+ keyWait : 'Timeout for entering shortcut sequence in ms. Default value - 480'
134
+ , streamKeys : 'False or a callback function that is called when a key is pressed. Default value - false'
135
+ ```
136
+
137
+ ### Plugin 'click' options
138
+ ```js
139
+ mouseWait : 'Timeout for entering multiple mouse events. Default value - 320.'
140
+ , clickTarget : 'Array of attribute names to recognize click items in HTML. Default value - ["data-click", "href"]' // checks for data-click='someName' or href attributes
141
+ ```
142
+
143
+ ### Plugin 'hover' options
144
+ ```js
145
+ wait : 'Time to wait for hover sequence in ms. Default value - 320.'
146
+ , hoverTarget : 'Array of attribute names to recognize hover items in HTML. Default value - ["data-hover"]' // checks for data-hover='someName' attribute
147
+ ```
148
+
149
+ ### Plugin 'scroll' options
150
+ ```js
151
+ scrollWait : 'Delay between scroll events in ms. Default value - 50.'
152
+ , endScrollWait : 'Delay when scroll was stopped in ms. Default value - 400.'
153
+ , minSpace : 'Minimum distance between scroll events in px. Default value - 40.'
154
+ ```
155
+
156
+ Plugin options are provided as a second argument during the plugin enabling:
157
+
158
+ ```js
159
+ short.enablePlugin ( pluginKey, {
160
+ keyWait: 500 // set the interval to 500ms
161
+ , streamKeys: (key) => console.log(key) // Log in console each pressed key
162
+ })
163
+
164
+ short.enablePlugin ( pluginClick, {
165
+ mouseWait: 200 // set the interval between multiple clicks to 200ms
166
+ , clickTarget: ['data-puk', 'data-button'] // array of attribute names to check
167
+ })
168
+
169
+ short.enablePlugin ( pluginHover, {
170
+ wait: 500 // set the hover delay to 500ms
171
+ , hoverTarget: ['data-hover-me', 'data-interactive'] // array of attribute names to check
172
+ })
173
+
174
+ short.enablePlugin ( pluginScroll, {
175
+ scrollWait: 100 // set the delay between scroll events to 100ms
176
+ , endScrollWait: 600 // set the end scroll delay to 600ms
177
+ , minSpace: 60 // set minimum distance to 60px
178
+ })
179
+ ```
180
+
181
+
182
+
183
+ ## Shortcut Description Rules
184
+
185
+ The shortcuts definition includes a context name and a set of rules (object). The rules are a set of key-value pairs. The key contains a plugin name and a shortcut name and the value is a function or array of functions, to be executed when the shortcut is triggered (action function).
56
186
 
57
187
  ```js
58
188
  // { context: { shortcutName: actionFunction } }
@@ -65,7 +195,7 @@ See individual plugin sections for specific setup examples.
65
195
  shortcutName : function () {
66
196
  // do something
67
197
  }
68
- , shortcutName : [
198
+ , shortcutName : [
69
199
  function action1() {
70
200
  // do something
71
201
  }
@@ -75,302 +205,169 @@ See individual plugin sections for specific setup examples.
75
205
  ]
76
206
  }
77
207
  }
78
- // shortcutName after v.3.0.0 have a plugin prefix. - 'pluginPrefix:shortcutName'.
208
+ // shortcutName after v.3.0.0 have a plugin prefix. - 'pluginPrefix:shortcutName'.
79
209
  // For example: 'key:s+alt' - for 's+alt' shortcut that is handled by 'key' plugin.
80
210
  ```
81
211
 
82
212
  Load a shortcut definition by calling `load` method.
83
213
 
84
214
  ```js
85
- // for es6 module projects:
86
- import { shortcuts, pluginKey, pluginClick, pluginForm, pluginHover, pluginScroll } from '@peter.naydenov/shortcuts'
87
- // for commonjs projects:
88
- const { shortcuts, pluginKey, pluginClick, pluginForm, pluginHover, pluginScroll } = require('@peter.naydenov/shortcuts')
89
-
90
-
91
-
92
215
  const short = shortcuts ();
93
- // Load a needed plugins
94
- // short.enablePlugin ( pluginCode, ?pluginOptions )
95
- short.enablePlugin ( pluginKey )
216
+ short.enablePlugin ( pluginKey )
96
217
  short.enablePlugin ( pluginClick )
97
- // Load a shortcut definition
98
218
  short.load ( shortcutDefinition )
99
-
219
+ short.changeContext ( contextName ) // activate a context
100
220
  ```
101
221
 
102
- Shortcuts are working only if contex is active. To activate a context call `changeContext` method.
222
+ Shortcuts only fire while a context is active. To deactivate a context without starting another, call `changeContext` with no argument.
103
223
 
104
224
  ```js
105
- short.changeContext ( contextName )
225
+ short.changeContext () // deactivates the current context
106
226
  ```
107
227
 
108
- To deactivate a context without starting other context, call `changeContext` method without arguments.
109
228
 
110
- ```js
111
- short.changeContext ()
112
- ```
113
229
 
230
+ ## Per-Context Setup vs `enablePlugin` Options
114
231
 
115
- ## Plugin 'hover' Shortcut Descriptions
116
- `Hover` plugin is used to detect when mouse enters or leaves specific HTML elements. The plugin supports two main events: hover on and hover off.
232
+ There are two ways to configure a plugin:
117
233
 
118
- ```js
119
- hover:on // Triggered when mouse enters a target element
120
- hover:off // Triggered when mouse leaves a target element
121
- ```
234
+ 1. **Globally** — pass options to `enablePlugin(plugin, options)`. They apply to every context that uses that plugin.
235
+ 2. **Per-context** — define a `plugin:setup` event inside the context object. The function receives `{ dependencies, defaults }` and returns an options object that overrides the defaults **for that context only**. The supported events are `key:setup`, `click:setup`, `hover:setup`, and `scroll:setup`.
122
236
 
123
- ### Define Hover Targets
124
- Target HTML elements for `hover` plugin are defined by `data-hover` attribute. The value of the attribute is the name of the target. Example:
237
+ **Per-context setup is the preferred method** because:
238
+ - Different contexts can use different settings (e.g. fast clicks in a game context, deliberate clicks in a form context).
239
+ - The configuration lives next to the shortcuts that depend on it.
240
+ - You don't have to re-enable the plugin when switching contexts.
125
241
 
126
- ```html
127
- <div data-hover="menu">Menu content</div>
128
- <!-- target name is 'menu' -->
129
- ```
130
-
131
- Attribute is customizable by setting `hoverTarget` hover plugin option. By default, it checks for `['data-hover']`. You can provide an array of attribute names. Read more in section `Options`.
132
-
133
-
134
-
135
- ### Hover Action Functions
136
- Hover plugin action functions receive the following arguments:
137
-
138
- ```js
139
- function myHoverHandler ({
140
- context // (string) Name of the current context;
141
- , note // (string) Name of the note or null if note isn't set;
142
- , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
143
- , target // (DOM element). Target element of the hover event;
144
- , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
145
- , x // (number). X coordinate of the target element;
146
- , y // (number). Y coordinate of the target element;
147
- , event // (object). Original hover event object;
148
- }) {
149
- // Body of the handler. Do something...
150
- }
151
- ```
152
-
153
- ### Hover Detection Timing
154
- Hover events are detected with a delay to avoid triggering when mouse quickly moves over elements. The default delay is 320ms but you can change it by setting `wait` hover plugin option.
155
-
156
- ```js
157
- short.enablePlugin ( pluginHover, { wait: 500 }) // set the delay to 500ms
158
- ```
242
+ The setup function receives:
243
+ - `dependencies` - External dependencies set via `setDependencies()`
244
+ - `defaults` - Default plugin options as a starting point or just for reference. Use them to read or partially override.
159
245
 
160
- ### Per-Context Setup (Preferred Method)
161
- Instead of global plugin options, you can use `hover:setup` event to configure plugin settings per context. This is the preferred method for customization.
246
+ Example pattern (applies to every plugin that supports `setup`):
162
247
 
163
248
  ```js
164
249
  const shortcutDefinition = {
165
- navigation: {
166
- 'hover:setup': ({ dependencies, defaults }) => {
167
- // Customize hover settings for this context only
168
- return {
169
- wait: 200, // Faster hover detection for navigation
170
- hoverTarget: ['data-nav-item', 'data-menu'] // Array of attribute names
171
- };
172
- },
173
- 'hover:on': ({ target }) => {
174
- target.classList.add('active');
175
- },
176
- 'hover:off': ({ target }) => {
177
- target.classList.remove('active');
178
- }
179
- },
180
- slowTooltips: {
181
- 'hover:setup': ({ dependencies, defaults }) => {
182
- // Slower hover detection for tooltips
250
+ context1: {
251
+ 'plugin:setup': ({ dependencies, defaults }) => {
183
252
  return {
184
- wait: 800, // Slower hover detection
185
- hoverTarget: ['data-tooltip', 'data-help'] // Different attributes for tooltips
253
+ // Override specific options for this context
254
+ option1: 'customValue1',
255
+ option2: 123
186
256
  };
187
257
  },
188
- 'hover:on': ({ target }) => {
189
- // Show tooltip with delay
190
- setTimeout(() => target.classList.add('visible'), 100);
191
- }
258
+ 'plugin:event': () => { /* your action */ }
192
259
  }
193
260
  };
194
-
195
- short.enablePlugin(pluginHover);
196
- short.load(shortcutDefinition);
197
- short.changeContext('navigation'); // Uses navigation settings
198
- // short.changeContext('slowTooltips'); // Uses tooltip settings
199
261
  ```
200
262
 
201
- The `hover:setup` function receives:
202
- - `dependencies` - External dependencies set via `setDependencies()`
203
- - `defaults` - Default plugin options as a starting point or just for reference
263
+ See individual plugin sections for specific setup examples (`key:setup`, `click:setup`, `hover:setup`, `scroll:setup`).
204
264
 
205
- Example usage:
265
+ **Rule of thumb:** use `enablePlugin(plugin, options)` only for values you want to be global to the app. Use `plugin:setup` for anything that varies by context.
206
266
 
207
- ```js
208
- const shortcutDefinition = {
209
- navigation: {
210
- 'hover:on': ({ target }) => {
211
- // Mouse entered the target
212
- target.classList.add('active');
213
- },
214
- 'hover:off': ({ target }) => {
215
- // Mouse left the target
216
- target.classList.remove('active');
217
- }
218
- }
219
- };
220
267
 
221
- short.enablePlugin(pluginHover);
222
- short.load(shortcutDefinition);
223
- short.changeContext('navigation');
224
- ```
225
268
 
226
- ## Plugin 'scroll' Shortcut Descriptions
227
- `Scroll` plugin is used to detect scroll events on the page. The plugin supports four main scroll directions:
269
+ ## Plugin Reference
228
270
 
229
- ```js
230
- scroll:up // Triggered when scrolling up
231
- scroll:down // Triggered when scrolling down
232
- scroll:left // Triggered when scrolling left
233
- scroll:right // Triggered when scrolling right
234
- scroll:end // Triggered when scrolling stops (after endScrollWait timeout)
235
- ```
236
271
 
237
- ### Scroll Detection Settings
238
- Scroll events are detected with specific timing and distance thresholds to avoid excessive triggering. The default settings are:
239
272
 
240
- - `scrollWait`: 50ms - Delay between scroll events
241
- - `endScrollWait`: 400ms - Delay when scroll was stopped
242
- - `minSpace`: 40px - Minimum distance between scroll events
273
+ ### Plugin 'key' Shortcut Descriptions
243
274
 
244
- These can be customized by setting scroll plugin options:
275
+ 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 `+`:
245
276
 
246
277
  ```js
247
- short.enablePlugin ( pluginScroll, {
248
- scrollWait: 100, // set delay to 100ms
249
- endScrollWait: 600, // set end scroll delay to 600ms
250
- minSpace: 60 // set minimum distance to 60px
251
- })
278
+ // example:
279
+ // key: ctrl+alt+shift+a -> for key 'a' with ctrl, alt and shift keys pressed
252
280
  ```
253
281
 
254
- ### Per-Context Setup (Preferred Method)
255
- Instead of global plugin options, you can use `scroll:setup` event to configure plugin settings per context. This is preferred method for customization.
282
+ 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 ):
256
283
 
257
284
  ```js
258
- const shortcutDefinition = {
259
- sensitiveScrolling: {
260
- 'scroll:setup': ({ dependencies, defaults }) => {
261
- // High sensitivity for gaming or precise interactions
262
- return {
263
- scrollWait: 20, // Very responsive
264
- endScrollWait: 200, // Quick end detection
265
- minSpace: 20 // Small movements trigger
266
- };
267
- },
268
- 'scroll:up': () => console.log('Sensitive scroll up'),
269
- 'scroll:down': () => console.log('Sensitive scroll down'),
270
- 'scroll:end': () => console.log('Sensitive scroll ended')
271
- },
272
- lazyScrolling: {
273
- 'scroll:setup': ({ dependencies, defaults }) => {
274
- // Low sensitivity for reading or casual browsing
275
- return {
276
- scrollWait: 150, // Less responsive
277
- endScrollWait: 800, // Slow end detection
278
- minSpace: 80 // Larger movements needed
279
- };
280
- },
281
- 'scroll:up': () => console.log('Lazy scroll up'),
282
- 'scroll:down': () => console.log('Lazy scroll down'),
283
- 'scroll:end': () => console.log('Lazy scroll ended')
284
- }
285
- };
286
-
287
- short.enablePlugin(pluginScroll);
288
- short.load(shortcutDefinition);
289
- short.changeContext('sensitiveScrolling'); // Uses sensitive settings
290
- // short.changeContext('lazyScrolling'); // Uses lazy settings
291
- ```
292
-
293
- The `scroll:setup` function receives:
294
- - `dependencies` - External dependencies set via `setDependencies()`
295
- - `defaults` - Default plugin options as a starting point
296
-
297
- ### Scroll Action Functions
298
- Scroll plugin action functions receive the following arguments:
285
+ // example:
286
+ // key: a,b,c -> for key 'a' then key 'b' then key 'c'
299
287
 
300
- ```js
301
- function myScrollHandler ({
302
- context // (string) Name of the current context;
303
- , note // (string) Name of the note or null if note isn't set;
304
- , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
305
- , event // (object). Original scroll event object;
306
- }) {
307
- // Body of the handler. Do something...
308
- }
288
+ // key: g+shift,o,t,o -> for key 'g' with shift, then key 'o', then key 't' then key 'o'
309
289
  ```
310
290
 
311
- Example usage:
291
+ Order of describing keyboard event and modifier keys is not important, but sequence elements are:
312
292
 
313
293
  ```js
314
- const shortcutDefinition = {
315
- scrollView: {
316
- 'scroll:up': () => {
317
- console.log('User scrolled up');
318
- },
319
- 'scroll:down': () => {
320
- console.log('User scrolled down');
321
- },
322
- 'scroll:end': () => {
323
- console.log('User stopped scrolling');
324
- }
325
- }
326
- };
327
-
328
- short.enablePlugin(pluginScroll);
329
- short.load(shortcutDefinition);
330
- short.changeContext('scrollView');
294
+ // example:
295
+ // key: a+ctrl,l,o,t -> a with ctrl, then l, then o, then t
296
+ // this is equal to:
297
+ // key: ctrl+a,l,o,t
298
+ // but not equal to:
299
+ // key: ctrl+a,o,t,l
331
300
  ```
332
301
 
333
- ```
302
+ Keyboard sequence is detected automatically by time interval between key presses. The default interval is 480ms but you can change it by setting `keyWait` key plugin option. Read more in section `Per-Plugin Options`.
334
303
 
335
- 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.
304
+ #### Per-Context Setup (Preferred Method)
305
+ Instead of global plugin options, you can use `key:setup` event to configure plugin settings per context. This is preferred method for customization.
336
306
 
337
307
  ```js
338
- short.setNote ( 'special' ) // set note to 'special'
339
- short.setNote () // remove the note
340
- ```
341
-
342
- 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`.
308
+ const shortcutDefinition = {
309
+ fastTyping: {
310
+ 'key:setup': ({ dependencies, defaults }) => {
311
+ // Fast key detection for gaming or rapid input
312
+ return {
313
+ keyWait: 200, // Very fast sequence detection
314
+ streamKeys: (key) => console.log('Key pressed:', key) // Enable key streaming
315
+ };
316
+ },
317
+ 'key:a,b,c': () => console.log('Fast sequence triggered'),
318
+ 'key:ctrl+s': () => console.log('Fast save')
319
+ },
320
+ slowTyping: {
321
+ 'key:setup': ({ dependencies, defaults }) => {
322
+ // Slower key detection for accessibility or careful input
323
+ return {
324
+ keyWait: 800, // Slower sequence detection
325
+ streamKeys: false // Disable key streaming
326
+ };
327
+ },
328
+ 'key:a,b,c': () => console.log('Slow sequence triggered'),
329
+ 'key:ctrl+s': () => console.log('Careful save')
330
+ }
331
+ };
343
332
 
344
- ```js
345
- {
346
- contextName : {
347
- shortcutName : function ( {context, note} ) {
348
- if ( note === 'special' ) {
349
- // do something
350
- }
351
- }
352
- }
353
- }
333
+ short.enablePlugin(pluginKey);
334
+ short.load(shortcutDefinition);
335
+ short.changeContext('fastTyping'); // Uses fast settings
336
+ // short.changeContext('slowTyping'); // Uses slow settings
354
337
  ```
355
338
 
356
- Context and notes are available inside action functions but you can check them from outside too.
357
- Check current context by calling `getContext` method.
339
+ The `key:setup` function receives:
340
+ - `dependencies` - External dependencies set via `setDependencies()`
341
+ - `defaults` - Default plugin options as a starting point
358
342
 
359
- ```js
360
- short.getContext ()
361
- ```
343
+ 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 [Action Functions → Keyboard Action Functions](#keyboard-action-functions).
362
344
 
363
- Check notes by calling `getNote` method.
345
+ Special characters that are available for your shortcut descriptions:
346
+ - 'left' - left arrow key
347
+ - 'right' - right arrow key
348
+ - 'up' - up arrow key
349
+ - 'down' - down arrow key
350
+ - 'enter' - enter key
351
+ - 'space' - space key
352
+ - 'esc' - escape key
353
+ - 'tab' - tab key
354
+ - 'backspace' - backspace key
355
+ - '=' - equal key
356
+ - F1 - F12 - function keys
357
+ - '/' - slash key
358
+ - '\\' - backslash key
359
+ - '[' - open square bracket key
360
+ - ']' - close square bracket key
361
+ - '`' - backtick key
364
362
 
365
- ```js
366
- short.getNote ()
367
- ```
363
+ **Warning**: For keys with two symbols(look at the keyboard), in shortcut description use the lower one. Examples: Use '=' instead of '+', use '/' instead of '?', etc. Modifier keys are available for special characters too.
368
364
 
365
+ **Warining**: Some of the shortcuts are used by OS and the browswer, so they are not available.
369
366
 
370
367
 
371
368
 
369
+ ### Plugin 'click' Shortcut Descriptions
372
370
 
373
- ## Plugin 'click' Shortcut Descriptions
374
371
  Mouse event name is build from the following parts:
375
372
  ```js
376
373
  // click:<mouse button>-<number of clicks>-<modifier key>-<modifier key>-<modifier key>
@@ -405,7 +402,7 @@ Multiple clicks are detected automatically by time interval between clicks. The
405
402
  short.enablePlugin ( pluginClick, { mouseWait: 500 }) // set the interval to 500ms
406
403
  ```
407
404
 
408
- ### Per-Context Setup (Preferred Method)
405
+ #### Per-Context Setup (Preferred Method)
409
406
  Instead of global plugin options, you can use `click:setup` event to configure plugin settings per context. This is preferred method for customization.
410
407
 
411
408
  ```js
@@ -430,7 +427,7 @@ const shortcutDefinition = {
430
427
  // Slower clicking for form submissions or important actions
431
428
  return {
432
429
  mouseWait: 600, // Slower click detection
433
- clickTarget: ['data-form-action', 'data-submit'] // Array of attributes for form actions
430
+ clickTarget: ['data-form-action', 'data-submit'] // Array of attributes for form submissions
434
431
  };
435
432
  },
436
433
  'click:left-1': ({ target }) => {
@@ -449,10 +446,12 @@ The `click:setup` function receives:
449
446
  - `dependencies` - External dependencies set via `setDependencies()`
450
447
  - `defaults` - Default plugin options as a starting point
451
448
 
452
- Read more in section `Options`.
449
+ Read more in section `Per-Plugin Options`.
450
+
453
451
 
454
452
 
455
- ## Define a Click Targets
453
+ ### Define a Click Targets
454
+
456
455
  Target HTML elements for `shortcuts` are defined by `data-click` attribute. The value of the attribute is the name of the target. Example:
457
456
 
458
457
  ```html
@@ -460,7 +459,7 @@ Target HTML elements for `shortcuts` are defined by `data-click` attribute. The
460
459
  <!-- target name is 'id' -->
461
460
  ```
462
461
 
463
- Attribute is customizable by setting `clickTarget` click plugin option. By default, it checks for `['data-click', 'href']`. You can provide an array of attribute names. Read more in section `Options`.
462
+ Attribute is customizable by setting `clickTarget` click plugin option. By default, it checks for `['data-click', 'href']`. You can provide an array of attribute names. Read more in section `Per-Plugin Options`.
464
463
 
465
464
  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:
466
465
 
@@ -493,121 +492,229 @@ Clicking on <a> tag will execute default browser behaviour. In your `click:left-
493
492
 
494
493
 
495
494
 
496
- ## Plugin 'key' Event Descriptions
497
- 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 `+`:
495
+ ### Plugin 'hover' Shortcut Descriptions
496
+
497
+ `Hover` plugin is used to detect when mouse enters or leaves specific HTML elements. The plugin supports two main events: hover on and hover off.
498
498
 
499
499
  ```js
500
- // example:
501
- // key: ctrl+alt+shift+a -> for key 'a' with ctrl, alt and shift keys pressed
500
+ hover:on // Triggered when mouse enters a target element
501
+ hover:off // Triggered when mouse leaves a target element
502
502
  ```
503
503
 
504
- 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 ):
504
+ #### Define Hover Targets
505
+ Target HTML elements for `hover` plugin are defined by `data-hover` attribute. The value of the attribute is the name of the target. Example:
506
+
507
+ ```html
508
+ <div data-hover="menu">Menu content</div>
509
+ <!-- target name is 'menu' -->
510
+ ```
511
+
512
+ Attribute is customizable by setting `hoverTarget` hover plugin option. By default, it checks for `['data-hover']`. You can provide an array of attribute names. Read more in section `Per-Plugin Options`.
513
+
514
+
515
+
516
+ ### Hover Detection Timing
517
+ Hover events are detected with a delay to avoid triggering when mouse quickly moves over elements. The default delay is 320ms but you can change it by setting `wait` hover plugin option.
505
518
 
506
519
  ```js
507
- // example:
508
- // key: a,b,c -> for key 'a' then key 'b' then key 'c'
520
+ short.enablePlugin ( pluginHover, { wait: 500 }) // set the delay to 500ms
521
+ ```
509
522
 
510
- // key: g+shift,o,t,o -> for key 'g' with shift, then key 'o', then key 't' then key 'o'
523
+ #### Per-Context Setup (Preferred Method)
524
+ Instead of global plugin options, you can use `hover:setup` event to configure plugin settings per context. This is the preferred method for customization.
525
+
526
+ ```js
527
+ const shortcutDefinition = {
528
+ navigation: {
529
+ 'hover:setup': ({ dependencies, defaults }) => {
530
+ // Customize hover settings for this context only
531
+ return {
532
+ wait: 200, // Faster hover detection for navigation
533
+ hoverTarget: ['data-nav-item', 'data-menu'] // Array of attribute names
534
+ };
535
+ },
536
+ 'hover:on': ({ target }) => {
537
+ target.classList.add('active');
538
+ },
539
+ 'hover:off': ({ target }) => {
540
+ target.classList.remove('active');
541
+ }
542
+ },
543
+ slowTooltips: {
544
+ 'hover:setup': ({ dependencies, defaults }) => {
545
+ // Slower hover detection for tooltips
546
+ return {
547
+ wait: 800, // Slower hover detection
548
+ hoverTarget: ['data-tooltip', 'data-help'] // Different attributes for tooltips
549
+ };
550
+ },
551
+ 'hover:on': ({ target }) => {
552
+ // Show tooltip with delay
553
+ setTimeout(() => target.classList.add('visible'), 100);
554
+ }
555
+ }
556
+ };
557
+
558
+ short.enablePlugin(pluginHover);
559
+ short.load(shortcutDefinition);
560
+ short.changeContext('navigation'); // Uses navigation settings
561
+ // short.changeContext('slowTooltips'); // Uses tooltip settings
511
562
  ```
512
563
 
513
- Order of describing keyboard event and modifier keys is not important, but sequence elements are:
564
+ The `hover:setup` function receives:
565
+ - `dependencies` - External dependencies set via `setDependencies()`
566
+ - `defaults` - Default plugin options as a starting point or just for reference
567
+
568
+ Example usage:
514
569
 
515
570
  ```js
516
- // example:
517
- // key: a+ctrl,l,o,t -> a with ctrl, then l, then o, then t
518
- // this is equal to:
519
- // key: ctrl+a,l,o,t
520
- // but not equal to:
521
- // key: ctrl+a,o,t,l
571
+ const shortcutDefinition = {
572
+ navigation: {
573
+ 'hover:on': ({ target }) => {
574
+ // Mouse entered the target
575
+ target.classList.add('active');
576
+ },
577
+ 'hover:off': ({ target }) => {
578
+ // Mouse left the target
579
+ target.classList.remove('active');
580
+ }
581
+ }
582
+ };
583
+
584
+ short.enablePlugin(pluginHover);
585
+ short.load(shortcutDefinition);
586
+ short.changeContext('navigation');
522
587
  ```
523
588
 
524
- Keyboard sequence is detected automatically by time interval between key presses. The default interval is 480ms but you can change it by setting `keyWait` key plugin option. Read more in section `Options`.
525
589
 
526
- ### Per-Context Setup (Preferred Method)
527
- Instead of global plugin options, you can use `key:setup` event to configure plugin settings per context. This is preferred method for customization.
590
+
591
+ ### Plugin 'scroll' Shortcut Descriptions
592
+
593
+ `Scroll` plugin is used to detect scroll events on the page. The plugin supports four main scroll directions:
594
+
595
+ ```js
596
+ scroll:up // Triggered when scrolling up
597
+ scroll:down // Triggered when scrolling down
598
+ scroll:left // Triggered when scrolling left
599
+ scroll:right // Triggered when scrolling right
600
+ scroll:end // Triggered when scrolling stops (after endScrollWait timeout)
601
+ ```
602
+
603
+ #### Scroll Detection Settings
604
+ Scroll events are detected with specific timing and distance thresholds to avoid excessive triggering. The default settings are:
605
+
606
+ - `scrollWait`: 50ms - Delay between scroll events
607
+ - `endScrollWait`: 400ms - Delay when scroll was stopped
608
+ - `minSpace`: 40px - Minimum distance between scroll events
609
+
610
+ These can be customized by setting scroll plugin options:
611
+
612
+ ```js
613
+ short.enablePlugin ( pluginScroll, {
614
+ scrollWait: 100, // set delay to 100ms
615
+ endScrollWait: 600, // set end scroll delay to 600ms
616
+ minSpace: 60 // set minimum distance to 60px
617
+ })
618
+ ```
619
+
620
+ #### Per-Context Setup (Preferred Method)
621
+ Instead of global plugin options, you can use `scroll:setup` event to configure plugin settings per context. This is preferred method for customization.
528
622
 
529
623
  ```js
530
624
  const shortcutDefinition = {
531
- fastTyping: {
532
- 'key:setup': ({ dependencies, defaults }) => {
533
- // Fast key detection for gaming or rapid input
625
+ sensitiveScrolling: {
626
+ 'scroll:setup': ({ dependencies, defaults }) => {
627
+ // High sensitivity for gaming or precise interactions
534
628
  return {
535
- keyWait: 200, // Very fast sequence detection
536
- streamKeys: (key) => console.log('Key pressed:', key) // Enable key streaming
629
+ scrollWait: 20, // Very responsive
630
+ endScrollWait: 200, // Quick end detection
631
+ minSpace: 20 // Small movements trigger
537
632
  };
538
633
  },
539
- 'key:a,b,c': () => console.log('Fast sequence triggered'),
540
- 'key:ctrl+s': () => console.log('Fast save')
634
+ 'scroll:up': () => console.log('Sensitive scroll up'),
635
+ 'scroll:down': () => console.log('Sensitive scroll down'),
636
+ 'scroll:end': () => console.log('Sensitive scroll ended')
541
637
  },
542
- slowTyping: {
543
- 'key:setup': ({ dependencies, defaults }) => {
544
- // Slower key detection for accessibility or careful input
638
+ lazyScrolling: {
639
+ 'scroll:setup': ({ dependencies, defaults }) => {
640
+ // Low sensitivity for reading or casual browsing
545
641
  return {
546
- keyWait: 800, // Slower sequence detection
547
- streamKeys: false // Disable key streaming
642
+ scrollWait: 150, // Less responsive
643
+ endScrollWait: 800, // Slow end detection
644
+ minSpace: 80 // Larger movements needed
548
645
  };
549
646
  },
550
- 'key:a,b,c': () => console.log('Slow sequence triggered'),
551
- 'key:ctrl+s': () => console.log('Careful save')
647
+ 'scroll:up': () => console.log('Lazy scroll up'),
648
+ 'scroll:down': () => console.log('Lazy scroll down'),
649
+ 'scroll:end': () => console.log('Lazy scroll ended')
552
650
  }
553
651
  };
554
652
 
555
- short.enablePlugin(pluginKey);
653
+ short.enablePlugin(pluginScroll);
556
654
  short.load(shortcutDefinition);
557
- short.changeContext('fastTyping'); // Uses fast settings
558
- // short.changeContext('slowTyping'); // Uses slow settings
655
+ short.changeContext('sensitiveScrolling'); // Uses sensitive settings
656
+ // short.changeContext('lazyScrolling'); // Uses lazy settings
559
657
  ```
560
658
 
561
- The `key:setup` function receives:
659
+ The `scroll:setup` function receives:
562
660
  - `dependencies` - External dependencies set via `setDependencies()`
563
661
  - `defaults` - Default plugin options as a starting point
564
662
 
565
- 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`.
663
+ Example usage:
566
664
 
567
- Special characters that are available for your shortcut descriptions:
568
- - 'left' - left arrow key
569
- - 'right' - right arrow key
570
- - 'up' - up arrow key
571
- - 'down' - down arrow key
572
- - 'enter' - enter key
573
- - 'space' - space key
574
- - 'esc' - escape key
575
- - 'tab' - tab key
576
- - 'backspace' - backspace key
577
- - '=' - equal key
578
- - F1 - F12 - function keys
579
- - '/' - slash key
580
- - '\\' - backslash key
581
- - '[' - open square bracket key
582
- - ']' - close square bracket key
583
- - '`' - backtick key
665
+ ```js
666
+ const shortcutDefinition = {
667
+ scrollView: {
668
+ 'scroll:up': () => {
669
+ console.log('User scrolled up');
670
+ },
671
+ 'scroll:down': () => {
672
+ console.log('User scrolled down');
673
+ },
674
+ 'scroll:end': () => {
675
+ console.log('User stopped scrolling');
676
+ }
677
+ }
678
+ };
584
679
 
585
- **Warning**: For keys with two symbols(look at the keyboard), in shortcut description use the lower one. Examples: Use '=' instead of '+', use '/' instead of '?', etc. Modifier keys are available for special characters too.
680
+ short.enablePlugin(pluginScroll);
681
+ short.load(shortcutDefinition);
682
+ short.changeContext('scrollView');
683
+ ```
586
684
 
587
- **Warining**: Some of the shortcuts are used by OS and the browswer, so they are not available.
685
+ 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. See [Contexts, Notes, and Dependencies](#contexts-notes-and-dependencies).
588
686
 
589
687
 
590
688
 
689
+ ### Plugin 'form' Shortcut Descriptions
591
690
 
691
+ `Form` plugin is used to watch for changes in inputs, textareas, select and textarea elements. All 3 possible shortcuts are predefined: `form: watch`, `form: define` and `form: action`.
592
692
 
593
- ## Plugin 'form' Shortcut Descriptions
594
- `Form` plugin is used to watch for changes in inputs, textareas, select and textarea elements. All 3 possible shortcuts are predefined: 'form: watch', 'form: define' and 'form: action'.
595
693
  ```js
596
- `form: watch` // (function). Should return list of elements to watch
597
- `form: define` // (function). Define every element you are watching as a type(own definition).
598
- `form: action` // (function). Function should return a list of objects with action function definitions.
694
+ form: watch // (function). Should return list of elements to watch
695
+ form: define // (function). Define every element you are watching as a type (own definition)
696
+ form: action // (function). Must return a list of step objects. Each step has a fn, type, timing and optional wait
599
697
  ```
600
- Action definitions have 4 possible properties:
698
+
699
+ #### Action definition step shape
700
+
701
+ `form:action` returns an array of steps. Each step has four possible properties:
702
+
601
703
  ```js
602
704
  {
603
- fn // (function) Action function
604
- , type // (string) Type of the element. Available types are according to `form: define` reponses
605
- , timing // (string) Possible values are: 'in', 'out', 'instant'
606
- , wait // (number) It's a event reducer timer in milliseconds. Worsk only for 'timing: instant'
705
+ fn // (function) Action function. Receives FormEventData.
706
+ , type // (string) Type of the element. Must match a value returned by `form: define`
707
+ , timing // (string) 'in' (focus in), 'out' (focus out), or 'instant' (value change)
708
+ , wait // (number) Event-reducer timer in ms. Only used when `timing: 'instant'`.
607
709
  }
608
710
  ```
609
711
 
610
- Definition Example:
712
+ - `timing: 'in'` fires once when focus enters the element.
713
+ - `timing: 'out'` fires once when focus leaves the element.
714
+ - `timing: 'instant'` fires on each value change but is throttled by `wait` ms so the action doesn't run on every keystroke.
715
+
716
+ #### Definition example
717
+
611
718
  ```js
612
719
  const shortcutScope = {
613
720
  ...
@@ -636,7 +743,7 @@ const shortcutScope = {
636
743
  fn: () => { console.log ( 'Update content') }
637
744
  , type : 'input'
638
745
  , timing : 'instant' // on content change. on each change
639
- , wait : 500 // Wait 500ms between changes.
746
+ , wait : 500 // Wait 500ms between changes.
640
747
  },
641
748
  {
642
749
  fn: () => { console.log('It was a button') }
@@ -646,9 +753,13 @@ const shortcutScope = {
646
753
  ] // form: action
647
754
  }
648
755
  ```
649
- `form:watch` can contains `.someClass` for selecting elements by class name or `#someId` for selecting elements by id. It's could be everything that works in querySelectorAll. The `form:define` gives you a way to separate different inputs and privide a custom callback for each of them or use single callback for all inputs.
650
756
 
651
- Plugin `form` has a default versions for `form:watch` and `form:define` functions. Only `form:action` is required but should use the default settings. Here are the defaults:
757
+ `form:watch` can contain `.someClass` for selecting elements by class name or `#someId` for selecting elements by id. It's could be everything that works in `querySelectorAll`. The `form:define` gives you a way to separate different inputs and privide a custom callback for each of them or use single callback for all inputs.
758
+
759
+ #### Default `form:watch` and `form:define`
760
+
761
+ The `form` plugin ships with defaults for `form:watch` and `form:define`. Only `form:action` is required.
762
+
652
763
  ```js
653
764
  const _defaults = {
654
765
  watch : () => 'input, select, textarea, button, a'
@@ -664,9 +775,18 @@ const _defaults = {
664
775
  } // defaults
665
776
  ```
666
777
 
667
- If you want to pause of resume event from `form` plugin, call `short.pause(eventName)` and `short.resume(eventName)` where eventName is a `${type}/${timing}`. Take type and timing from action definitions.
778
+ #### Pausing a specific form event
779
+
780
+ To pause or resume a single form event, call `short.pause(eventName)` / `short.resume(eventName)` where `eventName` is `${type}/${timing}`. Take `type` and `timing` from the action definitions.
781
+
782
+ ```js
783
+ // Disable 'instant' updates on inputs
784
+ short.pause('input/instant');
785
+ // Re-enable them
786
+ short.resume('input/instant');
787
+ ```
668
788
 
669
- ### Per-Context Setup (Coming Soon)
789
+ #### Per-Context Setup (Coming Soon)
670
790
  The `form:setup` event is planned for future versions to allow per-context configuration of form plugin settings. Currently, form plugin uses default settings or global plugin options.
671
791
 
672
792
  **Note**: In version 4.0.0, the `form:action` event now has access to `dependencies` at the top level, allowing you to minimize dependency declarations. Other named arguments are not available at the top level of `form:action`.
@@ -674,42 +794,89 @@ The `form:setup` event is planned for future versions to allow per-context confi
674
794
 
675
795
 
676
796
  ## Action Functions
797
+
677
798
  Action functions are called when a shortcut is triggered. There is a difference among plugin action functions. Arguments are slightly different.
678
799
 
800
+ > Every action function `data` object also contains `emit` — a reference to the library's internal event emitter. See [Custom Events & Workflows](#custom-events--workflows).
801
+
679
802
 
680
803
 
681
804
  ### Keyboard Action Functions
682
- Description of `key` plugin action functions is:
805
+
806
+ The `key` plugin's `data` object includes helper functions to control sequences manually (useful when the timer-based detection is undesirable). Disable auto-detection by using a multi-key sequence, then call `wait`/`end` to control when it is allowed to match.
807
+
683
808
  ```js
684
809
  function myKeyHandler ({
685
- context // (string) Name of the current context;
686
- , note // (string) Name of the note or null if note isn't set;
687
- , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
688
- , wait // (function). Call it to stop a sequence timer and write shortcut sequence without a timer.
689
- , end // (function). Recover the sequence timer;
690
- , ignore // (function). Call it to ignore the current shortcut from the sequence;
691
- , isWaiting // (boolean). True if the sequence timer is active;
692
- }) {
810
+ context // (string) Name of the current context;
811
+ , note // (string) Name of the note or null if note isn't set;
812
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
813
+ , wait // (function). Stop the sequence timer and write the shortcut sequence without a timer. Use inside a key handler when you want manual control of the sequence.
814
+ , end // (function). Recover the sequence timer (the opposite of `wait`).
815
+ , ignore // (function). Ignore the current shortcut from the sequence. Useful for keys that should not advance the sequence (e.g. modifier keys).
816
+ , isWaiting // (boolean). True if the sequence timer is currently active.
817
+ , emit // (function). Call it to trigger a shortcut or custom event;
818
+ }) {
693
819
  // Body of the handler. Do something...
694
820
  }
695
821
  ```
696
822
 
697
823
 
698
824
 
699
-
700
825
  ### Mouse Action Functions
826
+
701
827
  Click plugin action functions can be described like:
702
828
 
703
829
  ```js
704
830
  function myMouseHandler ({
705
- context // (string) Name of the current context;
706
- , note // (string) Name of the note or null if note isn't set;
707
- , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
708
- , target // (DOM element). Target element of the mouse event;
709
- , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
710
- , x // (number). X coordinate of the target element;
711
- , y // (number). Y coordinate of the target element;
712
- , event // (object). Original mouse event object;
831
+ context // (string) Name of the current context;
832
+ , note // (string) Name of the note or null if note isn't set;
833
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
834
+ , target // (DOM element). Target element of the mouse event;
835
+ , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
836
+ , x // (number). X coordinate of the target element;
837
+ , y // (number). Y coordinate of the target element;
838
+ , event // (object). Original mouse event object;
839
+ , emit // (function). Call it to trigger a shortcut or custom event;
840
+ }) {
841
+ // Body of the handler. Do something...
842
+ }
843
+ ```
844
+
845
+
846
+
847
+ ### Hover Action Functions
848
+
849
+ Hover plugin action functions receive the following arguments:
850
+
851
+ ```js
852
+ function myHoverHandler ({
853
+ context // (string) Name of the current context;
854
+ , note // (string) Name of the note or null if note isn't set;
855
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
856
+ , target // (DOM element). Target element of the hover event;
857
+ , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
858
+ , x // (number). X coordinate of the target element;
859
+ , y // (number). Y coordinate of the target element;
860
+ , event // (object). Original hover event object;
861
+ , emit // (function). Call it to trigger a shortcut or custom event;
862
+ }) {
863
+ // Body of the handler. Do something...
864
+ }
865
+ ```
866
+
867
+
868
+
869
+ ### Scroll Action Functions
870
+
871
+ Scroll plugin action functions receive the following arguments:
872
+
873
+ ```js
874
+ function myScrollHandler ({
875
+ context // (string) Name of the current context;
876
+ , note // (string) Name of the note or null if note isn't set;
877
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
878
+ , event // (object). Original scroll event object;
879
+ , emit // (function). Emit event to trigger a shortcut or custom event;
713
880
  }) {
714
881
  // Body of the handler. Do something...
715
882
  }
@@ -717,137 +884,474 @@ function myMouseHandler ({
717
884
 
718
885
 
719
886
 
887
+ ### Form Action Functions
888
+
889
+ The `fn` inside each `form:action` step receives the same shape as the other plugins, plus `target` and the type of change (`type`, `timing`):
890
+
891
+ ```js
892
+ function myFormHandler ({
893
+ context // (string) Name of the current context;
894
+ , note // (string) Name of the note or null if note isn't set;
895
+ , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
896
+ , target // (DOM element). Element that triggered the form event;
897
+ , type // (string). The type label returned by 'form: define';
898
+ , timing // (string). 'in' | 'out' | 'instant';
899
+ , wait // (number). The reducer interval in ms (only when timing: 'instant');
900
+ , emit // (function). Call it to trigger a shortcut or custom event;
901
+ }) {
902
+ // Body of the handler. Do something...
903
+ }
904
+ ```
905
+
906
+ > The `form:action` outer function itself receives `{ dependencies }` (and nothing else).
907
+
720
908
 
721
909
 
722
910
  ## Methods
723
911
 
724
- Description of the methods of shortcut instance:
912
+ Below is the full API of a `shortcuts()` instance. Every method is described with its signature, what it returns, and a usage example.
913
+
914
+
915
+
916
+ ### `load(shortcutsUpdate)`
917
+
918
+ Load a context with shortcuts. Can be called multiple times to add or replace contexts.
919
+
920
+ - **Returns:** `void`
921
+ - **Emits errors:** none
725
922
 
726
923
  ```js
727
- load : 'Load and extend a shortcut definition.'
728
- , unload : 'Remove a shortcut context with all its shortcuts.'
924
+ short.load({
925
+ editor: {
926
+ 'key:ctrl+s': () => save(),
927
+ 'click:left-1': ({ target }) => handleClick(target)
928
+ },
929
+ viewer: {
930
+ // ...
931
+ }
932
+ });
933
+ ```
934
+
935
+ > If you `load` a context that is **currently active**, the library will re-apply it so the new shortcuts take effect immediately. No need to call `changeContext` again.
936
+
729
937
 
730
- , enablePlugin : 'Enable a plugin.'
731
- , disablePlugin : 'Disable a plugin.'
732
- , mutePlugin : 'Mute a plugin. All events for the plugin will be ignored.'
733
- , unmutePlugin : 'Unmute a plugin. All events for the plugin will be listened again.'
734
938
 
735
- , changeContext : 'Switch to existing shortcut context or shitch off the context(if no argument).'
736
- , getContext : 'Return a name of current context or null if there is no context selected'
737
- , emit : 'Trigger a shortcut or custom event programmatically.'
738
- , pause : 'Stop listening for shortcuts.'
739
- , resume : 'Resume listening for shortcuts.'
740
- , listPlugins : "Return list of enabled plugins. Plugins are represented by plugin's prefixes."
741
- , listContexts : 'Return list of available contexts.'
742
- , listShortcuts : 'Return list of shortcuts per context.'
743
- , getNote : `Return a name of current note or null if note isn't set`
744
- , setNote : 'Set a note to current context.'
745
- , setDependencies : 'Set dependencies that will be available in action functions.'
746
- , getDependencies : 'Return a dependencies object.'
747
- , reset : 'Reset shortcut instance.'
939
+ ### `unload(contextName)`
940
+
941
+ Remove a context and all its shortcuts. Cannot remove the currently active context.
942
+
943
+ - **Returns:** `void`
944
+ - **Emits errors:**
945
+ - `'@shortcuts-error'` if `contextName` is the active context (call `changeContext` first).
946
+ - `'@shortcuts-error'` if `contextName` doesn't exist.
947
+
948
+ ```js
949
+ short.changeContext('editor');
950
+ short.unload('login'); // removes 'login' and its shortcuts
748
951
  ```
749
952
 
750
- ### How to 'pause' and 'resume'?
751
- 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')`.
953
+
954
+
955
+ ### `enablePlugin(plugin, options?)` and `disablePlugin(pluginName)`
956
+
957
+ Enable a plugin. Calling with a plugin that is already enabled will replace it (useful for hot-reloading options). `disablePlugin` permanently removes the plugin — you'll need to call `enablePlugin` again to use it.
958
+
959
+ - **Returns:** `void`
960
+ - **Emits errors:** none
752
961
 
753
962
  ```js
754
- // pause all shortcuts in the active context
755
- short.pause () // will stop all shortcuts in the active context
756
- short.resume ( 'shift+a' ) // will resume only 'shift+a' shortcut
963
+ short.enablePlugin(pluginKey); // enable with defaults
964
+ short.enablePlugin(pluginKey, { keyWait: 300 }); // enable with custom options
965
+ short.disablePlugin('key'); // permanently disable
966
+ ```
967
+
968
+ > Pass the **plugin function** to `enablePlugin`, but the plugin's **prefix string** to `disablePlugin` (the value returned by `listPlugins()`).
969
+
970
+
757
971
 
758
- short.resume ('*') // will resume all shortcuts
972
+ ### `mutePlugin(pluginName)` and `unmutePlugin(pluginName)`
973
+
974
+ Temporarily suspend / resume a plugin. Muted plugins keep their state but stop processing events. Use these for "soft" disable (e.g. when a modal opens) — they are reversible.
975
+
976
+ - **Returns:** `number` — index of the plugin in the internal array, or `-1` if not found.
977
+ - **Emits errors:** none
978
+
979
+ ```js
980
+ short.mutePlugin('key'); // ignore all keyboard shortcuts
981
+ short.unmutePlugin('key'); // resume keyboard shortcuts
759
982
  ```
760
983
 
984
+ > **`mutePlugin` vs `disablePlugin`** — `disablePlugin` destroys the plugin (you'll lose any per-context setup state). `mutePlugin` keeps the plugin registered and just stops it from processing events. Prefer `mutePlugin` for transient pauses.
761
985
 
762
986
 
763
- ## Options
764
987
 
765
- Shortcut receives `options` during the start. Here is the list of available options:
988
+ ### `changeContext(contextName?)`
989
+
990
+ Switch to a different context. Pass `null`, `undefined`, or no argument to **deactivate** the current context (no shortcuts fire).
991
+
992
+ - **Returns:** `void`
993
+ - **Emits errors:**
994
+ - `'@shortcuts-error'` — if `contextName` doesn't exist.
766
995
 
767
996
  ```js
768
- onShortcut : 'Callback function that is called when a shortcut is triggered. Default value - false'
997
+ short.changeContext('editor'); // switch to 'editor'
998
+ short.changeContext(); // deactivate all shortcuts
769
999
  ```
1000
+
1001
+
1002
+
1003
+ ### `getContext()`
1004
+
1005
+ Return the name of the currently active context, or `null` if none.
1006
+
1007
+ - **Returns:** `string | null`
1008
+
770
1009
  ```js
771
- const short = shortcut ({onShortcut: (shortcut) => console.log(shortcut) }) // Log in console each triggered shortcut
1010
+ if (short.getContext() === 'editor') {
1011
+ // editor-specific UI logic
1012
+ }
772
1013
  ```
773
1014
 
774
1015
 
775
1016
 
776
- ### Plugin 'key' options
1017
+ ### `getNote()` and `setNote(note?)`
1018
+
1019
+ `setNote` tags the current context with a sub-context string. `getNote` reads it back. Pass `null` / no argument to `setNote` to clear it. Non-string arguments are silently ignored.
1020
+
1021
+ - **Returns:** `getNote` returns `string | null`; `setNote` returns `void`.
1022
+ - **Emits errors:** none
1023
+
777
1024
  ```js
778
- keyWait : 'Timeout for entering shortcut sequence in ms. Default value - 480'
779
- , streamKeys : 'False or a callback function that is called when a key is pressed. Default value - false'
1025
+ short.setNote('special'); // tag the current context
1026
+ short.getNote(); // 'special'
1027
+ short.setNote(); // clear the note (passing nothing)
1028
+ short.setNote(null); // same — explicit null
1029
+
1030
+ // Inside an action function:
1031
+ 'key:ctrl+s': ({ note }) => {
1032
+ if (note === 'special') {
1033
+ // do the "special" variant of save
1034
+ }
1035
+ }
780
1036
  ```
781
1037
 
1038
+ The idea of `note` is to minimize the number of contexts if they are very similar — keep one context, switch its `note`, and branch inside the action by reading it.
782
1039
 
783
1040
 
784
- ### Plugin 'click' options
1041
+
1042
+ ### `setDependencies(deps)` and `getDependencies()`
1043
+
1044
+ `setDependencies` registers a bag of external values that will be merged into the `dependencies` object passed to every action function. `getDependencies` returns the live bag (mutating it affects future shortcut calls — `setDependencies` is `Object.assign`-based, not a snapshot).
1045
+
1046
+ - **Returns:** `getDependencies` returns `object`; `setDependencies` returns `void`.
1047
+ - **Emits errors:** none
1048
+
785
1049
  ```js
786
- mouseWait : 'Timeout for entering multiple mouse events. Default value - 320.'
787
- , clickTarget : 'Array of attribute names to recognize click items in HTML. Default value - ["data-click", "href"]' // checks for data-click='someName' or href attributes
1050
+ // Once at startup:
1051
+ short.setDependencies({
1052
+ api: myApi,
1053
+ router: myRouter,
1054
+ analytics: track
1055
+ });
1056
+
1057
+ // Inside any action function:
1058
+ 'key:ctrl+s': ({ dependencies }) => {
1059
+ dependencies.api.save();
1060
+ dependencies.analytics.track('save');
1061
+ }
788
1062
  ```
789
1063
 
790
- ### Plugin 'hover' options
1064
+ > `dependencies` is also available inside `form:action`, `key:setup`, `click:setup`, `hover:setup`, `scroll:setup`, `onShortcut`, and `streamKeys` — anywhere the library hands you a `data` object.
1065
+
1066
+
1067
+
1068
+ ### `listPlugins()`
1069
+
1070
+ Return an array of enabled plugin prefixes.
1071
+
1072
+ - **Returns:** `string[]` (e.g. `['key', 'click']`)
1073
+
791
1074
  ```js
792
- wait : 'Time to wait for hover sequence in ms. Default value - 320.'
793
- , hoverTarget : 'Array of attribute names to recognize hover items in HTML. Default value - ["data-hover"]' // checks for data-hover='someName' attribute
1075
+ short.listPlugins(); // ['key', 'click', 'hover']
794
1076
  ```
795
1077
 
796
- ### Plugin 'scroll' options
1078
+
1079
+
1080
+ ### `listContexts()`
1081
+
1082
+ Return an array of all loaded context names (including the inactive ones).
1083
+
1084
+ - **Returns:** `string[]`
1085
+
797
1086
  ```js
798
- scrollWait : 'Delay between scroll events in ms. Default value - 50.'
799
- , endScrollWait : 'Delay when scroll was stopped in ms. Default value - 400.'
800
- , minSpace : 'Minimum distance between scroll events in px. Default value - 40.'
1087
+ short.listContexts(); // ['login', 'editor', 'settings']
801
1088
  ```
802
1089
 
803
- Plugin options are provided as a second argument during the plugin enabling. It's look like this:
804
-
1090
+
1091
+
1092
+ ### `listShortcuts(contextName?)`
1093
+
1094
+ Return a list of shortcuts. Two return shapes:
1095
+
1096
+ - With no argument — an array of `{ context, shortcuts }` for every context.
1097
+ - With a context name — an array of shortcut names for that context, or `null` if the context doesn't exist.
1098
+
1099
+ - **Returns:** `string[] | { context: string, shortcuts: string[] }[] | null`
1100
+
805
1101
  ```js
806
- short.enablePlugin ( pluginKey, {
807
- keyWait: 500 // set the interval to 500ms
808
- , streamKeys: (key) => console.log(key) // Log in console each pressed key
809
- })
1102
+ short.listShortcuts();
1103
+ // [
1104
+ // { context: 'editor', shortcuts: ['KEY:CTRL+S', 'CLICK:LEFT-1', ...] },
1105
+ // { context: 'viewer', shortcuts: ['KEY:ESC'] }
1106
+ // ]
810
1107
 
811
- short.enablePlugin ( pluginClick, {
812
- mouseWait: 200 // set the interval between multiple clicks to 200ms
813
- , clickTarget: ['data-puk', 'data-button'] // array of attribute names to check
814
- })
1108
+ short.listShortcuts('editor');
1109
+ // ['KEY:CTRL+S', 'CLICK:LEFT-1']
815
1110
 
816
- short.enablePlugin ( pluginHover, {
817
- wait: 500 // set the hover delay to 500ms
818
- , hoverTarget: ['data-hover-me', 'data-interactive'] // array of attribute names to check
819
- })
1111
+ short.listShortcuts('does-not-exist'); // null
1112
+ ```
820
1113
 
821
- short.enablePlugin ( pluginScroll, {
822
- scrollWait: 100 // set the delay between scroll events to 100ms
823
- , endScrollWait: 600 // set the end scroll delay to 600ms
824
- , minSpace: 60 // set minimum distance to 60px
825
- })
826
1114
 
1115
+
1116
+ ### `pause(name?)` and `resume(name?)`
1117
+
1118
+ Stop / resume listening for shortcuts. Both default to `'*'` (all shortcuts in the active context). The `name` must be a **fully-prefixed** shortcut name (e.g. `'KEY:CTRL+S'`, not `'ctrl+s'`).
1119
+
1120
+ - **Returns:** `void`
1121
+ - **Emits errors:** none
1122
+ - **Side effect:** an in-flight key sequence is dropped on `pause`, not queued for resume.
1123
+
1124
+ ```js
1125
+ short.pause(); // pause all shortcuts in the active context
1126
+ short.pause('KEY:CTRL+S'); // pause just one shortcut (use the full prefix!)
1127
+ short.resume('KEY:CTRL+S'); // resume just that one
1128
+ short.resume('*'); // resume everything
1129
+ ```
1130
+
1131
+
1132
+
1133
+ ### `emit(name, ...args)`
1134
+
1135
+ Programmatically trigger a shortcut or a custom event. The first argument is the **fully-prefixed** shortcut name (or any custom event string you define — see [Custom Events & Workflows](#custom-events--workflows)). Extra arguments are forwarded to the action function.
1136
+
1137
+ - **Returns:** `void`
1138
+ - **Emits errors:** none
1139
+
1140
+ ```js
1141
+ short.emit('KEY:CTRL+S'); // fire the save shortcut
1142
+ short.emit('CLICK:LEFT-1', myTarget); // forward a synthetic click
1143
+ short.emit('@extend', { source: 'api' }); // fire a custom event
1144
+ ```
1145
+
1146
+ See the [Custom Events & Workflows](#custom-events--workflows) section for full examples of `emit` from inside a handler (`data.dependencies.emit(...)`) and from outside (`short.emit(...)`).
1147
+
1148
+
1149
+
1150
+ ### `reset()`
1151
+
1152
+ Reset the library instance to its initial state. Destroys every enabled plugin, unloads every context, deactivates the current context, and clears the `dependencies` bag. After `reset()`, you can re-`enablePlugin` and re-`load` from scratch.
1153
+
1154
+ - **Returns:** `void`
1155
+ - **Emits errors:** none
1156
+ - **Side effects:** `onShortcut` callback is cleared. `errorEventName` is preserved.
1157
+
1158
+ ```js
1159
+ short.reset();
1160
+ // short is now equivalent to a freshly created `shortcuts()` instance
1161
+ ```
1162
+
1163
+
1164
+
1165
+ ## Contexts, Notes, and Dependencies
1166
+
1167
+ The three orthogonal axes you control from outside the action functions:
1168
+
1169
+ - **Context** — which set of shortcuts is active. Use `changeContext` to switch.
1170
+ - **Note** — a sub-tag for the current context. Use `setNote` to set, `getNote` to read.
1171
+ - **Dependencies** — external services passed into actions. Use `setDependencies` to register, available as `dependencies` in every action.
1172
+
1173
+ ```js
1174
+ short.setDependencies({ api, router });
1175
+ short.changeContext('editor');
1176
+ short.setNote('draft');
1177
+
1178
+ 'key:ctrl+s': ({ context, note, dependencies }) => {
1179
+ // context === 'editor'
1180
+ // note === 'draft'
1181
+ // dependencies === { api, router }
1182
+ dependencies.api.save({ context, note });
1183
+ }
1184
+ ```
1185
+
1186
+ Read each value from outside the actions with `getContext()`, `getNote()`, `getDependencies()`. Set them with `changeContext()`, `setNote()`, `setDependencies()`.
1187
+
1188
+
1189
+
1190
+ ## Errors and the `errorEventName` Channel
1191
+
1192
+ The library publishes error messages on a single named event instead of throwing. By default that event is named `'@shortcuts-error'`. You can rename it with the `errorEventName` constructor option (see [Constructor Options](#constructor-options)).
1193
+
1194
+ Errors currently emitted:
1195
+ - `changeContext(name)` — `name` does not exist.
1196
+ - `unload(name)` — `name` is the currently active context.
1197
+ - `unload(name)` — `name` does not exist.
1198
+
1199
+ The message is delivered as the **first argument** to the listener. Because the library uses the [`@peter.naydenov/notice`](https://github.com/PeterNaydenov/notice) event emitter internally, the simplest way to subscribe is to install a listener via the public `emit` channel — but the cleanest way is to handle errors inside your own action handlers (e.g. wrap risky work in `try/catch`).
1200
+
1201
+ ```js
1202
+ const short = shortcuts({ errorEventName: '@my-app-errors' });
1203
+
1204
+ // Optional: forward errors anywhere you like. The library exposes
1205
+ // the event emitter indirectly through the action context:
1206
+ // If you need a programmatic hook, expose it via setDependencies:
1207
+ // short.setDependencies({
1208
+ // onError: (msg) => myLogger.error(msg)
1209
+ // });
1210
+ //
1211
+ // Then in any handler that can fail:
1212
+ // 'key:ctrl+s': ({ dependencies }) => {
1213
+ // try { save() }
1214
+ // catch (e) { dependencies.onError(`Save failed: ${e.message}`) }
1215
+ // }
1216
+ ```
1217
+
1218
+ > If you don't need to react to errors at runtime, you can simply ignore this channel — errors are advisory, not fatal.
1219
+
1220
+
1221
+
1222
+ ## Custom Events & Workflows
1223
+
1224
+ Every plugin callback receives a `data` object that includes a `dependencies` bag with an `emit` function — the same event emitter used internally by the library. This lets a handler trigger any other event in the system, so you can build pipelines where one signal chains into another.
1225
+
1226
+ ### The `data.dependencies.emit` property
1227
+
1228
+ The `data` argument passed to your callback contains:
1229
+
1230
+ ```js
1231
+ {
1232
+ // ... event-specific fields (target, key, context, type, etc.)
1233
+ dependencies: {
1234
+ // `emit` is always present — it's the library's event emitter.
1235
+ emit: ev.emit, // function(eventName, ...args)
1236
+ // ... any other keys you set via short.setDependencies({...})
1237
+ }
1238
+ }
1239
+ ```
1240
+
1241
+ `data.dependencies.emit` is a direct reference to the underlying event emitter. Calling it triggers the named event just like `shortcuts.emit(...)` does, but from inside a handler. Any extra values you pass to `short.setDependencies({...})` are merged into this same bag, so a single `data.dependencies` field carries both your application data and the `emit` function.
1242
+
1243
+ ### Example: chaining a key shortcut into a click
1244
+
1245
+ ```js
1246
+ import { shortcuts, pluginKey, pluginClick } from '@peter.naydenov/shortcuts';
1247
+
1248
+ const short = shortcuts();
1249
+ short.enablePlugin(pluginKey);
1250
+ short.enablePlugin(pluginClick);
1251
+
1252
+ short.load({
1253
+ editor: {
1254
+ 'key: ctrl+s': (data) => {
1255
+ console.log('Save requested');
1256
+
1257
+ // Trigger a click event on something — for example, a "Save" button
1258
+ data.dependencies.emit('CLICK:LEFT-1', { target: document.getElementById('save-btn') });
1259
+ },
1260
+ 'click: left-1': ({ target }) => {
1261
+ if (target?.id === 'save-btn') {
1262
+ console.log('Save button clicked by Ctrl+S');
1263
+ doSave();
1264
+ }
1265
+ }
1266
+ }
1267
+ });
1268
+
1269
+ short.changeContext('editor');
827
1270
  ```
828
1271
 
1272
+ ### Example: workflow with multiple plugins
829
1273
 
1274
+ You can mix signals from different plugins in a single workflow:
830
1275
 
831
- ### onShortcut option
832
1276
  ```js
833
- function onShortcut ({ shortcut, context, note, dependencies }) {
834
- // shortcut - (string) Triggered shortcut name
835
- // context - (string) Name of the current context
836
- // note - (string) Name of the note or null if note isn't set
837
- // dependencies - (object) Object with dependencies that you have set by calling `setDependencies` method
1277
+ short.load({
1278
+ main: {
1279
+ 'hover : on': (data) => {
1280
+ // show tooltip
1281
+ showTooltip(data.target);
1282
+ },
1283
+ 'key: escape': (data) => {
1284
+ // hide any open tooltip by emitting the hover-off signal
1285
+ data.dependencies.emit('HOVER:OFF');
1286
+ }
838
1287
  }
1288
+ });
839
1289
  ```
840
1290
 
1291
+ ### Notes
1292
+
1293
+ - The `emit` function lives on `data.dependencies` for every plugin (`key`, `click`, `hover`, `scroll`, `form`);
1294
+ - The event name passed to `emit` can be either a fully normalized plugin shortcut (e.g. `KEY:CTRL+S`, `CLICK:LEFT-1`, `SCROLL:DOWN`, `HOVER:ON`, `HOVER:OFF`) or a custom name you define. For form actions, use the `type/timing` key (e.g. `input/in`). See the [Custom Event Names](#custom-event-names-application-level-hooks) section below.
1295
+ - This is a *signal source*: any handler in the active context listening for the emitted event will run, including handlers attached to a different plugin. This makes it the foundation for pipelines and multi-step workflows.
1296
+
841
1297
 
842
1298
 
843
- ### streamKeys option
1299
+ ### Custom Event Names (Application-Level Hooks)
1300
+
1301
+ The event name you pass to `data.dependencies.emit` does not have to be a plugin shortcut. It can be **any string you invent** — this lets you turn a physical gesture (click, hover, scroll, key) into a custom application signal. Other code, plugins, or modules can listen for that name and react.
1302
+
1303
+ Use a prefix that won't collide with plugin shortcuts (e.g. `@`, `app:`, `domain:`):
1304
+
844
1305
  ```js
845
- function streamKeys ({ key, context, note, dependencies }) {
846
- // key - (string) Pressed key name
847
- // context - (string) Name of the current context
848
- // note - (string) Name of the note or null if note isn't set
849
- // dependencies - (object) Object with dependencies that you have set by calling `setDependencies` method
1306
+ import { shortcuts, pluginClick, pluginHover } from '@peter.naydenov/shortcuts';
1307
+
1308
+ const short = shortcuts();
1309
+ short.enablePlugin(pluginClick);
1310
+ short.enablePlugin(pluginHover);
1311
+
1312
+ short.load({
1313
+ editor: {
1314
+ // A click anywhere in the editor area triggers the custom "@extend" event.
1315
+ // No keyboard shortcut is involved — only the click.
1316
+ 'click: left-1': (data) => {
1317
+ // Forward the click as a custom application event.
1318
+ data.dependencies.emit('@extend', {
1319
+ target : data.target,
1320
+ at : { x: data.x, y: data.y },
1321
+ when : Date.now()
1322
+ });
1323
+ },
1324
+
1325
+ // A custom handler listens for "@extend" — this is NOT a plugin shortcut.
1326
+ // It is a hook that other parts of your app can subscribe to.
1327
+ '@extend': ({ target, at, when }) => {
1328
+ console.log('extend requested at', at, 'on', target);
1329
+ doExtend({ target, at, when });
1330
+ },
1331
+
1332
+ // Hover signals also work — you can wire the same hook to multiple gestures:
1333
+ 'hover : on': (data) => {
1334
+ data.dependencies.emit('@extend', { target: data.target, source: 'hover' });
1335
+ }
850
1336
  }
1337
+ });
1338
+
1339
+ short.changeContext('editor');
1340
+ ```
1341
+
1342
+ **Why this is useful:**
1343
+
1344
+ - **Decoupling** — the gesture layer (click/hover/key) is separate from the application logic (`@extend`). You can change which gesture triggers it without touching the handler.
1345
+ - **Multiple gestures, one hook** — bind `@extend` to clicks, hovers, key combos, or even `shortcuts.emit('@extend')` from anywhere in your code.
1346
+ - **No collision with plugin events** — using a non-plugin prefix (`@`, `app:`, etc.) keeps your custom names from ever being emitted by the library itself.
1347
+
1348
+ You can also trigger a custom event from **outside** any handler using the public API:
1349
+
1350
+ ```js
1351
+ // Trigger a custom event programmatically — no plugin involved
1352
+ shortcuts.emit('@extend', { source: 'api' });
1353
+ // or, if you have a short instance:
1354
+ short.emit('@extend', { source: 'api' });
851
1355
  ```
852
1356
 
853
1357
 
@@ -882,19 +1386,24 @@ short.changeContext('myContext');
882
1386
 
883
1387
  The `ShortcutsAPI` interface provides full type safety for all methods and their parameters.
884
1388
 
1389
+
1390
+
885
1391
  ## Links
886
1392
 
887
1393
  - [API reference](https://github.com/PeterNaydenov/shortcuts/blob/main/API.md)
888
- - [How to make a plugin](https://github.com/PeterNaydenov/shortcuts/blob/main/How.to.make.plugins.md)
889
- - [ Build a SPA apps with shortcuts (@peter.naydenov/cuts)](https://github.com/PeterNaydenov/cuts )
1394
+ - [How to make a plugin](https://github.com/PeterNaydenov/shortcuts/blob/main/How-to-create-a-plugin.md)
1395
+ - [Build a SPA apps with shortcuts (@peter.naydenov/cuts)](https://github.com/PeterNaydenov/cuts )
890
1396
  - [History of changes - changelog](https://github.com/PeterNaydenov/shortcuts/blob/main/Changelog.md)
891
1397
  - [Migration guide](https://github.com/PeterNaydenov/shortcuts/blob/main/Migration.guide.md)
892
1398
 
893
1399
 
1400
+
894
1401
  ## Credits
1402
+
895
1403
  '@peter.naydenov/shortcuts' was created and supported by Peter Naydenov.
896
1404
 
897
1405
 
898
1406
 
899
1407
  ## License
900
- '@peter.naydenov/shortcuts' is released under the MIT License.
1408
+
1409
+ '@peter.naydenov/shortcuts' is released under the MIT License.