@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/API.md
ADDED
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
Complete API documentation for @peter.naydenov/shortcuts library.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Core API](#core-api)
|
|
8
|
+
- [Plugin API](#plugin-api)
|
|
9
|
+
- [Event Data](#event-data)
|
|
10
|
+
- [Configuration Options](#configuration-options)
|
|
11
|
+
- [TypeScript Definitions](#typescript-definitions)
|
|
12
|
+
- [Examples](#examples)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core API
|
|
17
|
+
|
|
18
|
+
### shortcuts(options?)
|
|
19
|
+
|
|
20
|
+
Creates a new shortcuts instance with optional configuration.
|
|
21
|
+
|
|
22
|
+
**Parameters:**
|
|
23
|
+
- `options` (Object, optional): Global configuration options
|
|
24
|
+
- `onShortcut` (Function): Callback function called when any shortcut is triggered
|
|
25
|
+
|
|
26
|
+
**Returns:** `ShortcutsAPI` instance
|
|
27
|
+
|
|
28
|
+
**Example:**
|
|
29
|
+
```javascript
|
|
30
|
+
import { shortcuts } from '@peter.naydenov/shortcuts'
|
|
31
|
+
|
|
32
|
+
const short = shortcuts({
|
|
33
|
+
onShortcut: ({ shortcut, context, note }) => {
|
|
34
|
+
console.log(`Shortcut triggered: ${shortcut} in context: ${context}`)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### ShortcutsAPI Methods
|
|
40
|
+
|
|
41
|
+
#### load(shortcutDefinition)
|
|
42
|
+
|
|
43
|
+
Load and extend shortcut definitions.
|
|
44
|
+
|
|
45
|
+
**Parameters:**
|
|
46
|
+
- `shortcutDefinition` (Object): Context-based shortcut definitions
|
|
47
|
+
|
|
48
|
+
**Returns:** `void`
|
|
49
|
+
|
|
50
|
+
**Example:**
|
|
51
|
+
```javascript
|
|
52
|
+
const definition = {
|
|
53
|
+
navigation: {
|
|
54
|
+
'key:ctrl+s': () => console.log('Save'),
|
|
55
|
+
'click:left-1': ({ target }) => console.log('Clicked:', target)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
short.load(definition)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### unload(contextName)
|
|
63
|
+
|
|
64
|
+
Remove a shortcut context with all its shortcuts.
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
- `contextName` (string): Name of the context to remove
|
|
68
|
+
|
|
69
|
+
**Returns:** `void`
|
|
70
|
+
|
|
71
|
+
**Example:**
|
|
72
|
+
```javascript
|
|
73
|
+
short.unload('navigation')
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#### changeContext(contextName?)
|
|
79
|
+
|
|
80
|
+
Switch to existing shortcut context or deactivate all contexts.
|
|
81
|
+
|
|
82
|
+
**Parameters:**
|
|
83
|
+
- `contextName` (string, optional): Name of the context to activate
|
|
84
|
+
|
|
85
|
+
**Returns:** `void`
|
|
86
|
+
|
|
87
|
+
**Example:**
|
|
88
|
+
```javascript
|
|
89
|
+
short.changeContext('navigation') // Activate navigation context
|
|
90
|
+
short.changeContext() // Deactivate all contexts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
#### getContext()
|
|
98
|
+
|
|
99
|
+
Get the name of the current active context.
|
|
100
|
+
|
|
101
|
+
**Returns:** `string | null` - Current context name or null if no context is active
|
|
102
|
+
|
|
103
|
+
**Example:**
|
|
104
|
+
```javascript
|
|
105
|
+
const currentContext = short.getContext()
|
|
106
|
+
console.log('Active context:', currentContext)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
#### setNote(note?)
|
|
114
|
+
|
|
115
|
+
Set a note for the current context (sub-context).
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
- `note` (string, optional): Note name or undefined to remove note
|
|
119
|
+
|
|
120
|
+
**Returns:** `void`
|
|
121
|
+
|
|
122
|
+
**Example:**
|
|
123
|
+
```javascript
|
|
124
|
+
short.setNote('special') // Set note to 'special'
|
|
125
|
+
short.setNote() // Remove note
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
#### getNote()
|
|
133
|
+
|
|
134
|
+
Get the current note for the active context.
|
|
135
|
+
|
|
136
|
+
**Returns:** `string | null` - Current note name or null if no note is set
|
|
137
|
+
|
|
138
|
+
**Example:**
|
|
139
|
+
```javascript
|
|
140
|
+
const currentNote = short.getNote()
|
|
141
|
+
console.log('Current note:', currentNote)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
#### enablePlugin(plugin, options?)
|
|
149
|
+
|
|
150
|
+
Enable a plugin with optional configuration.
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
- `plugin` (Function): Plugin function (e.g., `pluginKey`, `pluginClick`)
|
|
154
|
+
- `options` (Object, optional): Plugin-specific configuration options
|
|
155
|
+
|
|
156
|
+
**Returns:** `void`
|
|
157
|
+
|
|
158
|
+
**Example:**
|
|
159
|
+
```javascript
|
|
160
|
+
import { pluginKey, pluginClick } from '@peter.naydenov/shortcuts'
|
|
161
|
+
|
|
162
|
+
short.enablePlugin(pluginKey, { keyWait: 500 })
|
|
163
|
+
short.enablePlugin(pluginClick, { clickTarget: ['data-action'] })
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### disablePlugin(pluginPrefix)
|
|
167
|
+
|
|
168
|
+
Disable a plugin by its prefix.
|
|
169
|
+
|
|
170
|
+
**Parameters:**
|
|
171
|
+
- `pluginPrefix` (string): Plugin prefix (e.g., 'key', 'click', 'hover')
|
|
172
|
+
|
|
173
|
+
**Returns:** `void`
|
|
174
|
+
|
|
175
|
+
**Example:**
|
|
176
|
+
```javascript
|
|
177
|
+
short.disablePlugin('key')
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### mutePlugin(pluginPrefix)
|
|
181
|
+
|
|
182
|
+
Temporarily mute a plugin (stop listening for events).
|
|
183
|
+
|
|
184
|
+
**Parameters:**
|
|
185
|
+
- `pluginPrefix` (string): Plugin prefix
|
|
186
|
+
|
|
187
|
+
**Returns:** `void`
|
|
188
|
+
|
|
189
|
+
**Example:**
|
|
190
|
+
```javascript
|
|
191
|
+
short.mutePlugin('click') // Stop listening for click events
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### unmutePlugin(pluginPrefix)
|
|
195
|
+
|
|
196
|
+
Resume listening for events from a muted plugin.
|
|
197
|
+
|
|
198
|
+
**Parameters:**
|
|
199
|
+
- `pluginPrefix` (string): Plugin prefix
|
|
200
|
+
|
|
201
|
+
**Returns:** `void`
|
|
202
|
+
|
|
203
|
+
**Example:**
|
|
204
|
+
```javascript
|
|
205
|
+
short.unmutePlugin('click') // Resume listening for click events
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### pause(shortcutName?)
|
|
209
|
+
|
|
210
|
+
Stop execution of specific shortcuts or all shortcuts in active context.
|
|
211
|
+
|
|
212
|
+
**Parameters:**
|
|
213
|
+
- `shortcutName` (string, optional): Specific shortcut name or '*' for all
|
|
214
|
+
|
|
215
|
+
**Returns:** `void`
|
|
216
|
+
|
|
217
|
+
**Example:**
|
|
218
|
+
```javascript
|
|
219
|
+
short.pause() // Pause all shortcuts
|
|
220
|
+
short.pause('key:ctrl+s') // Pause specific shortcut
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### resume(shortcutName?)
|
|
224
|
+
|
|
225
|
+
Resume execution of paused shortcuts.
|
|
226
|
+
|
|
227
|
+
**Parameters:**
|
|
228
|
+
- `shortcutName` (string, optional): Specific shortcut name or '*' for all
|
|
229
|
+
|
|
230
|
+
**Returns:** `void`
|
|
231
|
+
|
|
232
|
+
**Example:**
|
|
233
|
+
```javascript
|
|
234
|
+
short.resume() // Resume all shortcuts
|
|
235
|
+
short.resume('key:ctrl+s') // Resume specific shortcut
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### emit(shortcutName, data?)
|
|
239
|
+
|
|
240
|
+
Trigger a shortcut or custom event programmatically.
|
|
241
|
+
|
|
242
|
+
**Parameters:**
|
|
243
|
+
- `shortcutName` (string): Name of the shortcut to trigger
|
|
244
|
+
- `data` (Object, optional): Additional data to pass to the action function
|
|
245
|
+
|
|
246
|
+
**Returns:** `void`
|
|
247
|
+
|
|
248
|
+
**Example:**
|
|
249
|
+
```javascript
|
|
250
|
+
short.emit('key:ctrl+s', { programmatic: true })
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### listPlugins()
|
|
254
|
+
|
|
255
|
+
Get list of enabled plugins.
|
|
256
|
+
|
|
257
|
+
**Returns:** `string[]` - Array of plugin prefixes
|
|
258
|
+
|
|
259
|
+
**Example:**
|
|
260
|
+
```javascript
|
|
261
|
+
const enabledPlugins = short.listPlugins()
|
|
262
|
+
console.log('Enabled plugins:', enabledPlugins) // ['key', 'click', 'hover']
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### listContexts()
|
|
266
|
+
|
|
267
|
+
Get list of available contexts.
|
|
268
|
+
|
|
269
|
+
**Returns:** `string[]` - Array of context names
|
|
270
|
+
|
|
271
|
+
**Example:**
|
|
272
|
+
```javascript
|
|
273
|
+
const contexts = short.listContexts()
|
|
274
|
+
console.log('Available contexts:', contexts) // ['navigation', 'editor', 'viewer']
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### listShortcuts(contextName?)
|
|
278
|
+
|
|
279
|
+
Get list of shortcuts for a specific context or all contexts.
|
|
280
|
+
|
|
281
|
+
**Parameters:**
|
|
282
|
+
- `contextName` (string, optional): Context name or undefined for all contexts
|
|
283
|
+
|
|
284
|
+
**Returns:** `Object` - Context to shortcuts mapping
|
|
285
|
+
|
|
286
|
+
**Example:**
|
|
287
|
+
```javascript
|
|
288
|
+
const allShortcuts = short.listShortcuts()
|
|
289
|
+
const navShortcuts = short.listShortcuts('navigation')
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
#### setDependencies(dependencies)
|
|
293
|
+
|
|
294
|
+
Set external dependencies that will be available in action functions.
|
|
295
|
+
|
|
296
|
+
**Parameters:**
|
|
297
|
+
- `dependencies` (Object): Object containing dependencies
|
|
298
|
+
|
|
299
|
+
**Returns:** `void`
|
|
300
|
+
|
|
301
|
+
**Example:**
|
|
302
|
+
```javascript
|
|
303
|
+
short.setDependencies({
|
|
304
|
+
api: myApiService,
|
|
305
|
+
utils: myUtilityLibrary
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### getDependencies()
|
|
310
|
+
|
|
311
|
+
Get the current dependencies object.
|
|
312
|
+
|
|
313
|
+
**Returns:** `Object` - Current dependencies
|
|
314
|
+
|
|
315
|
+
**Example:**
|
|
316
|
+
```javascript
|
|
317
|
+
const deps = short.getDependencies()
|
|
318
|
+
console.log('Available dependencies:', Object.keys(deps))
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### reset()
|
|
322
|
+
|
|
323
|
+
Reset the shortcuts instance to initial state.
|
|
324
|
+
|
|
325
|
+
**Returns:** `void`
|
|
326
|
+
|
|
327
|
+
**Example:**
|
|
328
|
+
```javascript
|
|
329
|
+
short.reset() // Clear all contexts, plugins, and settings
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Plugin API
|
|
335
|
+
|
|
336
|
+
### Plugin Key (pluginKey)
|
|
337
|
+
|
|
338
|
+
Handles keyboard events and shortcuts.
|
|
339
|
+
|
|
340
|
+
#### Options
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
interface KeyPluginOptions {
|
|
344
|
+
keyWait?: number; // Timeout for key sequences (default: 480ms)
|
|
345
|
+
streamKeys?: Function; // Callback for each key press (default: false)
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### Shortcut Patterns
|
|
350
|
+
|
|
351
|
+
- `key:a` - Single key 'a'
|
|
352
|
+
- `key:ctrl+a` - Key 'a' with Ctrl modifier
|
|
353
|
+
- `key:ctrl+alt+shift+a` - Multiple modifiers
|
|
354
|
+
- `key:a,b,c` - Key sequence (a then b then c)
|
|
355
|
+
- `key:ctrl+a,l` - Sequence with modifiers
|
|
356
|
+
|
|
357
|
+
#### Special Keys
|
|
358
|
+
|
|
359
|
+
- Arrow keys: `left`, `right`, `up`, `down`
|
|
360
|
+
- Function keys: `F1` through `F12`
|
|
361
|
+
- Special: `enter`, `space`, `esc`, `tab`, `backspace`
|
|
362
|
+
- Symbols: `=`, `/`, `\`, `[`, `]`, `` ` ``
|
|
363
|
+
|
|
364
|
+
#### Example
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
short.enablePlugin(pluginKey, {
|
|
368
|
+
keyWait: 600,
|
|
369
|
+
streamKeys: (key) => console.log('Key pressed:', key)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
const shortcuts = {
|
|
373
|
+
editor: {
|
|
374
|
+
'key:setup': () => ({ keyWait: 300 }),
|
|
375
|
+
'key:ctrl+s': () => saveDocument(),
|
|
376
|
+
'key:ctrl+z': () => undo(),
|
|
377
|
+
'key:ctrl+shift+z': () => redo(),
|
|
378
|
+
'key:g,g': () => gotoLine(),
|
|
379
|
+
'key:ctrl+p': () => showCommandPalette()
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
### Plugin Click (pluginClick)
|
|
388
|
+
|
|
389
|
+
Handles mouse click events.
|
|
390
|
+
|
|
391
|
+
#### Options
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
interface ClickPluginOptions {
|
|
395
|
+
mouseWait?: number; // Timeout for multiple clicks (default: 320ms)
|
|
396
|
+
clickTarget?: string[]; // Target attributes (default: ['data-click', 'href'])
|
|
397
|
+
streamKeys?: Function; // Keyboard stream function
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### Shortcut Patterns
|
|
402
|
+
|
|
403
|
+
- `click:left-1` - Single left click
|
|
404
|
+
- `click:left-2` - Double left click
|
|
405
|
+
- `click:right-1` - Single right click
|
|
406
|
+
- `click:left-1-ctrl` - Left click with Ctrl
|
|
407
|
+
- `click:left-1-ctrl-alt` - Multiple modifiers
|
|
408
|
+
|
|
409
|
+
#### Target Elements
|
|
410
|
+
|
|
411
|
+
Elements with these attributes are clickable:
|
|
412
|
+
- `data-click="name"` - Custom click target
|
|
413
|
+
- `href="url"` - All anchor links (automatically included)
|
|
414
|
+
- `data-quick-click` - Disable multi-click detection
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
#### Example
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
short.enablePlugin(pluginClick, {
|
|
423
|
+
mouseWait: 200,
|
|
424
|
+
clickTarget: ['data-action', 'data-button', 'href']
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const shortcuts = {
|
|
428
|
+
navigation: {
|
|
429
|
+
'click:setup': () => ({
|
|
430
|
+
mouseWait: 150,
|
|
431
|
+
clickTarget: ['data-nav-item']
|
|
432
|
+
}),
|
|
433
|
+
'click:left-1': ({ target }) => {
|
|
434
|
+
if (target.tagName === 'A') {
|
|
435
|
+
event.preventDefault()
|
|
436
|
+
navigateTo(target.href)
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
'click:left-2': ({ target }) => {
|
|
440
|
+
target.classList.toggle('expanded')
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Plugin Hover (pluginHover)
|
|
447
|
+
|
|
448
|
+
Handles mouse hover events.
|
|
449
|
+
|
|
450
|
+
#### Options
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
interface HoverPluginOptions {
|
|
454
|
+
wait?: number; // Hover detection delay (default: 320ms)
|
|
455
|
+
hoverTarget?: string[]; // Target attributes (default: ['data-hover'])
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### Shortcut Patterns
|
|
460
|
+
|
|
461
|
+
- `hover:on` - Mouse enters target element
|
|
462
|
+
- `hover:off` - Mouse leaves target element
|
|
463
|
+
|
|
464
|
+
#### Target Elements
|
|
465
|
+
|
|
466
|
+
Elements with `data-hover="name"` attribute are hover targets.
|
|
467
|
+
|
|
468
|
+
#### Example
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
short.enablePlugin(pluginHover, {
|
|
472
|
+
wait: 500,
|
|
473
|
+
hoverTarget: ['data-interactive', 'data-tooltip']
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
const shortcuts = {
|
|
477
|
+
tooltips: {
|
|
478
|
+
'hover:setup': () => ({
|
|
479
|
+
wait: 200,
|
|
480
|
+
hoverTarget: ['data-tooltip']
|
|
481
|
+
}),
|
|
482
|
+
'hover:on': ({ target, targetProps }) => {
|
|
483
|
+
showTooltip(target, targetProps)
|
|
484
|
+
},
|
|
485
|
+
'hover:off': ({ target }) => {
|
|
486
|
+
hideTooltip(target)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Plugin Scroll (pluginScroll)
|
|
493
|
+
|
|
494
|
+
Handles scroll events.
|
|
495
|
+
|
|
496
|
+
#### Options
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
interface ScrollPluginOptions {
|
|
500
|
+
scrollWait?: number; // Delay between scroll events (default: 50ms)
|
|
501
|
+
endScrollWait?: number; // Delay when scroll stops (default: 400ms)
|
|
502
|
+
minSpace?: number; // Minimum distance between events (default: 40px)
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
#### Shortcut Patterns
|
|
507
|
+
|
|
508
|
+
- `scroll:up` - Scrolling up
|
|
509
|
+
- `scroll:down` - Scrolling down
|
|
510
|
+
- `scroll:left` - Scrolling left
|
|
511
|
+
- `scroll:right` - Scrolling right
|
|
512
|
+
- `scroll:end` - Scrolling has stopped
|
|
513
|
+
|
|
514
|
+
#### Example
|
|
515
|
+
|
|
516
|
+
```javascript
|
|
517
|
+
short.enablePlugin(pluginScroll, {
|
|
518
|
+
scrollWait: 100,
|
|
519
|
+
endScrollWait: 600,
|
|
520
|
+
minSpace: 60
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
const shortcuts = {
|
|
524
|
+
reader: {
|
|
525
|
+
'scroll:setup': () => ({
|
|
526
|
+
scrollWait: 150,
|
|
527
|
+
minSpace: 80
|
|
528
|
+
}),
|
|
529
|
+
'scroll:down': () => nextPage(),
|
|
530
|
+
'scroll:up': () => previousPage(),
|
|
531
|
+
'scroll:end': () => saveReadingProgress()
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Plugin Form (pluginForm)
|
|
537
|
+
|
|
538
|
+
Handles form element changes.
|
|
539
|
+
|
|
540
|
+
#### Options
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
interface FormPluginOptions {
|
|
544
|
+
// No specific options, uses default configuration
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### Shortcut Patterns
|
|
549
|
+
|
|
550
|
+
- `form:watch` - Function returning CSS selector for elements to watch
|
|
551
|
+
- `form:define` - Function defining element types
|
|
552
|
+
- `form:action` - Array of action definitions
|
|
553
|
+
|
|
554
|
+
#### Action Definition
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
interface FormAction {
|
|
558
|
+
fn: Function; // Action function
|
|
559
|
+
type: string; // Element type from form:define
|
|
560
|
+
timing: 'in' | 'out' | 'instant'; // When to trigger
|
|
561
|
+
wait?: number; // Debounce time for 'instant' timing
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
#### Example
|
|
566
|
+
|
|
567
|
+
```javascript
|
|
568
|
+
short.enablePlugin(pluginForm)
|
|
569
|
+
|
|
570
|
+
const shortcuts = {
|
|
571
|
+
forms: {
|
|
572
|
+
'form:watch': () => 'input, textarea, select',
|
|
573
|
+
'form:define': ({ target }) => {
|
|
574
|
+
if (target.type === 'checkbox') return 'checkbox'
|
|
575
|
+
if (target.tagName === 'SELECT') return 'select'
|
|
576
|
+
return 'input'
|
|
577
|
+
},
|
|
578
|
+
'form:action': () => [
|
|
579
|
+
{
|
|
580
|
+
fn: ({ target }) => console.log('Focus in:', target),
|
|
581
|
+
type: 'input',
|
|
582
|
+
timing: 'in'
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
fn: ({ target }) => console.log('Focus out:', target),
|
|
586
|
+
type: 'input',
|
|
587
|
+
timing: 'out'
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
fn: ({ target }) => console.log('Changed:', target.value),
|
|
591
|
+
type: 'input',
|
|
592
|
+
timing: 'instant',
|
|
593
|
+
wait: 300
|
|
594
|
+
}
|
|
595
|
+
]
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## Event Data
|
|
603
|
+
|
|
604
|
+
### Standard Event Properties
|
|
605
|
+
|
|
606
|
+
All action functions receive an object with these properties:
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
interface EventData {
|
|
610
|
+
context: string; // Current context name
|
|
611
|
+
note: string | null; // Current note or null
|
|
612
|
+
dependencies: Object; // External dependencies
|
|
613
|
+
event: Event; // Original DOM event (if applicable)
|
|
614
|
+
options: Object; // Plugin-specific options
|
|
615
|
+
viewport: { // Viewport information
|
|
616
|
+
X: number; // Horizontal scroll position
|
|
617
|
+
Y: number; // Vertical scroll position
|
|
618
|
+
width: number; // Viewport width
|
|
619
|
+
height: number; // Viewport height
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Plugin-Specific Properties
|
|
625
|
+
|
|
626
|
+
#### Key Plugin
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
interface KeyEventData extends EventData {
|
|
630
|
+
key: string; // Pressed key
|
|
631
|
+
wait: Function; // Stop sequence timer
|
|
632
|
+
end: Function; // Resume sequence timer
|
|
633
|
+
ignore: Function; // Ignore current key from sequence
|
|
634
|
+
isWaiting: boolean; // True if sequence timer is active
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
#### Click/Hover Plugin
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
interface MouseEventData extends EventData {
|
|
642
|
+
target: HTMLElement; // Target element
|
|
643
|
+
targetProps: { // Element coordinates
|
|
644
|
+
top: number;
|
|
645
|
+
left: number;
|
|
646
|
+
right: number;
|
|
647
|
+
bottom: number;
|
|
648
|
+
width: number;
|
|
649
|
+
height: number;
|
|
650
|
+
} | null;
|
|
651
|
+
x: number; // X coordinate
|
|
652
|
+
y: number; // Y coordinate
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
#### Scroll Plugin
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
interface ScrollEventData extends EventData {
|
|
660
|
+
// Uses only standard EventData properties
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### Form Plugin
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
interface FormActionData {
|
|
668
|
+
target: HTMLElement; // Form element
|
|
669
|
+
dependencies: Object; // External dependencies (top-level access)
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
## Configuration Options
|
|
676
|
+
|
|
677
|
+
### Global Options
|
|
678
|
+
|
|
679
|
+
```typescript
|
|
680
|
+
interface GlobalOptions {
|
|
681
|
+
onShortcut?: Function; // Global shortcut callback
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
#### onShortcut Callback
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
function onShortcut({
|
|
689
|
+
shortcut, // string: Triggered shortcut name
|
|
690
|
+
context, // string: Current context
|
|
691
|
+
note, // string | null: Current note
|
|
692
|
+
dependencies // Object: External dependencies
|
|
693
|
+
}) {
|
|
694
|
+
// Handle shortcut trigger
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Plugin Options
|
|
699
|
+
|
|
700
|
+
Each plugin can be configured globally or per-context:
|
|
701
|
+
|
|
702
|
+
#### Global Configuration
|
|
703
|
+
|
|
704
|
+
```javascript
|
|
705
|
+
short.enablePlugin(pluginKey, {
|
|
706
|
+
keyWait: 500,
|
|
707
|
+
streamKeys: (key) => console.log(key)
|
|
708
|
+
})
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### Per-Context Configuration
|
|
712
|
+
|
|
713
|
+
```javascript
|
|
714
|
+
const shortcuts = {
|
|
715
|
+
myContext: {
|
|
716
|
+
'key:setup': ({ dependencies, defaults }) => ({
|
|
717
|
+
keyWait: 300,
|
|
718
|
+
streamKeys: false
|
|
719
|
+
}),
|
|
720
|
+
'key:ctrl+s': () => save()
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
---
|
|
726
|
+
|
|
727
|
+
## TypeScript Definitions
|
|
728
|
+
|
|
729
|
+
### Core Types
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
interface ShortcutsAPI {
|
|
733
|
+
load(shortcutDefinition: Object): void;
|
|
734
|
+
unload(contextName: string): void;
|
|
735
|
+
changeContext(contextName?: string): void;
|
|
736
|
+
getContext(): string | null;
|
|
737
|
+
setNote(note?: string): void;
|
|
738
|
+
getNote(): string | null;
|
|
739
|
+
enablePlugin(plugin: Function, options?: Object): void;
|
|
740
|
+
disablePlugin(pluginPrefix: string): void;
|
|
741
|
+
mutePlugin(pluginPrefix: string): void;
|
|
742
|
+
unmutePlugin(pluginPrefix: string): void;
|
|
743
|
+
pause(shortcutName?: string): void;
|
|
744
|
+
resume(shortcutName?: string): void;
|
|
745
|
+
emit(shortcutName: string, data?: Object): void;
|
|
746
|
+
listPlugins(): string[];
|
|
747
|
+
listContexts(): string[];
|
|
748
|
+
listShortcuts(contextName?: string): Object;
|
|
749
|
+
setDependencies(dependencies: Object): void;
|
|
750
|
+
getDependencies(): Object;
|
|
751
|
+
reset(): void;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
declare function shortcuts(options?: GlobalOptions): ShortcutsAPI;
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### Plugin Types
|
|
758
|
+
|
|
759
|
+
```typescript
|
|
760
|
+
interface KeyPluginOptions {
|
|
761
|
+
keyWait?: number;
|
|
762
|
+
streamKeys?: ((key: string) => void) | false;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
interface ClickPluginOptions {
|
|
766
|
+
mouseWait?: number;
|
|
767
|
+
clickTarget?: string[];
|
|
768
|
+
streamKeys?: ((key: string) => void) | false;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
interface HoverPluginOptions {
|
|
772
|
+
wait?: number;
|
|
773
|
+
hoverTarget?: string[];
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
interface ScrollPluginOptions {
|
|
777
|
+
scrollWait?: number;
|
|
778
|
+
endScrollWait?: number;
|
|
779
|
+
minSpace?: number;
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## Examples
|
|
786
|
+
|
|
787
|
+
### Basic Usage
|
|
788
|
+
|
|
789
|
+
```javascript
|
|
790
|
+
import { shortcuts, pluginKey, pluginClick } from '@peter.naydenov/shortcuts'
|
|
791
|
+
|
|
792
|
+
const short = shortcuts()
|
|
793
|
+
|
|
794
|
+
// Enable plugins
|
|
795
|
+
short.enablePlugin(pluginKey)
|
|
796
|
+
short.enablePlugin(pluginClick)
|
|
797
|
+
|
|
798
|
+
// Define shortcuts
|
|
799
|
+
const shortcuts = {
|
|
800
|
+
editor: {
|
|
801
|
+
'key:ctrl+s': () => saveDocument(),
|
|
802
|
+
'key:ctrl+z': () => undo(),
|
|
803
|
+
'click:left-1': ({ target }) => {
|
|
804
|
+
if (target.matches('.toolbar button')) {
|
|
805
|
+
handleToolbarClick(target)
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Load and activate
|
|
812
|
+
short.load(shortcuts)
|
|
813
|
+
short.changeContext('editor')
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### Advanced Configuration
|
|
817
|
+
|
|
818
|
+
```javascript
|
|
819
|
+
const short = shortcuts({
|
|
820
|
+
onShortcut: ({ shortcut, context }) => {
|
|
821
|
+
console.log(`${shortcut} triggered in ${context}`)
|
|
822
|
+
}
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
// Configure plugins with custom options
|
|
826
|
+
short.enablePlugin(pluginKey, {
|
|
827
|
+
keyWait: 600,
|
|
828
|
+
streamKeys: (key) => console.log('Key:', key)
|
|
829
|
+
})
|
|
830
|
+
|
|
831
|
+
short.enablePlugin(pluginClick, {
|
|
832
|
+
mouseWait: 200,
|
|
833
|
+
clickTarget: ['data-action', 'data-button', 'href']
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
short.enablePlugin(pluginHover, {
|
|
837
|
+
wait: 100,
|
|
838
|
+
hoverTarget: ['data-interactive']
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
// Per-context configuration
|
|
842
|
+
const shortcuts = {
|
|
843
|
+
gaming: {
|
|
844
|
+
'key:setup': () => ({ keyWait: 100 }),
|
|
845
|
+
'click:setup': () => ({ mouseWait: 50 }),
|
|
846
|
+
'hover:setup': () => ({ wait: 10 }),
|
|
847
|
+
'key:w': () => moveUp(),
|
|
848
|
+
'key:a': () => moveLeft(),
|
|
849
|
+
'key:s': () => moveDown(),
|
|
850
|
+
'key:d': () => moveRight()
|
|
851
|
+
},
|
|
852
|
+
accessibility: {
|
|
853
|
+
'key:setup': () => ({ keyWait: 1000 }),
|
|
854
|
+
'hover:setup': () => ({ wait: 500 }),
|
|
855
|
+
'hover:on': ({ target }) => {
|
|
856
|
+
announceElement(target)
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
short.load(shortcuts)
|
|
862
|
+
short.changeContext('gaming')
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Plugin Development Example
|
|
866
|
+
|
|
867
|
+
```javascript
|
|
868
|
+
// Custom touch plugin
|
|
869
|
+
function pluginTouch(setupPlugin, options = {}) {
|
|
870
|
+
const deps = {
|
|
871
|
+
regex: /TOUCH:.+/i
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const pluginState = {
|
|
875
|
+
active: false,
|
|
876
|
+
defaultOptions: {
|
|
877
|
+
swipeThreshold: 50
|
|
878
|
+
},
|
|
879
|
+
listenOptions: {}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function resetState() {
|
|
883
|
+
// Cleanup logic
|
|
884
|
+
}
|
|
885
|
+
deps.resetState = resetState
|
|
886
|
+
|
|
887
|
+
return setupPlugin({
|
|
888
|
+
prefix: 'touch',
|
|
889
|
+
_normalizeShortcutName: (name) => name.toUpperCase(),
|
|
890
|
+
_registerShortcutEvents: (deps, state) => {
|
|
891
|
+
// Registration logic
|
|
892
|
+
},
|
|
893
|
+
_listenDOM: (deps, state, ev) => {
|
|
894
|
+
// DOM listening logic
|
|
895
|
+
return { start: () => {}, stop: () => {} }
|
|
896
|
+
},
|
|
897
|
+
pluginState,
|
|
898
|
+
deps
|
|
899
|
+
})
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Use custom plugin
|
|
903
|
+
short.enablePlugin(pluginTouch, { swipeThreshold: 75 })
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
---
|
|
907
|
+
|
|
908
|
+
## Migration Guide
|
|
909
|
+
|
|
910
|
+
### From v3.x.x to v4.0.0
|
|
911
|
+
|
|
912
|
+
#### Breaking Changes
|
|
913
|
+
|
|
914
|
+
1. **Array-based target attributes:**
|
|
915
|
+
```javascript
|
|
916
|
+
// v3.x.x (old)
|
|
917
|
+
short.enablePlugin(pluginClick, { clickTarget: 'data-button' })
|
|
918
|
+
short.enablePlugin(pluginHover, { hoverTarget: 'data-menu' })
|
|
919
|
+
|
|
920
|
+
// v4.0.0 (new)
|
|
921
|
+
short.enablePlugin(pluginClick, { clickTarget: ['data-button'] })
|
|
922
|
+
short.enablePlugin(pluginHover, { hoverTarget: ['data-menu'] })
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
2. **Default values updated:**
|
|
926
|
+
- `clickTarget`: `['data-click', 'href']` (was `'data-click'`)
|
|
927
|
+
- `hoverTarget`: `['data-hover']` (was `'data-hover'`)
|
|
928
|
+
|
|
929
|
+
#### Benefits
|
|
930
|
+
|
|
931
|
+
- More flexible target detection
|
|
932
|
+
- Support for multiple attribute patterns
|
|
933
|
+
- Better compatibility with existing HTML
|
|
934
|
+
|
|
935
|
+
---
|
|
936
|
+
|
|
937
|
+
**Last updated:** November 2025
|
|
938
|
+
**Version:** 4.0.0
|
|
939
|
+
**Library:** @peter.naydenov/shortcuts
|