@peter.naydenov/shortcuts 3.5.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +939 -0
- package/CODE_OF_CONDUCT.md +84 -0
- package/CONTRIBUTING.md +476 -0
- package/Changelog.md +26 -2
- package/How.to.create.plugins.md +929 -0
- package/Migration.guide.md +48 -0
- package/README.md +396 -24
- package/dist/main.d.ts +54 -2
- package/dist/methods/_normalizeWithPlugins.d.ts +63 -1
- package/dist/methods/_readShortcutWithPlugins.d.ts +8 -1
- package/dist/methods/_setupPlugin.d.ts +9 -0
- package/dist/methods/_systemAction.d.ts +8 -1
- package/dist/methods/changeContext.d.ts +8 -1
- package/dist/methods/index.d.ts +2 -0
- package/dist/methods/listShortcuts.d.ts +1 -16
- package/dist/methods/load.d.ts +8 -1
- package/dist/methods/unload.d.ts +8 -1
- package/dist/plugins/click/_findTarget.d.ts +9 -1
- package/dist/plugins/click/_listenDOM.d.ts +76 -3
- package/dist/plugins/click/_normalizeShortcutName.d.ts +7 -1
- package/dist/plugins/click/_registerShortcutEvents.d.ts +26 -0
- package/dist/plugins/click/index.d.ts +6 -31
- package/dist/plugins/form/_defaults.d.ts +13 -1
- package/dist/plugins/form/_listenDOM.d.ts +66 -3
- package/dist/plugins/form/_registerShortcutEvents.d.ts +95 -1
- package/dist/plugins/form/index.d.ts +2 -29
- package/dist/plugins/hover/_findTarget.d.ts +10 -0
- package/dist/plugins/hover/_listenDOM.d.ts +68 -0
- package/dist/plugins/hover/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/hover/_registerShortcutEvents.d.ts +28 -0
- package/dist/plugins/hover/index.d.ts +14 -0
- package/dist/plugins/key/_listenDOM.d.ts +61 -3
- package/dist/plugins/key/_registerShortcutEvents.d.ts +26 -0
- package/dist/plugins/key/_specialChars.d.ts +6 -31
- package/dist/plugins/key/index.d.ts +2 -29
- package/dist/plugins/scroll/_listenDOM.d.ts +58 -0
- package/dist/plugins/scroll/_normalizeShortcutName.d.ts +2 -0
- package/dist/plugins/scroll/_registerShortcutEvents.d.ts +28 -0
- package/dist/plugins/scroll/index.d.ts +16 -0
- package/dist/shortcuts.cjs +1 -1
- package/dist/shortcuts.esm.mjs +1 -1
- package/dist/shortcuts.umd.js +1 -1
- package/eslint.config.js +80 -0
- package/html/assets/index-COTh6lXR.css +1 -0
- package/html/assets/index-DOkKC3NI.js +53 -0
- package/html/bg.png +0 -0
- package/html/favicon.ico +0 -0
- package/html/favicon.svg +5 -0
- package/html/html.meta.json.gz +0 -0
- package/html/index.html +32 -0
- package/package.json +16 -12
- package/shortcuts.png +0 -0
- package/src/main.js +52 -22
- package/src/methods/_normalizeWithPlugins.js +26 -2
- package/src/methods/_readShortcutWithPlugins.js +9 -2
- package/src/methods/_setupPlugin.js +93 -0
- package/src/methods/_systemAction.js +12 -4
- package/src/methods/changeContext.js +11 -3
- package/src/methods/index.js +2 -0
- package/src/methods/listShortcuts.js +5 -12
- package/src/methods/load.js +11 -4
- package/src/methods/unload.js +8 -1
- package/src/plugins/click/_findTarget.js +11 -5
- package/src/plugins/click/_listenDOM.js +58 -20
- package/src/plugins/click/_normalizeShortcutName.js +11 -4
- package/src/plugins/click/_readClickEvent.js +1 -1
- package/src/plugins/click/_registerShortcutEvents.js +33 -5
- package/src/plugins/click/index.js +33 -60
- package/src/plugins/form/_defaults.js +13 -3
- package/src/plugins/form/_listenDOM.js +46 -9
- package/src/plugins/form/_normalizeShortcutName.js +2 -2
- package/src/plugins/form/_registerShortcutEvents.js +93 -17
- package/src/plugins/form/index.js +25 -56
- package/src/plugins/hover/_findTarget.js +26 -0
- package/src/plugins/hover/_listenDOM.js +154 -0
- package/src/plugins/hover/_normalizeShortcutName.js +21 -0
- package/src/plugins/hover/_registerShortcutEvents.js +51 -0
- package/src/plugins/hover/index.js +71 -0
- package/src/plugins/key/_listenDOM.js +67 -33
- package/src/plugins/key/_normalizeShortcutName.js +4 -3
- package/src/plugins/key/_readKeyEvent.js +1 -1
- package/src/plugins/key/_registerShortcutEvents.js +34 -5
- package/src/plugins/key/_specialChars.js +5 -0
- package/src/plugins/key/index.js +34 -59
- package/src/plugins/scroll/_listenDOM.js +141 -0
- package/src/plugins/scroll/_normalizeShortcutName.js +21 -0
- package/src/plugins/scroll/_registerShortcutEvents.js +50 -0
- package/src/plugins/scroll/index.js +61 -0
- package/test/01-general.test.js +92 -23
- package/test/02-key.test.js +241 -40
- package/test/03-click.test.js +291 -47
- package/test/04-form.test.js +241 -47
- package/test/05-hover.test.js +463 -0
- package/test/06-scroll.test.js +374 -0
- package/test-helpers/Block.jsx +3 -2
- package/test-helpers/style.css +6 -1
- package/vitest.config.js +13 -11
- package/How..to.make.plugins.md +0 -41
package/test/04-form.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, afterEach, describe, it, test, expect } from 'vitest'
|
|
2
|
-
import { userEvent } from '
|
|
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) =>
|
|
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
|
-
|
|
67
|
+
const short = shortcuts ();
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
describe
|
|
71
|
+
describe ( 'Form plugin', () => {
|
|
71
72
|
|
|
72
73
|
beforeEach ( async () => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|