@peter.naydenov/shortcuts 3.5.2 → 4.0.1

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 (135) hide show
  1. package/API.md +939 -0
  2. package/CODE_OF_CONDUCT.md +84 -0
  3. package/CONTRIBUTING.md +476 -0
  4. package/Changelog.md +32 -2
  5. package/How.to.create.plugins.md +929 -0
  6. package/Migration.guide.md +48 -0
  7. package/README.md +396 -24
  8. package/dist/main.d.ts +54 -2
  9. package/dist/methods/_normalizeWithPlugins.d.ts +63 -1
  10. package/dist/methods/_readShortcutWithPlugins.d.ts +8 -1
  11. package/dist/methods/_setupPlugin.d.ts +9 -0
  12. package/dist/methods/_systemAction.d.ts +8 -1
  13. package/dist/methods/changeContext.d.ts +8 -1
  14. package/dist/methods/index.d.ts +2 -0
  15. package/dist/methods/listShortcuts.d.ts +1 -16
  16. package/dist/methods/load.d.ts +8 -1
  17. package/dist/methods/unload.d.ts +8 -1
  18. package/dist/plugins/click/_findTarget.d.ts +9 -1
  19. package/dist/plugins/click/_listenDOM.d.ts +76 -3
  20. package/dist/plugins/click/_normalizeShortcutName.d.ts +7 -1
  21. package/dist/plugins/click/_registerShortcutEvents.d.ts +26 -0
  22. package/dist/plugins/click/index.d.ts +6 -31
  23. package/dist/plugins/form/_defaults.d.ts +13 -1
  24. package/dist/plugins/form/_listenDOM.d.ts +66 -3
  25. package/dist/plugins/form/_registerShortcutEvents.d.ts +95 -1
  26. package/dist/plugins/form/index.d.ts +2 -29
  27. package/dist/plugins/hover/_findTarget.d.ts +10 -0
  28. package/dist/plugins/hover/_listenDOM.d.ts +68 -0
  29. package/dist/plugins/hover/_normalizeShortcutName.d.ts +2 -0
  30. package/dist/plugins/hover/_registerShortcutEvents.d.ts +28 -0
  31. package/dist/plugins/hover/index.d.ts +14 -0
  32. package/dist/plugins/key/_listenDOM.d.ts +61 -3
  33. package/dist/plugins/key/_registerShortcutEvents.d.ts +26 -0
  34. package/dist/plugins/key/_specialChars.d.ts +6 -31
  35. package/dist/plugins/key/index.d.ts +2 -29
  36. package/dist/plugins/scroll/_listenDOM.d.ts +58 -0
  37. package/dist/plugins/scroll/_normalizeShortcutName.d.ts +2 -0
  38. package/dist/plugins/scroll/_registerShortcutEvents.d.ts +28 -0
  39. package/dist/plugins/scroll/index.d.ts +16 -0
  40. package/dist/shortcuts.cjs +1 -1
  41. package/dist/shortcuts.esm.mjs +1 -1
  42. package/dist/shortcuts.umd.js +1 -1
  43. package/dist/src/main.d.ts +172 -0
  44. package/dist/src/methods/_normalizeWithPlugins.d.ts +64 -0
  45. package/dist/src/methods/_readShortcutWithPlugins.d.ts +9 -0
  46. package/dist/src/methods/_setupPlugin.d.ts +9 -0
  47. package/dist/src/methods/_systemAction.d.ts +9 -0
  48. package/dist/src/methods/changeContext.d.ts +9 -0
  49. package/dist/src/methods/index.d.ts +19 -0
  50. package/dist/src/methods/listShortcuts.d.ts +2 -0
  51. package/dist/src/methods/load.d.ts +9 -0
  52. package/dist/src/methods/unload.d.ts +9 -0
  53. package/dist/src/plugins/click/_findTarget.d.ts +10 -0
  54. package/dist/src/plugins/click/_listenDOM.d.ts +78 -0
  55. package/dist/src/plugins/click/_normalizeShortcutName.d.ts +8 -0
  56. package/dist/src/plugins/click/_readClickEvent.d.ts +2 -0
  57. package/dist/src/plugins/click/_registerShortcutEvents.d.ts +28 -0
  58. package/dist/src/plugins/click/index.d.ts +16 -0
  59. package/dist/src/plugins/form/_defaults.d.ts +17 -0
  60. package/dist/src/plugins/form/_listenDOM.d.ts +68 -0
  61. package/dist/src/plugins/form/_normalizeShortcutName.d.ts +2 -0
  62. package/dist/src/plugins/form/_registerShortcutEvents.d.ts +96 -0
  63. package/dist/src/plugins/form/index.d.ts +9 -0
  64. package/dist/src/plugins/hover/_findTarget.d.ts +10 -0
  65. package/dist/src/plugins/hover/_listenDOM.d.ts +68 -0
  66. package/dist/src/plugins/hover/_normalizeShortcutName.d.ts +2 -0
  67. package/dist/src/plugins/hover/_registerShortcutEvents.d.ts +28 -0
  68. package/dist/src/plugins/hover/index.d.ts +14 -0
  69. package/dist/src/plugins/key/_listenDOM.d.ts +63 -0
  70. package/dist/src/plugins/key/_normalizeShortcutName.d.ts +2 -0
  71. package/dist/src/plugins/key/_readKeyEvent.d.ts +2 -0
  72. package/dist/src/plugins/key/_registerShortcutEvents.d.ts +28 -0
  73. package/dist/src/plugins/key/_specialChars.d.ts +7 -0
  74. package/dist/src/plugins/key/index.d.ts +14 -0
  75. package/dist/src/plugins/scroll/_listenDOM.d.ts +58 -0
  76. package/dist/src/plugins/scroll/_normalizeShortcutName.d.ts +2 -0
  77. package/dist/src/plugins/scroll/_registerShortcutEvents.d.ts +28 -0
  78. package/dist/src/plugins/scroll/index.d.ts +16 -0
  79. package/eslint.config.js +80 -0
  80. package/html/assets/index-COTh6lXR.css +1 -0
  81. package/html/assets/index-DOkKC3NI.js +53 -0
  82. package/html/bg.png +0 -0
  83. package/html/favicon.ico +0 -0
  84. package/html/favicon.svg +5 -0
  85. package/html/html.meta.json.gz +0 -0
  86. package/html/index.html +32 -0
  87. package/package.json +24 -19
  88. package/shortcuts.png +0 -0
  89. package/src/main.js +52 -22
  90. package/src/methods/_normalizeWithPlugins.js +26 -2
  91. package/src/methods/_readShortcutWithPlugins.js +9 -2
  92. package/src/methods/_setupPlugin.js +93 -0
  93. package/src/methods/_systemAction.js +12 -4
  94. package/src/methods/changeContext.js +11 -3
  95. package/src/methods/index.js +2 -0
  96. package/src/methods/listShortcuts.js +5 -12
  97. package/src/methods/load.js +11 -4
  98. package/src/methods/unload.js +8 -1
  99. package/src/plugins/click/_findTarget.js +11 -5
  100. package/src/plugins/click/_listenDOM.js +58 -20
  101. package/src/plugins/click/_normalizeShortcutName.js +11 -4
  102. package/src/plugins/click/_readClickEvent.js +1 -1
  103. package/src/plugins/click/_registerShortcutEvents.js +33 -5
  104. package/src/plugins/click/index.js +33 -60
  105. package/src/plugins/form/_defaults.js +13 -3
  106. package/src/plugins/form/_listenDOM.js +46 -9
  107. package/src/plugins/form/_normalizeShortcutName.js +2 -2
  108. package/src/plugins/form/_registerShortcutEvents.js +93 -17
  109. package/src/plugins/form/index.js +25 -56
  110. package/src/plugins/hover/_findTarget.js +26 -0
  111. package/src/plugins/hover/_listenDOM.js +154 -0
  112. package/src/plugins/hover/_normalizeShortcutName.js +21 -0
  113. package/src/plugins/hover/_registerShortcutEvents.js +51 -0
  114. package/src/plugins/hover/index.js +71 -0
  115. package/src/plugins/key/_listenDOM.js +67 -33
  116. package/src/plugins/key/_normalizeShortcutName.js +4 -3
  117. package/src/plugins/key/_readKeyEvent.js +1 -1
  118. package/src/plugins/key/_registerShortcutEvents.js +34 -5
  119. package/src/plugins/key/_specialChars.js +5 -0
  120. package/src/plugins/key/index.js +34 -59
  121. package/src/plugins/scroll/_listenDOM.js +141 -0
  122. package/src/plugins/scroll/_normalizeShortcutName.js +21 -0
  123. package/src/plugins/scroll/_registerShortcutEvents.js +50 -0
  124. package/src/plugins/scroll/index.js +61 -0
  125. package/test/01-general.test.js +92 -23
  126. package/test/02-key.test.js +241 -40
  127. package/test/03-click.test.js +291 -47
  128. package/test/04-form.test.js +241 -47
  129. package/test/05-hover.test.js +463 -0
  130. package/test/06-scroll.test.js +374 -0
  131. package/test-helpers/Block.jsx +3 -2
  132. package/test-helpers/style.css +6 -1
  133. package/tsconfig.json +2 -1
  134. package/vitest.config.js +13 -11
  135. package/How..to.make.plugins.md +0 -41
