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 +198 -22
- package/fesm2022/ngx-keys.mjs +299 -21
- package/fesm2022/ngx-keys.mjs.map +1 -1
- package/index.d.ts +64 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -19,14 +19,21 @@ npm install ngx-keys
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Quick Start
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
140
|
-
|
|
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
|
|
161
|
-
|
|
162
|
-
| `ctrl`
|
|
163
|
-
| `alt`
|
|
164
|
-
| `shift`
|
|
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
|
|
169
|
-
|
|
170
|
-
| Function keys | `f1`, `f2`, `f3`, ... `f12`
|
|
171
|
-
| Arrow keys
|
|
172
|
-
| Navigation
|
|
173
|
-
| Editing
|
|
174
|
-
| Other
|
|
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:
|
package/fesm2022/ngx-keys.mjs
CHANGED
|
@@ -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.
|
|
170
|
-
macKeys: this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
469
|
-
|
|
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":";;;;;AAAA;;;AAGG;AACI,MAAM,uBAAuB,GAAG;;IAErC,2BAA2B,EAAE,CAAC,EAAU,KAAK,CAAA,UAAA,EAAa,EAAE,CAAA,oBAAA,CAAsB;IAClF,wBAAwB,EAAE,CAAC,EAAU,KAAK,CAAA,OAAA,EAAU,EAAE,CAAA,oBAAA,CAAsB;IAC5E,YAAY,EAAE,CAAC,UAAkB,KAAK,CAAA,mBAAA,EAAsB,UAAU,CAAA,CAAA,CAAG;AACzE,IAAA,+BAA+B,EAAE,CAAC,GAAa,KAAK,CAAA,iCAAA,EAAoC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE;AACxG,IAAA,4BAA4B,EAAE,CAAC,GAAa,KAAK,CAAA,8BAAA,EAAiC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE;AAClG,IAAA,sBAAsB,EAAE,CAAC,SAAmB,KAAK,CAAA,eAAA,EAAkB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE;;IAGzF,uBAAuB,EAAE,CAAC,EAAU,KAAK,CAAA,UAAA,EAAa,EAAE,CAAA,gBAAA,CAAkB;IAC1E,oBAAoB,EAAE,CAAC,EAAU,KAAK,CAAA,OAAA,EAAU,EAAE,CAAA,gBAAA,CAAkB;;IAGpE,wBAAwB,EAAE,CAAC,EAAU,KAAK,CAAA,0BAAA,EAA6B,EAAE,CAAA,iBAAA,CAAmB;IAC5F,0BAA0B,EAAE,CAAC,EAAU,KAAK,CAAA,4BAAA,EAA+B,EAAE,CAAA,iBAAA,CAAmB;IAChG,qBAAqB,EAAE,CAAC,EAAU,KAAK,CAAA,uBAAA,EAA0B,EAAE,CAAA,iBAAA,CAAmB;IACtF,uBAAuB,EAAE,CAAC,EAAU,KAAK,CAAA,yBAAA,EAA4B,EAAE,CAAA,iBAAA,CAAmB;;IAG1F,0BAA0B,EAAE,CAAC,EAAU,KAAK,CAAA,4BAAA,EAA+B,EAAE,CAAA,iBAAA,CAAmB;IAChG,uBAAuB,EAAE,CAAC,EAAU,KAAK,CAAA,yBAAA,EAA4B,EAAE,CAAA,iBAAA,CAAmB;;AAQ5F;;AAEG;AACG,MAAO,qBAAsB,SAAQ,KAAK,CAAA;AAE5B,IAAA,SAAA;AAEA,IAAA,OAAA;AAHlB,IAAA,WAAA,CACkB,SAAqC,EACrD,OAAe,EACC,OAA6B,EAAA;QAE7C,KAAK,CAAC,OAAO,CAAC;QAJE,IAAA,CAAA,SAAS,GAAT,SAAS;QAET,IAAA,CAAA,OAAO,GAAP,OAAO;AAGvB,QAAA,IAAI,CAAC,IAAI,GAAG,uBAAuB;IACrC;AACD;AAED;;AAEG;MACU,6BAA6B,CAAA;IACxC,OAAO,yBAAyB,CAAC,EAAU,EAAA;AACzC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,6BAA6B,EAC7B,uBAAuB,CAAC,2BAA2B,CAAC,EAAE,CAAC,EACvD,EAAE,UAAU,EAAE,EAAE,EAAE,CACnB;IACH;IAEA,OAAO,sBAAsB,CAAC,EAAU,EAAA;AACtC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,0BAA0B,EAC1B,uBAAuB,CAAC,wBAAwB,CAAC,EAAE,CAAC,EACpD,EAAE,OAAO,EAAE,EAAE,EAAE,CAChB;IACH;IAEA,OAAO,WAAW,CAAC,UAAkB,EAAA;AACnC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,cAAc,EACd,uBAAuB,CAAC,YAAY,CAAC,UAAU,CAAC,EAChD,EAAE,UAAU,EAAE,CACf;IACH;IAEA,OAAO,4BAA4B,CAAC,GAAa,EAAA;AAC/C,QAAA,OAAO,IAAI,qBAAqB,CAC9B,iCAAiC,EACjC,uBAAuB,CAAC,+BAA+B,CAAC,GAAG,CAAC,EAC5D,EAAE,YAAY,EAAE,GAAG,EAAE,CACtB;IACH;IAEA,OAAO,yBAAyB,CAAC,GAAa,EAAA;AAC5C,QAAA,OAAO,IAAI,qBAAqB,CAC9B,8BAA8B,EAC9B,uBAAuB,CAAC,4BAA4B,CAAC,GAAG,CAAC,EACzD,EAAE,YAAY,EAAE,GAAG,EAAE,CACtB;IACH;IAEA,OAAO,mBAAmB,CAAC,SAAmB,EAAA;AAC5C,QAAA,OAAO,IAAI,qBAAqB,CAC9B,wBAAwB,EACxB,uBAAuB,CAAC,sBAAsB,CAAC,SAAS,CAAC,EACzD,EAAE,SAAS,EAAE,CACd;IACH;IAEA,OAAO,qBAAqB,CAAC,EAAU,EAAA;AACrC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,yBAAyB,EACzB,uBAAuB,CAAC,uBAAuB,CAAC,EAAE,CAAC,EACnD,EAAE,UAAU,EAAE,EAAE,EAAE,CACnB;IACH;IAEA,OAAO,kBAAkB,CAAC,EAAU,EAAA;AAClC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,sBAAsB,EACtB,uBAAuB,CAAC,oBAAoB,CAAC,EAAE,CAAC,EAChD,EAAE,OAAO,EAAE,EAAE,EAAE,CAChB;IACH;IAEA,OAAO,sBAAsB,CAAC,EAAU,EAAA;AACtC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,0BAA0B,EAC1B,uBAAuB,CAAC,wBAAwB,CAAC,EAAE,CAAC,EACpD,EAAE,UAAU,EAAE,EAAE,EAAE,CACnB;IACH;IAEA,OAAO,wBAAwB,CAAC,EAAU,EAAA;AACxC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,4BAA4B,EAC5B,uBAAuB,CAAC,0BAA0B,CAAC,EAAE,CAAC,EACtD,EAAE,UAAU,EAAE,EAAE,EAAE,CACnB;IACH;IAEA,OAAO,mBAAmB,CAAC,EAAU,EAAA;AACnC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,uBAAuB,EACvB,uBAAuB,CAAC,qBAAqB,CAAC,EAAE,CAAC,EACjD,EAAE,OAAO,EAAE,EAAE,EAAE,CAChB;IACH;IAEA,OAAO,qBAAqB,CAAC,EAAU,EAAA;AACrC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,yBAAyB,EACzB,uBAAuB,CAAC,uBAAuB,CAAC,EAAE,CAAC,EACnD,EAAE,OAAO,EAAE,EAAE,EAAE,CAChB;IACH;IAEA,OAAO,wBAAwB,CAAC,EAAU,EAAA;AACxC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,4BAA4B,EAC5B,uBAAuB,CAAC,0BAA0B,CAAC,EAAE,CAAC,EACtD,EAAE,UAAU,EAAE,EAAE,EAAE,CACnB;IACH;IAEA,OAAO,qBAAqB,CAAC,EAAU,EAAA;AACrC,QAAA,OAAO,IAAI,qBAAqB,CAC9B,yBAAyB,EACzB,uBAAuB,CAAC,uBAAuB,CAAC,EAAE,CAAC,EACnD,EAAE,OAAO,EAAE,EAAE,EAAE,CAChB;IACH;AACD;;MCzJY,iBAAiB,CAAA;AACX,IAAA,SAAS,GAAG,IAAI,GAAG,EAA4B;AAC/C,IAAA,MAAM,GAAG,IAAI,GAAG,EAAiC;AACjD,IAAA,eAAe,GAAG,IAAI,GAAG,EAAU;AACnC,IAAA,YAAY,GAAG,IAAI,GAAG,EAAU;AAChC,IAAA,iBAAiB,GAAG,IAAI,GAAG,EAAU;;IAGrC,KAAK,GAAG,MAAM,CAAC;QAC9B,SAAS,EAAE,IAAI,GAAG,EAA4B;QAC9C,MAAM,EAAE,IAAI,GAAG,EAAiC;QAChD,eAAe,EAAE,IAAI,GAAG,EAAU;QAClC,YAAY,EAAE,IAAI,GAAG,EAAU;QAC/B,OAAO,EAAE,CAAC;AACX,KAAA,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,OAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;;AAGO,IAAA,UAAU,GAAG,QAAQ,CAAC,MAAK;AAClC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;QAC1B,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe;AACrD,aAAA,GAAG,CAAC,EAAE,IAAI,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;aACjC,MAAM,CAAC,CAAC,CAAC,KAA4B,CAAC,KAAK,SAAS,CAAC;AAExD,QAAA,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE;AAC1D,aAAA,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAEhD,OAAO;AACL,YAAA,MAAM,EAAE,eAAe;AACvB,YAAA,QAAQ,EAAE,iBAAiB;YAC3B,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;AACzC,YAAA,MAAM,EAAE;gBACN,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;gBACtC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;AACrC,qBAAA,MAAM,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;AAC5C;SACF;AACH,IAAA,CAAC,sDAAC;;AAGO,IAAA,YAAY,GAAG,QAAQ,CAAC,MAAK;AACpC,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE;QACnC,OAAO;AACL,YAAA,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAC9D,YAAA,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAClE,YAAA,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;SACxD;AACH,IAAA,CAAC,wDAAC;IAEe,eAAe,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;IAC/C,aAAa,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;IAC3C,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;IAC/C,kBAAkB,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;IACpE,WAAW,GAAG,KAAK;AACjB,IAAA,SAAS;;IAET,eAAe,GAAG,IAAI;;IAGxB,eAAe,GAIZ,IAAI;AAEf,IAAA,WAAA,GAAA;;AAEE,QAAA,IAAI;AACF,YAAA,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC;AACtC,YAAA,IAAI,CAAC,SAAS,GAAG,iBAAiB,CAAC,UAAU,CAAC;QAChD;AAAE,QAAA,MAAM;;AAEN,YAAA,IAAI,CAAC,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW;QACnF;AAEA,QAAA,IAAI,IAAI,CAAC,SAAS,EAAE;YAClB,IAAI,CAAC,cAAc,EAAE;QACvB;IACF;IAEA,WAAW,GAAA;QACT,IAAI,CAAC,aAAa,EAAE;IACtB;AAEA;;AAEG;IACK,WAAW,GAAA;QACjB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK;AAC5B,YAAA,SAAS,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;AAClC,YAAA,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;AAC5B,YAAA,eAAe,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC;AAC9C,YAAA,YAAY,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC;AACxC,YAAA,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG;AAC5B,SAAA,CAAC,CAAC;IACL;AAEA;;AAEG;AACH,IAAA,mBAAmB,CAAC,QAA0B,EAAA;QAC5C,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,EAAE;AACf,YAAA,IAAI,EAAE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,CAAC;AAC9E,YAAA,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,QAAQ,IAAI,EAAE,EAAE,IAAI,CAAC;YACtF,WAAW,EAAE,QAAQ,CAAC;SACvB;IACH;AAEA;;AAEG;AACH,IAAA,WAAW,CAAC,UAAsB,EAAA;AAChC,QAAA,UAAU,EAAE;QACZ,IAAI,CAAC,WAAW,EAAE;IACpB;AAEA;;AAEG;AACK,IAAA,oBAAoB,CAAC,IAAc,EAAE,KAAK,GAAG,KAAK,EAAA;AACxD,QAAA,MAAM,MAAM,GAA2B,KAAK,GAAG;AAC7C,YAAA,MAAM,EAAE,GAAG;AACX,YAAA,KAAK,EAAE,GAAG;AACV,YAAA,OAAO,EAAE,GAAG;AACZ,YAAA,MAAM,EAAE,GAAG;AACX,YAAA,KAAK,EAAE,GAAG;AACV,YAAA,SAAS,EAAE;AACZ,SAAA,GAAG;AACF,YAAA,MAAM,EAAE,MAAM;AACd,YAAA,KAAK,EAAE,KAAK;AACZ,YAAA,OAAO,EAAE,OAAO;AAChB,YAAA,MAAM,EAAE;SACT;AAED,QAAA,OAAO;AACJ,aAAA,GAAG,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE;aACzD,IAAI,CAAC,GAAG,CAAC;IACd;AAEQ,IAAA,qBAAqB,CAAC,KAA4B,EAAE,KAAK,GAAG,KAAK,EAAA;AACvE,QAAA,IAAI,CAAC,KAAK;AAAE,YAAA,OAAO,EAAE;;QAGrB,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAA6B,CAAC;AACvE,QAAA,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,EAAE;AACtC,QAAA,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;QACnF,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAClF;AAEQ,IAAA,gBAAgB,CAAC,KAA2B,EAAA;AAClD,QAAA,IAAI,CAAC,KAAK;AAAE,YAAA,OAAO,EAAE;;QAErB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;AAC3B,YAAA,OAAO,KAAkB;QAC3B;;QAEA,OAAO,CAAC,KAAiB,CAAC;IAC5B;AAEA;;;AAGG;AACK,IAAA,YAAY,CAAC,WAA6B,EAAA;QAChD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE;;YAE9C,IAAI,WAAW,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE;gBACxF,OAAO,QAAQ,CAAC,EAAE;YACpB;YACA,IAAI,WAAW,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;gBACpG,OAAO,QAAQ,CAAC,EAAE;YACpB;;YAGA,IAAI,WAAW,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE;gBAC7F,OAAO,QAAQ,CAAC,EAAE;YACpB;YACA,IAAI,WAAW,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE;gBACzG,OAAO,QAAQ,CAAC,EAAE;YACpB;QACF;AACA,QAAA,OAAO,IAAI;IACb;AAEA;;;AAGG;AACH,IAAA,QAAQ,CAAC,QAA0B,EAAA;QACjC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;YACnC,MAAM,6BAA6B,CAAC,yBAAyB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5E;QAEA,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC9C,IAAI,UAAU,EAAE;AACd,YAAA,MAAM,6BAA6B,CAAC,WAAW,CAAC,UAAU,CAAC;QAC7D;QAEA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;QACzC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,WAAW,EAAE;QAElB,IAAI,CAAC,gBAAgB,CACnB,QAAQ,CAAC,WAAW,EACpB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CACxC;IACH;AAEA;;;AAGG;AACH,IAAA,aAAa,CAAC,OAAe,EAAE,SAA6B,EAAE,WAAyC,EAAA;;QAErG,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;AAC5B,YAAA,MAAM,6BAA6B,CAAC,sBAAsB,CAAC,OAAO,CAAC;QACrE;;QAGA,MAAM,YAAY,GAAa,EAAE;QACjC,MAAM,YAAY,GAAa,EAAE;AACjC,QAAA,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAG;YAC3B,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;AACnC,gBAAA,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChC;YACA,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC9C,IAAI,UAAU,EAAE;gBACd,YAAY,CAAC,IAAI,CAAC,CAAA,CAAA,EAAI,QAAQ,CAAC,EAAE,CAAA,kBAAA,EAAqB,UAAU,CAAA,CAAA,CAAG,CAAC;YACtE;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3B,YAAA,MAAM,6BAA6B,CAAC,4BAA4B,CAAC,YAAY,CAAC;QAChF;AAEA,QAAA,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3B,YAAA,MAAM,6BAA6B,CAAC,mBAAmB,CAAC,YAAY,CAAC;QACvE;;AAGA,QAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU;QAClC,MAAM,iBAAiB,GAAa,EAAE;AACtC,QAAA,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAG;YAC3B,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;AAC7B,gBAAA,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC;iBAAO;AACL,gBAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE;AAChC,YAAA,MAAM,6BAA6B,CAAC,yBAAyB,CAAC,iBAAiB,CAAC;QAClF;;AAGA,QAAA,IAAI,CAAC,WAAW,CAAC,MAAK;AACpB,YAAA,MAAM,KAAK,GAA0B;AACnC,gBAAA,EAAE,EAAE,OAAO;gBACX,SAAS;AACT,gBAAA,MAAM,EAAE;aACT;YAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;AAC/B,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC;;AAG9B,YAAA,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAG;gBAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;gBACzC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;AACvC,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,CAAC,gBAAgB,CACnB,WAAW,EACX,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CACzC;IACH;AAEA;;;AAGG;AACH,IAAA,UAAU,CAAC,UAAkB,EAAA;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;AACnC,YAAA,MAAM,6BAA6B,CAAC,wBAAwB,CAAC,UAAU,CAAC;QAC1E;AAEA,QAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC;AACjC,QAAA,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC;QACvC,IAAI,CAAC,WAAW,EAAE;IACpB;AAEA;;;AAGG;AACH,IAAA,eAAe,CAAC,OAAe,EAAA;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE;AACV,YAAA,MAAM,6BAA6B,CAAC,qBAAqB,CAAC,OAAO,CAAC;QACpE;AAEA,QAAA,IAAI,CAAC,WAAW,CAAC,MAAK;AACpB,YAAA,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAG;gBACjC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC1C,YAAA,CAAC,CAAC;AACF,YAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;AAC3B,YAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC;AACnC,QAAA,CAAC,CAAC;IACJ;AAEA;;;AAGG;AACH,IAAA,QAAQ,CAAC,UAAkB,EAAA;QACzB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;AACnC,YAAA,MAAM,6BAA6B,CAAC,sBAAsB,CAAC,UAAU,CAAC;QACxE;AAEA,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC;QACpC,IAAI,CAAC,WAAW,EAAE;IACpB;AAEA;;;AAGG;AACH,IAAA,UAAU,CAAC,UAAkB,EAAA;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;AACnC,YAAA,MAAM,6BAA6B,CAAC,wBAAwB,CAAC,UAAU,CAAC;QAC1E;AAEA,QAAA,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC;QACvC,IAAI,CAAC,WAAW,EAAE;IACpB;AAEA;;;AAGG;AACH,IAAA,aAAa,CAAC,OAAe,EAAA;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE;AACV,YAAA,MAAM,6BAA6B,CAAC,mBAAmB,CAAC,OAAO,CAAC;QAClE;AAEA,QAAA,IAAI,CAAC,WAAW,CAAC,MAAK;AACpB,YAAA,KAAK,CAAC,MAAM,GAAG,IAAI;AACnB,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC;AAC9B,YAAA,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAG;gBACjC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;AACvC,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;AAEA;;;AAGG;AACH,IAAA,eAAe,CAAC,OAAe,EAAA;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE;AACV,YAAA,MAAM,6BAA6B,CAAC,qBAAqB,CAAC,OAAO,CAAC;QACpE;AAEA,QAAA,IAAI,CAAC,WAAW,CAAC,MAAK;AACpB,YAAA,KAAK,CAAC,MAAM,GAAG,KAAK;AACpB,YAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC;AACjC,YAAA,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAG;gBACjC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC1C,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;AACH,IAAA,QAAQ,CAAC,UAAkB,EAAA;QACzB,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC;IAC7C;AAEA;;AAEG;AACH,IAAA,YAAY,CAAC,UAAkB,EAAA;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;IACvC;AAEA;;AAEG;AACH,IAAA,aAAa,CAAC,OAAe,EAAA;QAC3B,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC;IACvC;AAEA;;AAEG;AACH,IAAA,iBAAiB,CAAC,OAAe,EAAA;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;IACjC;AAEA;;AAEG;IACH,YAAY,GAAA;QACV,OAAO,IAAI,CAAC,SAAS;IACvB;AAEA;;AAEG;IACH,SAAS,GAAA;QACP,OAAO,IAAI,CAAC,MAAM;IACpB;IAEQ,cAAc,GAAA;QACpB,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE;YACvC;QACF;;;;AAKA,QAAA,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC9E,QAAA,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;;QAG1E,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC;QAClD,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,CAAC;AACtE,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI;IACzB;IAEQ,aAAa,GAAA;QACnB,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACxC;QACF;QAEA,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,eAAe,CAAC;QAC7D,QAAQ,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC;QACzD,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC;QACrD,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,CAAC;AACzE,QAAA,IAAI,CAAC,WAAW,GAAG,KAAK;IAC1B;AAEU,IAAA,aAAa,CAAC,KAAoB,EAAA;;AAE1C,QAAA,IAAI,CAAC,gCAAgC,CAAC,KAAK,CAAC;;;;;QAM9C,MAAM,WAAW,GAAG,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC;AACtD,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE;;AAGlC,QAAA,IAAI,IAAI,CAAC,eAAe,EAAE;AACxB,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe;AACpC,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC;YACvD,IAAI,QAAQ,EAAE;gBACZ,MAAM,KAAK,GAAG;AACZ,uBAAG,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,IAAI,EAAE;uBAC9E,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;gBACpF,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAA6B,CAAC;gBAC5E,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC;;;;;;;gBAQnD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;gBAE9C,IAAI,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE;;AAErD,oBAAA,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC;AAC7B,oBAAA,OAAO,CAAC,SAAS,IAAI,CAAC;oBAEtB,IAAI,OAAO,CAAC,SAAS,IAAI,eAAe,CAAC,MAAM,EAAE;;wBAE/C,KAAK,CAAC,cAAc,EAAE;wBACtB,KAAK,CAAC,eAAe,EAAE;AACvB,wBAAA,IAAI;4BACF,QAAQ,CAAC,MAAM,EAAE;wBACnB;wBAAE,OAAO,KAAK,EAAE;4BACd,OAAO,CAAC,KAAK,CAAC,CAAA,mCAAA,EAAsC,QAAQ,CAAC,EAAE,CAAA,EAAA,CAAI,EAAE,KAAK,CAAC;wBAC7E;AACA,wBAAA,IAAI,CAAC,eAAe,GAAG,IAAI;wBAC3B;oBACF;;oBAGA,OAAO,CAAC,OAAO,GAAG,UAAU,CAAC,QAAQ,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC;oBAC1F;gBACF;qBAAO;;oBAEL,IAAI,CAAC,oBAAoB,EAAE;gBAC7B;YACF;iBAAO;;gBAEX,IAAI,CAAC,oBAAoB,EAAE;YACvB;QACF;;AAGA,QAAA,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,eAAe,EAAE;YAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;AAC/C,YAAA,IAAI,CAAC,QAAQ;gBAAE;YAEf,MAAM,KAAK,GAAG;AACZ,mBAAG,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,IAAI,EAAE;mBAC9E,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;YACpF,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAA6B,CAAC;AAE5E,YAAA,MAAM,SAAS,GAAG,eAAe,CAAC,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE;AAC1C,gBAAA,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE;;oBAEhC,KAAK,CAAC,cAAc,EAAE;oBACtB,KAAK,CAAC,eAAe,EAAE;AACvB,oBAAA,IAAI;wBACF,QAAQ,CAAC,MAAM,EAAE;oBACnB;oBAAE,OAAO,KAAK,EAAE;wBACd,OAAO,CAAC,KAAK,CAAC,CAAA,mCAAA,EAAsC,QAAQ,CAAC,EAAE,CAAA,EAAA,CAAI,EAAE,KAAK,CAAC;oBAC7E;oBACA;gBACF;qBAAO;;AAEL,oBAAA,IAAI,IAAI,CAAC,eAAe,EAAE;wBACxB,IAAI,CAAC,oBAAoB,EAAE;oBAC7B;oBACA,IAAI,CAAC,eAAe,GAAG;wBACrB,UAAU,EAAE,QAAQ,CAAC,EAAE;AACvB,wBAAA,SAAS,EAAE,CAAC;AACZ,wBAAA,OAAO,EAAE,UAAU,CAAC,MAAK,EAAG,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe;qBACjF;oBACD,KAAK,CAAC,cAAc,EAAE;oBACtB,KAAK,CAAC,eAAe,EAAE;oBACvB;gBACF;YACF;QACF;IACF;AAEU,IAAA,WAAW,CAAC,KAAoB,EAAA;;AAExC,QAAA,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE;AACpD,QAAA,IAAI,GAAG,IAAI,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC7D,YAAA,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC;QACpC;IACF;AAEA;;;AAGG;IACH,sBAAsB,GAAA;AACpB,QAAA,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE;IAChC;IAEU,gBAAgB,GAAA;QACxB,IAAI,CAAC,sBAAsB,EAAE;;;QAG7B,IAAI,CAAC,oBAAoB,EAAE;IAC7B;IAEU,sBAAsB,GAAA;AAC9B,QAAA,IAAI,QAAQ,CAAC,eAAe,KAAK,QAAQ,EAAE;;;;YAIzC,IAAI,CAAC,sBAAsB,EAAE;YAC7B,IAAI,CAAC,oBAAoB,EAAE;QAC7B;IACF;AAEA;;;;AAIG;AACO,IAAA,gCAAgC,CAAC,KAAoB,EAAA;AAC7D,QAAA,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE;;AAGpD,QAAA,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;YACrD;QACF;;QAGA,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;AACzE,YAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpD;QACF;AAEA,QAAA,IAAI,GAAG,KAAK,GAAG,EAAE;AACf,YAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC;YACnC;QACF;AAEA,QAAA,IAAI,GAAG,KAAK,QAAQ,EAAE;AACpB,YAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC;YACpC;QACF;AAEA,QAAA,IAAI,GAAG,KAAK,OAAO,EAAE;AACnB,YAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC;YACnC;QACF;QAEA,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE;AACzB,YAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC;QACjC;IACF;AAEA;;;;AAIG;AACH;;;;;;;AAOG;AACO,IAAA,wBAAwB,CAAC,KAAoB,EAAA;AACrD,QAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU;QACnC,IAAI,KAAK,CAAC,OAAO;AAAE,YAAA,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;QACxC,IAAI,KAAK,CAAC,MAAM;AAAE,YAAA,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;QACtC,IAAI,KAAK,CAAC,QAAQ;AAAE,YAAA,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;QAC1C,IAAI,KAAK,CAAC,OAAO;AAAE,YAAA,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;;AAGxC,QAAA,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAExH,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU;;AAEhC,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAErC,QAAA,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE;AAC9B,YAAA,eAAe,CAAC,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACzD,YAAA,OAAO,MAAM;QACf;;QAGA,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE;AACnC,QAAA,IAAI,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtD,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QACjB;AACA,QAAA,OAAO,MAAM;IACf;AAEU,IAAA,cAAc,CAAC,KAAoB,EAAA;QAC3C,MAAM,IAAI,GAAa,EAAE;QAEzB,IAAI,KAAK,CAAC,OAAO;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,IAAI,KAAK,CAAC,MAAM;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAClC,IAAI,KAAK,CAAC,QAAQ;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;QACtC,IAAI,KAAK,CAAC,OAAO;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;;QAGpC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE;AACnC,QAAA,IAAI,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtD,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAChB;AAEA,QAAA,OAAO,IAAI;IACb;AAEA;;;;AAIG;IACO,SAAS,CAAC,WAAmC,EAAE,UAAoB,EAAA;;AAE3E,QAAA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAS,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;;AAG9E,QAAA,MAAM,UAAU,GAAgB,KAAK,CAAC,OAAO,CAAC,WAAW;AACvD,cAAE,IAAI,GAAG,CAAS,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;cACrD,IAAI,GAAG,CAAS,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAEtE,IAAI,UAAU,CAAC,IAAI,KAAK,gBAAgB,CAAC,IAAI,EAAE;AAC7C,YAAA,OAAO,KAAK;QACd;;AAGA,QAAA,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE;YAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACxB,gBAAA,OAAO,KAAK;YACd;QACF;AAEA,QAAA,OAAO,IAAI;IACb;;IAGU,UAAU,CAAC,CAAa,EAAE,CAAa,EAAA;AAC/C,QAAA,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;AAAE,YAAA,OAAO,KAAK;AACvC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACjC,YAAA,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAAE,gBAAA,OAAO,KAAK;QAC/C;AACA,QAAA,OAAO,IAAI;IACb;;IAGQ,oBAAoB,GAAA;QAC1B,IAAI,CAAC,IAAI,CAAC,eAAe;YAAE;AAC3B,QAAA,IAAI;AACF,YAAA,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QAC5C;AAAE,QAAA,MAAM,eAAe;AACvB,QAAA,IAAI,CAAC,eAAe,GAAG,IAAI;IAC7B;IAEU,aAAa,GAAA;AACrB,QAAA,OAAO,IAAI,CAAC,SAAS,IAAI,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;IAC1E;IAEU,gBAAgB,CAAE,WAAkD,EAAE,UAAsB,EAAA;QACpG,IAAI,CAAC,WAAW,EAAE;YAChB;QACF;AAEA,QAAA,IAAI,WAAW,KAAK,UAAU,EAAE;YAC9B,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC;YACxC;QACF;AAEA,QAAA,IAAI,WAAW,YAAY,UAAU,EAAE;AACrC,YAAA,WAAW,CAAC,SAAS,CAAC,UAAU,CAAC;YACjC;QACF;AAEA,QAAA,IAAI,WAAW,YAAY,UAAU,EAAE;AACrC,YAAA,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC;YAC/C;QACF;IACF;uGA1uBW,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAjB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,iBAAiB,cAFhB,MAAM,EAAA,CAAA;;2FAEP,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAH7B,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE;AACb,iBAAA;;;ACRD;;AAEG;;ACFH;;AAEG;;;;"}
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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
|
+
}
|