rx-hotkeys 4.2.0 → 5.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.
@@ -1,17 +1,16 @@
1
- import { fromEvent, BehaviorSubject, EMPTY, filter, map, bufferCount, withLatestFrom, tap, catchError, scan, merge, Subject, takeUntil, share, distinctUntilChanged, combineLatest, } from "rxjs";
1
+ var _a;
2
+ import { fromEvent, BehaviorSubject, EMPTY, Observable, filter, map, bufferCount, withLatestFrom, tap, catchError, scan, merge, Subject, takeUntil, share, distinctUntilChanged, combineLatest, } from "rxjs";
2
3
  import { Keys, KeyAliases } from "./keys.js";
3
4
  // --- Enums, Interfaces and Types ---
4
- export var ShortcutTypes;
5
- (function (ShortcutTypes) {
6
- ShortcutTypes["Combination"] = "combination";
7
- ShortcutTypes["Sequence"] = "sequence";
8
- })(ShortcutTypes || (ShortcutTypes = {}));
9
- var EmitStates;
10
- (function (EmitStates) {
11
- EmitStates[EmitStates["Emit"] = 0] = "Emit";
12
- EmitStates[EmitStates["Ignore"] = 1] = "Ignore";
13
- EmitStates[EmitStates["InProgress"] = 2] = "InProgress";
14
- })(EmitStates || (EmitStates = {}));
5
+ export const ShortcutTypes = Object.freeze({
6
+ Combination: "combination",
7
+ Sequence: "sequence",
8
+ });
9
+ const EmitStates = Object.freeze({
10
+ Emit: "Emit",
11
+ Ignore: "Ignore",
12
+ InProgress: "InProgress",
13
+ });
15
14
  // --- Helper function to compare keys ---
16
15
  /**
17
16
  * Compares a browser event's key with a configured key.
@@ -50,30 +49,31 @@ function normalizeKey(key) {
50
49
  return finalKey || null;
51
50
  }
52
51
  // --- Hotkeys Library ---
52
+ const NO_OVERRIDE = Symbol("No Hotkey Override");
53
53
  /**
54
54
  * Manages keyboard shortcuts for web applications.
55
55
  * Allows registration of single key combinations (e.g., Ctrl+S) and key sequences (e.g., g -> i).
56
56
  * Supports contexts to enable/disable shortcuts based on application state.
57
57
  */