@@ -1,5 +1,5 @@
1
1
  import { beforeEach, afterEach, describe, it, test, expect } from 'vitest'
2
- import { userEvent } from '@vitest/browser/context'
2
+ import { userEvent } from 'vitest/browser'
3
3
  import {
4
4
  getByLabelText,
5
5
  getByText,
@@ -7,7 +7,8 @@ import {
7
7
  queryByTestId,
8
8
  // Tip: all queries are also exposed on an object
9
9
  // called "queries" which you could import here as well
10
- waitFor
10
+ waitFor,
11
+ fireEvent
11
12
  } from '@testing-library/dom'
12
13
 
13
14
 
@@ -55,7 +56,7 @@ const contextDefinition = {
55
56
  , 'form : define' : () => 'input'
56
57
  , 'form : action' : () => [
57
58
  {
58
- fn : (e) => console.log ( e.target )
59
+ fn : (e) => e.target
59
60
  , type : 'input'
60
61
  , timing : 'in'
61
62
  }
@@ -63,71 +64,264 @@ const contextDefinition = {
63
64
  }
64
65
  }
65
66
 
66
- let short = shortcuts ();
67
+ const short = shortcuts ();
67
68
 
68
69
 
69
70
 
70
- describe.only ( 'Form plugin', () => {
71
+ describe ( 'Form plugin', () => {
71
72
 
72
73
  beforeEach ( async () => {
73
- short.load ( contextDefinition )
74
- let container = document.createElement ( 'div' )
75
- container.id = 'app'
76
- document.body.appendChild ( container )
77
- await html.publish ( Block, {}, 'app' )
78
- a = false, b = false
79
- }) // beforeEach
74
+ short.load ( contextDefinition )
75
+ const container = document.createElement ( 'div' )
76
+ container.id = 'app'
77
+ document.body.appendChild ( container )
78
+ await html.publish ( Block, {}, 'app' )
79
+ a = false, b = false
80
+ }) // beforeEach
80
81
 
81
82
 
82
83
 
83
84
  afterEach ( async () => {
84
- short.reset ()
85
- a = false, b = false, c = null;
86
- }) // afterEach
85
+ html.destroy ()
86
+ short.reset ()
87
+ short.disablePlugin ( 'form' )
88
+ short.disablePlugin ( 'click' )
89
+ a = false, b = false, c = null;
90
+ document.body.querySelector ( '#app' ).remove ()
91
+ }) // afterEach
87
92
 
88
93
 
94
+
89
95
  it ( 'Shortcut when plugin is not installed', async () => {
90
- const ls = short.listShortcuts ( 'extend' )
91
- expect ( ls ).to.includes ( 'form : action' )
92
- }) // it Shortcuts when plugin is not installed
96
+ const ls = short.listShortcuts ( 'extend' )
97
+ expect ( ls ).to.includes ( 'form : action' )
98
+ }) // it Shortcuts when plugin is not installed
93
99
 
