ngx-keys 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,382 @@
1
+ # ngx-keys
2
+
3
+ A lightweight, reactive Angular service for managing keyboard shortcuts with signals-based UI integration.
4
+
5
+ ## Features
6
+
7
+ - **🎯 Service-Focused**: Clean, focused API without unnecessary UI components
8
+ - **⚡ Reactive Signals**: Track active/inactive shortcuts with Angular signals
9
+ - **🔧 UI-Agnostic**: Build your own UI using the provided reactive signals
10
+ - **🌍 Cross-Platform**: Automatic Mac/PC key display formatting
11
+ - **🔄 Dynamic Management**: Add, remove, activate/deactivate shortcuts at runtime
12
+ - **📁 Group Management**: Organize shortcuts into logical groups
13
+ - **🪶 Lightweight**: Zero dependencies, minimal bundle impact
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install ngx-keys
19
+ ```
20
+
21
+ ## Quick Start
22
+ ### Displaying Shortcuts
23
+
24
+ ```typescript
25
+ import { Component, inject } from '@angular/core';
26
+ import { KeyboardShortcuts } from 'ngx-keys';
27
+
28
+ @Component({
29
+ template: `
30
+ @for (shortcut of activeShortcuts(); track shortcut.id) {
31
+ <div>
32
+ <kbd>{{ shortcut.keys }}</kbd> - {{ shortcut.description }}
33
+ </div>
34
+ }
35
+ `
36
+ })
37
+ export class ShortcutsComponent {
38
+ private readonly keyboardService = inject(KeyboardShortcuts);
39
+ protected readonly activeShortcuts = () => this.keyboardService.shortcutsUI$().active;
40
+ }
41
+ ```
42
+ ## Key Concepts
43
+
44
+ ### Automatic Activation
45
+
46
+ > [!IMPORTANT]
47
+ > When you register shortcuts using `register()` or `registerGroup()`, they are **automatically activated** and ready to use immediately. You don't need to call `activate()` unless you've previously deactivated them.
48
+
49
+ ```typescript
50
+ // This shortcut is immediately active after registration
51
+ this.keyboardService.register({
52
+ id: 'save',
53
+ keys: ['ctrl', 's'],
54
+ macKeys: ['meta', 's'],
55
+ action: () => this.save(),
56
+ description: 'Save document'
57
+ });
58
+ ```
59
+
60
+ Use the `activate()` and `deactivate()` methods for dynamic control after registration:
61
+
62
+ ```typescript
63
+ // Temporarily disable a shortcut
64
+ this.keyboardService.deactivate('save');
65
+
66
+ // Re-enable it later
67
+ this.keyboardService.activate('save');
68
+ ```
69
+
70
+ ## API Reference
71
+
72
+ ### KeyboardShortcuts Service
73
+
74
+ #### Methods
75
+
76
+ **Registration Methods:**
77
+ - `register(shortcut: KeyboardShortcut)` - Register and automatically activate a single shortcut *Throws error on conflicts*
78
+ - `registerGroup(groupId: string, shortcuts: KeyboardShortcut[])` - Register and automatically activate a group of shortcuts *Throws error on conflicts*
79
+
80
+ **Management Methods:**
81
+ - `unregister(shortcutId: string)` - Remove a shortcut *Throws error if not found*
82
+ - `unregisterGroup(groupId: string)` - Remove a group *Throws error if not found*
83
+ - `activate(shortcutId: string)` - Activate a shortcut *Throws error if not registered*
84
+ - `deactivate(shortcutId: string)` - Deactivate a shortcut *Throws error if not registered*
85
+ - `activateGroup(groupId: string)` - Activate all shortcuts in a group *Throws error if not found*
86
+ - `deactivateGroup(groupId: string)` - Deactivate all shortcuts in a group *Throws error if not found*
87
+
88
+ **Query Methods:**
89
+ - `isActive(shortcutId: string): boolean` - Check if a shortcut is active
90
+ - `isRegistered(shortcutId: string): boolean` - Check if a shortcut is registered
91
+ - `isGroupActive(groupId: string): boolean` - Check if a group is active
92
+ - `isGroupRegistered(groupId: string): boolean` - Check if a group is registered
93
+ - `getShortcuts(): ReadonlyMap<string, KeyboardShortcut>` - Get all registered shortcuts
94
+ - `getGroups(): ReadonlyMap<string, KeyboardShortcutGroup>` - Get all registered groups
95
+
96
+ **Utility Methods:**
97
+ - `formatShortcutForUI(shortcut: KeyboardShortcut): KeyboardShortcutUI` - Format a shortcut for display
98
+ - `batchUpdate(operations: () => void): void` - Batch multiple operations to reduce signal updates
99
+
100
+ #### Reactive Signals
101
+
102
+ The service provides reactive signals for UI integration:
103
+
104
+ ```typescript
105
+ // Primary signal containing all shortcut state
106
+ shortcuts$: Signal<{
107
+ active: KeyboardShortcut[];
108
+ inactive: KeyboardShortcut[];
109
+ all: KeyboardShortcut[];
110
+ groups: {
111
+ active: string[];
112
+ inactive: string[];
113
+ };
114
+ }>
115
+
116
+ // Pre-formatted UI signal for easy display
117
+ shortcutsUI$: Signal<{
118
+ active: ShortcutUI[];
119
+ inactive: ShortcutUI[];
120
+ all: ShortcutUI[];
121
+ }>
122
+ ```
123
+
124
+ **ShortcutUI Interface:**
125
+ ```typescript
126
+ interface KeyboardShortcutUI {
127
+ id: string; // Shortcut identifier
128
+ keys: string; // Formatted PC/Linux keys (e.g., "Ctrl+S")
129
+ macKeys: string; // Formatted Mac keys (e.g., "⌘+S")
130
+ description: string; // Human-readable description
131
+ }
132
+ ```
133
+
134
+ ### KeyboardShortcut Interface
135
+
136
+ ```typescript
137
+ interface KeyboardShortcut {
138
+ id: string; // Unique identifier
139
+ keys: string[]; // Key combination for PC/Linux (e.g., ['ctrl', 's'])
140
+ macKeys: string[]; // Key combination for Mac (e.g., ['meta', 's'])
141
+ action: () => void; // Function to execute
142
+ description: string; // Human-readable description
143
+ }
144
+ ```
145
+
146
+ ### KeyboardShortcutGroup Interface
147
+
148
+ ```typescript
149
+ interface KeyboardShortcutGroup {
150
+ id: string; // Unique group identifier
151
+ shortcuts: KeyboardShortcut[]; // Array of shortcuts in this group
152
+ active: boolean; // Whether the group is currently active
153
+ }
154
+ ```
155
+
156
+ ## Key Mapping Reference
157
+
158
+ ### Modifier Keys
159
+
160
+ | PC/Linux | Mac | Description |
161
+ |----------|-----|-------------|
162
+ | `ctrl` | `meta` | Control/Command key |
163
+ | `alt` | `alt` | Alt/Option key |
164
+ | `shift` | `shift` | Shift key |
165
+
166
+ ### Special Keys
167
+
168
+ | Key | Value |
169
+ |-----|-------|
170
+ | Function keys | `f1`, `f2`, `f3`, ... `f12` |
171
+ | Arrow keys | `arrowup`, `arrowdown`, `arrowleft`, `arrowright` |
172
+ | Navigation | `home`, `end`, `pageup`, `pagedown` |
173
+ | Editing | `insert`, `delete`, `backspace` |
174
+ | Other | `escape`, `tab`, `enter`, `space` |
175
+
176
+ ## Browser Conflicts Warning
177
+
178
+ > [!WARNING]
179
+ > Some key combinations conflict with browser defaults. Use these with caution:
180
+
181
+ ### High-Risk Combinations (avoid these)
182
+ - `Ctrl+N` / `⌘+N` - New tab/window
183
+ - `Ctrl+T` / `⌘+T` - New tab
184
+ - `Ctrl+W` / `⌘+W` - Close tab
185
+ - `Ctrl+R` / `⌘+R` - Reload page
186
+ - `Ctrl+L` / `⌘+L` - Focus address bar
187
+ - `Ctrl+D` / `⌘+D` - Bookmark page
188
+
189
+ ### Safer Alternatives
190
+ - Function keys: `F1`, `F2`, `F3`, etc.
191
+ - Custom combinations: `Ctrl+Shift+S`, `Alt+Enter`
192
+ - Arrow keys with modifiers: `Ctrl+ArrowUp`
193
+ - Application-specific: `Ctrl+K`, `Ctrl+P` (if not conflicting)
194
+
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
+
200
+ ## Advanced Usage
201
+
202
+ ### Reactive UI Integration
203
+
204
+ ```typescript
205
+ import { Component, inject } from '@angular/core';
206
+ import { KeyboardShortcuts } from 'ngx-keys';
207
+
208
+ @Component({
209
+ template: `
210
+ <section>
211
+ <h3>Active Shortcuts ({{ activeShortcuts().length }})</h3>
212
+ @for (shortcut of activeShortcuts(); track shortcut.id) {
213
+ <div>
214
+ <kbd>{{ shortcut.keys }}</kbd> - {{ shortcut.description }}
215
+ </div>
216
+ }
217
+ </section>
218
+
219
+ <section>
220
+ <h3>Active Groups</h3>
221
+ @for (groupId of activeGroups(); track groupId) {
222
+ <div>{{ groupId }}</div>
223
+ }
224
+ </section>
225
+ `
226
+ })
227
+ export class ShortcutsDisplayComponent {
228
+ private readonly keyboardService = inject(KeyboardShortcuts);
229
+
230
+ // Access formatted shortcuts for display
231
+ protected readonly activeShortcuts = () => this.keyboardService.shortcutsUI$().active;
232
+ protected readonly inactiveShortcuts = () => this.keyboardService.shortcutsUI$().inactive;
233
+ protected readonly allShortcuts = () => this.keyboardService.shortcutsUI$().all;
234
+
235
+ // Access group information
236
+ protected readonly activeGroups = () => this.keyboardService.shortcuts$().groups.active;
237
+ protected readonly inactiveGroups = () => this.keyboardService.shortcuts$().groups.inactive;
238
+ }
239
+ ```
240
+ ### Group Management
241
+
242
+ ```typescript
243
+ import { Component, DestroyRef, inject } from '@angular/core';
244
+ import { KeyboardShortcuts, KeyboardShortcut } from 'ngx-keys';
245
+
246
+ export class FeatureComponent {
247
+ private readonly keyboardService = inject(KeyboardShortcuts);
248
+ private readonly destroyRef = inject(DestroyRef);
249
+
250
+ constructor() {
251
+ const shortcuts: KeyboardShortcut[] = [
252
+ {
253
+ id: 'cut',
254
+ keys: ['ctrl', 'x'],
255
+ macKeys: ['meta', 'x'],
256
+ action: () => this.cut(),
257
+ description: 'Cut selection'
258
+ },
259
+ {
260
+ id: 'copy',
261
+ keys: ['ctrl', 'c'],
262
+ macKeys: ['meta', 'c'],
263
+ action: () => this.copy(),
264
+ description: 'Copy selection'
265
+ }
266
+ ];
267
+
268
+ // Group is automatically activated when registered
269
+ this.keyboardService.registerGroup('edit-shortcuts', shortcuts);
270
+
271
+ // Setup cleanup on destroy
272
+ this.destroyRef.onDestroy(() => {
273
+ this.keyboardService.unregisterGroup('edit-shortcuts');
274
+ });
275
+ }
276
+
277
+ toggleEditMode(enabled: boolean) {
278
+ if (enabled) {
279
+ this.keyboardService.activateGroup('edit-shortcuts');
280
+ } else {
281
+ this.keyboardService.deactivateGroup('edit-shortcuts');
282
+ }
283
+ }
284
+
285
+ private cut() { /* implementation */ }
286
+ private copy() { /* implementation */ }
287
+ }
288
+ ```
289
+
290
+ ### Batch Operations
291
+
292
+ For better performance when making multiple changes, use the `batchUpdate` method:
293
+
294
+ ```typescript
295
+ import { Component, inject } from '@angular/core';
296
+ import { KeyboardShortcuts } from 'ngx-keys';
297
+
298
+ export class BatchUpdateComponent {
299
+ private readonly keyboardService = inject(KeyboardShortcuts);
300
+
301
+ constructor() {
302
+ this.setupMultipleShortcuts();
303
+ }
304
+
305
+ private setupMultipleShortcuts() {
306
+ // Batch multiple operations to reduce signal updates
307
+ // Note: Shortcuts are automatically activated when registered
308
+ this.keyboardService.batchUpdate(() => {
309
+ this.keyboardService.register({
310
+ id: 'action1',
311
+ keys: ['ctrl', '1'],
312
+ macKeys: ['meta', '1'],
313
+ action: () => this.action1(),
314
+ description: 'Action 1'
315
+ });
316
+
317
+ this.keyboardService.register({
318
+ id: 'action2',
319
+ keys: ['ctrl', '2'],
320
+ macKeys: ['meta', '2'],
321
+ action: () => this.action2(),
322
+ description: 'Action 2'
323
+ });
324
+ });
325
+ }
326
+
327
+ private action1() { /* implementation */ }
328
+ private action2() { /* implementation */ }
329
+ }
330
+ ```
331
+
332
+ ### Checking Status
333
+
334
+ ```typescript
335
+ import { Component, inject } from '@angular/core';
336
+ import { KeyboardShortcuts } from 'ngx-keys';
337
+
338
+ export class MyComponent {
339
+ private readonly keyboardService = inject(KeyboardShortcuts);
340
+
341
+ checkAndActivate() {
342
+ // Check before performing operations
343
+ if (this.keyboardService.isRegistered('my-shortcut')) {
344
+ this.keyboardService.activate('my-shortcut');
345
+ }
346
+
347
+ if (this.keyboardService.isGroupRegistered('my-group')) {
348
+ this.keyboardService.activateGroup('my-group');
349
+ }
350
+ }
351
+ }
352
+ ```
353
+
354
+ ## Building
355
+
356
+ To build the library:
357
+
358
+ ```bash
359
+ ng build ngx-keys
360
+ ```
361
+
362
+ ## Testing
363
+
364
+ To run tests:
365
+
366
+ ```bash
367
+ ng test ngx-keys
368
+ ```
369
+
370
+ ## License
371
+
372
+ 0BSD © [ngx-keys Contributors](LICENSE)
373
+
374
+ ## Contributing
375
+
376
+ Contributions are welcome! Please feel free to submit a Pull Request.
377
+
378
+ 1. Fork the repository
379
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
380
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
381
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
382
+ 5. Open a Pull Request
@@ -0,0 +1,495 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, computed, inject, PLATFORM_ID, Injectable } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+
5
+ /**
6
+ * Centralized error messages for keyboard shortcuts service
7
+ * This ensures consistency across the application and makes testing easier
8
+ */
9
+ const KeyboardShortcutsErrors = {
10
+ // Registration errors
11
+ SHORTCUT_ALREADY_REGISTERED: (id) => `Shortcut "${id}" already registered`,
12
+ GROUP_ALREADY_REGISTERED: (id) => `Group "${id}" already registered`,
13
+ KEY_CONFLICT: (conflictId) => `Key conflict with "${conflictId}"`,
14
+ SHORTCUT_IDS_ALREADY_REGISTERED: (ids) => `Shortcut IDs already registered: ${ids.join(', ')}`,
15
+ DUPLICATE_SHORTCUTS_IN_GROUP: (ids) => `Duplicate shortcuts in group: ${ids.join(', ')}`,
16
+ KEY_CONFLICTS_IN_GROUP: (conflicts) => `Key conflicts: ${conflicts.join(', ')}`,
17
+ // Operation errors
18
+ SHORTCUT_NOT_REGISTERED: (id) => `Shortcut "${id}" not registered`,
19
+ GROUP_NOT_REGISTERED: (id) => `Group "${id}" not registered`,
20
+ // Activation/Deactivation errors
21
+ CANNOT_ACTIVATE_SHORTCUT: (id) => `Cannot activate shortcut "${id}": not registered`,
22
+ CANNOT_DEACTIVATE_SHORTCUT: (id) => `Cannot deactivate shortcut "${id}": not registered`,
23
+ CANNOT_ACTIVATE_GROUP: (id) => `Cannot activate group "${id}": not registered`,
24
+ CANNOT_DEACTIVATE_GROUP: (id) => `Cannot deactivate group "${id}": not registered`,
25
+ // Unregistration errors
26
+ CANNOT_UNREGISTER_SHORTCUT: (id) => `Cannot unregister shortcut "${id}": not registered`,
27
+ CANNOT_UNREGISTER_GROUP: (id) => `Cannot unregister group "${id}": not registered`,
28
+ };
29
+ /**
30
+ * Custom error class for keyboard shortcuts
31
+ */
32
+ class KeyboardShortcutError extends Error {
33
+ errorType;
34
+ context;
35
+ constructor(errorType, message, context) {
36
+ super(message);
37
+ this.errorType = errorType;
38
+ this.context = context;
39
+ this.name = 'KeyboardShortcutError';
40
+ }
41
+ }
42
+ /**
43
+ * Error factory for creating consistent errors
44
+ */
45
+ class KeyboardShortcutsErrorFactory {
46
+ static shortcutAlreadyRegistered(id) {
47
+ return new KeyboardShortcutError('SHORTCUT_ALREADY_REGISTERED', KeyboardShortcutsErrors.SHORTCUT_ALREADY_REGISTERED(id), { shortcutId: id });
48
+ }
49
+ static groupAlreadyRegistered(id) {
50
+ return new KeyboardShortcutError('GROUP_ALREADY_REGISTERED', KeyboardShortcutsErrors.GROUP_ALREADY_REGISTERED(id), { groupId: id });
51
+ }
52
+ static keyConflict(conflictId) {
53
+ return new KeyboardShortcutError('KEY_CONFLICT', KeyboardShortcutsErrors.KEY_CONFLICT(conflictId), { conflictId });
54
+ }
55
+ static shortcutIdsAlreadyRegistered(ids) {
56
+ return new KeyboardShortcutError('SHORTCUT_IDS_ALREADY_REGISTERED', KeyboardShortcutsErrors.SHORTCUT_IDS_ALREADY_REGISTERED(ids), { duplicateIds: ids });
57
+ }
58
+ static duplicateShortcutsInGroup(ids) {
59
+ return new KeyboardShortcutError('DUPLICATE_SHORTCUTS_IN_GROUP', KeyboardShortcutsErrors.DUPLICATE_SHORTCUTS_IN_GROUP(ids), { duplicateIds: ids });
60
+ }
61
+ static keyConflictsInGroup(conflicts) {
62
+ return new KeyboardShortcutError('KEY_CONFLICTS_IN_GROUP', KeyboardShortcutsErrors.KEY_CONFLICTS_IN_GROUP(conflicts), { conflicts });
63
+ }
64
+ static shortcutNotRegistered(id) {
65
+ return new KeyboardShortcutError('SHORTCUT_NOT_REGISTERED', KeyboardShortcutsErrors.SHORTCUT_NOT_REGISTERED(id), { shortcutId: id });
66
+ }
67
+ static groupNotRegistered(id) {
68
+ return new KeyboardShortcutError('GROUP_NOT_REGISTERED', KeyboardShortcutsErrors.GROUP_NOT_REGISTERED(id), { groupId: id });
69
+ }
70
+ static cannotActivateShortcut(id) {
71
+ return new KeyboardShortcutError('CANNOT_ACTIVATE_SHORTCUT', KeyboardShortcutsErrors.CANNOT_ACTIVATE_SHORTCUT(id), { shortcutId: id });
72
+ }
73
+ static cannotDeactivateShortcut(id) {
74
+ return new KeyboardShortcutError('CANNOT_DEACTIVATE_SHORTCUT', KeyboardShortcutsErrors.CANNOT_DEACTIVATE_SHORTCUT(id), { shortcutId: id });
75
+ }
76
+ static cannotActivateGroup(id) {
77
+ return new KeyboardShortcutError('CANNOT_ACTIVATE_GROUP', KeyboardShortcutsErrors.CANNOT_ACTIVATE_GROUP(id), { groupId: id });
78
+ }
79
+ static cannotDeactivateGroup(id) {
80
+ return new KeyboardShortcutError('CANNOT_DEACTIVATE_GROUP', KeyboardShortcutsErrors.CANNOT_DEACTIVATE_GROUP(id), { groupId: id });
81
+ }
82
+ static cannotUnregisterShortcut(id) {
83
+ return new KeyboardShortcutError('CANNOT_UNREGISTER_SHORTCUT', KeyboardShortcutsErrors.CANNOT_UNREGISTER_SHORTCUT(id), { shortcutId: id });
84
+ }
85
+ static cannotUnregisterGroup(id) {
86
+ return new KeyboardShortcutError('CANNOT_UNREGISTER_GROUP', KeyboardShortcutsErrors.CANNOT_UNREGISTER_GROUP(id), { groupId: id });
87
+ }
88
+ }
89
+
90
+ class KeyboardShortcuts {
91
+ shortcuts = new Map();
92
+ groups = new Map();
93
+ activeShortcuts = new Set();
94
+ activeGroups = new Set();
95
+ // Single consolidated state signal - reduces memory overhead
96
+ state = signal({
97
+ shortcuts: new Map(),
98
+ groups: new Map(),
99
+ activeShortcuts: new Set(),
100
+ activeGroups: new Set(),
101
+ version: 0 // for change detection optimization
102
+ }, ...(ngDevMode ? [{ debugName: "state" }] : []));
103
+ // Primary computed signal - consumers derive what they need from this
104
+ shortcuts$ = computed(() => {
105
+ const state = this.state();
106
+ const activeShortcuts = Array.from(state.activeShortcuts)
107
+ .map(id => state.shortcuts.get(id))
108
+ .filter((s) => s !== undefined);
109
+ const inactiveShortcuts = Array.from(state.shortcuts.values())
110
+ .filter(s => !state.activeShortcuts.has(s.id));
111
+ return {
112
+ active: activeShortcuts,
113
+ inactive: inactiveShortcuts,
114
+ all: Array.from(state.shortcuts.values()),
115
+ groups: {
116
+ active: Array.from(state.activeGroups),
117
+ inactive: Array.from(state.groups.keys())
118
+ .filter(id => !state.activeGroups.has(id))
119
+ }
120
+ };
121
+ }, ...(ngDevMode ? [{ debugName: "shortcuts$" }] : []));
122
+ // Optional: Pre-formatted UI signal for components that need it
123
+ shortcutsUI$ = computed(() => {
124
+ const shortcuts = this.shortcuts$();
125
+ return {
126
+ active: shortcuts.active.map(s => this.formatShortcutForUI(s)),
127
+ inactive: shortcuts.inactive.map(s => this.formatShortcutForUI(s)),
128
+ all: shortcuts.all.map(s => this.formatShortcutForUI(s))
129
+ };
130
+ }, ...(ngDevMode ? [{ debugName: "shortcutsUI$" }] : []));
131
+ keydownListener = this.handleKeydown.bind(this);
132
+ isListening = false;
133
+ isBrowser;
134
+ constructor() {
135
+ // Use try-catch to handle injection context for better testability
136
+ try {
137
+ const platformId = inject(PLATFORM_ID);
138
+ this.isBrowser = isPlatformBrowser(platformId);
139
+ }
140
+ catch {
141
+ // Fallback for testing - assume browser environment
142
+ this.isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
143
+ }
144
+ if (this.isBrowser) {
145
+ this.startListening();
146
+ }
147
+ }
148
+ ngOnDestroy() {
149
+ this.stopListening();
150
+ }
151
+ /**
152
+ * Batch updates and increment version for change detection
153
+ */
154
+ updateState() {
155
+ this.state.update(current => ({
156
+ shortcuts: new Map(this.shortcuts),
157
+ groups: new Map(this.groups),
158
+ activeShortcuts: new Set(this.activeShortcuts),
159
+ activeGroups: new Set(this.activeGroups),
160
+ version: current.version + 1
161
+ }));
162
+ }
163
+ /**
164
+ * Utility method for UI formatting
165
+ */
166
+ formatShortcutForUI(shortcut) {
167
+ return {
168
+ id: shortcut.id,
169
+ keys: this.formatKeysForDisplay(shortcut.keys, false),
170
+ macKeys: this.formatKeysForDisplay(shortcut.macKeys, true),
171
+ description: shortcut.description
172
+ };
173
+ }
174
+ /**
175
+ * Utility method for batch operations - reduces signal updates
176
+ */
177
+ batchUpdate(operations) {
178
+ operations();
179
+ this.updateState();
180
+ }
181
+ /**
182
+ * Format keys for display with proper Unicode symbols
183
+ */
184
+ formatKeysForDisplay(keys, isMac = false) {
185
+ const keyMap = isMac ? {
186
+ 'ctrl': '⌃',
187
+ 'alt': '⌥',
188
+ 'shift': '⇧',
189
+ 'meta': '⌘',
190
+ 'cmd': '⌘',
191
+ 'command': '⌘'
192
+ } : {
193
+ 'ctrl': 'Ctrl',
194
+ 'alt': 'Alt',
195
+ 'shift': 'Shift',
196
+ 'meta': 'Win'
197
+ };
198
+ return keys
199
+ .map(key => keyMap[key.toLowerCase()] || key.toUpperCase())
200
+ .join('+');
201
+ }
202
+ /**
203
+ * Check if a key combination is already registered
204
+ * @returns The ID of the conflicting shortcut, or null if no conflict
205
+ */
206
+ findConflict(newShortcut) {
207
+ for (const existing of this.shortcuts.values()) {
208
+ if (this.keysMatch(newShortcut.keys, existing.keys)) {
209
+ return existing.id;
210
+ }
211
+ if (this.keysMatch(newShortcut.macKeys, existing.macKeys)) {
212
+ return existing.id;
213
+ }
214
+ }
215
+ return null;
216
+ }
217
+ /**
218
+ * Register a single keyboard shortcut
219
+ * @throws KeyboardShortcutError if shortcut ID is already registered or key combination is in use
220
+ */
221
+ register(shortcut) {
222
+ if (this.shortcuts.has(shortcut.id)) {
223
+ throw KeyboardShortcutsErrorFactory.shortcutAlreadyRegistered(shortcut.id);
224
+ }
225
+ const conflictId = this.findConflict(shortcut);
226
+ if (conflictId) {
227
+ throw KeyboardShortcutsErrorFactory.keyConflict(conflictId);
228
+ }
229
+ this.shortcuts.set(shortcut.id, shortcut);
230
+ this.activeShortcuts.add(shortcut.id);
231
+ this.updateState();
232
+ }
233
+ /**
234
+ * Register multiple keyboard shortcuts as a group
235
+ * @throws KeyboardShortcutError if group ID is already registered or if any shortcut ID or key combination conflicts
236
+ */
237
+ registerGroup(groupId, shortcuts) {
238
+ // Check if group ID already exists
239
+ if (this.groups.has(groupId)) {
240
+ throw KeyboardShortcutsErrorFactory.groupAlreadyRegistered(groupId);
241
+ }
242
+ // Check for duplicate shortcut IDs and key combination conflicts
243
+ const duplicateIds = [];
244
+ const keyConflicts = [];
245
+ shortcuts.forEach(shortcut => {
246
+ if (this.shortcuts.has(shortcut.id)) {
247
+ duplicateIds.push(shortcut.id);
248
+ }
249
+ const conflictId = this.findConflict(shortcut);
250
+ if (conflictId) {
251
+ keyConflicts.push(`"${shortcut.id}" conflicts with "${conflictId}"`);
252
+ }
253
+ });
254
+ if (duplicateIds.length > 0) {
255
+ throw KeyboardShortcutsErrorFactory.shortcutIdsAlreadyRegistered(duplicateIds);
256
+ }
257
+ if (keyConflicts.length > 0) {
258
+ throw KeyboardShortcutsErrorFactory.keyConflictsInGroup(keyConflicts);
259
+ }
260
+ // Validate that all shortcuts have unique IDs within the group
261
+ const groupIds = new Set();
262
+ const duplicatesInGroup = [];
263
+ shortcuts.forEach(shortcut => {
264
+ if (groupIds.has(shortcut.id)) {
265
+ duplicatesInGroup.push(shortcut.id);
266
+ }
267
+ else {
268
+ groupIds.add(shortcut.id);
269
+ }
270
+ });
271
+ if (duplicatesInGroup.length > 0) {
272
+ throw KeyboardShortcutsErrorFactory.duplicateShortcutsInGroup(duplicatesInGroup);
273
+ }
274
+ // Use batch update to reduce signal updates
275
+ this.batchUpdate(() => {
276
+ const group = {
277
+ id: groupId,
278
+ shortcuts,
279
+ active: true
280
+ };
281
+ this.groups.set(groupId, group);
282
+ this.activeGroups.add(groupId);
283
+ // Register individual shortcuts
284
+ shortcuts.forEach(shortcut => {
285
+ this.shortcuts.set(shortcut.id, shortcut);
286
+ this.activeShortcuts.add(shortcut.id);
287
+ });
288
+ });
289
+ }
290
+ /**
291
+ * Unregister a single keyboard shortcut
292
+ * @throws KeyboardShortcutError if shortcut ID doesn't exist
293
+ */
294
+ unregister(shortcutId) {
295
+ if (!this.shortcuts.has(shortcutId)) {
296
+ throw KeyboardShortcutsErrorFactory.cannotUnregisterShortcut(shortcutId);
297
+ }
298
+ this.shortcuts.delete(shortcutId);
299
+ this.activeShortcuts.delete(shortcutId);
300
+ this.updateState();
301
+ }
302
+ /**
303
+ * Unregister a group of keyboard shortcuts
304
+ * @throws KeyboardShortcutError if group ID doesn't exist
305
+ */
306
+ unregisterGroup(groupId) {
307
+ const group = this.groups.get(groupId);
308
+ if (!group) {
309
+ throw KeyboardShortcutsErrorFactory.cannotUnregisterGroup(groupId);
310
+ }
311
+ this.batchUpdate(() => {
312
+ group.shortcuts.forEach(shortcut => {
313
+ this.shortcuts.delete(shortcut.id);
314
+ this.activeShortcuts.delete(shortcut.id);
315
+ });
316
+ this.groups.delete(groupId);
317
+ this.activeGroups.delete(groupId);
318
+ });
319
+ }
320
+ /**
321
+ * Activate a single keyboard shortcut
322
+ * @throws KeyboardShortcutError if shortcut ID doesn't exist
323
+ */
324
+ activate(shortcutId) {
325
+ if (!this.shortcuts.has(shortcutId)) {
326
+ throw KeyboardShortcutsErrorFactory.cannotActivateShortcut(shortcutId);
327
+ }
328
+ this.activeShortcuts.add(shortcutId);
329
+ this.updateState();
330
+ }
331
+ /**
332
+ * Deactivate a single keyboard shortcut
333
+ * @throws KeyboardShortcutError if shortcut ID doesn't exist
334
+ */
335
+ deactivate(shortcutId) {
336
+ if (!this.shortcuts.has(shortcutId)) {
337
+ throw KeyboardShortcutsErrorFactory.cannotDeactivateShortcut(shortcutId);
338
+ }
339
+ this.activeShortcuts.delete(shortcutId);
340
+ this.updateState();
341
+ }
342
+ /**
343
+ * Activate a group of keyboard shortcuts
344
+ * @throws KeyboardShortcutError if group ID doesn't exist
345
+ */
346
+ activateGroup(groupId) {
347
+ const group = this.groups.get(groupId);
348
+ if (!group) {
349
+ throw KeyboardShortcutsErrorFactory.cannotActivateGroup(groupId);
350
+ }
351
+ this.batchUpdate(() => {
352
+ group.active = true;
353
+ this.activeGroups.add(groupId);
354
+ group.shortcuts.forEach(shortcut => {
355
+ this.activeShortcuts.add(shortcut.id);
356
+ });
357
+ });
358
+ }
359
+ /**
360
+ * Deactivate a group of keyboard shortcuts
361
+ * @throws KeyboardShortcutError if group ID doesn't exist
362
+ */
363
+ deactivateGroup(groupId) {
364
+ const group = this.groups.get(groupId);
365
+ if (!group) {
366
+ throw KeyboardShortcutsErrorFactory.cannotDeactivateGroup(groupId);
367
+ }
368
+ this.batchUpdate(() => {
369
+ group.active = false;
370
+ this.activeGroups.delete(groupId);
371
+ group.shortcuts.forEach(shortcut => {
372
+ this.activeShortcuts.delete(shortcut.id);
373
+ });
374
+ });
375
+ }
376
+ /**
377
+ * Check if a shortcut is active
378
+ */
379
+ isActive(shortcutId) {
380
+ return this.activeShortcuts.has(shortcutId);
381
+ }
382
+ /**
383
+ * Check if a shortcut is registered
384
+ */
385
+ isRegistered(shortcutId) {
386
+ return this.shortcuts.has(shortcutId);
387
+ }
388
+ /**
389
+ * Check if a group is active
390
+ */
391
+ isGroupActive(groupId) {
392
+ return this.activeGroups.has(groupId);
393
+ }
394
+ /**
395
+ * Check if a group is registered
396
+ */
397
+ isGroupRegistered(groupId) {
398
+ return this.groups.has(groupId);
399
+ }
400
+ /**
401
+ * Get all registered shortcuts
402
+ */
403
+ getShortcuts() {
404
+ return this.shortcuts;
405
+ }
406
+ /**
407
+ * Get all registered groups
408
+ */
409
+ getGroups() {
410
+ return this.groups;
411
+ }
412
+ startListening() {
413
+ if (!this.isBrowser || this.isListening) {
414
+ return;
415
+ }
416
+ document.addEventListener('keydown', this.keydownListener, { passive: false });
417
+ this.isListening = true;
418
+ }
419
+ stopListening() {
420
+ if (!this.isBrowser || !this.isListening) {
421
+ return;
422
+ }
423
+ document.removeEventListener('keydown', this.keydownListener);
424
+ this.isListening = false;
425
+ }
426
+ handleKeydown(event) {
427
+ const pressedKeys = this.getPressedKeys(event);
428
+ const isMac = this.isMacPlatform();
429
+ for (const shortcutId of this.activeShortcuts) {
430
+ const shortcut = this.shortcuts.get(shortcutId);
431
+ if (!shortcut)
432
+ continue;
433
+ const targetKeys = isMac ? shortcut.macKeys : shortcut.keys;
434
+ if (this.keysMatch(pressedKeys, targetKeys)) {
435
+ event.preventDefault();
436
+ event.stopPropagation();
437
+ try {
438
+ shortcut.action();
439
+ }
440
+ catch (error) {
441
+ console.error(`Error executing keyboard shortcut "${shortcut.id}":`, error);
442
+ }
443
+ break; // Only execute the first matching shortcut
444
+ }
445
+ }
446
+ }
447
+ getPressedKeys(event) {
448
+ const keys = [];
449
+ if (event.ctrlKey)
450
+ keys.push('ctrl');
451
+ if (event.altKey)
452
+ keys.push('alt');
453
+ if (event.shiftKey)
454
+ keys.push('shift');
455
+ if (event.metaKey)
456
+ keys.push('meta');
457
+ // Add the main key (normalize to lowercase)
458
+ const key = event.key.toLowerCase();
459
+ if (!['control', 'alt', 'shift', 'meta'].includes(key)) {
460
+ keys.push(key);
461
+ }
462
+ return keys;
463
+ }
464
+ keysMatch(pressedKeys, targetKeys) {
465
+ if (pressedKeys.length !== targetKeys.length) {
466
+ return false;
467
+ }
468
+ // Normalize and sort both arrays for comparison
469
+ const normalizedPressed = pressedKeys.map(key => key.toLowerCase()).sort();
470
+ const normalizedTarget = targetKeys.map(key => key.toLowerCase()).sort();
471
+ return normalizedPressed.every((key, index) => key === normalizedTarget[index]);
472
+ }
473
+ isMacPlatform() {
474
+ return this.isBrowser && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
475
+ }
476
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: KeyboardShortcuts, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
477
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: KeyboardShortcuts, providedIn: 'root' });
478
+ }
479
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: KeyboardShortcuts, decorators: [{
480
+ type: Injectable,
481
+ args: [{
482
+ providedIn: 'root'
483
+ }]
484
+ }], ctorParameters: () => [] });
485
+
486
+ /*
487
+ * Public API Surface of ngx-keys
488
+ */
489
+
490
+ /**
491
+ * Generated bundle index. Do not edit.
492
+ */
493
+
494
+ export { KeyboardShortcutError, KeyboardShortcuts, KeyboardShortcutsErrorFactory, KeyboardShortcutsErrors };
495
+ //# sourceMappingURL=ngx-keys.mjs.map
@@ -0,0 +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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"}
package/index.d.ts ADDED
@@ -0,0 +1,199 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { OnDestroy } from '@angular/core';
3
+
4
+ interface KeyboardShortcut {
5
+ id: string;
6
+ keys: string[];
7
+ macKeys: string[];
8
+ action: () => void;
9
+ description: string;
10
+ }
11
+ interface KeyboardShortcutGroup {
12
+ id: string;
13
+ shortcuts: KeyboardShortcut[];
14
+ active: boolean;
15
+ }
16
+ /**
17
+ * Interface for keyboard shortcut data optimized for UI display
18
+ */
19
+ interface KeyboardShortcutUI {
20
+ id: string;
21
+ keys: string;
22
+ macKeys: string;
23
+ description: string;
24
+ }
25
+
26
+ declare class KeyboardShortcuts implements OnDestroy {
27
+ private readonly shortcuts;
28
+ private readonly groups;
29
+ private readonly activeShortcuts;
30
+ private readonly activeGroups;
31
+ private readonly state;
32
+ readonly shortcuts$: _angular_core.Signal<{
33
+ active: KeyboardShortcut[];
34
+ inactive: KeyboardShortcut[];
35
+ all: KeyboardShortcut[];
36
+ groups: {
37
+ active: string[];
38
+ inactive: string[];
39
+ };
40
+ }>;
41
+ readonly shortcutsUI$: _angular_core.Signal<{
42
+ active: KeyboardShortcutUI[];
43
+ inactive: KeyboardShortcutUI[];
44
+ all: KeyboardShortcutUI[];
45
+ }>;
46
+ private readonly keydownListener;
47
+ private isListening;
48
+ protected isBrowser: boolean;
49
+ constructor();
50
+ ngOnDestroy(): void;
51
+ /**
52
+ * Batch updates and increment version for change detection
53
+ */
54
+ private updateState;
55
+ /**
56
+ * Utility method for UI formatting
57
+ */
58
+ formatShortcutForUI(shortcut: KeyboardShortcut): KeyboardShortcutUI;
59
+ /**
60
+ * Utility method for batch operations - reduces signal updates
61
+ */
62
+ batchUpdate(operations: () => void): void;
63
+ /**
64
+ * Format keys for display with proper Unicode symbols
65
+ */
66
+ private formatKeysForDisplay;
67
+ /**
68
+ * Check if a key combination is already registered
69
+ * @returns The ID of the conflicting shortcut, or null if no conflict
70
+ */
71
+ private findConflict;
72
+ /**
73
+ * Register a single keyboard shortcut
74
+ * @throws KeyboardShortcutError if shortcut ID is already registered or key combination is in use
75
+ */
76
+ register(shortcut: KeyboardShortcut): void;
77
+ /**
78
+ * Register multiple keyboard shortcuts as a group
79
+ * @throws KeyboardShortcutError if group ID is already registered or if any shortcut ID or key combination conflicts
80
+ */
81
+ registerGroup(groupId: string, shortcuts: KeyboardShortcut[]): void;
82
+ /**
83
+ * Unregister a single keyboard shortcut
84
+ * @throws KeyboardShortcutError if shortcut ID doesn't exist
85
+ */
86
+ unregister(shortcutId: string): void;
87
+ /**
88
+ * Unregister a group of keyboard shortcuts
89
+ * @throws KeyboardShortcutError if group ID doesn't exist
90
+ */
91
+ unregisterGroup(groupId: string): void;
92
+ /**
93
+ * Activate a single keyboard shortcut
94
+ * @throws KeyboardShortcutError if shortcut ID doesn't exist
95
+ */
96
+ activate(shortcutId: string): void;
97
+ /**
98
+ * Deactivate a single keyboard shortcut
99
+ * @throws KeyboardShortcutError if shortcut ID doesn't exist
100
+ */
101
+ deactivate(shortcutId: string): void;
102
+ /**
103
+ * Activate a group of keyboard shortcuts
104
+ * @throws KeyboardShortcutError if group ID doesn't exist
105
+ */
106
+ activateGroup(groupId: string): void;
107
+ /**
108
+ * Deactivate a group of keyboard shortcuts
109
+ * @throws KeyboardShortcutError if group ID doesn't exist
110
+ */
111
+ deactivateGroup(groupId: string): void;
112
+ /**
113
+ * Check if a shortcut is active
114
+ */
115
+ isActive(shortcutId: string): boolean;
116
+ /**
117
+ * Check if a shortcut is registered
118
+ */
119
+ isRegistered(shortcutId: string): boolean;
120
+ /**
121
+ * Check if a group is active
122
+ */
123
+ isGroupActive(groupId: string): boolean;
124
+ /**
125
+ * Check if a group is registered
126
+ */
127
+ isGroupRegistered(groupId: string): boolean;
128
+ /**
129
+ * Get all registered shortcuts
130
+ */
131
+ getShortcuts(): ReadonlyMap<string, KeyboardShortcut>;
132
+ /**
133
+ * Get all registered groups
134
+ */
135
+ getGroups(): ReadonlyMap<string, KeyboardShortcutGroup>;
136
+ private startListening;
137
+ private stopListening;
138
+ protected handleKeydown(event: KeyboardEvent): void;
139
+ protected getPressedKeys(event: KeyboardEvent): string[];
140
+ protected keysMatch(pressedKeys: string[], targetKeys: string[]): boolean;
141
+ protected isMacPlatform(): boolean;
142
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<KeyboardShortcuts, never>;
143
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<KeyboardShortcuts>;
144
+ }
145
+
146
+ /**
147
+ * Centralized error messages for keyboard shortcuts service
148
+ * This ensures consistency across the application and makes testing easier
149
+ */
150
+ declare const KeyboardShortcutsErrors: {
151
+ readonly SHORTCUT_ALREADY_REGISTERED: (id: string) => string;
152
+ readonly GROUP_ALREADY_REGISTERED: (id: string) => string;
153
+ readonly KEY_CONFLICT: (conflictId: string) => string;
154
+ readonly SHORTCUT_IDS_ALREADY_REGISTERED: (ids: string[]) => string;
155
+ readonly DUPLICATE_SHORTCUTS_IN_GROUP: (ids: string[]) => string;
156
+ readonly KEY_CONFLICTS_IN_GROUP: (conflicts: string[]) => string;
157
+ readonly SHORTCUT_NOT_REGISTERED: (id: string) => string;
158
+ readonly GROUP_NOT_REGISTERED: (id: string) => string;
159
+ readonly CANNOT_ACTIVATE_SHORTCUT: (id: string) => string;
160
+ readonly CANNOT_DEACTIVATE_SHORTCUT: (id: string) => string;
161
+ readonly CANNOT_ACTIVATE_GROUP: (id: string) => string;
162
+ readonly CANNOT_DEACTIVATE_GROUP: (id: string) => string;
163
+ readonly CANNOT_UNREGISTER_SHORTCUT: (id: string) => string;
164
+ readonly CANNOT_UNREGISTER_GROUP: (id: string) => string;
165
+ };
166
+ /**
167
+ * Error types for type safety
168
+ */
169
+ type KeyboardShortcutsErrorType = keyof typeof KeyboardShortcutsErrors;
170
+ /**
171
+ * Custom error class for keyboard shortcuts
172
+ */
173
+ declare class KeyboardShortcutError extends Error {
174
+ readonly errorType: KeyboardShortcutsErrorType;
175
+ readonly context?: Record<string, any> | undefined;
176
+ constructor(errorType: KeyboardShortcutsErrorType, message: string, context?: Record<string, any> | undefined);
177
+ }
178
+ /**
179
+ * Error factory for creating consistent errors
180
+ */
181
+ declare class KeyboardShortcutsErrorFactory {
182
+ static shortcutAlreadyRegistered(id: string): KeyboardShortcutError;
183
+ static groupAlreadyRegistered(id: string): KeyboardShortcutError;
184
+ static keyConflict(conflictId: string): KeyboardShortcutError;
185
+ static shortcutIdsAlreadyRegistered(ids: string[]): KeyboardShortcutError;
186
+ static duplicateShortcutsInGroup(ids: string[]): KeyboardShortcutError;
187
+ static keyConflictsInGroup(conflicts: string[]): KeyboardShortcutError;
188
+ static shortcutNotRegistered(id: string): KeyboardShortcutError;
189
+ static groupNotRegistered(id: string): KeyboardShortcutError;
190
+ static cannotActivateShortcut(id: string): KeyboardShortcutError;
191
+ static cannotDeactivateShortcut(id: string): KeyboardShortcutError;
192
+ static cannotActivateGroup(id: string): KeyboardShortcutError;
193
+ static cannotDeactivateGroup(id: string): KeyboardShortcutError;
194
+ static cannotUnregisterShortcut(id: string): KeyboardShortcutError;
195
+ static cannotUnregisterGroup(id: string): KeyboardShortcutError;
196
+ }
197
+
198
+ export { KeyboardShortcutError, KeyboardShortcuts, KeyboardShortcutsErrorFactory, KeyboardShortcutsErrors };
199
+ export type { KeyboardShortcut, KeyboardShortcutGroup, KeyboardShortcutUI, KeyboardShortcutsErrorType };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "ngx-keys",
3
+ "version": "1.0.0",
4
+ "description": "A reactive Angular library for managing keyboard shortcuts with signals-based UI integration",
5
+ "keywords": [
6
+ "angular",
7
+ "typescript",
8
+ "keyboard-shortcuts",
9
+ "hotkeys",
10
+ "angular-library",
11
+ "reactive",
12
+ "signals",
13
+ "ui-components",
14
+ "cross-platform",
15
+ "angular-signals"
16
+ ],
17
+ "license": "0BSD",
18
+ "author": "NgxKeys Contributors",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/mrivasperez/ngx-keys.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/mrivasperez/ngx-keys/issues"
25
+ },
26
+ "homepage": "https://github.com/mrivasperez/ngx-keys#readme",
27
+ "peerDependencies": {
28
+ "@angular/common": "^20.2.0",
29
+ "@angular/core": "^20.2.0"
30
+ },
31
+ "dependencies": {
32
+ "tslib": "^2.3.0"
33
+ },
34
+ "sideEffects": false,
35
+ "module": "fesm2022/ngx-keys.mjs",
36
+ "typings": "index.d.ts",
37
+ "exports": {
38
+ "./package.json": {
39
+ "default": "./package.json"
40
+ },
41
+ ".": {
42
+ "types": "./index.d.ts",
43
+ "default": "./fesm2022/ngx-keys.mjs"
44
+ }
45
+ }
46
+ }