58
58
  export class Hotkeys {
59
- static KEYDOWN_EVENT = "keydown";
60
- static KEYUP_EVENT = "keyup";
61
- static LOG_PREFIX = "Hotkeys:";
59
+ static #KEYDOWN_EVENT = "keydown";
60
+ static #KEYUP_EVENT = "keyup";
61
+ static #LOG_PREFIX = "Hotkeys:";
62
62
  // --- Sentinel value for no override ---
63
- static NO_OVERRIDE = Symbol("No Hotkey Override");
63
+ static #NO_OVERRIDE = NO_OVERRIDE;
64
64
  // NEW: A unified stream cache that handles different listener options.
65
- eventStreams;
66
- activeShortcuts;
67
- debugMode;
65
+ #eventStreams;
66
+ #activeShortcuts;
67
+ #debugMode;
68
68
  // --- Separate states for stack and override ---
69
- contextStack$;
70
- overrideContext$;
69
+ #contextStack$;
70
+ #overrideContext$;
71
71
  /**
72
72
  * An Observable that emits the new active context name (or null) whenever it changes.
73
73
  * The active context is the override context if one is set, otherwise it's the context
74
74
  * from the top of the stack.
75
75
  */
76
- activeContext$;
76
+ #activeContext$;
77
77
  /**
78
78
  * Creates an instance of Hotkeys.
79
79
  * @param initialContext - Optional initial context name. This forms the base of the context stack.
@@ -81,47 +81,47 @@ export class Hotkeys {
81
81
  * @throws Error if not in a browser environment (i.e., `document` or `performance` is undefined).
82
82
  */
83
83
  constructor(initialContext = null, debugMode = false) {
84
- this.debugMode = debugMode;
84
+ this.#debugMode = debugMode;
85
85
  if (typeof document === "undefined" || typeof performance === "undefined") {
86
- throw new Error(`${Hotkeys.LOG_PREFIX} Hotkeys can only be used in a browser environment.`);
86
+ throw new Error(`${_a.#LOG_PREFIX} Hotkeys can only be used in a browser environment.`);
87
87
  }
88
- this.eventStreams = new WeakMap(); // Initialize the new unified cache
89
- this.activeShortcuts = new Map();
88
+ this.#eventStreams = new WeakMap(); // Initialize the new unified cache
89
+ this.#activeShortcuts = new Map();
90
90
  // The context stack is the source of truth for the active context.
91
- this.contextStack$ = new BehaviorSubject([initialContext]);
92
- this.overrideContext$ = new BehaviorSubject(Hotkeys.NO_OVERRIDE);
91
+ this.#contextStack$ = new BehaviorSubject([initialContext]);
92
+ this.#overrideContext$ = new BehaviorSubject(_a.#NO_OVERRIDE);
93
93
  // The public activeContext$ now correctly handles the sentinel value.
94
- this.activeContext$ = combineLatest([
95
- this.overrideContext$,
96
- this.contextStack$.pipe(map(stack => stack.length > 0 ? stack[stack.length - 1] : null))
97
- ]).pipe(map(([overrideCtx, stackCtx]) => this._resolveActiveContext(overrideCtx, stackCtx)), distinctUntilChanged());
98
- if (this.debugMode) {
99
- console.log(`${Hotkeys.LOG_PREFIX} Library initialized. Initial context: "${initialContext}". Debug mode: ${debugMode}.`);
94
+ this.#activeContext$ = combineLatest([
95
+ this.#overrideContext$,
96
+ this.#contextStack$.pipe(map(stack => stack.length > 0 ? stack[stack.length - 1] : null))
97
+ ]).pipe(map(([overrideCtx, stackCtx]) => this.#resolveActiveContext(overrideCtx, stackCtx)), distinctUntilChanged());
98
+ if (this.#debugMode) {
99
+ console.log(`${_a.#LOG_PREFIX} Library initialized. Initial context: "${initialContext}". Debug mode: ${debugMode}.`);
100
100
  // Optional: Log context changes for debugging
101
- this.activeContext$.subscribe(newContext => {
102
- console.log(`${Hotkeys.LOG_PREFIX} Active context changed to: ${newContext}`);
101
+ this.#activeContext$.subscribe(newContext => {
102
+ console.log(`${_a.#LOG_PREFIX} Active context changed to: ${newContext}`);
103
103
  });
104
104
  }
105
105
  }
106
106
  /**
107
107
  * Helper method to determine the active context based on override and stack.
108
108
  */
109
- _resolveActiveContext(overrideCtx, stackCtx) {
110
- return overrideCtx !== Hotkeys.NO_OVERRIDE ? overrideCtx : stackCtx;
109
+ #resolveActiveContext(overrideCtx, stackCtx) {
110
+ return overrideCtx !== _a.#NO_OVERRIDE ? overrideCtx : stackCtx;
111
111
  }
112
- _normalizeAndParseTriggers(keys, shortcutId) {
112
+ #normalizeAndParseTriggers(keys, shortcutId) {
113
113
  const keyInputs = Array.isArray(keys) ? keys : [keys];
114
114
  const parsedTriggers = [];
115
115
  for (const input of keyInputs) {
116
116
  let triggersToParse = [];
117
117
  if (typeof input === "string" && input.length > 1 && input.includes("+")) {
118
- triggersToParse.push(...this._parseCombinationString(input));
118
+ triggersToParse.push(...this.#parseCombinationString(input));
119
119
  }
120
120
  else {
121
121
  triggersToParse.push(input);
122
122
  }
123
123
  for (const trigger of triggersToParse) {
124
- const parsed = this._parseKeyTrigger(trigger, shortcutId);
124
+ const parsed = this.#parseKeyTrigger(trigger, shortcutId);
125
125
  if (parsed) {
126
126
  parsedTriggers.push({
127
127
  key: parsed.configuredMainKey,
@@ -135,7 +135,7 @@ export class Hotkeys {
135
135
  }
136
136
  return parsedTriggers;
137
137
  }
138
- _normalizeSequence(sequence, shortcutId) {
138
+ #normalizeSequence(sequence, shortcutId) {
139
139
  const keyInputs = typeof sequence === "string" ? sequence.split("->") : sequence;
140
140
  const results = [];
141
141
  for (const keyStr of keyInputs) {
@@ -144,7 +144,7 @@ export class Hotkeys {
144
144
  results.push(finalKey);
145
145
  }
146
146
  else {
147
- console.warn(`${Hotkeys.LOG_PREFIX} Could not parse key: "${keyStr}" in sequence for shortcut "${shortcutId}".`);
147
+ console.warn(`${_a.#LOG_PREFIX} Could not parse key: "${keyStr}" in sequence for shortcut "${shortcutId}".`);
148
148
  return null; // Fail fast if any key is invalid
149
149
  }
150
150
  }
@@ -153,7 +153,7 @@ export class Hotkeys {
153
153
  /**
154
154
  * [PRIVATE] Generates a unique key for the stream cache based on event type and options.
155
155
  */
156
- _getStreamCacheKey(eventType, options) {
156
+ #getStreamCacheKey(eventType, options) {
157
157
  if (!options) {
158
158
  return eventType;
159
159
  }
@@ -170,19 +170,19 @@ export class Hotkeys {
170
170
  * @param options The AddEventListenerOptions.
171
171
  * @returns A shared Observable for the specified event.
172
172
  */
173
- _getEventStream(eventType, target, options) {
174
- if (!this.eventStreams.has(target)) {
175
- this.eventStreams.set(target, new Map());
173
+ #getEventStream(eventType, target, options) {
174
+ if (!this.#eventStreams.has(target)) {
175
+ this.#eventStreams.set(target, new Map());
176
176
  }
177
- const targetCache = this.eventStreams.get(target);
178
- const cacheKey = this._getStreamCacheKey(eventType, options);
177
+ const targetCache = this.#eventStreams.get(target);
178
+ const cacheKey = this.#getStreamCacheKey(eventType, options);
179
179
  if (!targetCache.has(cacheKey)) {
180
180
  // Create the new stream with the specified options
181
181
  const newStream = fromEvent(target, eventType, options).pipe(share());
182
182
  targetCache.set(cacheKey, newStream);
183
- if (this.debugMode) {
183
+ if (this.#debugMode) {
184
184
  const targetName = target === document ? "document" : `element "${target.id || target.tagName}"`;
185
- console.log(`${Hotkeys.LOG_PREFIX} Created new shared listener for "${cacheKey}" on ${targetName}.`);
185
+ console.log(`${_a.#LOG_PREFIX} Created new shared listener for "${cacheKey}" on ${targetName}.`);
186
186
  }
187
187
  }
188
188
  return targetCache.get(cacheKey);
@@ -193,18 +193,18 @@ export class Hotkeys {
193
193
  * @returns A `restore` function that, when called, clears the override context, reverting to the stack.
194
194
  */
195
195
  setContext(contextName) {
196
- if (this.debugMode) {
197
- console.log(`${Hotkeys.LOG_PREFIX} Setting override context to: "${contextName}".`);
196
+ if (this.#debugMode) {
197
+ console.log(`${_a.#LOG_PREFIX} Setting override context to: "${contextName}".`);
198
198
  }
199
- this.overrideContext$.next(contextName);
199
+ this.#overrideContext$.next(contextName);
200
200
  const restore = () => {
201
201
  // Only clear the override if it's still the one we set.
202
- if (this.overrideContext$.getValue() === contextName) {
203
- if (this.debugMode) {
204
- console.log(`${Hotkeys.LOG_PREFIX} Restoring/clearing override context from: "${contextName}".`);
202
+ if (this.#overrideContext$.getValue() === contextName) {
203
+ if (this.#debugMode) {
204
+ console.log(`${_a.#LOG_PREFIX} Restoring/clearing override context from: "${contextName}".`);
205
205
  }
206
206
  // Restore now sets the special "NO_OVERRIDE" value.
207
- this.overrideContext$.next(Hotkeys.NO_OVERRIDE);
207
+ this.#overrideContext$.next(_a.#NO_OVERRIDE);
208
208
  }
209
209
  };
210
210
  return restore;
@@ -215,7 +215,7 @@ export class Hotkeys {
215
215
  * @returns The current context name as a string, or `null` if no context is set.
216
216
  */
217
217
  getContext() {
218
- console.warn(`${Hotkeys.LOG_PREFIX} "getContext" is deprecated. Use "getActiveContext()" or subscribe to "onContextChange$" instead.`);
218
+ console.warn(`${_a.#LOG_PREFIX} "getContext" is deprecated. Use "getActiveContext()" or subscribe to "onContextChange$" instead.`);
219
219
  return this.getActiveContext();
220
220
  }
221
221
  /**
@@ -223,42 +223,42 @@ export class Hotkeys {
223
223
  * @returns The current context name as a string, or `null` if no context is set.
224
224
  */
225
225
  getActiveContext() {
226
- const overrideCtx = this.overrideContext$.getValue();
227
- const stack = this.contextStack$.getValue();
226
+ const overrideCtx = this.#overrideContext$.getValue();
227
+ const stack = this.#contextStack$.getValue();
228
228
  const stackCtx = stack.length > 0 ? stack[stack.length - 1] : null;
229
229
  // Also uses the abstracted helper method.
230
- return this._resolveActiveContext(overrideCtx, stackCtx);
230
+ return this.#resolveActiveContext(overrideCtx, stackCtx);
231
231
  }
232
232
  /**
233
233
  * Pushes a new context onto the context stack. It will become active if no override context is set.
234
234
  * @param contextName The name of the context to enter (e.g., "modal", "editor").
235
235
  */
236
236
  enterContext(contextName) {
237
- const currentStack = this.contextStack$.getValue();
237
+ const currentStack = this.#contextStack$.getValue();
238
238
  const newStack = [...currentStack, contextName];
239
- if (this.debugMode) {
240
- console.log(`${Hotkeys.LOG_PREFIX} Entering context: "${contextName}". New stack: [${newStack.join(", ")}]`);
239
+ if (this.#debugMode) {
240
+ console.log(`${_a.#LOG_PREFIX} Entering context: "${contextName}". New stack: [${newStack.join(", ")}]`);
241
241
  }
242
- this.contextStack$.next(newStack);
242
+ this.#contextStack$.next(newStack);
243
243
  }
244
244
  /**
245
245
  * Pops the current context from the stack.
246
246
  * @returns The context that was just left from the stack, or `undefined` if at the base.
247
247
  */
248
248
  leaveContext() {
249
- const currentStack = this.contextStack$.getValue();
249
+ const currentStack = this.#contextStack$.getValue();
250
250
  if (currentStack.length <= 1) {
251
- if (this.debugMode) {
252
- console.log(`${Hotkeys.LOG_PREFIX} Attempted to leave the base stack context. No change made.`);
251
+ if (this.#debugMode) {
252
+ console.log(`${_a.#LOG_PREFIX} Attempted to leave the base stack context. No change made.`);
253
253
  }
254
254
  return undefined; // Nothing was left
255
255
  }
256
256
  const leavingContext = currentStack[currentStack.length - 1];
257
257
  const newStack = currentStack.slice(0, -1);
258
- if (this.debugMode) {
259
- console.log(`${Hotkeys.LOG_PREFIX} Leaving context: "${leavingContext}". New stack: [${newStack.join(", ")}]`);
258
+ if (this.#debugMode) {
259
+ console.log(`${_a.#LOG_PREFIX} Leaving context: "${leavingContext}". New stack: [${newStack.join(", ")}]`);
260
260
  }
261
- this.contextStack$.next(newStack);
261
+ this.#contextStack$.next(newStack);
262
262
  return leavingContext;
263
263
  }
264
264
  /**
@@ -267,15 +267,15 @@ export class Hotkeys {
267
267
  * @param enable - True to enable debug logs, false to disable.
268
268
  */
269
269
  setDebugMode(enable) {
270
- if (this.debugMode === enable) {
270
+ if (this.#debugMode === enable) {
271
271
  return;
272
272
  }
273
- this.debugMode = enable;
273
+ this.#debugMode = enable;
274
274
  if (enable) {
275
- console.log(`${Hotkeys.LOG_PREFIX} Debug mode enabled.`);
275
+ console.log(`${_a.#LOG_PREFIX} Debug mode enabled.`);
276
276
  }
277
277
  else {
278
- console.log(`${Hotkeys.LOG_PREFIX} Debug mode disabled.`);
278
+ console.log(`${_a.#LOG_PREFIX} Debug mode disabled.`);
279
279
  }
280
280
  }
281
281
  /**
@@ -284,7 +284,7 @@ export class Hotkeys {
284
284
  * @returns True if a shortcut with the specified ID exists, false otherwise.
285
285
  */
286
286
  hasShortcut(id) {
287
- return this.activeShortcuts.has(id);
287
+ return this.#activeShortcuts.has(id);
288
288
  }
289
289
  /**
290
290
  * An Observable that emits the new context name (or null) whenever the active context changes.
@@ -301,7 +301,7 @@ export class Hotkeys {
301
301
  * ```
302
302
  */
303
303
  get onContextChange$() {
304
- return this.activeContext$;
304
+ return this.#activeContext$;
305
305
  }
306
306
  /**
307
307
  * Compares two sequences of StandardKey arrays to see if they are identical.
@@ -309,7 +309,7 @@ export class Hotkeys {
309
309
  * @param seq2 - The second sequence array.
310
310
  * @returns True if the sequences are identical, false otherwise.
311
311
  */
312
- _areSequencesIdentical(seq1, seq2) {
312
+ #areSequencesIdentical(seq1, seq2) {
313
313
  if (seq1.length !== seq2.length) {
314
314
  return false;
315
315
  }
@@ -327,7 +327,7 @@ export class Hotkeys {
327
327
  * @param event The KeyboardEvent to match against.
328
328
  * @returns True if the shortcutConfig matches the event, false otherwise.
329
329
  */
330
- _shortcutMatchesEvent(parsedTriggers, event) {
330
+ #shortcutMatchesEvent(parsedTriggers, event) {
331
331
  // It no longer does any parsing. It just compares against the pre-parsed triggers.
332
332
  for (const trigger of parsedTriggers) {
333
333
  const keyMatch = compareKey(event.key, trigger.key);
@@ -342,8 +342,8 @@ export class Hotkeys {
342
342
  }
343
343
  return false;
344
344
  }
345
- filterByContext(source$, context, strict) {
346
- return source$.pipe(withLatestFrom(this.activeContext$), filter(([/* event */ , activeCtx]) => {
345
+ #filterByContext(source$, context, strict) {
346
+ return source$.pipe(withLatestFrom(this.#activeContext$), filter(([/* event */ , activeCtx]) => {
347
347
  if (context == null) {
348
348
  if (strict) {
349
349
  return activeCtx == null;
@@ -357,16 +357,101 @@ export class Hotkeys {
357
357
  }
358
358
  }), map(([event, /* _activeCtx */]) => event));
359
359
  }
360
- _registerShortcut(config, terminator$, type, detailsForLog, parsedTriggers) {
361
- const existingShortcut = this.activeShortcuts.get(config.id);
360
+ #checkIsContentEditable(element) {
361
+ if (typeof element.isContentEditable === "boolean") {
362
+ return element.isContentEditable;
363
+ }
364
+ const closest = element.closest?.("[contenteditable]");
365
+ if (closest) {
366
+ const val = closest.getAttribute("contenteditable")?.toLowerCase();
367
+ return val !== "false";
368
+ }
369
+ return false;
370
+ }
371
+ #shouldHandleKeyEvent(event, config) {
372
+ // 1. IME composition check
373
+ const ignoreComposing = config.ignoreComposing ?? true;
374
+ if (ignoreComposing) {
375
+ // @ts-ignore
376
+ if (event.isComposing || event.keyCode === 229 || event.which === 229 || event.key === "Dead") {
377
+ if (this.#debugMode) {
378
+ console.log(`${_a.#LOG_PREFIX} Shortcut "${config.id}" suppressed during IME composition.`);
379
+ }
380
+ return false;
381
+ }
382
+ }
383
+ // 2. Custom filter predicate
384
+ if (config.filter && !config.filter(event)) {
385
+ if (this.#debugMode) {
386
+ console.log(`${_a.#LOG_PREFIX} Shortcut "${config.id}" suppressed by custom filter.`);
387
+ }
388
+ return false;
389
+ }
390
+ // 3. Resolve target element (supporting Shadow DOM)
391
+ let targetElement = null;
392
+ const rawTarget = (typeof event.composedPath === "function" ? event.composedPath()[0] : event.target);
393
+ if (rawTarget) {
394
+ if (rawTarget instanceof HTMLElement) {
395
+ targetElement = rawTarget;
396
+ }
397
+ else if (rawTarget.parentElement instanceof HTMLElement) {
398
+ targetElement = rawTarget.parentElement;
399
+ }
400
+ }
401
+ if (!targetElement) {
402
+ return true;
403
+ }
404
+ // 4. Target exemption: if explicitly attached to this target element, allow it
405
+ if (config.target && config.target === targetElement) {
406
+ return true;
407
+ }
408
+ const tagName = targetElement.tagName ? targetElement.tagName.toLowerCase() : "";
409
+ // 5. Form tags (<input>, <textarea>, <select>)
410
+ const isFormTag = tagName === "input" || tagName === "textarea" || tagName === "select";
411
+ if (isFormTag) {
412
+ const { enableOnFormTags = false } = config;
413
+ if (enableOnFormTags === false) {
414
+ if (this.#debugMode) {
415
+ console.log(`${_a.#LOG_PREFIX} Shortcut "${config.id}" suppressed in <${tagName}> form element.`);
416
+ }
417
+ return false;
418
+ }
419
+ if (Array.isArray(enableOnFormTags)) {
420
+ const allowed = enableOnFormTags.map(t => t.toLowerCase());
421
+ if (!allowed.includes(tagName)) {
422
+ if (this.#debugMode) {
423
+ console.log(`${_a.#LOG_PREFIX} Shortcut "${config.id}" suppressed in <${tagName}> (allowed: [${allowed.join(", ")}]).`);
424
+ }
425
+ return false;
426
+ }
427
+ }
428
+ }
429
+ // 6. Rich-text editor (contentEditable)
430
+ const isContentEditable = this.#checkIsContentEditable(targetElement);
431
+ if (isContentEditable) {
432
+ const { enableOnContentEditable = false } = config;
433
+ if (!enableOnContentEditable) {
434
+ if (this.#debugMode) {
435
+ console.log(`${_a.#LOG_PREFIX} Shortcut "${config.id}" suppressed in contentEditable element.`);
436
+ }
437
+ return false;
438
+ }
439
+ }
440
+ return true;
441
+ }
442
+ #filterEvent(source$, config) {
443
+ return this.#filterByContext(source$, config.context, config.strict ?? false).pipe(filter(event => this.#shouldHandleKeyEvent(event, config)));
444
+ }
445
+ #registerShortcut(config, terminator$, type, detailsForLog, parsedTriggers) {
446
+ const existingShortcut = this.#activeShortcuts.get(config.id);
362
447
  if (existingShortcut) {
363
- console.warn(`${Hotkeys.LOG_PREFIX} Shortcut with ID "${config.id}" already exists. The old instance will be terminated and overwritten.`);
448
+ console.warn(`${_a.#LOG_PREFIX} Shortcut with ID "${config.id}" already exists. The old instance will be terminated and overwritten.`);
364
449
  existingShortcut.terminator$.next();
365
450
  existingShortcut.terminator$.complete();
366
451
  }
367
- this.activeShortcuts.set(config.id, { id: config.id, config, terminator$, parsedTriggers });
368
- if (this.debugMode) {
369
- console.log(`${Hotkeys.LOG_PREFIX} ${type} shortcut "${config.id}" added. ${detailsForLog}, Context: ${config.context ?? "any"}`);
452
+ this.#activeShortcuts.set(config.id, { id: config.id, config, terminator$, parsedTriggers });
453
+ if (this.#debugMode) {
454
+ console.log(`${_a.#LOG_PREFIX} ${type} shortcut "${config.id}" added. ${detailsForLog}, Context: ${config.context ?? "any"}`);
370
455
  }
371
456
  }
372
457
  /**
@@ -376,11 +461,11 @@ export class Hotkeys {
376
461
  * @param shortcutId - The ID of the shortcut this key trigger belongs to (for logging).
377
462
  * @returns An object containing configuredMainKey and modifier states, or null if parsing fails.
378
463
  */
379
- _parseKeyTrigger(keyInput, shortcutId) {
464
+ #parseKeyTrigger(keyInput, shortcutId) {
380
465
  if (typeof keyInput === "string") {
381
466
  const finalKey = normalizeKey(keyInput);
382
467
  if (!finalKey) {
383
- console.warn(`${Hotkeys.LOG_PREFIX} Could not parse key: "${keyInput}" in shortcut "${shortcutId}".`);
468
+ console.warn(`${_a.#LOG_PREFIX} Could not parse key: "${keyInput}" in shortcut "${shortcutId}".`);
384
469
  return null;
385
470
  }
386
471
  return {
@@ -393,7 +478,7 @@ export class Hotkeys {
393
478
  }
394
479
  else {
395
480
  if (!keyInput.key || typeof keyInput.key !== "string" || keyInput.key === "") {
396
- console.warn(`${Hotkeys.LOG_PREFIX} Invalid "key" property in shortcut "${shortcutId}". Key must be a non-empty string value from Keys.`);
481
+ console.warn(`${_a.#LOG_PREFIX} Invalid "key" property in shortcut "${shortcutId}". Key must be a non-empty string value from Keys.`);
397
482
  return null;
398
483
  }
399
484
  return {
@@ -405,11 +490,11 @@ export class Hotkeys {
405
490
  };
406
491
  }
407
492
  }
408
- _parseCombinationString(shortcut) {
493
+ #parseCombinationString(shortcut) {
409
494
  const parts = shortcut.toLowerCase().split("+").map(p => p.trim());
410
495
  const mainKeyStr = parts.pop();
411
496
  if (!mainKeyStr) {
412
- console.warn(`${Hotkeys.LOG_PREFIX} Invalid shortcut string: "${shortcut}". No main key found.`);
497
+ console.warn(`${_a.#LOG_PREFIX} Invalid shortcut string: "${shortcut}". No main key found.`);
413
498
  return [];
414
499
  }
415
500
  const trigger = {
@@ -418,7 +503,7 @@ export class Hotkeys {
418
503
  };
419
504
  const finalKey = normalizeKey(mainKeyStr);
420
505
  if (!finalKey) {
421
- console.warn(`${Hotkeys.LOG_PREFIX} Could not parse key: "${mainKeyStr}" in shortcut string "${shortcut}".`);
506
+ console.warn(`${_a.#LOG_PREFIX} Could not parse key: "${mainKeyStr}" in shortcut string "${shortcut}".`);
422
507
  return [];
423
508
  }
424
509
  trigger.key = finalKey;
@@ -432,11 +517,11 @@ export class Hotkeys {
432
517
  else if (part === "meta" || part === "cmd" || part === "command" || part === "win")
433
518
  trigger.metaKey = true;
434
519
  else
435
- console.warn(`${Hotkeys.LOG_PREFIX} Unknown modifier: "${part}" in shortcut string "${shortcut}".`);
520
+ console.warn(`${_a.#LOG_PREFIX} Unknown modifier: "${part}" in shortcut string "${shortcut}".`);
436
521
  }
437
522
  return [trigger];
438
523
  }
439
- _parseSequenceString(sequence) {
524
+ #parseSequenceString(sequence) {
440
525
  const keyStrings = sequence.split("->").map(k => k.trim());
441
526
  const results = [];
442
527
  for (const keyStr of keyStrings) {
@@ -445,7 +530,7 @@ export class Hotkeys {
445
530
  results.push(finalKey);
446
531
  }
447
532
  else {
448
- console.warn(`${Hotkeys.LOG_PREFIX} Could not parse key: "${keyStr}" in sequence string "${sequence}".`);
533
+ console.warn(`${_a.#LOG_PREFIX} Could not parse key: "${keyStr}" in sequence string "${sequence}".`);
449
534
  return []; // Fail fast
450
535
  }
451
536
  }
@@ -490,24 +575,25 @@ export class Hotkeys {
490
575
  addCombination(config) {
491
576
  const { keys, context, preventDefault = false, id, strict = false, target = document, event: eventType = "keydown", options } = config;
492
577
  if (context != null && strict) {
493
- console.warn(`${Hotkeys.LOG_PREFIX} Shortcut "${id}" has both a context(${context}) and the "strict" flag. The "strict" flag will be ignored.`);
578
+ console.warn(`${_a.#LOG_PREFIX} Shortcut "${id}" has both a context(${context}) and the "strict" flag. The "strict" flag will be ignored.`);
494
579
  }
495
- const parsedTriggers = this._normalizeAndParseTriggers(keys, id);
580
+ const parsedTriggers = this.#normalizeAndParseTriggers(keys, id);
496
581
  if (parsedTriggers.length === 0) {
497
- console.error(`${Hotkeys.LOG_PREFIX} "keys" definition for combination shortcut "${id}" is empty or invalid. Shortcut not added.`);
582
+ console.error(`${_a.#LOG_PREFIX} "keys" definition for combination shortcut "${id}" is empty or invalid. Shortcut not added.`);
498
583
  return EMPTY;
499
584
  }
500
- const sourceStream$ = this._getEventStream(eventType, target, options);
585
+ const sourceStream$ = this.#getEventStream(eventType, target, options);
586
+ const baseStream$ = this.#filterEvent(sourceStream$, config);
501
587
  const observables = [];
502
588
  for (const trigger of parsedTriggers) {
503
- const stream = this.filterByContext(sourceStream$, context, strict).pipe(filter(event => {
589
+ const stream = baseStream$.pipe(filter(event => {
504
590
  return event.ctrlKey === trigger.ctrlKey &&
505
591
  event.altKey === trigger.altKey &&
506
592
  event.shiftKey === trigger.shiftKey &&
507
593
  event.metaKey === trigger.metaKey;
508
594
  }), filter(event => compareKey(event.key, trigger.key)),
509
595
  // New filter for priority: Specific context > Global context
510
- withLatestFrom(this.activeContext$), filter(([event, activeCtx]) => {
596
+ withLatestFrom(this.#activeContext$), filter(([event, activeCtx]) => {
511
597
  if (context != null || strict) { // This shortcut is NOT global or strict
512
598
  return true;
513
599
  }
@@ -515,13 +601,13 @@ export class Hotkeys {
515
601
  if (activeCtx == null) { // No specific context active
516
602
  return true;
517
603
  }
518
- for (const [, otherAS] of this.activeShortcuts) {
604
+ for (const [, otherAS] of this.#activeShortcuts) {
519
605
  if (otherAS.config.id !== id &&
520
606
  "keys" in otherAS.config &&
521
607
  otherAS.config.context === activeCtx &&
522
- this._shortcutMatchesEvent(otherAS.parsedTriggers ?? [], event)) {
523
- if (this.debugMode) {
524
- console.log(`${Hotkeys.LOG_PREFIX} Global shortcut "${id}" (key: "${event.key}") suppressed by specific context shortcut "${otherAS.config.id}".`);
608
+ this.#shortcutMatchesEvent(otherAS.parsedTriggers ?? [], event)) {
609
+ if (this.#debugMode) {
610
+ console.log(`${_a.#LOG_PREFIX} Global shortcut "${id}" (key: "${event.key}") suppressed by specific context shortcut "${otherAS.config.id}".`);
525
611
  }
526
612
  return false; // Suppress global
527
613
  }
@@ -532,7 +618,7 @@ export class Hotkeys {
532
618
  }
533
619
  if (observables.length === 0) {
534
620
  // This path should now be much harder to hit, but remains a safeguard.
535
- console.warn(`${Hotkeys.LOG_PREFIX} No valid key triggers for combination shortcut "${id}". Shortcut not added.`);
621
+ console.warn(`${_a.#LOG_PREFIX} No valid key triggers for combination shortcut "${id}". Shortcut not added.`);
536
622
  return EMPTY;
537
623
  }
538
624
  const terminator$ = new Subject();
@@ -554,16 +640,16 @@ export class Hotkeys {
554
640
  return `{ ${parts.join(", ")} }`;
555
641
  });
556
642
  const logDetails = `Triggers: [ ${logParts.join(", ")} ]`;
557
- this._registerShortcut(config, terminator$, ShortcutTypes.Combination, logDetails, parsedTriggers);
643
+ this.#registerShortcut(config, terminator$, ShortcutTypes.Combination, logDetails, parsedTriggers);
558
644
  return finalShortcut$.pipe(tap(event => {
559
- if (this.debugMode) {
645
+ if (this.#debugMode) {
560
646
  const preventAction = preventDefault ? ", preventing default" : "";
561
- console.log(`${Hotkeys.LOG_PREFIX} Combination "${id}" triggered by key "${event.key}" ${preventAction}.`);
647
+ console.log(`${_a.#LOG_PREFIX} Combination "${id}" triggered by key "${event.key}" ${preventAction}.`);
562
648
  }
563
649
  if (preventDefault)
564
650
  event.preventDefault();
565
651
  }), catchError(err => {
566
- console.error(`${Hotkeys.LOG_PREFIX} Error in combination stream for shortcut "${id}":`, err);
652
+ console.error(`${_a.#LOG_PREFIX} Error in combination stream for shortcut "${id}":`, err);
567
653
  return EMPTY;
568
654
  }), takeUntil(terminator$));
569
655
  }
@@ -600,18 +686,18 @@ export class Hotkeys {
600
686
  */
601
687
  addSequence(config) {
602
688
  const { sequence, context, preventDefault = false, id, sequenceTimeoutMs, strict = false, target = document, event: eventType = "keydown", options } = config;
603
- const configuredSequence = this._normalizeSequence(sequence, id);
689
+ const configuredSequence = this.#normalizeSequence(sequence, id);
604
690
  if (!configuredSequence || configuredSequence.length === 0) {
605
- console.error(`${Hotkeys.LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
691
+ console.error(`${_a.#LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
606
692
  return EMPTY;
607
693
  }
608
694
  if (context && strict) {
609
- console.warn(`${Hotkeys.LOG_PREFIX} Shortcut "${id}" has both a context and the "strict" flag. The "strict" flag will be ignored.`);
695
+ console.warn(`${_a.#LOG_PREFIX} Shortcut "${id}" has both a context and the "strict" flag. The "strict" flag will be ignored.`);
610
696
  }
611
697
  const sequenceLength = configuredSequence.length;
612
698
  let shortcut$;
613
- const sourceStream$ = this._getEventStream(eventType, target, options);
614
- const baseKeydownStream$ = this.filterByContext(sourceStream$, context, strict);
699
+ const sourceStream$ = this.#getEventStream(eventType, target, options);
700
+ const baseKeydownStream$ = this.#filterEvent(sourceStream$, config);
615
701
  if (sequenceTimeoutMs && sequenceTimeoutMs > 0) {
616
702
  shortcut$ = baseKeydownStream$.pipe(scan((acc, event) => {
617
703
  let { matchedEvents, lastEventTime } = acc;
@@ -621,8 +707,8 @@ export class Hotkeys {
621
707
  lastEventTime = 0;
622
708
  }
623
709
  if (matchedEvents.length > 0 && (currentTime - lastEventTime > sequenceTimeoutMs)) {
624
- if (this.debugMode) {
625
- console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) attempt timed out. Matched: ${matchedEvents.map(e => e.key).join(",")}. Resetting.`);
710
+ if (this.#debugMode) {
711
+ console.log(`${_a.#LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) attempt timed out. Matched: ${matchedEvents.map(e => e.key).join(",")}. Resetting.`);
626
712
  }
627
713
  matchedEvents = [];
628
714
  }
@@ -638,8 +724,8 @@ export class Hotkeys {
638
724
  if (compareKey(event.key, configuredSequence[nextExpectedKeyIndex])) {
639
725
  const newMatchedEvents = [...matchedEvents, event];
640
726
  if (newMatchedEvents.length === sequenceLength) {
641
- if (this.debugMode && acc.emitState !== EmitStates.Emit)
642
- console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) matched.`);
727
+ if (this.#debugMode && acc.emitState !== EmitStates.Emit)
728
+ console.log(`${_a.#LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) matched.`);
643
729
  return { matchedEvents: newMatchedEvents, lastEventTime: currentTime, emitState: EmitStates.Emit };
644
730
  }
645
731
  else {
@@ -648,8 +734,8 @@ export class Hotkeys {
648
734
  }
649
735
  else {
650
736
  // If current key breaks sequence, check if it starts a new sequence
651
- if (matchedEvents.length > 0 && this.debugMode) {
652
- console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) broken by key "${event.key}". Matched: ${matchedEvents.map(e => e.key).join(",")}. Resetting.`);
737
+ if (matchedEvents.length > 0 && this.#debugMode) {
738
+ console.log(`${_a.#LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) broken by key "${event.key}". Matched: ${matchedEvents.map(e => e.key).join(",")}. Resetting.`);
653
739
  }
654
740
  if (sequenceLength > 0 && compareKey(event.key, configuredSequence[0])) {
655
741
  return { matchedEvents: [event], lastEventTime: currentTime, emitState: EmitStates.InProgress };
@@ -669,7 +755,7 @@ export class Hotkeys {
669
755
  }));
670
756
  }
671
757
  const terminator$ = new Subject();
672
- const finalShortcutWithPriority$ = shortcut$.pipe(withLatestFrom(this.activeContext$), filter(([_completedEvents, activeCtx]) => {
758
+ const finalShortcutWithPriority$ = shortcut$.pipe(withLatestFrom(this.#activeContext$), filter(([_completedEvents, activeCtx]) => {
673
759
  if (context != null || strict) { // This sequence is NOT global or strict
674
760
  return true;
675
761
  }
@@ -677,33 +763,33 @@ export class Hotkeys {
677
763
  if (activeCtx == null) { // No specific context active
678
764
  return true;
679
765
  }
680
- for (const [, otherAS] of this.activeShortcuts) {
766
+ for (const [, otherAS] of this.#activeShortcuts) {
681
767
  if (otherAS.config.id !== id &&
682
768
  "sequence" in otherAS.config &&
683
769
  otherAS.config.context === activeCtx &&
684
- this._areSequencesIdentical(configuredSequence, typeof otherAS.config.sequence === "string" ? this._parseSequenceString(otherAS.config.sequence) : otherAS.config.sequence)) {
685
- if (this.debugMode) {
686
- console.log(`${Hotkeys.LOG_PREFIX} Global sequence shortcut "${id}" suppressed by identical specific-context shortcut "${otherAS.config.id}".`);
770
+ this.#areSequencesIdentical(configuredSequence, typeof otherAS.config.sequence === "string" ? this.#parseSequenceString(otherAS.config.sequence) : otherAS.config.sequence)) {
771
+ if (this.#debugMode) {
772
+ console.log(`${_a.#LOG_PREFIX} Global sequence shortcut "${id}" suppressed by identical specific-context shortcut "${otherAS.config.id}".`);
687
773
  }
688
774
  return false; // Suppress global
689
775
  }
690
776
  }
691
777
  return true; // Global sequence can proceed
692
778
  }), map(([events]) => events), tap((events) => {
693
- if (this.debugMode) {
779
+ if (this.#debugMode) {
694
780
  const timeoutInfo = (sequenceTimeoutMs && sequenceTimeoutMs > 0) ? ` (with timeout logic)` : ` (no timeout logic)`;
695
781
  const preventAction = preventDefault ? ", preventing default for last event" : "";
696
- console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" triggered${timeoutInfo}${preventAction}.`);
782
+ console.log(`${_a.#LOG_PREFIX} Sequence "${id}" triggered${timeoutInfo}${preventAction}.`);
697
783
  }
698
784
  if (preventDefault && events.length > 0) {
699
785
  events[events.length - 1].preventDefault();
700
786
  }
701
787
  }), catchError(err => {
702
- console.error(`${Hotkeys.LOG_PREFIX} Error in sequence stream for shortcut "${id}":`, err);
788
+ console.error(`${_a.#LOG_PREFIX} Error in sequence stream for shortcut "${id}":`, err);
703
789
  return EMPTY;
704
790
  }));
705
791
  const logDetails = `Sequence: ${configuredSequence.join(" -> ")}${sequenceTimeoutMs && sequenceTimeoutMs > 0 ? ` (timeout: ${sequenceTimeoutMs}ms)` : ""}`;
706
- this._registerShortcut(config, terminator$, ShortcutTypes.Sequence, logDetails);
792
+ this.#registerShortcut(config, terminator$, ShortcutTypes.Sequence, logDetails);
707
793
  return finalShortcutWithPriority$.pipe(map((events) => events[events.length - 1]), takeUntil(terminator$));
708
794
  }
709
795
  /**
@@ -714,16 +800,16 @@ export class Hotkeys {
714
800
  * A warning is logged to the console if no shortcut with the given ID is found.
715
801
  */
716
802
  remove(id) {
717
- const shortcut = this.activeShortcuts.get(id);
803
+ const shortcut = this.#activeShortcuts.get(id);
718
804
  if (shortcut) {
719
805
  shortcut.terminator$.next();
720
806
  shortcut.terminator$.complete();
721
- this.activeShortcuts.delete(id);
722
- if (this.debugMode)
723
- console.log(`${Hotkeys.LOG_PREFIX} Shortcut "${id}" removed.`);
807
+ this.#activeShortcuts.delete(id);
808
+ if (this.#debugMode)
809
+ console.log(`${_a.#LOG_PREFIX} Shortcut "${id}" removed.`);
724
810
  return true;
725
811
  }
726
- console.warn(`${Hotkeys.LOG_PREFIX} Shortcut with ID "${id}" not found for removal.`);
812
+ console.warn(`${_a.#LOG_PREFIX} Shortcut with ID "${id}" not found for removal.`);
727
813
  return false;
728
814
  }
729
815
  /**
@@ -735,7 +821,7 @@ export class Hotkeys {
735
821
  */
736
822
  getActiveShortcuts() {
737
823
  const shortcuts = [];
738
- for (const [id, activeShortcut] of this.activeShortcuts.entries()) {
824
+ for (const [id, activeShortcut] of this.#activeShortcuts.entries()) {
739
825
  shortcuts.push({
740
826
  id,
741
827
  description: activeShortcut.config.description,
@@ -752,16 +838,17 @@ export class Hotkeys {
752
838
  * After calling `destroy()`, the instance should not be used further.
753
839
  */
754
840
  destroy() {
755
- if (this.debugMode)
756
- console.log(`${Hotkeys.LOG_PREFIX} Destroying library instance and terminating all shortcut streams.`);
757
- this.activeShortcuts.forEach(shortcut => {
841
+ if (this.#debugMode)
842
+ console.log(`${_a.#LOG_PREFIX} Destroying library instance and terminating all shortcut streams.`);
843
+ this.#activeShortcuts.forEach(shortcut => {
758
844
  shortcut.terminator$.next();
759
845
  shortcut.terminator$.complete();
760
846
  });
761
- this.activeShortcuts.clear();
762
- this.contextStack$.complete();
763
- if (this.debugMode)
764
- console.log(`${Hotkeys.LOG_PREFIX} Library destroyed.`);
847
+ this.#activeShortcuts.clear();
848
+ this.#contextStack$.complete();
849
+ if (this.#debugMode)
850
+ console.log(`${_a.#LOG_PREFIX} Library destroyed.`);
765
851
  }
766
852
  }
853
+ _a = Hotkeys;
767
854
  //# sourceMappingURL=hotkeys.js.map