94
-
100
+
101
+
95
102
  it ( 'Shortcuts when plugin is enabled', async () => {
96
- short.enablePlugin ( pluginForm )
97
- const ls = short.listShortcuts ( 'extend' )
98
- expect ( ls ).to.includes ( 'FORM:WATCH' )
99
- // Shortcut names are normalized by the plugins!
100
- expect ( ls ).to.not.includes ( 'form : watch' )
101
- }) // it Shortcuts when plugin is enabled
103
+ short.enablePlugin ( pluginForm )
104
+ const ls = short.listShortcuts ( 'extend' )
105
+ expect ( ls ).to.includes ( 'FORM:WATCH' )
106
+ // Shortcut names are normalized by the plugins!
107
+ expect ( ls ).to.not.includes ( 'form : watch' )
108
+ }) // it Shortcuts when plugin is enabled
102
109
 
103
110
 
104
111
 
105
112
  it ( 'Simpler form listener. Only "form:action" defined', async () => {
106
- // Uses predefined 'watch' and 'define' functions
107
- short.enablePlugin ( pluginForm )
108
- let edit = 'none';
109
-
110
- const contextExtension = {
111
- 'local' : {
112
- 'form: action' : () => [{
113
- fn : () => edit = 'changed'
114
- , type : 'input'
115
- , timing : 'instant'
113
+ // Uses predefined 'watch' and 'define' functions
114
+ short.enablePlugin ( pluginForm )
115
+ let edit = 'none';
116
+
117
+ const contextExtension = {
118
+ 'local' : {
119
+ 'form: action' : () => [{
120
+ fn : () => edit = 'changed'
121
+ , type : 'input'
122
+ , timing : 'instant'
116
123
  }]
117
124
  }
118
- };
119
- short.load ( contextExtension )
120
- short.changeContext ( 'local' )
121
- let input = document.getElementById ( 'name' )
122
- input.focus ()
123
- await userEvent.keyboard ( 'hello' )
124
- await wait ( 50 )
125
- await waitFor ( () => {
126
- expect ( edit ).to.equal ( 'changed' )
127
- }, { timeout: 1000, interval: 12 })
128
- }) // it Simpler form listener
125
+ };
126
+ short.load ( contextExtension )
127
+ short.changeContext ( 'local' )
128
+ const input = document.getElementById ( 'name' )
129
+ input.focus ()
130
+ await userEvent.keyboard ( 'hello' )
131
+ await wait ( 50 )
132
+ await waitFor ( () => {
133
+ expect ( edit ).to.equal ( 'changed' )
134
+ }, { timeout: 1000, interval: 12 })
135
+ }) // it Simpler form listener
136
+
137
+
138
+
139
+ it ( 'Arguments for action function', async () => {
140
+ short.enablePlugin ( pluginForm )
141
+ const storage = [];
142
+ short.setDependencies ({ storage })
143
+ const contextExtension = {
144
+ local: {
145
+ // Access to 'dependencies' on 'form:action' is available to optimize dev experience
146
+ // if many functions need access to it. Write ones, use everywhere.
147
+ 'form:action' : ({ dependencies }) => [{
148
+ fn : ({ target }) => {
149
+ // dependencies are available here as named argument as well
150
+ // But if I have 20 actions, I need to add 20 times 'dependencies'
151
+ const { storage } = dependencies;
152
+ storage.push ({ target })
153
+ }
154
+ , type : 'input'
155
+ , timing : 'instant'
156
+ }]
157
+ }
158
+ } // contextExtension
159
+ short.load ( contextExtension )
160
+ short.changeContext ( 'local' )
161
+ const input = document.getElementById ( 'name' )
162
+ input.focus ()
163
+ await userEvent.keyboard ( 'hello' )
164
+ await wait ( 50 )
165
+ await waitFor ( () => {
166
+ expect ( storage.length ).to.be.greaterThan ( 0 )
167
+ const last = storage[storage.length - 1]
168
+ expect ( last.target ).to.be.an.instanceof ( HTMLElement )
169
+ expect ( last.target.value ).toBe ( 'hello' )
170
+ })
171
+ }) // it arguments for action function
172
+
173
+
174
+
175
+ it ( 'Arguments for "form:watch"', async () => {
176
+ short.enablePlugin ( pluginForm )
177
+ const storage = [];
178
+ short.setDependencies ({ storage })
179
+ const contextExtension = {
180
+ local: {
181
+ 'form:watch' : ({ dependencies }) => {
182
+ const { storage } = dependencies;
183
+ storage.push ( 'watch' )
184
+ return 'input'
185
+ },
186
+ 'form:action' : ({ dependencies }) => [{
187
+ fn : ({ target }) => {
188
+ const { storage } = dependencies;
189
+ storage.push ({ target })
190
+ }
191
+ , type : 'input'
192
+ , timing : 'instant'
193
+ }]
194
+ }
195
+ } // contextExtension
196
+ short.load ( contextExtension )
197
+ short.changeContext ( 'local' )
198
+ const last = storage[storage.length - 1]
199
+ expect ( last ).to.equal ( 'watch' )
200
+ }) // it arguments for "form:watch"
201
+
202
+
203
+
204
+ it ( 'Arguments for "form:define"', async () => {
205
+ short.enablePlugin ( pluginForm )
206
+ const storage = [];
207
+ short.setDependencies ({ storage })
208
+ const contextExtension = {
209
+ local: {
210
+ 'form:define' : ({ dependencies, target }) => {
211
+ const { storage } = dependencies;
212
+ storage.push ( 'define' )
213
+ return 'input'
214
+ },
215
+ 'form:action' : ({ dependencies }) => [{
216
+ fn : ({ target }) => {
217
+ const { storage } = dependencies;
218
+ storage.push ({ target })
219
+ }
220
+ , type : 'input'
221
+ , timing : 'instant'
222
+ }]
223
+ }
224
+ } // contextExtension
225
+ short.load ( contextExtension )
226
+ short.changeContext ( 'local' )
227
+ const last = storage[storage.length - 1]
228
+ expect ( last ).to.equal ( 'define' )
229
+ }) // it arguments for "form:watch"
230
+
231
+
232
+
233
+ it ( 'Reveal and click', async () => {
234
+ // Clear any existing plugins first to ensure clean state
235
+ short.enablePlugin ( pluginForm )
236
+ const x = short.enablePlugin ( pluginClick )
237
+
238
+ let sum = 0;
239
+ const contextExtension = {
240
+ reveal : {
129
241
 
242
+ 'click : left-1': ({ dependencies, target }) => {
243
+ if ( target.dataset.click === 'red' ) sum = 1
244
+ } // click:left-1
245
+ , 'form:action' : ({ dependencies }) => [
246
+ {
247
+ fn : ({ target }) => {
248
+ // dependencies are available here as named argument as well
249
+ // But if I have 20 actions, I need to add 20 times 'dependencies'
250
+ if ( target.id === 'name' ) {
251
+ const hidden = document.getElementById ( 'hidden' )
252
+ hidden.classList.remove ( 'hide' )
253
+ }
254
+ }
255
+ , type : 'input'
256
+ , timing : 'instant'
257
+ }] // form:action
258
+ }
259
+ };
260
+ short.load ( contextExtension )
261
+ short.changeContext ( 'reveal' )
262
+ const
263
+ input = document.getElementById ( 'name' )
264
+ , hidden = document.getElementById ( 'hidden' )
265
+ , red = document.getElementById ( 'rspan' )
266
+ ;
267
+
268
+ input.focus ()
269
+ await userEvent.keyboard ( 'hello' )
270
+ await waitFor ( () => {
271
+ expect ( getComputedStyle(hidden).display ).to.not.equal ( 'none' )
272
+ })
273
+ // Wait for any pending timers from previous tests to clear
274
+ await wait ( 350 )
275
+
276
+ await userEvent.click ( red )
277
+ await wait ( 350 )
278
+ await waitFor ( () => {
279
+ // console.log ( sum )
280
+ expect ( sum ).to.equal ( 1 )
281
+ }, { timeout: 1000, interval: 12 })
282
+ }) // it Reveal and click
130
283
 
284
+
285
+ it ( 'Extra parameters to plugin options', async () => {
286
+ short.enablePlugin ( pluginForm )
287
+ const emit = [];
288
+ const setupContext = {
289
+ 'form:setup' : () => {
290
+ emit.push ( 'setup' )
291
+ return { wait: 100, customParam: 'test-value', emit }
292
+ },
293
+ 'form:watch' : () => 'input',
294
+ 'form:action' : ({options}) => {
295
+ expect ( options.wait ).to.equal ( 100 )
296
+ expect ( options.customParam ).to.equal ( 'test-value' )
297
+ options.emit.push ( 'action' )
298
+ return [{
299
+ fn : ({ target }) => {
300
+ // Action function that uses options
301
+ expect ( options.wait ).to.equal ( 100 )
302
+ expect ( options.customParam ).to.equal ( 'test-value' )
303
+ options.emit.push ( 'executed' )
304
+ }
305
+ , type : 'input'
306
+ , timing : 'instant'
307
+ }]
308
+ }
309
+ } // setupContext
310
+
311
+ short.load ({ setupContext })
312
+ short.changeContext ( 'setupContext' )
131
313
 
132
-
314
+ // Setup event execution is on change context:
315
+ expect ( emit[0] ).to.equal ( 'setup' )
316
+
317
+ const input = document.getElementById ( 'name' )
318
+ input.focus ()
319
+ await userEvent.keyboard ( 'hello' )
320
+ await wait ( 50 )
321
+ await waitFor ( () => {
322
+ expect ( emit ).to.includes ( 'action' )
323
+ expect ( emit ).to.includes ( 'executed' )
324
+ }, { timeout: 1000, interval: 12 })
325
+ }) // it Extra parameters to plugin options
326
+
133
327
  }) // describe