ngx-keys 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,14 +19,21 @@ npm install ngx-keys
19
19
  ```
20
20
 
21
21
  ## Quick Start
22
- ### Displaying Shortcuts
22
+
23
+ ### Register and Display Shortcuts
24
+ >[!NOTE]
25
+ For related shortcuts, use groups for easier management (*[See group management section](#group-management)*).
23
26
 
24
27
  ```typescript
25
- import { Component, inject } from '@angular/core';
28
+ import { Component, DestroyRef, inject } from '@angular/core';
26
29
  import { KeyboardShortcuts } from 'ngx-keys';
27
30
 
28
31
  @Component({
29
32
  template: `
33
+ <h3>My App</h3>
34
+ <p>Last action: {{ lastAction }}</p>
35
+
36
+ <h4>Available Shortcuts:</h4>
30
37
  @for (shortcut of activeShortcuts(); track shortcut.id) {
31
38
  <div>
32
39
  <kbd>{{ shortcut.keys }}</kbd> - {{ shortcut.description }}
@@ -34,11 +41,69 @@ import { KeyboardShortcuts } from 'ngx-keys';
34
41
  }
35
42
  `
36
43
  })
37
- export class ShortcutsComponent {
44
+ export class MyComponent {
38
45
  private readonly keyboardService = inject(KeyboardShortcuts);
46
+ private readonly destroyRef = inject(DestroyRef);
47
+
48
+ protected lastAction = 'Try pressing Ctrl+S or Ctrl+H';
39
49
  protected readonly activeShortcuts = () => this.keyboardService.shortcutsUI$().active;
50
+
51
+ constructor() {
52
+ // Register individual shortcuts (automatically activated)
53
+ this.keyboardService.register({
54
+ id: 'save',
55
+ keys: ['ctrl', 's'],
56
+ macKeys: ['meta', 's'],
57
+ action: () => this.save(),
58
+ description: 'Save document'
59
+ });
60
+
61
+ this.keyboardService.register({
62
+ id: 'help',
63
+ keys: ['ctrl', 'h'],
64
+ macKeys: ['meta', 'h'],
65
+ action: () => this.showHelp(),
66
+ description: 'Show help'
67
+ });
68
+
69
+ // Clean up on component destroy
70
+ this.destroyRef.onDestroy(() => {
71
+ this.keyboardService.unregister('save');
72
+ this.keyboardService.unregister('help');
73
+ });
74
+ }
75
+
76
+ private save() {
77
+ this.lastAction = 'Document saved!';
78
+ }
79
+
80
+ private showHelp() {
81
+ this.lastAction = 'Help displayed!';
82
+ }
40
83
  }
41
84
  ```
85
+
86
+
87
+
88
+ ## Explore the Demo
89
+
90
+ Want to see ngx-keys in action? Check out our comprehensive [demo application](/projects/demo) with:
91
+
92
+ | Component | Purpose | Key Features |
93
+ | ---------------------------------------------------------------------------------- | -------------------------- | --------------------------------------------- |
94
+ | [**App Component**](/projects/demo/src/app/app.ts) | Global shortcuts | Single & group registration, cleanup patterns |
95
+ | [**Home Component**](/projects/demo/src/app/home/home.component.ts) | Reactive UI | Real-time status display, toggle controls |
96
+ | [**Feature Component**](/projects/demo/src/app/feature/feature.component.ts) | Route-specific shortcuts | Scoped shortcuts, lifecycle management |
97
+ | [**Customize Component**](/projects/demo/src/app/customize/customize.component.ts) | Dynamic shortcut recording | Real-time key capture, shortcut customization |
98
+
99
+ **Run the demo:**
100
+ ```bash
101
+ git clone https://github.com/mrivasperez/ngx-keys
102
+ cd ngx-keys
103
+ npm install
104
+ npm start
105
+ ```
106
+
42
107
  ## Key Concepts
43
108
 
44
109
  ### Automatic Activation
@@ -57,6 +122,29 @@ this.keyboardService.register({
57
122
  });
58
123
  ```
59
124
 
125
+ ### Multi-step (sequence) shortcuts
126
+
127
+ In addition to single-step shortcuts using `keys` / `macKeys`, ngx-keys supports ordered multi-step sequences using `steps` and `macSteps` on the `KeyboardShortcut` object. Each element in `steps` is itself an array of key tokens that must be pressed together for that step.
128
+
129
+ Example: register a sequence that requires `Ctrl+K` followed by `S`:
130
+
131
+ ```typescript
132
+ this.keyboardService.register({
133
+ id: 'open-settings-seq',
134
+ steps: [['ctrl', 'k'], ['s']],
135
+ macSteps: [['meta', 'k'], ['s']],
136
+ action: () => this.openSettings(),
137
+ description: 'Open settings (Ctrl+K then S)'
138
+ });
139
+ ```
140
+
141
+ Important behavior notes:
142
+
143
+ - Default sequence timeout: the service requires the next step to be entered within 2000ms (2 seconds) of the previous step; otherwise the pending sequence is cleared. This timeout is intentionally conservative and can be changed in future releases or exposed per-shortcut if needed.
144
+ - Steps are order-sensitive. `steps: [['ctrl','k'], ['s']]` is different from `steps: [['s'], ['ctrl','k']]`.
145
+ - Existing single-step `keys` / `macKeys` remain supported and continue to work as before.
146
+
147
+
60
148
  Use the `activate()` and `deactivate()` methods for dynamic control after registration:
61
149
 
62
150
  ```typescript
@@ -136,8 +224,14 @@ interface KeyboardShortcutUI {
136
224
  ```typescript
137
225
  interface KeyboardShortcut {
138
226
  id: string; // Unique identifier
139
- keys: string[]; // Key combination for PC/Linux (e.g., ['ctrl', 's'])
140
- macKeys: string[]; // Key combination for Mac (e.g., ['meta', 's'])
227
+ // Single-step combinations (existing API)
228
+ keys?: string[]; // Key combination for PC/Linux (e.g., ['ctrl', 's'])
229
+ macKeys?: string[]; // Key combination for Mac (e.g., ['meta', 's'])
230
+
231
+ // Multi-step sequences (new)
232
+ // Each step is an array of keys pressed together. Example: steps: [['ctrl','k'], ['s']]
233
+ steps?: string[][];
234
+ macSteps?: string[][];
141
235
  action: () => void; // Function to execute
142
236
  description: string; // Human-readable description
143
237
  }
@@ -157,21 +251,21 @@ interface KeyboardShortcutGroup {
157
251
 
158
252
  ### Modifier Keys
159
253
 
160
- | PC/Linux | Mac | Description |
161
- |----------|-----|-------------|
162
- | `ctrl` | `meta` | Control/Command key |
163
- | `alt` | `alt` | Alt/Option key |
164
- | `shift` | `shift` | Shift key |
254
+ | PC/Linux | Mac | Description |
255
+ | -------- | ------- | ------------------- |
256
+ | `ctrl` | `meta` | Control/Command key |
257
+ | `alt` | `alt` | Alt/Option key |
258
+ | `shift` | `shift` | Shift key |
165
259
 
166
260
  ### Special Keys
167
261
 
168
- | Key | Value |
169
- |-----|-------|
170
- | Function keys | `f1`, `f2`, `f3`, ... `f12` |
171
- | Arrow keys | `arrowup`, `arrowdown`, `arrowleft`, `arrowright` |
172
- | Navigation | `home`, `end`, `pageup`, `pagedown` |
173
- | Editing | `insert`, `delete`, `backspace` |
174
- | Other | `escape`, `tab`, `enter`, `space` |
262
+ | Key | Value |
263
+ | ------------- | ------------------------------------------------- |
264
+ | Function keys | `f1`, `f2`, `f3`, ... `f12` |
265
+ | Arrow keys | `arrowup`, `arrowdown`, `arrowleft`, `arrowright` |
266
+ | Navigation | `home`, `end`, `pageup`, `pagedown` |
267
+ | Editing | `insert`, `delete`, `backspace` |
268
+ | Other | `escape`, `tab`, `enter`, `space` |
175
269
 
176
270
  ## Browser Conflicts Warning
177
271
 
@@ -187,18 +281,19 @@ interface KeyboardShortcutGroup {
187
281
  - `Ctrl+D` / `⌘+D` - Bookmark page
188
282
 
189
283
  ### Safer Alternatives
284
+ > [!TIP]
285
+ > Always test your shortcuts across different browsers and operating systems. Consider providing alternative key combinations or allow users to customize shortcuts.
190
286
  - Function keys: `F1`, `F2`, `F3`, etc.
191
287
  - Custom combinations: `Ctrl+Shift+S`, `Alt+Enter`
192
288
  - Arrow keys with modifiers: `Ctrl+ArrowUp`
193
289
  - Application-specific: `Ctrl+K`, `Ctrl+P` (if not conflicting)
194
290
 
195
- ### Testing Browser Conflicts
196
-
197
- > [!TIP]
198
- > Always test your shortcuts across different browsers and operating systems. Consider providing alternative key combinations or allow users to customize shortcuts.
199
291
 
200
292
  ## Advanced Usage
201
293
 
294
+ > [!TIP]
295
+ Check out our [demo application](/projects/demo/src/app) for full implementations of all patterns shown below.
296
+
202
297
  ### Reactive UI Integration
203
298
 
204
299
  ```typescript
@@ -239,6 +334,9 @@ export class ShortcutsDisplayComponent {
239
334
  ```
240
335
  ### Group Management
241
336
 
337
+ > [!NOTE]
338
+ > **Live Example**: See this pattern in action in [feature.component.ts](/projects/demo/src/app/feature/feature.component.ts)
339
+
242
340
  ```typescript
243
341
  import { Component, DestroyRef, inject } from '@angular/core';
244
342
  import { KeyboardShortcuts, KeyboardShortcut } from 'ngx-keys';
@@ -287,9 +385,65 @@ export class FeatureComponent {
287
385
  }
288
386
  ```
289
387
 
388
+ ### Automatic unregistering
389
+
390
+ `register` and `registerGroup` have the optional parameter: `activeUntil`.
391
+ The `activeUntil` parameter allows you to connect the shortcut to the wrappers lifecycle or logic in general.
392
+
393
+ `activeUntil` supports three types:
394
+ - `'destruct'`: the shortcut injects the parents `DestroyRef` and unregisters once the component destructs
395
+ - `DestroyRef`: DestroyRef which should trigger the destruction of the shortcut
396
+ - `Observable<unknown>`: an Observable which will unregister the shortcut when triggered
397
+
398
+ #### Example: `'destruct'`
399
+ Shortcuts defined by this component will only be listening during the lifecycle of the component.
400
+ Shortcuts are registered on construction and are automatically unregistered on destruction.
401
+
402
+ ```typescript
403
+ export class Component {
404
+ constructor() {
405
+ const keyboardService = inject(KeyboardShortcuts)
406
+
407
+ keyboardService.register({
408
+ // ...
409
+ activeUntil: 'destruct', // alternatively: inject(DestroyRef)
410
+ });
411
+
412
+ keyboardService.registerGroup(
413
+ 'shortcuts',
414
+ [/* ... */],
415
+ 'destruct', // alternatively: inject(DestroyRef)
416
+ );
417
+ }
418
+ }
419
+ ```
420
+
421
+ #### Example: `Observable`
422
+
423
+ ```typescript
424
+ const shortcutTTL = new Subject<void>();
425
+
426
+ keyboardService.register({
427
+ // ...
428
+ activeUntil: shortcutTTL,
429
+ });
430
+
431
+ keyboardService.registerGroup(
432
+ 'shortcuts',
433
+ [/* ... */],
434
+ shortcutTTL,
435
+ );
436
+
437
+ // Shortcuts are listening...
438
+
439
+ shortcutTTL.next();
440
+
441
+ // Shortcuts are unregistered
442
+ ```
443
+
290
444
  ### Batch Operations
291
445
 
292
- For better performance when making multiple changes, use the `batchUpdate` method:
446
+ For better performance when making multiple changes, use the `batchUpdate` method.
293
447
 
294
448
  ```typescript
295
449
  import { Component, inject } from '@angular/core';
@@ -331,6 +485,9 @@ export class BatchUpdateComponent {
331
485
 
332
486
  ### Checking Status
333
487
 
488
+ > [!NOTE]
489
+ > See status checking in [home.component.ts](/projects/demo/src/app/home/home.component.ts)
490
+
334
491
  ```typescript
335
492
  import { Component, inject } from '@angular/core';
336
493
  import { KeyboardShortcuts } from 'ngx-keys';
@@ -351,6 +508,25 @@ export class MyComponent {
351
508
  }
352
509
  ```
353
510
 
511
+ ### Chords (multiple non-modifier keys)
512
+
513
+ - ngx-keys supports chords composed of multiple non-modifier keys pressed simultaneously (for example `C + A`).
514
+ - When multiple non-modifier keys are physically held down at the same time, the service uses the set of currently pressed keys plus any modifier flags to match registered shortcuts.
515
+ - Example: register a chord with `keys: ['c','a']` and pressing and holding `c` then pressing `a` will trigger the shortcut.
516
+ - Note: Browsers deliver separate keydown events for each physical key; the library maintains a Set of currently-down keys via `keydown`/`keyup` listeners to enable chords. This approach attempts to be robust but can be affected by browser focus changes — ensure tests in your target browsers.
517
+
518
+ Example registration:
519
+
520
+ ```typescript
521
+ this.keyboardService.register({
522
+ id: 'chord-ca',
523
+ keys: ['c', 'a'],
524
+ macKeys: ['c', 'a'],
525
+ action: () => console.log('Chord C+A executed'),
526
+ description: 'Demo chord'
527
+ });
528
+ ```
529
+
354
530
  ## Building
355
531
 
356
532
  To build the library:
@@ -1,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, inject, PLATFORM_ID, Injectable } from '@angular/core';
2
+ import { signal, computed, inject, PLATFORM_ID, DestroyRef, Injectable } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
+ import { Observable, take } from 'rxjs';
4
5
 
5
6
  /**
6
7
  * Centralized error messages for keyboard shortcuts service
@@ -92,6 +93,7 @@ class KeyboardShortcuts {
92
93
  groups = new Map();
93
94
  activeShortcuts = new Set();
94
95
  activeGroups = new Set();
96
+ currentlyDownKeys = new Set();
95
97
  // Single consolidated state signal - reduces memory overhead
96
98
  state = signal({
97
99
  shortcuts: new Map(),
@@ -129,8 +131,15 @@ class KeyboardShortcuts {
129
131
  };
130
132
  }, ...(ngDevMode ? [{ debugName: "shortcutsUI$" }] : []));
131
133
  keydownListener = this.handleKeydown.bind(this);
134
+ keyupListener = this.handleKeyup.bind(this);
135
+ blurListener = this.handleWindowBlur.bind(this);
136
+ visibilityListener = this.handleVisibilityChange.bind(this);
132
137
  isListening = false;
133
138
  isBrowser;
139
+ /** Default timeout (ms) for completing a multi-step sequence */
140
+ sequenceTimeout = 2000;
141
+ /** Runtime state for multi-step sequences */
142
+ pendingSequence = null;
134
143
  constructor() {
135
144
  // Use try-catch to handle injection context for better testability
136
145
  try {
@@ -166,8 +175,8 @@ class KeyboardShortcuts {
166
175
  formatShortcutForUI(shortcut) {
167
176
  return {
168
177
  id: shortcut.id,
169
- keys: this.formatKeysForDisplay(shortcut.keys, false),
170
- macKeys: this.formatKeysForDisplay(shortcut.macKeys, true),
178
+ keys: this.formatStepsForDisplay(shortcut.keys ?? shortcut.steps ?? [], false),
179
+ macKeys: this.formatStepsForDisplay(shortcut.macKeys ?? shortcut.macSteps ?? [], true),
171
180
  description: shortcut.description
172
181
  };
173
182
  }
@@ -199,16 +208,45 @@ class KeyboardShortcuts {
199
208
  .map(key => keyMap[key.toLowerCase()] || key.toUpperCase())
200
209
  .join('+');
201
210
  }
211
+ formatStepsForDisplay(steps, isMac = false) {
212
+ if (!steps)
213
+ return '';
214
+ // If the first element is an array, assume steps is string[][]
215
+ const normalized = this.normalizeToSteps(steps);
216
+ if (normalized.length === 0)
217
+ return '';
218
+ if (normalized.length === 1)
219
+ return this.formatKeysForDisplay(normalized[0], isMac);
220
+ return normalized.map(step => this.formatKeysForDisplay(step, isMac)).join(', ');
221
+ }
222
+ normalizeToSteps(input) {
223
+ if (!input)
224
+ return [];
225
+ // If first element is an array, assume already KeyStep[]
226
+ if (Array.isArray(input[0])) {
227
+ return input;
228
+ }
229
+ // Single step array
230
+ return [input];
231
+ }
202
232
  /**
203
233
  * Check if a key combination is already registered
204
234
  * @returns The ID of the conflicting shortcut, or null if no conflict
205
235
  */
206
236
  findConflict(newShortcut) {
207
237
  for (const existing of this.shortcuts.values()) {
208
- if (this.keysMatch(newShortcut.keys, existing.keys)) {
238
+ // Compare single-step shapes if provided
239
+ if (newShortcut.keys && existing.keys && this.keysMatch(newShortcut.keys, existing.keys)) {
209
240
  return existing.id;
210
241
  }
211
- if (this.keysMatch(newShortcut.macKeys, existing.macKeys)) {
242
+ if (newShortcut.macKeys && existing.macKeys && this.keysMatch(newShortcut.macKeys, existing.macKeys)) {
243
+ return existing.id;
244
+ }
245
+ // Compare multi-step shapes
246
+ if (newShortcut.steps && existing.steps && this.stepsMatch(newShortcut.steps, existing.steps)) {
247
+ return existing.id;
248
+ }
249
+ if (newShortcut.macSteps && existing.macSteps && this.stepsMatch(newShortcut.macSteps, existing.macSteps)) {
212
250
  return existing.id;
213
251
  }
214
252
  }
@@ -229,12 +267,13 @@ class KeyboardShortcuts {
229
267
  this.shortcuts.set(shortcut.id, shortcut);
230
268
  this.activeShortcuts.add(shortcut.id);
231
269
  this.updateState();
270
+ this.setupActiveUntil(shortcut.activeUntil, this.unregister.bind(this, shortcut.id));
232
271
  }
233
272
  /**
234
273
  * Register multiple keyboard shortcuts as a group
235
274
  * @throws KeyboardShortcutError if group ID is already registered or if any shortcut ID or key combination conflicts
236
275
  */
237
- registerGroup(groupId, shortcuts) {
276
+ registerGroup(groupId, shortcuts, activeUntil) {
238
277
  // Check if group ID already exists
239
278
  if (this.groups.has(groupId)) {
240
279
  throw KeyboardShortcutsErrorFactory.groupAlreadyRegistered(groupId);
@@ -286,6 +325,7 @@ class KeyboardShortcuts {
286
325
  this.activeShortcuts.add(shortcut.id);
287
326
  });
288
327
  });
328
+ this.setupActiveUntil(activeUntil, this.unregisterGroup.bind(this, groupId));
289
329
  }
290
330
  /**
291
331
  * Unregister a single keyboard shortcut
@@ -413,7 +453,15 @@ class KeyboardShortcuts {
413
453
  if (!this.isBrowser || this.isListening) {
414
454
  return;
415
455
  }
456
+ // Listen to both keydown and keyup so we can maintain a Set of currently
457
+ // pressed physical keys. We avoid passive:true because we may call
458
+ // preventDefault() when matching shortcuts.
416
459
  document.addEventListener('keydown', this.keydownListener, { passive: false });
460
+ document.addEventListener('keyup', this.keyupListener, { passive: false });
461
+ // Listen for blur/visibility changes so we can clear the currently-down keys
462
+ // and avoid stale state when the browser or tab loses focus.
463
+ window.addEventListener('blur', this.blurListener);
464
+ document.addEventListener('visibilitychange', this.visibilityListener);
417
465
  this.isListening = true;
418
466
  }
419
467
  stopListening() {
@@ -421,29 +469,208 @@ class KeyboardShortcuts {
421
469
  return;
422
470
  }
423
471
  document.removeEventListener('keydown', this.keydownListener);
472
+ document.removeEventListener('keyup', this.keyupListener);
473
+ window.removeEventListener('blur', this.blurListener);
474
+ document.removeEventListener('visibilitychange', this.visibilityListener);
424
475
  this.isListening = false;
425
476
  }
426
477
  handleKeydown(event) {
427
- const pressedKeys = this.getPressedKeys(event);
478
+ // Update the currently down keys with this event's key
479
+ this.updateCurrentlyDownKeysOnKeydown(event);
480
+ // Build the pressed keys set used for matching. Prefer the currentlyDownKeys
481
+ // if it contains more than one non-modifier key; otherwise fall back to the
482
+ // traditional per-event pressed keys calculation for compatibility.
483
+ // Use a Set for matching to avoid allocations and sorting on every event
484
+ const pressedKeys = this.buildPressedKeysForMatch(event);
428
485
  const isMac = this.isMacPlatform();
486
+ // If there is a pending multi-step sequence, try to advance it first
487
+ if (this.pendingSequence) {
488
+ const pending = this.pendingSequence;
489
+ const shortcut = this.shortcuts.get(pending.shortcutId);
490
+ if (shortcut) {
491
+ const steps = isMac
492
+ ? (shortcut.macSteps ?? shortcut.macKeys ?? shortcut.steps ?? shortcut.keys ?? [])
493
+ : (shortcut.steps ?? shortcut.keys ?? shortcut.macSteps ?? shortcut.macKeys ?? []);
494
+ const normalizedSteps = this.normalizeToSteps(steps);
495
+ const expected = normalizedSteps[pending.stepIndex];
496
+ // Use per-event pressed keys for advancing sequence steps. Relying on
497
+ // the accumulated `currentlyDownKeys` can accidentally include keys
498
+ // from previous steps (if tests or callers don't emit keyup), which
499
+ // would prevent matching a simple single-key step like ['s'] after
500
+ // a prior ['k'] step. Use getPressedKeys(event) which reflects the
501
+ // actual modifier/main-key state for this event.
502
+ const stepPressed = this.getPressedKeys(event);
503
+ if (expected && this.keysMatch(stepPressed, expected)) {
504
+ // Advance sequence
505
+ clearTimeout(pending.timerId);
506
+ pending.stepIndex += 1;
507
+ if (pending.stepIndex >= normalizedSteps.length) {
508
+ // Completed
509
+ event.preventDefault();
510
+ event.stopPropagation();
511
+ try {
512
+ shortcut.action();
513
+ }
514
+ catch (error) {
515
+ console.error(`Error executing keyboard shortcut "${shortcut.id}":`, error);
516
+ }
517
+ this.pendingSequence = null;
518
+ return;
519
+ }
520
+ // Reset timer for next step
521
+ pending.timerId = setTimeout(() => { this.pendingSequence = null; }, this.sequenceTimeout);
522
+ return;
523
+ }
524
+ else {
525
+ // Cancel pending if doesn't match
526
+ this.clearPendingSequence();
527
+ }
528
+ }
529
+ else {
530
+ // pending exists but shortcut not found
531
+ this.clearPendingSequence();
532
+ }
533
+ }
534
+ // No pending sequence - check active shortcuts for a match or sequence start
429
535
  for (const shortcutId of this.activeShortcuts) {
430
536
  const shortcut = this.shortcuts.get(shortcutId);
431
537
  if (!shortcut)
432
538
  continue;
433
- const targetKeys = isMac ? shortcut.macKeys : shortcut.keys;
434
- if (this.keysMatch(pressedKeys, targetKeys)) {
435
- event.preventDefault();
436
- event.stopPropagation();
437
- try {
438
- shortcut.action();
539
+ const steps = isMac
540
+ ? (shortcut.macSteps ?? shortcut.macKeys ?? shortcut.steps ?? shortcut.keys ?? [])
541
+ : (shortcut.steps ?? shortcut.keys ?? shortcut.macSteps ?? shortcut.macKeys ?? []);
542
+ const normalizedSteps = this.normalizeToSteps(steps);
543
+ const firstStep = normalizedSteps[0];
544
+ if (this.keysMatch(pressedKeys, firstStep)) {
545
+ if (normalizedSteps.length === 1) {
546
+ // single-step
547
+ event.preventDefault();
548
+ event.stopPropagation();
549
+ try {
550
+ shortcut.action();
551
+ }
552
+ catch (error) {
553
+ console.error(`Error executing keyboard shortcut "${shortcut.id}":`, error);
554
+ }
555
+ break;
439
556
  }
440
- catch (error) {
441
- console.error(`Error executing keyboard shortcut "${shortcut.id}":`, error);
557
+ else {
558
+ // start pending sequence
559
+ if (this.pendingSequence) {
560
+ this.clearPendingSequence();
561
+ }
562
+ this.pendingSequence = {
563
+ shortcutId: shortcut.id,
564
+ stepIndex: 1,
565
+ timerId: setTimeout(() => { this.pendingSequence = null; }, this.sequenceTimeout)
566
+ };
567
+ event.preventDefault();
568
+ event.stopPropagation();
569
+ return;
442
570
  }
443
- break; // Only execute the first matching shortcut
444
571
  }
445
572
  }
446
573
  }
574
+ handleKeyup(event) {
575
+ // Remove the key from currentlyDownKeys on keyup
576
+ const key = event.key ? event.key.toLowerCase() : '';
577
+ if (key && !['control', 'alt', 'shift', 'meta'].includes(key)) {
578
+ this.currentlyDownKeys.delete(key);
579
+ }
580
+ }
581
+ /**
582
+ * Clear the currently-down keys. Exposed for testing and for use by
583
+ * blur/visibilitychange handlers to avoid stale state when the page loses focus.
584
+ */
585
+ clearCurrentlyDownKeys() {
586
+ this.currentlyDownKeys.clear();
587
+ }
588
+ handleWindowBlur() {
589
+ this.clearCurrentlyDownKeys();
590
+ // Clear any pressed keys and any pending multi-step sequence to avoid
591
+ // stale state when the window loses focus.
592
+ this.clearPendingSequence();
593
+ }
594
+ handleVisibilityChange() {
595
+ if (document.visibilityState === 'hidden') {
596
+ // When the document becomes hidden, clear both pressed keys and any
597
+ // pending multi-step sequence. This prevents sequences from remaining
598
+ // active when the user switches tabs or minimizes the window.
599
+ this.clearCurrentlyDownKeys();
600
+ this.clearPendingSequence();
601
+ }
602
+ }
603
+ /**
604
+ * Update the currentlyDownKeys set when keydown events happen.
605
+ * Normalizes common keys (function keys, space, etc.) to the same values
606
+ * used by getPressedKeys/keysMatch.
607
+ */
608
+ updateCurrentlyDownKeysOnKeydown(event) {
609
+ const key = event.key ? event.key.toLowerCase() : '';
610
+ // Ignore modifier-only keydown entries
611
+ if (['control', 'alt', 'shift', 'meta'].includes(key)) {
612
+ return;
613
+ }
614
+ // Normalize some special cases similar to the demo component's recording logic
615
+ if (event.code && event.code.startsWith('F') && /^F\d+$/.test(event.code)) {
616
+ this.currentlyDownKeys.add(event.code.toLowerCase());
617
+ return;
618
+ }
619
+ if (key === ' ') {
620
+ this.currentlyDownKeys.add('space');
621
+ return;
622
+ }
623
+ if (key === 'escape') {
624
+ this.currentlyDownKeys.add('escape');
625
+ return;
626
+ }
627
+ if (key === 'enter') {
628
+ this.currentlyDownKeys.add('enter');
629
+ return;
630
+ }
631
+ if (key && key.length > 0) {
632
+ this.currentlyDownKeys.add(key);
633
+ }
634
+ }
635
+ /**
636
+ * Build the pressed keys array used for matching against registered shortcuts.
637
+ * If multiple non-modifier keys are currently down, include them (chord support).
638
+ * Otherwise fall back to single main-key detection from the event for compatibility.
639
+ */
640
+ /**
641
+ * Build the pressed keys set used for matching against registered shortcuts.
642
+ * If multiple non-modifier keys are currently down, include them (chord support).
643
+ * Otherwise fall back to single main-key detection from the event for compatibility.
644
+ *
645
+ * Returns a Set<string> (lowercased) to allow O(1) lookups and O(n) comparisons
646
+ * without sorting or allocating sorted arrays on every event.
647
+ */
648
+ buildPressedKeysForMatch(event) {
649
+ const modifiers = new Set();
650
+ if (event.ctrlKey)
651
+ modifiers.add('ctrl');
652
+ if (event.altKey)
653
+ modifiers.add('alt');
654
+ if (event.shiftKey)
655
+ modifiers.add('shift');
656
+ if (event.metaKey)
657
+ modifiers.add('meta');
658
+ // Collect non-modifier keys from currentlyDownKeys (excluding modifiers)
659
+ const nonModifierKeys = Array.from(this.currentlyDownKeys).filter(k => !['control', 'alt', 'shift', 'meta'].includes(k));
660
+ const result = new Set();
661
+ // Add modifiers first
662
+ modifiers.forEach(m => result.add(m));
663
+ if (nonModifierKeys.length > 0) {
664
+ nonModifierKeys.forEach(k => result.add(k.toLowerCase()));
665
+ return result;
666
+ }
667
+ // Fallback: single main key from the event (existing behavior)
668
+ const key = event.key.toLowerCase();
669
+ if (!['control', 'alt', 'shift', 'meta'].includes(key)) {
670
+ result.add(key);
671
+ }
672
+ return result;
673
+ }
447
674
  getPressedKeys(event) {
448
675
  const keys = [];
449
676
  if (event.ctrlKey)
@@ -461,18 +688,69 @@ class KeyboardShortcuts {
461
688
  }
462
689
  return keys;
463
690
  }
691
+ /**
692
+ * Compare pressed keys against a target key combination.
693
+ * Accepts either a Set<string> (preferred) or an array for backwards compatibility.
694
+ * Uses Set-based comparison: sizes must match and every element in target must exist in pressed.
695
+ */
464
696
  keysMatch(pressedKeys, targetKeys) {
465
- if (pressedKeys.length !== targetKeys.length) {
697
+ // Normalize targetKeys into a Set<string> (lowercased)
698
+ const normalizedTarget = new Set(targetKeys.map(k => k.toLowerCase()));
699
+ // Normalize pressedKeys into a Set<string> if it's an array
700
+ const pressedSet = Array.isArray(pressedKeys)
701
+ ? new Set(pressedKeys.map(k => k.toLowerCase()))
702
+ : new Set(Array.from(pressedKeys).map(k => k.toLowerCase()));
703
+ if (pressedSet.size !== normalizedTarget.size) {
704
+ return false;
705
+ }
706
+ // Check if every element in normalizedTarget exists in pressedSet
707
+ for (const key of normalizedTarget) {
708
+ if (!pressedSet.has(key)) {
709
+ return false;
710
+ }
711
+ }
712
+ return true;
713
+ }
714
+ /** Compare two multi-step sequences for equality */
715
+ stepsMatch(a, b) {
716
+ if (a.length !== b.length)
466
717
  return false;
718
+ for (let i = 0; i < a.length; i++) {
719
+ if (!this.keysMatch(a[i], b[i]))
720
+ return false;
721
+ }
722
+ return true;
723
+ }
724
+ /** Safely clear any pending multi-step sequence */
725
+ clearPendingSequence() {
726
+ if (!this.pendingSequence)
727
+ return;
728
+ try {
729
+ clearTimeout(this.pendingSequence.timerId);
467
730
  }
468
- // Normalize and sort both arrays for comparison
469
- const normalizedPressed = pressedKeys.map(key => key.toLowerCase()).sort();
470
- const normalizedTarget = targetKeys.map(key => key.toLowerCase()).sort();
471
- return normalizedPressed.every((key, index) => key === normalizedTarget[index]);
731
+ catch { /* ignore */ }
732
+ this.pendingSequence = null;
472
733
  }
473
734
  isMacPlatform() {
474
735
  return this.isBrowser && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
475
736
  }
737
+ setupActiveUntil(activeUntil, unregister) {
738
+ if (!activeUntil) {
739
+ return;
740
+ }
741
+ if (activeUntil === 'destruct') {
742
+ inject(DestroyRef).onDestroy(unregister);
743
+ return;
744
+ }
745
+ if (activeUntil instanceof DestroyRef) {
746
+ activeUntil.onDestroy(unregister);
747
+ return;
748
+ }
749
+ if (activeUntil instanceof Observable) {
750
+ activeUntil.pipe(take(1)).subscribe(unregister);
751
+ return;
752
+ }
753
+ }
476
754
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: KeyboardShortcuts, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
477
755
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: KeyboardShortcuts, providedIn: 'root' });
478
756
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ngx-keys.mjs","sources":["../../../projects/ngx-keys/src/lib/keyboard-shortcuts.errors.ts","../../../projects/ngx-keys/src/lib/keyboard-shortcuts.ts","../../../projects/ngx-keys/src/public-api.ts","../../../projects/ngx-keys/src/ngx-keys.ts"],"sourcesContent":["/**\n * Centralized error messages for keyboard shortcuts service\n * This ensures consistency across the application and makes testing easier\n */\nexport const KeyboardShortcutsErrors = {\n // Registration errors\n SHORTCUT_ALREADY_REGISTERED: (id: string) => `Shortcut \"${id}\" already registered`,\n GROUP_ALREADY_REGISTERED: (id: string) => `Group \"${id}\" already registered`,\n KEY_CONFLICT: (conflictId: string) => `Key conflict with \"${conflictId}\"`,\n SHORTCUT_IDS_ALREADY_REGISTERED: (ids: string[]) => `Shortcut IDs already registered: ${ids.join(', ')}`,\n DUPLICATE_SHORTCUTS_IN_GROUP: (ids: string[]) => `Duplicate shortcuts in group: ${ids.join(', ')}`,\n KEY_CONFLICTS_IN_GROUP: (conflicts: string[]) => `Key conflicts: ${conflicts.join(', ')}`,\n \n // Operation errors\n SHORTCUT_NOT_REGISTERED: (id: string) => `Shortcut \"${id}\" not registered`,\n GROUP_NOT_REGISTERED: (id: string) => `Group \"${id}\" not registered`,\n \n // Activation/Deactivation errors\n CANNOT_ACTIVATE_SHORTCUT: (id: string) => `Cannot activate shortcut \"${id}\": not registered`,\n CANNOT_DEACTIVATE_SHORTCUT: (id: string) => `Cannot deactivate shortcut \"${id}\": not registered`,\n CANNOT_ACTIVATE_GROUP: (id: string) => `Cannot activate group \"${id}\": not registered`,\n CANNOT_DEACTIVATE_GROUP: (id: string) => `Cannot deactivate group \"${id}\": not registered`,\n \n // Unregistration errors\n CANNOT_UNREGISTER_SHORTCUT: (id: string) => `Cannot unregister shortcut \"${id}\": not registered`,\n CANNOT_UNREGISTER_GROUP: (id: string) => `Cannot unregister group \"${id}\": not registered`,\n} as const;\n\n/**\n * Error types for type safety\n */\nexport type KeyboardShortcutsErrorType = keyof typeof KeyboardShortcutsErrors;\n\n/**\n * Custom error class for keyboard shortcuts\n */\nexport class KeyboardShortcutError extends Error {\n constructor(\n public readonly errorType: KeyboardShortcutsErrorType,\n message: string,\n public readonly context?: Record<string, any>\n ) {\n super(message);\n this.name = 'KeyboardShortcutError';\n }\n}\n\n/**\n * Error factory for creating consistent errors\n */\nexport class KeyboardShortcutsErrorFactory {\n static shortcutAlreadyRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'SHORTCUT_ALREADY_REGISTERED',\n KeyboardShortcutsErrors.SHORTCUT_ALREADY_REGISTERED(id),\n { shortcutId: id }\n );\n }\n\n static groupAlreadyRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'GROUP_ALREADY_REGISTERED',\n KeyboardShortcutsErrors.GROUP_ALREADY_REGISTERED(id),\n { groupId: id }\n );\n }\n\n static keyConflict(conflictId: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'KEY_CONFLICT',\n KeyboardShortcutsErrors.KEY_CONFLICT(conflictId),\n { conflictId }\n );\n }\n\n static shortcutIdsAlreadyRegistered(ids: string[]): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'SHORTCUT_IDS_ALREADY_REGISTERED',\n KeyboardShortcutsErrors.SHORTCUT_IDS_ALREADY_REGISTERED(ids),\n { duplicateIds: ids }\n );\n }\n\n static duplicateShortcutsInGroup(ids: string[]): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'DUPLICATE_SHORTCUTS_IN_GROUP',\n KeyboardShortcutsErrors.DUPLICATE_SHORTCUTS_IN_GROUP(ids),\n { duplicateIds: ids }\n );\n }\n\n static keyConflictsInGroup(conflicts: string[]): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'KEY_CONFLICTS_IN_GROUP',\n KeyboardShortcutsErrors.KEY_CONFLICTS_IN_GROUP(conflicts),\n { conflicts }\n );\n }\n\n static shortcutNotRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'SHORTCUT_NOT_REGISTERED',\n KeyboardShortcutsErrors.SHORTCUT_NOT_REGISTERED(id),\n { shortcutId: id }\n );\n }\n\n static groupNotRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'GROUP_NOT_REGISTERED',\n KeyboardShortcutsErrors.GROUP_NOT_REGISTERED(id),\n { groupId: id }\n );\n }\n\n static cannotActivateShortcut(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_ACTIVATE_SHORTCUT',\n KeyboardShortcutsErrors.CANNOT_ACTIVATE_SHORTCUT(id),\n { shortcutId: id }\n );\n }\n\n static cannotDeactivateShortcut(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_DEACTIVATE_SHORTCUT',\n KeyboardShortcutsErrors.CANNOT_DEACTIVATE_SHORTCUT(id),\n { shortcutId: id }\n );\n }\n\n static cannotActivateGroup(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_ACTIVATE_GROUP',\n KeyboardShortcutsErrors.CANNOT_ACTIVATE_GROUP(id),\n { groupId: id }\n );\n }\n\n static cannotDeactivateGroup(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_DEACTIVATE_GROUP',\n KeyboardShortcutsErrors.CANNOT_DEACTIVATE_GROUP(id),\n { groupId: id }\n );\n }\n\n static cannotUnregisterShortcut(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_UNREGISTER_SHORTCUT',\n KeyboardShortcutsErrors.CANNOT_UNREGISTER_SHORTCUT(id),\n { shortcutId: id }\n );\n }\n\n static cannotUnregisterGroup(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_UNREGISTER_GROUP',\n KeyboardShortcutsErrors.CANNOT_UNREGISTER_GROUP(id),\n { groupId: id }\n );\n }\n}\n","import { Injectable, OnDestroy, PLATFORM_ID, inject, signal, computed } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KeyboardShortcut, KeyboardShortcutGroup, KeyboardShortcutUI } from './keyboard-shortcut.interface';\nimport { KeyboardShortcutsErrorFactory } from './keyboard-shortcuts.errors';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class KeyboardShortcuts implements OnDestroy {\n private readonly shortcuts = new Map<string, KeyboardShortcut>();\n private readonly groups = new Map<string, KeyboardShortcutGroup>();\n private readonly activeShortcuts = new Set<string>();\n private readonly activeGroups = new Set<string>();\n \n // Single consolidated state signal - reduces memory overhead\n private readonly state = signal({\n shortcuts: new Map<string, KeyboardShortcut>(),\n groups: new Map<string, KeyboardShortcutGroup>(),\n activeShortcuts: new Set<string>(),\n activeGroups: new Set<string>(),\n version: 0 // for change detection optimization\n });\n \n // Primary computed signal - consumers derive what they need from this\n readonly shortcuts$ = computed(() => {\n const state = this.state();\n const activeShortcuts = Array.from(state.activeShortcuts)\n .map(id => state.shortcuts.get(id))\n .filter((s): s is KeyboardShortcut => s !== undefined);\n \n const inactiveShortcuts = Array.from(state.shortcuts.values())\n .filter(s => !state.activeShortcuts.has(s.id));\n \n return {\n active: activeShortcuts,\n inactive: inactiveShortcuts,\n all: Array.from(state.shortcuts.values()),\n groups: {\n active: Array.from(state.activeGroups),\n inactive: Array.from(state.groups.keys())\n .filter(id => !state.activeGroups.has(id))\n }\n };\n });\n\n // Optional: Pre-formatted UI signal for components that need it\n readonly shortcutsUI$ = computed(() => {\n const shortcuts = this.shortcuts$();\n return {\n active: shortcuts.active.map(s => this.formatShortcutForUI(s)),\n inactive: shortcuts.inactive.map(s => this.formatShortcutForUI(s)),\n all: shortcuts.all.map(s => this.formatShortcutForUI(s))\n };\n });\n \n private readonly keydownListener = this.handleKeydown.bind(this);\n private isListening = false;\n protected isBrowser: boolean;\n\n constructor() {\n // Use try-catch to handle injection context for better testability\n try {\n const platformId = inject(PLATFORM_ID);\n this.isBrowser = isPlatformBrowser(platformId);\n } catch {\n // Fallback for testing - assume browser environment\n this.isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\n }\n\n if (this.isBrowser) {\n this.startListening();\n }\n }\n\n ngOnDestroy(): void {\n this.stopListening();\n }\n\n /**\n * Batch updates and increment version for change detection\n */\n private updateState(): void {\n this.state.update(current => ({\n shortcuts: new Map(this.shortcuts),\n groups: new Map(this.groups),\n activeShortcuts: new Set(this.activeShortcuts),\n activeGroups: new Set(this.activeGroups),\n version: current.version + 1\n }));\n }\n\n /**\n * Utility method for UI formatting\n */\n formatShortcutForUI(shortcut: KeyboardShortcut): KeyboardShortcutUI {\n return {\n id: shortcut.id,\n keys: this.formatKeysForDisplay(shortcut.keys, false),\n macKeys: this.formatKeysForDisplay(shortcut.macKeys, true),\n description: shortcut.description\n };\n }\n\n /**\n * Utility method for batch operations - reduces signal updates\n */\n batchUpdate(operations: () => void): void {\n operations();\n this.updateState();\n }\n\n /**\n * Format keys for display with proper Unicode symbols\n */\n private formatKeysForDisplay(keys: string[], isMac = false): string {\n const keyMap: Record<string, string> = isMac ? {\n 'ctrl': '⌃',\n 'alt': '⌥', \n 'shift': '⇧',\n 'meta': '⌘',\n 'cmd': '⌘',\n 'command': '⌘'\n } : {\n 'ctrl': 'Ctrl',\n 'alt': 'Alt',\n 'shift': 'Shift', \n 'meta': 'Win'\n };\n\n return keys\n .map(key => keyMap[key.toLowerCase()] || key.toUpperCase())\n .join('+');\n }\n\n /**\n * Check if a key combination is already registered\n * @returns The ID of the conflicting shortcut, or null if no conflict\n */\n private findConflict(newShortcut: KeyboardShortcut): string | null {\n for (const existing of this.shortcuts.values()) {\n if (this.keysMatch(newShortcut.keys, existing.keys)) {\n return existing.id;\n }\n if (this.keysMatch(newShortcut.macKeys, existing.macKeys)) {\n return existing.id;\n }\n }\n return null;\n }\n\n /**\n * Register a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID is already registered or key combination is in use\n */\n register(shortcut: KeyboardShortcut): void {\n if (this.shortcuts.has(shortcut.id)) {\n throw KeyboardShortcutsErrorFactory.shortcutAlreadyRegistered(shortcut.id);\n }\n\n const conflictId = this.findConflict(shortcut);\n if (conflictId) {\n throw KeyboardShortcutsErrorFactory.keyConflict(conflictId);\n }\n \n this.shortcuts.set(shortcut.id, shortcut);\n this.activeShortcuts.add(shortcut.id);\n this.updateState();\n }\n\n /**\n * Register multiple keyboard shortcuts as a group\n * @throws KeyboardShortcutError if group ID is already registered or if any shortcut ID or key combination conflicts\n */\n registerGroup(groupId: string, shortcuts: KeyboardShortcut[]): void {\n // Check if group ID already exists\n if (this.groups.has(groupId)) {\n throw KeyboardShortcutsErrorFactory.groupAlreadyRegistered(groupId);\n }\n \n // Check for duplicate shortcut IDs and key combination conflicts\n const duplicateIds: string[] = [];\n const keyConflicts: string[] = [];\n shortcuts.forEach(shortcut => {\n if (this.shortcuts.has(shortcut.id)) {\n duplicateIds.push(shortcut.id);\n }\n const conflictId = this.findConflict(shortcut);\n if (conflictId) {\n keyConflicts.push(`\"${shortcut.id}\" conflicts with \"${conflictId}\"`);\n }\n });\n \n if (duplicateIds.length > 0) {\n throw KeyboardShortcutsErrorFactory.shortcutIdsAlreadyRegistered(duplicateIds);\n }\n\n if (keyConflicts.length > 0) {\n throw KeyboardShortcutsErrorFactory.keyConflictsInGroup(keyConflicts);\n }\n \n // Validate that all shortcuts have unique IDs within the group\n const groupIds = new Set<string>();\n const duplicatesInGroup: string[] = [];\n shortcuts.forEach(shortcut => {\n if (groupIds.has(shortcut.id)) {\n duplicatesInGroup.push(shortcut.id);\n } else {\n groupIds.add(shortcut.id);\n }\n });\n \n if (duplicatesInGroup.length > 0) {\n throw KeyboardShortcutsErrorFactory.duplicateShortcutsInGroup(duplicatesInGroup);\n }\n \n // Use batch update to reduce signal updates\n this.batchUpdate(() => {\n const group: KeyboardShortcutGroup = {\n id: groupId,\n shortcuts,\n active: true\n };\n \n this.groups.set(groupId, group);\n this.activeGroups.add(groupId);\n \n // Register individual shortcuts\n shortcuts.forEach(shortcut => {\n this.shortcuts.set(shortcut.id, shortcut);\n this.activeShortcuts.add(shortcut.id);\n });\n });\n }\n\n /**\n * Unregister a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID doesn't exist\n */\n unregister(shortcutId: string): void {\n if (!this.shortcuts.has(shortcutId)) {\n throw KeyboardShortcutsErrorFactory.cannotUnregisterShortcut(shortcutId);\n }\n \n this.shortcuts.delete(shortcutId);\n this.activeShortcuts.delete(shortcutId);\n this.updateState();\n }\n\n /**\n * Unregister a group of keyboard shortcuts\n * @throws KeyboardShortcutError if group ID doesn't exist\n */\n unregisterGroup(groupId: string): void {\n const group = this.groups.get(groupId);\n if (!group) {\n throw KeyboardShortcutsErrorFactory.cannotUnregisterGroup(groupId);\n }\n \n this.batchUpdate(() => {\n group.shortcuts.forEach(shortcut => {\n this.shortcuts.delete(shortcut.id);\n this.activeShortcuts.delete(shortcut.id);\n });\n this.groups.delete(groupId);\n this.activeGroups.delete(groupId);\n });\n }\n\n /**\n * Activate a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID doesn't exist\n */\n activate(shortcutId: string): void {\n if (!this.shortcuts.has(shortcutId)) {\n throw KeyboardShortcutsErrorFactory.cannotActivateShortcut(shortcutId);\n }\n \n this.activeShortcuts.add(shortcutId);\n this.updateState();\n }\n\n /**\n * Deactivate a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID doesn't exist\n */\n deactivate(shortcutId: string): void {\n if (!this.shortcuts.has(shortcutId)) {\n throw KeyboardShortcutsErrorFactory.cannotDeactivateShortcut(shortcutId);\n }\n \n this.activeShortcuts.delete(shortcutId);\n this.updateState();\n }\n\n /**\n * Activate a group of keyboard shortcuts\n * @throws KeyboardShortcutError if group ID doesn't exist\n */\n activateGroup(groupId: string): void {\n const group = this.groups.get(groupId);\n if (!group) {\n throw KeyboardShortcutsErrorFactory.cannotActivateGroup(groupId);\n }\n \n this.batchUpdate(() => {\n group.active = true;\n this.activeGroups.add(groupId);\n group.shortcuts.forEach(shortcut => {\n this.activeShortcuts.add(shortcut.id);\n });\n });\n }\n\n /**\n * Deactivate a group of keyboard shortcuts\n * @throws KeyboardShortcutError if group ID doesn't exist\n */\n deactivateGroup(groupId: string): void {\n const group = this.groups.get(groupId);\n if (!group) {\n throw KeyboardShortcutsErrorFactory.cannotDeactivateGroup(groupId);\n }\n \n this.batchUpdate(() => {\n group.active = false;\n this.activeGroups.delete(groupId);\n group.shortcuts.forEach(shortcut => {\n this.activeShortcuts.delete(shortcut.id);\n });\n });\n }\n\n /**\n * Check if a shortcut is active\n */\n isActive(shortcutId: string): boolean {\n return this.activeShortcuts.has(shortcutId);\n }\n\n /**\n * Check if a shortcut is registered\n */\n isRegistered(shortcutId: string): boolean {\n return this.shortcuts.has(shortcutId);\n }\n\n /**\n * Check if a group is active\n */\n isGroupActive(groupId: string): boolean {\n return this.activeGroups.has(groupId);\n }\n\n /**\n * Check if a group is registered\n */\n isGroupRegistered(groupId: string): boolean {\n return this.groups.has(groupId);\n }\n\n /**\n * Get all registered shortcuts\n */\n getShortcuts(): ReadonlyMap<string, KeyboardShortcut> {\n return this.shortcuts;\n }\n\n /**\n * Get all registered groups\n */\n getGroups(): ReadonlyMap<string, KeyboardShortcutGroup> {\n return this.groups;\n }\n\n private startListening(): void {\n if (!this.isBrowser || this.isListening) {\n return;\n }\n \n document.addEventListener('keydown', this.keydownListener, { passive: false });\n this.isListening = true;\n }\n\n private stopListening(): void {\n if (!this.isBrowser || !this.isListening) {\n return;\n }\n \n document.removeEventListener('keydown', this.keydownListener);\n this.isListening = false;\n }\n\n protected handleKeydown(event: KeyboardEvent): void {\n const pressedKeys = this.getPressedKeys(event);\n const isMac = this.isMacPlatform();\n \n for (const shortcutId of this.activeShortcuts) {\n const shortcut = this.shortcuts.get(shortcutId);\n if (!shortcut) continue;\n \n const targetKeys = isMac ? shortcut.macKeys : shortcut.keys;\n \n if (this.keysMatch(pressedKeys, targetKeys)) {\n event.preventDefault();\n event.stopPropagation();\n \n try {\n shortcut.action();\n } catch (error) {\n console.error(`Error executing keyboard shortcut \"${shortcut.id}\":`, error);\n }\n \n break; // Only execute the first matching shortcut\n }\n }\n }\n\n protected getPressedKeys(event: KeyboardEvent): string[] {\n const keys: string[] = [];\n \n if (event.ctrlKey) keys.push('ctrl');\n if (event.altKey) keys.push('alt');\n if (event.shiftKey) keys.push('shift');\n if (event.metaKey) keys.push('meta');\n \n // Add the main key (normalize to lowercase)\n const key = event.key.toLowerCase();\n if (!['control', 'alt', 'shift', 'meta'].includes(key)) {\n keys.push(key);\n }\n \n return keys;\n }\n\n protected keysMatch(pressedKeys: string[], targetKeys: string[]): boolean {\n if (pressedKeys.length !== targetKeys.length) {\n return false;\n }\n \n // Normalize and sort both arrays for comparison\n const normalizedPressed = pressedKeys.map(key => key.toLowerCase()).sort();\n const normalizedTarget = targetKeys.map(key => key.toLowerCase()).sort();\n \n return normalizedPressed.every((key, index) => key === normalizedTarget[index]);\n }\n\n protected isMacPlatform(): boolean {\n return this.isBrowser && /Mac|iPod|iPhone|iPad/.test(navigator.platform);\n }\n}\n","/*\n * Public API Surface of ngx-keys\n */\n\nexport * from './lib/keyboard-shortcuts';\nexport * from './lib/keyboard-shortcut.interface';\nexport * from './lib/keyboard-shortcuts.errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngx-keys.mjs","sources":["../../../projects/ngx-keys/src/lib/keyboard-shortcuts.errors.ts","../../../projects/ngx-keys/src/lib/keyboard-shortcuts.ts","../../../projects/ngx-keys/src/public-api.ts","../../../projects/ngx-keys/src/ngx-keys.ts"],"sourcesContent":["/**\n * Centralized error messages for keyboard shortcuts service\n * This ensures consistency across the application and makes testing easier\n */\nexport const KeyboardShortcutsErrors = {\n // Registration errors\n SHORTCUT_ALREADY_REGISTERED: (id: string) => `Shortcut \"${id}\" already registered`,\n GROUP_ALREADY_REGISTERED: (id: string) => `Group \"${id}\" already registered`,\n KEY_CONFLICT: (conflictId: string) => `Key conflict with \"${conflictId}\"`,\n SHORTCUT_IDS_ALREADY_REGISTERED: (ids: string[]) => `Shortcut IDs already registered: ${ids.join(', ')}`,\n DUPLICATE_SHORTCUTS_IN_GROUP: (ids: string[]) => `Duplicate shortcuts in group: ${ids.join(', ')}`,\n KEY_CONFLICTS_IN_GROUP: (conflicts: string[]) => `Key conflicts: ${conflicts.join(', ')}`,\n \n // Operation errors\n SHORTCUT_NOT_REGISTERED: (id: string) => `Shortcut \"${id}\" not registered`,\n GROUP_NOT_REGISTERED: (id: string) => `Group \"${id}\" not registered`,\n \n // Activation/Deactivation errors\n CANNOT_ACTIVATE_SHORTCUT: (id: string) => `Cannot activate shortcut \"${id}\": not registered`,\n CANNOT_DEACTIVATE_SHORTCUT: (id: string) => `Cannot deactivate shortcut \"${id}\": not registered`,\n CANNOT_ACTIVATE_GROUP: (id: string) => `Cannot activate group \"${id}\": not registered`,\n CANNOT_DEACTIVATE_GROUP: (id: string) => `Cannot deactivate group \"${id}\": not registered`,\n \n // Unregistration errors\n CANNOT_UNREGISTER_SHORTCUT: (id: string) => `Cannot unregister shortcut \"${id}\": not registered`,\n CANNOT_UNREGISTER_GROUP: (id: string) => `Cannot unregister group \"${id}\": not registered`,\n} as const;\n\n/**\n * Error types for type safety\n */\nexport type KeyboardShortcutsErrorType = keyof typeof KeyboardShortcutsErrors;\n\n/**\n * Custom error class for keyboard shortcuts\n */\nexport class KeyboardShortcutError extends Error {\n constructor(\n public readonly errorType: KeyboardShortcutsErrorType,\n message: string,\n public readonly context?: Record<string, any>\n ) {\n super(message);\n this.name = 'KeyboardShortcutError';\n }\n}\n\n/**\n * Error factory for creating consistent errors\n */\nexport class KeyboardShortcutsErrorFactory {\n static shortcutAlreadyRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'SHORTCUT_ALREADY_REGISTERED',\n KeyboardShortcutsErrors.SHORTCUT_ALREADY_REGISTERED(id),\n { shortcutId: id }\n );\n }\n\n static groupAlreadyRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'GROUP_ALREADY_REGISTERED',\n KeyboardShortcutsErrors.GROUP_ALREADY_REGISTERED(id),\n { groupId: id }\n );\n }\n\n static keyConflict(conflictId: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'KEY_CONFLICT',\n KeyboardShortcutsErrors.KEY_CONFLICT(conflictId),\n { conflictId }\n );\n }\n\n static shortcutIdsAlreadyRegistered(ids: string[]): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'SHORTCUT_IDS_ALREADY_REGISTERED',\n KeyboardShortcutsErrors.SHORTCUT_IDS_ALREADY_REGISTERED(ids),\n { duplicateIds: ids }\n );\n }\n\n static duplicateShortcutsInGroup(ids: string[]): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'DUPLICATE_SHORTCUTS_IN_GROUP',\n KeyboardShortcutsErrors.DUPLICATE_SHORTCUTS_IN_GROUP(ids),\n { duplicateIds: ids }\n );\n }\n\n static keyConflictsInGroup(conflicts: string[]): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'KEY_CONFLICTS_IN_GROUP',\n KeyboardShortcutsErrors.KEY_CONFLICTS_IN_GROUP(conflicts),\n { conflicts }\n );\n }\n\n static shortcutNotRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'SHORTCUT_NOT_REGISTERED',\n KeyboardShortcutsErrors.SHORTCUT_NOT_REGISTERED(id),\n { shortcutId: id }\n );\n }\n\n static groupNotRegistered(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'GROUP_NOT_REGISTERED',\n KeyboardShortcutsErrors.GROUP_NOT_REGISTERED(id),\n { groupId: id }\n );\n }\n\n static cannotActivateShortcut(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_ACTIVATE_SHORTCUT',\n KeyboardShortcutsErrors.CANNOT_ACTIVATE_SHORTCUT(id),\n { shortcutId: id }\n );\n }\n\n static cannotDeactivateShortcut(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_DEACTIVATE_SHORTCUT',\n KeyboardShortcutsErrors.CANNOT_DEACTIVATE_SHORTCUT(id),\n { shortcutId: id }\n );\n }\n\n static cannotActivateGroup(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_ACTIVATE_GROUP',\n KeyboardShortcutsErrors.CANNOT_ACTIVATE_GROUP(id),\n { groupId: id }\n );\n }\n\n static cannotDeactivateGroup(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_DEACTIVATE_GROUP',\n KeyboardShortcutsErrors.CANNOT_DEACTIVATE_GROUP(id),\n { groupId: id }\n );\n }\n\n static cannotUnregisterShortcut(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_UNREGISTER_SHORTCUT',\n KeyboardShortcutsErrors.CANNOT_UNREGISTER_SHORTCUT(id),\n { shortcutId: id }\n );\n }\n\n static cannotUnregisterGroup(id: string): KeyboardShortcutError {\n return new KeyboardShortcutError(\n 'CANNOT_UNREGISTER_GROUP',\n KeyboardShortcutsErrors.CANNOT_UNREGISTER_GROUP(id),\n { groupId: id }\n );\n }\n}\n","import { DestroyRef, Injectable, OnDestroy, PLATFORM_ID, inject, signal, computed } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KeyboardShortcut, KeyboardShortcutActiveUntil, KeyboardShortcutGroup, KeyboardShortcutUI, KeyStep } from './keyboard-shortcut.interface'\nimport { KeyboardShortcutsErrorFactory } from './keyboard-shortcuts.errors';\nimport { Observable, take } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class KeyboardShortcuts implements OnDestroy {\n private readonly shortcuts = new Map<string, KeyboardShortcut>();\n private readonly groups = new Map<string, KeyboardShortcutGroup>();\n private readonly activeShortcuts = new Set<string>();\n private readonly activeGroups = new Set<string>();\n private readonly currentlyDownKeys = new Set<string>();\n \n // Single consolidated state signal - reduces memory overhead\n private readonly state = signal({\n shortcuts: new Map<string, KeyboardShortcut>(),\n groups: new Map<string, KeyboardShortcutGroup>(),\n activeShortcuts: new Set<string>(),\n activeGroups: new Set<string>(),\n version: 0 // for change detection optimization\n });\n \n // Primary computed signal - consumers derive what they need from this\n readonly shortcuts$ = computed(() => {\n const state = this.state();\n const activeShortcuts = Array.from(state.activeShortcuts)\n .map(id => state.shortcuts.get(id))\n .filter((s): s is KeyboardShortcut => s !== undefined);\n \n const inactiveShortcuts = Array.from(state.shortcuts.values())\n .filter(s => !state.activeShortcuts.has(s.id));\n \n return {\n active: activeShortcuts,\n inactive: inactiveShortcuts,\n all: Array.from(state.shortcuts.values()),\n groups: {\n active: Array.from(state.activeGroups),\n inactive: Array.from(state.groups.keys())\n .filter(id => !state.activeGroups.has(id))\n }\n };\n });\n\n // Optional: Pre-formatted UI signal for components that need it\n readonly shortcutsUI$ = computed(() => {\n const shortcuts = this.shortcuts$();\n return {\n active: shortcuts.active.map(s => this.formatShortcutForUI(s)),\n inactive: shortcuts.inactive.map(s => this.formatShortcutForUI(s)),\n all: shortcuts.all.map(s => this.formatShortcutForUI(s))\n };\n });\n \n private readonly keydownListener = this.handleKeydown.bind(this);\n private readonly keyupListener = this.handleKeyup.bind(this);\n private readonly blurListener = this.handleWindowBlur.bind(this);\n private readonly visibilityListener = this.handleVisibilityChange.bind(this);\n private isListening = false;\n protected isBrowser: boolean;\n /** Default timeout (ms) for completing a multi-step sequence */\n protected sequenceTimeout = 2000;\n\n /** Runtime state for multi-step sequences */\n private pendingSequence: {\n shortcutId: string;\n stepIndex: number;\n timerId: any;\n } | null = null;\n\n constructor() {\n // Use try-catch to handle injection context for better testability\n try {\n const platformId = inject(PLATFORM_ID);\n this.isBrowser = isPlatformBrowser(platformId);\n } catch {\n // Fallback for testing - assume browser environment\n this.isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\n }\n\n if (this.isBrowser) {\n this.startListening();\n }\n }\n\n ngOnDestroy(): void {\n this.stopListening();\n }\n\n /**\n * Batch updates and increment version for change detection\n */\n private updateState(): void {\n this.state.update(current => ({\n shortcuts: new Map(this.shortcuts),\n groups: new Map(this.groups),\n activeShortcuts: new Set(this.activeShortcuts),\n activeGroups: new Set(this.activeGroups),\n version: current.version + 1\n }));\n }\n\n /**\n * Utility method for UI formatting\n */\n formatShortcutForUI(shortcut: KeyboardShortcut): KeyboardShortcutUI {\n return {\n id: shortcut.id,\n keys: this.formatStepsForDisplay(shortcut.keys ?? shortcut.steps ?? [], false),\n macKeys: this.formatStepsForDisplay(shortcut.macKeys ?? shortcut.macSteps ?? [], true),\n description: shortcut.description\n };\n }\n\n /**\n * Utility method for batch operations - reduces signal updates\n */\n batchUpdate(operations: () => void): void {\n operations();\n this.updateState();\n }\n\n /**\n * Format keys for display with proper Unicode symbols\n */\n private formatKeysForDisplay(keys: string[], isMac = false): string {\n const keyMap: Record<string, string> = isMac ? {\n 'ctrl': '⌃',\n 'alt': '⌥', \n 'shift': '⇧',\n 'meta': '⌘',\n 'cmd': '⌘',\n 'command': '⌘'\n } : {\n 'ctrl': 'Ctrl',\n 'alt': 'Alt',\n 'shift': 'Shift', \n 'meta': 'Win'\n };\n\n return keys\n .map(key => keyMap[key.toLowerCase()] || key.toUpperCase())\n .join('+');\n }\n\n private formatStepsForDisplay(steps: string[] | string[][], isMac = false): string {\n if (!steps) return '';\n\n // If the first element is an array, assume steps is string[][]\n const normalized = this.normalizeToSteps(steps as KeyStep[] | string[]);\n if (normalized.length === 0) return '';\n if (normalized.length === 1) return this.formatKeysForDisplay(normalized[0], isMac);\n return normalized.map(step => this.formatKeysForDisplay(step, isMac)).join(', ');\n }\n\n private normalizeToSteps(input: KeyStep[] | string[]): KeyStep[] {\n if (!input) return [];\n // If first element is an array, assume already KeyStep[]\n if (Array.isArray(input[0])) {\n return input as KeyStep[];\n }\n // Single step array\n return [input as string[]];\n }\n\n /**\n * Check if a key combination is already registered\n * @returns The ID of the conflicting shortcut, or null if no conflict\n */\n private findConflict(newShortcut: KeyboardShortcut): string | null {\n for (const existing of this.shortcuts.values()) {\n // Compare single-step shapes if provided\n if (newShortcut.keys && existing.keys && this.keysMatch(newShortcut.keys, existing.keys)) {\n return existing.id;\n }\n if (newShortcut.macKeys && existing.macKeys && this.keysMatch(newShortcut.macKeys, existing.macKeys)) {\n return existing.id;\n }\n\n // Compare multi-step shapes\n if (newShortcut.steps && existing.steps && this.stepsMatch(newShortcut.steps, existing.steps)) {\n return existing.id;\n }\n if (newShortcut.macSteps && existing.macSteps && this.stepsMatch(newShortcut.macSteps, existing.macSteps)) {\n return existing.id;\n }\n }\n return null;\n }\n\n /**\n * Register a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID is already registered or key combination is in use\n */\n register(shortcut: KeyboardShortcut): void {\n if (this.shortcuts.has(shortcut.id)) {\n throw KeyboardShortcutsErrorFactory.shortcutAlreadyRegistered(shortcut.id);\n }\n\n const conflictId = this.findConflict(shortcut);\n if (conflictId) {\n throw KeyboardShortcutsErrorFactory.keyConflict(conflictId);\n }\n \n this.shortcuts.set(shortcut.id, shortcut);\n this.activeShortcuts.add(shortcut.id);\n this.updateState();\n\n this.setupActiveUntil(\n shortcut.activeUntil,\n this.unregister.bind(this, shortcut.id),\n );\n }\n\n /**\n * Register multiple keyboard shortcuts as a group\n * @throws KeyboardShortcutError if group ID is already registered or if any shortcut ID or key combination conflicts\n */\n registerGroup(groupId: string, shortcuts: KeyboardShortcut[], activeUntil?: KeyboardShortcutActiveUntil): void {\n // Check if group ID already exists\n if (this.groups.has(groupId)) {\n throw KeyboardShortcutsErrorFactory.groupAlreadyRegistered(groupId);\n }\n \n // Check for duplicate shortcut IDs and key combination conflicts\n const duplicateIds: string[] = [];\n const keyConflicts: string[] = [];\n shortcuts.forEach(shortcut => {\n if (this.shortcuts.has(shortcut.id)) {\n duplicateIds.push(shortcut.id);\n }\n const conflictId = this.findConflict(shortcut);\n if (conflictId) {\n keyConflicts.push(`\"${shortcut.id}\" conflicts with \"${conflictId}\"`);\n }\n });\n \n if (duplicateIds.length > 0) {\n throw KeyboardShortcutsErrorFactory.shortcutIdsAlreadyRegistered(duplicateIds);\n }\n\n if (keyConflicts.length > 0) {\n throw KeyboardShortcutsErrorFactory.keyConflictsInGroup(keyConflicts);\n }\n \n // Validate that all shortcuts have unique IDs within the group\n const groupIds = new Set<string>();\n const duplicatesInGroup: string[] = [];\n shortcuts.forEach(shortcut => {\n if (groupIds.has(shortcut.id)) {\n duplicatesInGroup.push(shortcut.id);\n } else {\n groupIds.add(shortcut.id);\n }\n });\n \n if (duplicatesInGroup.length > 0) {\n throw KeyboardShortcutsErrorFactory.duplicateShortcutsInGroup(duplicatesInGroup);\n }\n \n // Use batch update to reduce signal updates\n this.batchUpdate(() => {\n const group: KeyboardShortcutGroup = {\n id: groupId,\n shortcuts,\n active: true\n };\n \n this.groups.set(groupId, group);\n this.activeGroups.add(groupId);\n \n // Register individual shortcuts\n shortcuts.forEach(shortcut => {\n this.shortcuts.set(shortcut.id, shortcut);\n this.activeShortcuts.add(shortcut.id);\n });\n });\n\n this.setupActiveUntil(\n activeUntil,\n this.unregisterGroup.bind(this, groupId),\n );\n }\n\n /**\n * Unregister a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID doesn't exist\n */\n unregister(shortcutId: string): void {\n if (!this.shortcuts.has(shortcutId)) {\n throw KeyboardShortcutsErrorFactory.cannotUnregisterShortcut(shortcutId);\n }\n \n this.shortcuts.delete(shortcutId);\n this.activeShortcuts.delete(shortcutId);\n this.updateState();\n }\n\n /**\n * Unregister a group of keyboard shortcuts\n * @throws KeyboardShortcutError if group ID doesn't exist\n */\n unregisterGroup(groupId: string): void {\n const group = this.groups.get(groupId);\n if (!group) {\n throw KeyboardShortcutsErrorFactory.cannotUnregisterGroup(groupId);\n }\n \n this.batchUpdate(() => {\n group.shortcuts.forEach(shortcut => {\n this.shortcuts.delete(shortcut.id);\n this.activeShortcuts.delete(shortcut.id);\n });\n this.groups.delete(groupId);\n this.activeGroups.delete(groupId);\n });\n }\n\n /**\n * Activate a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID doesn't exist\n */\n activate(shortcutId: string): void {\n if (!this.shortcuts.has(shortcutId)) {\n throw KeyboardShortcutsErrorFactory.cannotActivateShortcut(shortcutId);\n }\n \n this.activeShortcuts.add(shortcutId);\n this.updateState();\n }\n\n /**\n * Deactivate a single keyboard shortcut\n * @throws KeyboardShortcutError if shortcut ID doesn't exist\n */\n deactivate(shortcutId: string): void {\n if (!this.shortcuts.has(shortcutId)) {\n throw KeyboardShortcutsErrorFactory.cannotDeactivateShortcut(shortcutId);\n }\n \n this.activeShortcuts.delete(shortcutId);\n this.updateState();\n }\n\n /**\n * Activate a group of keyboard shortcuts\n * @throws KeyboardShortcutError if group ID doesn't exist\n */\n activateGroup(groupId: string): void {\n const group = this.groups.get(groupId);\n if (!group) {\n throw KeyboardShortcutsErrorFactory.cannotActivateGroup(groupId);\n }\n \n this.batchUpdate(() => {\n group.active = true;\n this.activeGroups.add(groupId);\n group.shortcuts.forEach(shortcut => {\n this.activeShortcuts.add(shortcut.id);\n });\n });\n }\n\n /**\n * Deactivate a group of keyboard shortcuts\n * @throws KeyboardShortcutError if group ID doesn't exist\n */\n deactivateGroup(groupId: string): void {\n const group = this.groups.get(groupId);\n if (!group) {\n throw KeyboardShortcutsErrorFactory.cannotDeactivateGroup(groupId);\n }\n \n this.batchUpdate(() => {\n group.active = false;\n this.activeGroups.delete(groupId);\n group.shortcuts.forEach(shortcut => {\n this.activeShortcuts.delete(shortcut.id);\n });\n });\n }\n\n /**\n * Check if a shortcut is active\n */\n isActive(shortcutId: string): boolean {\n return this.activeShortcuts.has(shortcutId);\n }\n\n /**\n * Check if a shortcut is registered\n */\n isRegistered(shortcutId: string): boolean {\n return this.shortcuts.has(shortcutId);\n }\n\n /**\n * Check if a group is active\n */\n isGroupActive(groupId: string): boolean {\n return this.activeGroups.has(groupId);\n }\n\n /**\n * Check if a group is registered\n */\n isGroupRegistered(groupId: string): boolean {\n return this.groups.has(groupId);\n }\n\n /**\n * Get all registered shortcuts\n */\n getShortcuts(): ReadonlyMap<string, KeyboardShortcut> {\n return this.shortcuts;\n }\n\n /**\n * Get all registered groups\n */\n getGroups(): ReadonlyMap<string, KeyboardShortcutGroup> {\n return this.groups;\n }\n\n private startListening(): void {\n if (!this.isBrowser || this.isListening) {\n return;\n }\n \n // Listen to both keydown and keyup so we can maintain a Set of currently\n // pressed physical keys. We avoid passive:true because we may call\n // preventDefault() when matching shortcuts.\n document.addEventListener('keydown', this.keydownListener, { passive: false });\n document.addEventListener('keyup', this.keyupListener, { passive: false });\n // Listen for blur/visibility changes so we can clear the currently-down keys\n // and avoid stale state when the browser or tab loses focus.\n window.addEventListener('blur', this.blurListener);\n document.addEventListener('visibilitychange', this.visibilityListener);\n this.isListening = true;\n }\n\n private stopListening(): void {\n if (!this.isBrowser || !this.isListening) {\n return;\n }\n \n document.removeEventListener('keydown', this.keydownListener);\n document.removeEventListener('keyup', this.keyupListener);\n window.removeEventListener('blur', this.blurListener);\n document.removeEventListener('visibilitychange', this.visibilityListener);\n this.isListening = false;\n }\n\n protected handleKeydown(event: KeyboardEvent): void {\n // Update the currently down keys with this event's key\n this.updateCurrentlyDownKeysOnKeydown(event);\n\n // Build the pressed keys set used for matching. Prefer the currentlyDownKeys\n // if it contains more than one non-modifier key; otherwise fall back to the\n // traditional per-event pressed keys calculation for compatibility.\n // Use a Set for matching to avoid allocations and sorting on every event\n const pressedKeys = this.buildPressedKeysForMatch(event);\n const isMac = this.isMacPlatform();\n\n // If there is a pending multi-step sequence, try to advance it first\n if (this.pendingSequence) {\n const pending = this.pendingSequence;\n const shortcut = this.shortcuts.get(pending.shortcutId);\n if (shortcut) {\n const steps = isMac\n ? (shortcut.macSteps ?? shortcut.macKeys ?? shortcut.steps ?? shortcut.keys ?? [])\n : (shortcut.steps ?? shortcut.keys ?? shortcut.macSteps ?? shortcut.macKeys ?? []);\n const normalizedSteps = this.normalizeToSteps(steps as KeyStep[] | string[]);\n const expected = normalizedSteps[pending.stepIndex];\n\n // Use per-event pressed keys for advancing sequence steps. Relying on\n // the accumulated `currentlyDownKeys` can accidentally include keys\n // from previous steps (if tests or callers don't emit keyup), which\n // would prevent matching a simple single-key step like ['s'] after\n // a prior ['k'] step. Use getPressedKeys(event) which reflects the\n // actual modifier/main-key state for this event.\n const stepPressed = this.getPressedKeys(event);\n\n if (expected && this.keysMatch(stepPressed, expected)) {\n // Advance sequence\n clearTimeout(pending.timerId);\n pending.stepIndex += 1;\n\n if (pending.stepIndex >= normalizedSteps.length) {\n // Completed\n event.preventDefault();\n event.stopPropagation();\n try {\n shortcut.action();\n } catch (error) {\n console.error(`Error executing keyboard shortcut \"${shortcut.id}\":`, error);\n }\n this.pendingSequence = null;\n return;\n }\n\n // Reset timer for next step\n pending.timerId = setTimeout(() => { this.pendingSequence = null; }, this.sequenceTimeout);\n return;\n } else {\n // Cancel pending if doesn't match\n this.clearPendingSequence();\n }\n } else {\n // pending exists but shortcut not found\n this.clearPendingSequence();\n }\n }\n\n // No pending sequence - check active shortcuts for a match or sequence start\n for (const shortcutId of this.activeShortcuts) {\n const shortcut = this.shortcuts.get(shortcutId);\n if (!shortcut) continue;\n\n const steps = isMac\n ? (shortcut.macSteps ?? shortcut.macKeys ?? shortcut.steps ?? shortcut.keys ?? [])\n : (shortcut.steps ?? shortcut.keys ?? shortcut.macSteps ?? shortcut.macKeys ?? []);\n const normalizedSteps = this.normalizeToSteps(steps as KeyStep[] | string[]);\n\n const firstStep = normalizedSteps[0];\n if (this.keysMatch(pressedKeys, firstStep)) {\n if (normalizedSteps.length === 1) {\n // single-step\n event.preventDefault();\n event.stopPropagation();\n try {\n shortcut.action();\n } catch (error) {\n console.error(`Error executing keyboard shortcut \"${shortcut.id}\":`, error);\n }\n break;\n } else {\n // start pending sequence\n if (this.pendingSequence) {\n this.clearPendingSequence();\n }\n this.pendingSequence = {\n shortcutId: shortcut.id,\n stepIndex: 1,\n timerId: setTimeout(() => { this.pendingSequence = null; }, this.sequenceTimeout)\n };\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n }\n }\n }\n\n protected handleKeyup(event: KeyboardEvent): void {\n // Remove the key from currentlyDownKeys on keyup\n const key = event.key ? event.key.toLowerCase() : '';\n if (key && !['control', 'alt', 'shift', 'meta'].includes(key)) {\n this.currentlyDownKeys.delete(key);\n }\n }\n\n /**\n * Clear the currently-down keys. Exposed for testing and for use by\n * blur/visibilitychange handlers to avoid stale state when the page loses focus.\n */\n clearCurrentlyDownKeys(): void {\n this.currentlyDownKeys.clear();\n }\n\n protected handleWindowBlur(): void {\n this.clearCurrentlyDownKeys();\n // Clear any pressed keys and any pending multi-step sequence to avoid\n // stale state when the window loses focus.\n this.clearPendingSequence();\n }\n\n protected handleVisibilityChange(): void {\n if (document.visibilityState === 'hidden') {\n // When the document becomes hidden, clear both pressed keys and any\n // pending multi-step sequence. This prevents sequences from remaining\n // active when the user switches tabs or minimizes the window.\n this.clearCurrentlyDownKeys();\n this.clearPendingSequence();\n }\n }\n\n /**\n * Update the currentlyDownKeys set when keydown events happen.\n * Normalizes common keys (function keys, space, etc.) to the same values\n * used by getPressedKeys/keysMatch.\n */\n protected updateCurrentlyDownKeysOnKeydown(event: KeyboardEvent): void {\n const key = event.key ? event.key.toLowerCase() : '';\n\n // Ignore modifier-only keydown entries\n if (['control', 'alt', 'shift', 'meta'].includes(key)) {\n return;\n }\n\n // Normalize some special cases similar to the demo component's recording logic\n if (event.code && event.code.startsWith('F') && /^F\\d+$/.test(event.code)) {\n this.currentlyDownKeys.add(event.code.toLowerCase());\n return;\n }\n\n if (key === ' ') {\n this.currentlyDownKeys.add('space');\n return;\n }\n\n if (key === 'escape') {\n this.currentlyDownKeys.add('escape');\n return;\n }\n\n if (key === 'enter') {\n this.currentlyDownKeys.add('enter');\n return;\n }\n\n if (key && key.length > 0) {\n this.currentlyDownKeys.add(key);\n }\n }\n\n /**\n * Build the pressed keys array used for matching against registered shortcuts.\n * If multiple non-modifier keys are currently down, include them (chord support).\n * Otherwise fall back to single main-key detection from the event for compatibility.\n */\n /**\n * Build the pressed keys set used for matching against registered shortcuts.\n * If multiple non-modifier keys are currently down, include them (chord support).\n * Otherwise fall back to single main-key detection from the event for compatibility.\n *\n * Returns a Set<string> (lowercased) to allow O(1) lookups and O(n) comparisons\n * without sorting or allocating sorted arrays on every event.\n */\n protected buildPressedKeysForMatch(event: KeyboardEvent): Set<string> {\n const modifiers = new Set<string>();\n if (event.ctrlKey) modifiers.add('ctrl');\n if (event.altKey) modifiers.add('alt');\n if (event.shiftKey) modifiers.add('shift');\n if (event.metaKey) modifiers.add('meta');\n\n // Collect non-modifier keys from currentlyDownKeys (excluding modifiers)\n const nonModifierKeys = Array.from(this.currentlyDownKeys).filter(k => !['control', 'alt', 'shift', 'meta'].includes(k));\n\n const result = new Set<string>();\n // Add modifiers first\n modifiers.forEach(m => result.add(m));\n\n if (nonModifierKeys.length > 0) {\n nonModifierKeys.forEach(k => result.add(k.toLowerCase()));\n return result;\n }\n\n // Fallback: single main key from the event (existing behavior)\n const key = event.key.toLowerCase();\n if (!['control', 'alt', 'shift', 'meta'].includes(key)) {\n result.add(key);\n }\n return result;\n }\n\n protected getPressedKeys(event: KeyboardEvent): string[] {\n const keys: string[] = [];\n \n if (event.ctrlKey) keys.push('ctrl');\n if (event.altKey) keys.push('alt');\n if (event.shiftKey) keys.push('shift');\n if (event.metaKey) keys.push('meta');\n \n // Add the main key (normalize to lowercase)\n const key = event.key.toLowerCase();\n if (!['control', 'alt', 'shift', 'meta'].includes(key)) {\n keys.push(key);\n }\n \n return keys;\n }\n\n /**\n * Compare pressed keys against a target key combination.\n * Accepts either a Set<string> (preferred) or an array for backwards compatibility.\n * Uses Set-based comparison: sizes must match and every element in target must exist in pressed.\n */\n protected keysMatch(pressedKeys: Set<string> | string[], targetKeys: string[]): boolean {\n // Normalize targetKeys into a Set<string> (lowercased)\n const normalizedTarget = new Set<string>(targetKeys.map(k => k.toLowerCase()));\n\n // Normalize pressedKeys into a Set<string> if it's an array\n const pressedSet: Set<string> = Array.isArray(pressedKeys)\n ? new Set<string>(pressedKeys.map(k => k.toLowerCase()))\n : new Set<string>(Array.from(pressedKeys).map(k => k.toLowerCase()));\n\n if (pressedSet.size !== normalizedTarget.size) {\n return false;\n }\n \n // Check if every element in normalizedTarget exists in pressedSet\n for (const key of normalizedTarget) {\n if (!pressedSet.has(key)) {\n return false;\n }\n }\n \n return true;\n }\n\n /** Compare two multi-step sequences for equality */\n protected stepsMatch(a: string[][], b: string[][]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!this.keysMatch(a[i], b[i])) return false;\n }\n return true;\n }\n\n /** Safely clear any pending multi-step sequence */\n private clearPendingSequence(): void {\n if (!this.pendingSequence) return;\n try {\n clearTimeout(this.pendingSequence.timerId);\n } catch { /* ignore */ }\n this.pendingSequence = null;\n }\n\n protected isMacPlatform(): boolean {\n return this.isBrowser && /Mac|iPod|iPhone|iPad/.test(navigator.platform);\n }\n \n protected setupActiveUntil (activeUntil: KeyboardShortcutActiveUntil|undefined, unregister: () => void) {\n if (!activeUntil) {\n return\n }\n\n if (activeUntil === 'destruct') {\n inject(DestroyRef).onDestroy(unregister);\n return\n } \n \n if (activeUntil instanceof DestroyRef) {\n activeUntil.onDestroy(unregister);\n return\n } \n \n if (activeUntil instanceof Observable) {\n activeUntil.pipe(take(1)).subscribe(unregister);\n return\n }\n }\n}\n","/*\n * Public API Surface of ngx-keys\n */\n\nexport * from './lib/keyboard-shortcuts';\nexport * from './lib/keyboard-shortcut.interface';\nexport * from './lib/keyboard-shortcuts.errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { OnDestroy } from '@angular/core';
2
+ import { DestroyRef, OnDestroy } from '@angular/core';
3
+ import { Observable } from 'rxjs';
3
4
 
5
+ type KeyboardShortcutActiveUntil = Observable<unknown> | DestroyRef | 'destruct';
6
+ type KeyStep = string[];
4
7
  interface KeyboardShortcut {
5
8
  id: string;
6
- keys: string[];
7
- macKeys: string[];
9
+ /**
10
+ * Single-step shortcuts keep the existing shape using `keys`/`macKeys`.
11
+ * For multi-step shortcuts, use `steps` (array of steps), where each step is an array of keys.
12
+ */
13
+ keys?: string[];
14
+ macKeys?: string[];
15
+ steps?: KeyStep[];
16
+ macSteps?: KeyStep[];
8
17
  action: () => void;
9
18
  description: string;
19
+ activeUntil?: KeyboardShortcutActiveUntil;
10
20
  }
11
21
  interface KeyboardShortcutGroup {
12
22
  id: string;
@@ -28,6 +38,7 @@ declare class KeyboardShortcuts implements OnDestroy {
28
38
  private readonly groups;
29
39
  private readonly activeShortcuts;
30
40
  private readonly activeGroups;
41
+ private readonly currentlyDownKeys;
31
42
  private readonly state;
32
43
  readonly shortcuts$: _angular_core.Signal<{
33
44
  active: KeyboardShortcut[];
@@ -44,8 +55,15 @@ declare class KeyboardShortcuts implements OnDestroy {
44
55
  all: KeyboardShortcutUI[];
45
56
  }>;
46
57
  private readonly keydownListener;
58
+ private readonly keyupListener;
59
+ private readonly blurListener;
60
+ private readonly visibilityListener;
47
61
  private isListening;
48
62
  protected isBrowser: boolean;
63
+ /** Default timeout (ms) for completing a multi-step sequence */
64
+ protected sequenceTimeout: number;
65
+ /** Runtime state for multi-step sequences */
66
+ private pendingSequence;
49
67
  constructor();
50
68
  ngOnDestroy(): void;
51
69
  /**
@@ -64,6 +82,8 @@ declare class KeyboardShortcuts implements OnDestroy {
64
82
  * Format keys for display with proper Unicode symbols
65
83
  */
66
84
  private formatKeysForDisplay;
85
+ private formatStepsForDisplay;
86
+ private normalizeToSteps;
67
87
  /**
68
88
  * Check if a key combination is already registered
69
89
  * @returns The ID of the conflicting shortcut, or null if no conflict
@@ -78,7 +98,7 @@ declare class KeyboardShortcuts implements OnDestroy {
78
98
  * Register multiple keyboard shortcuts as a group
79
99
  * @throws KeyboardShortcutError if group ID is already registered or if any shortcut ID or key combination conflicts
80
100
  */
81
- registerGroup(groupId: string, shortcuts: KeyboardShortcut[]): void;
101
+ registerGroup(groupId: string, shortcuts: KeyboardShortcut[], activeUntil?: KeyboardShortcutActiveUntil): void;
82
102
  /**
83
103
  * Unregister a single keyboard shortcut
84
104
  * @throws KeyboardShortcutError if shortcut ID doesn't exist
@@ -136,9 +156,47 @@ declare class KeyboardShortcuts implements OnDestroy {
136
156
  private startListening;
137
157
  private stopListening;
138
158
  protected handleKeydown(event: KeyboardEvent): void;
159
+ protected handleKeyup(event: KeyboardEvent): void;
160
+ /**
161
+ * Clear the currently-down keys. Exposed for testing and for use by
162
+ * blur/visibilitychange handlers to avoid stale state when the page loses focus.
163
+ */
164
+ clearCurrentlyDownKeys(): void;
165
+ protected handleWindowBlur(): void;
166
+ protected handleVisibilityChange(): void;
167
+ /**
168
+ * Update the currentlyDownKeys set when keydown events happen.
169
+ * Normalizes common keys (function keys, space, etc.) to the same values
170
+ * used by getPressedKeys/keysMatch.
171
+ */
172
+ protected updateCurrentlyDownKeysOnKeydown(event: KeyboardEvent): void;
173
+ /**
174
+ * Build the pressed keys array used for matching against registered shortcuts.
175
+ * If multiple non-modifier keys are currently down, include them (chord support).
176
+ * Otherwise fall back to single main-key detection from the event for compatibility.
177
+ */
178
+ /**
179
+ * Build the pressed keys set used for matching against registered shortcuts.
180
+ * If multiple non-modifier keys are currently down, include them (chord support).
181
+ * Otherwise fall back to single main-key detection from the event for compatibility.
182
+ *
183
+ * Returns a Set<string> (lowercased) to allow O(1) lookups and O(n) comparisons
184
+ * without sorting or allocating sorted arrays on every event.
185
+ */
186
+ protected buildPressedKeysForMatch(event: KeyboardEvent): Set<string>;
139
187
  protected getPressedKeys(event: KeyboardEvent): string[];
140
- protected keysMatch(pressedKeys: string[], targetKeys: string[]): boolean;
188
+ /**
189
+ * Compare pressed keys against a target key combination.
190
+ * Accepts either a Set<string> (preferred) or an array for backwards compatibility.
191
+ * Uses Set-based comparison: sizes must match and every element in target must exist in pressed.
192
+ */
193
+ protected keysMatch(pressedKeys: Set<string> | string[], targetKeys: string[]): boolean;
194
+ /** Compare two multi-step sequences for equality */
195
+ protected stepsMatch(a: string[][], b: string[][]): boolean;
196
+ /** Safely clear any pending multi-step sequence */
197
+ private clearPendingSequence;
141
198
  protected isMacPlatform(): boolean;
199
+ protected setupActiveUntil(activeUntil: KeyboardShortcutActiveUntil | undefined, unregister: () => void): void;
142
200
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<KeyboardShortcuts, never>;
143
201
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<KeyboardShortcuts>;
144
202
  }
@@ -196,4 +254,4 @@ declare class KeyboardShortcutsErrorFactory {
196
254
  }
197
255
 
198
256
  export { KeyboardShortcutError, KeyboardShortcuts, KeyboardShortcutsErrorFactory, KeyboardShortcutsErrors };
199
- export type { KeyboardShortcut, KeyboardShortcutGroup, KeyboardShortcutUI, KeyboardShortcutsErrorType };
257
+ export type { KeyStep, KeyboardShortcut, KeyboardShortcutActiveUntil, KeyboardShortcutGroup, KeyboardShortcutUI, KeyboardShortcutsErrorType };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-keys",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A reactive Angular library for managing keyboard shortcuts with signals-based UI integration",
5
5
  "keywords": [
6
6
  "angular",
@@ -18,7 +18,7 @@
18
18
  "author": "NgxKeys Contributors",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "git+https://github.com/mrivasperez/ngx-keys.git"
21
+ "url": "https://github.com/mrivasperez/ngx-keys.git"
22
22
  },
23
23
  "bugs": {
24
24
  "url": "https://github.com/mrivasperez/ngx-keys/issues"
@@ -43,4 +43,4 @@
43
43
  "default": "./fesm2022/ngx-keys.mjs"
44
44
  }
45
45
  }
46
- }
46
+ }