@peter.naydenov/shortcuts 3.5.1 → 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.
Files changed (98) 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 +30 -1
  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 -5
  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 -3
  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 -3
  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/eslint.config.js +80 -0
  44. package/html/assets/index-COTh6lXR.css +1 -0
  45. package/html/assets/index-DOkKC3NI.js +53 -0
  46. package/html/bg.png +0 -0
  47. package/html/favicon.ico +0 -0
  48. package/html/favicon.svg +5 -0
  49. package/html/html.meta.json.gz +0 -0
  50. package/html/index.html +32 -0
  51. package/package.json +16 -12
  52. package/shortcuts.png +0 -0
  53. package/src/main.js +52 -22
  54. package/src/methods/_normalizeWithPlugins.js +26 -2
  55. package/src/methods/_readShortcutWithPlugins.js +9 -2
  56. package/src/methods/_setupPlugin.js +93 -0
  57. package/src/methods/_systemAction.js +12 -4
  58. package/src/methods/changeContext.js +11 -3
  59. package/src/methods/index.js +2 -0
  60. package/src/methods/listShortcuts.js +5 -12
  61. package/src/methods/load.js +11 -4
  62. package/src/methods/unload.js +8 -1
  63. package/src/plugins/click/_findTarget.js +11 -5
  64. package/src/plugins/click/_listenDOM.js +58 -20
  65. package/src/plugins/click/_normalizeShortcutName.js +11 -4
  66. package/src/plugins/click/_readClickEvent.js +1 -1
  67. package/src/plugins/click/_registerShortcutEvents.js +33 -5
  68. package/src/plugins/click/index.js +34 -51
  69. package/src/plugins/form/_defaults.js +13 -3
  70. package/src/plugins/form/_listenDOM.js +46 -9
  71. package/src/plugins/form/_normalizeShortcutName.js +2 -2
  72. package/src/plugins/form/_registerShortcutEvents.js +93 -17
  73. package/src/plugins/form/index.js +26 -47
  74. package/src/plugins/hover/_findTarget.js +26 -0
  75. package/src/plugins/hover/_listenDOM.js +154 -0
  76. package/src/plugins/hover/_normalizeShortcutName.js +21 -0
  77. package/src/plugins/hover/_registerShortcutEvents.js +51 -0
  78. package/src/plugins/hover/index.js +71 -0
  79. package/src/plugins/key/_listenDOM.js +67 -33
  80. package/src/plugins/key/_normalizeShortcutName.js +4 -3
  81. package/src/plugins/key/_readKeyEvent.js +1 -1
  82. package/src/plugins/key/_registerShortcutEvents.js +34 -5
  83. package/src/plugins/key/_specialChars.js +5 -0
  84. package/src/plugins/key/index.js +35 -50
  85. package/src/plugins/scroll/_listenDOM.js +141 -0
  86. package/src/plugins/scroll/_normalizeShortcutName.js +21 -0
  87. package/src/plugins/scroll/_registerShortcutEvents.js +50 -0
  88. package/src/plugins/scroll/index.js +61 -0
  89. package/test/01-general.test.js +92 -23
  90. package/test/02-key.test.js +241 -40
  91. package/test/03-click.test.js +291 -47
  92. package/test/04-form.test.js +241 -47
  93. package/test/05-hover.test.js +463 -0
  94. package/test/06-scroll.test.js +374 -0
  95. package/test-helpers/Block.jsx +3 -2
  96. package/test-helpers/style.css +6 -1
  97. package/vitest.config.js +13 -11
  98. 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