rx-hotkeys 1.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,18 +26,18 @@ npm install rxjs rx-hotkeys
26
26
  First, ensure you have the `rx-hotkeys` library and its helper Keys imported:
27
27
 
28
28
  ```typescript
29
- import { HotKeys, Keys, KeyCombinationConfig, KeySequenceConfig } from 'rx-hotkeys';
29
+ import { Hotkeys, Keys, KeyCombinationConfig, KeySequenceConfig } from 'rx-hotkeys';
30
30
  ```
31
31
 
32
- 1. Initialize HotKeys
32
+ 1. Initialize Hotkeys
33
33
 
34
34
  Create an instance of the `Hotkeys` class. You can optionally provide an initial context and enable debug mode.
35
35
 
36
36
  ```typescript
37
- const keyManager = new HotKeys(); // No initial context, debug mode off
37
+ const keyManager = new Hotkeys(); // No initial context, debug mode off
38
38
 
39
39
  // With an initial context and debug mode enabled:
40
- // const keyManager = new HotKeys('editor', true);
40
+ // const keyManager = new Hotkeys('editor', true);
41
41
  ```
42
42
 
43
43
  2. Add a Key Combination
@@ -93,7 +93,7 @@ keyManager.setContext(null);
93
93
 
94
94
  5. Clean Up
95
95
 
96
- When the HotKeys instance is no longer needed (e.g., component unmount), call `destroy()` to clean up subscriptions and prevent memory leaks.
96
+ When the Hotkeys instance is no longer needed (e.g., component unmount), call `destroy()` to clean up subscriptions and prevent memory leaks.
97
97
 
98
98
  ```typescript
99
99
  // In a component lifecycle cleanup method or similar:
@@ -108,11 +108,11 @@ keyManager.destroy();
108
108
  * `Keys`: An exported constant object containing standard KeyboardEvent.key string values (e.g., Keys.Enter, Keys.ArrowUp, Keys.A). It's highly recommended to use these when defining key in `KeyCombinationConfig` or keys in the sequence array of `KeySequenceConfig`.
109
109
  * `StandardKey`: A TypeScript type representing any valid key string from the Keys object.
110
110
 
111
- ### `HotKeys` Class
111
+ ### `Hotkeys` Class
112
112
 
113
113
  `constructor(initialContext?: string | null, debugMode?: boolean)`
114
114
 
115
- Creates a new HotKeys instance.
115
+ Creates a new Hotkeys instance.
116
116
 
117
117
  `addCombination(config: KeyCombinationConfig): string | undefined`
118
118
 
@@ -165,7 +165,7 @@ Cleans up all subscriptions and resources. Essential to call to prevent memory l
165
165
  `KeyCombinationConfig`
166
166
 
167
167
  * `id: string` (required): Unique identifier for the shortcut.
168
- * `keys: { key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; }` (required): Defines the main key (from Keys) and optional modifier keys.
168
+ * `keys: { key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; } | StandardKey` (required): Defines the main key (from Keys) and optional modifier keys.
169
169
  * `callback: (event?: KeyboardEvent) => void` (required): Function to execute when the shortcut is triggered. The triggering `KeyboardEvent` is passed as an argument.
170
170
  * `context?: string | null`: Specifies the context in which this shortcut is active. If `null` or `undefined`, it's a global shortcut.
171
171
  * `preventDefault?: boolean`: If true, `event.preventDefault()` will be called when the shortcut triggers. Defaults to `false`.
package/dist/hotkeys.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { StandardKey } from "./keys.js";
2
+ export declare enum ShortcutTypes {
3
+ Combination = "combination",
4
+ Sequence = "sequence"
5
+ }
2
6
  interface ShortcutConfigBase {
3
7
  id: string;
4
8
  callback: (event?: KeyboardEvent) => void;
@@ -7,6 +11,17 @@ interface ShortcutConfigBase {
7
11
  description?: string;
8
12
  }
9
13
  export interface KeyCombinationConfig extends ShortcutConfigBase {
14
+ /**
15
+ * Defines the key or key combination.
16
+ * Can be an object specifying the main `key` (from `StandardKey`) and optional
17
+ * modifiers (`ctrlKey`, `altKey`, `shiftKey`, `metaKey`).
18
+ * Example: `{ key: Keys.S, ctrlKey: true }` for Ctrl+S.
19
+ *
20
+ * Alternatively, for a simple key press without any modifiers, this can be
21
+ * a `StandardKey` directly.
22
+ * Example: `Keys.Escape` for the Escape key. When using this shorthand,
23
+ * it implies that no modifier keys (Ctrl, Alt, Shift, Meta) should be active.
24
+ */
10
25
  keys: {
11
26
  /**
12
27
  * The main key for the combination.
@@ -23,7 +38,7 @@ export interface KeyCombinationConfig extends ShortcutConfigBase {
23
38
  altKey?: boolean;
24
39
  shiftKey?: boolean;
25
40
  metaKey?: boolean;
26
- };
41
+ } | StandardKey;
27
42
  }
28
43
  export interface KeySequenceConfig extends ShortcutConfigBase {
29
44
  /**
@@ -48,7 +63,7 @@ export interface KeySequenceConfig extends ShortcutConfigBase {
48
63
  * Allows registration of single key combinations (e.g., Ctrl+S) and key sequences (e.g., g -> i).
49
64
  * Supports contexts to enable/disable shortcuts based on application state.
50
65
  */
51
- export declare class HotKeys {
66
+ export declare class Hotkeys {
52
67
  private static readonly KEYDOWN_EVENT;
53
68
  private static readonly LOG_PREFIX;
54
69
  private keydown$;
@@ -90,22 +105,29 @@ export declare class HotKeys {
90
105
  private filterByContext;
91
106
  private _registerShortcut;
92
107
  /**
93
- * Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter).
94
- * The callback is triggered when the specified key and modifier keys are pressed simultaneously.
108
+ * Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter, or a single key like Escape).
109
+ * The callback is triggered when the specified key and modifier keys (if any) are pressed.
95
110
  * @param config - Configuration object for the key combination.
96
111
  * See {@link KeyCombinationConfig} for details.
97
- * The `key` property within `config.keys` must be a value from the `Keys` object.
98
- * @returns The ID of the registered shortcut if successful, or `undefined` if the configuration is invalid (e.g., empty key).
112
+ * The `key` property (or the direct `StandardKey` if using shorthand) must be a value from the `Keys` object.
113
+ * @returns The ID of the registered shortcut if successful, or `undefined` if the configuration is invalid.
99
114
  * A warning is logged to the console if the configuration is invalid or if a shortcut with the same ID is overwritten.
100
115
  * @example
101
116
  * ```typescript
102
- * import { Keys } from './keys';
117
+ * import { Keys } from "./keys";
118
+ * // For Ctrl+S
103
119
  * keyManager.addCombination({
104
120
  * id: "saveFile",
105
121
  * keys: { key: Keys.S, ctrlKey: true },
106
122
  * callback: () => console.log("File saved!"),
107
123
  * context: "editor"
108
124
  * });
125
+ * // For just the Escape key
126
+ * keyManager.addCombination({
127
+ * id: "closeModal",
128
+ * keys: Keys.Escape, // Shorthand syntax
129
+ * callback: () => console.log("Modal closed!")
130
+ * });
109
131
  * ```
110
132
  */
111
133
  addCombination(config: KeyCombinationConfig): string | undefined;
@@ -120,7 +142,7 @@ export declare class HotKeys {
120
142
  * A warning is logged to the console if the configuration is invalid or if a shortcut with the same ID is overwritten.
121
143
  * @example
122
144
  * ```typescript
123
- * import { Keys } from './keys';
145
+ * import { Keys } from "./keys";
124
146
  * keyManager.addSequence({
125
147
  * id: "konamiCode",
126
148
  * sequence: [Keys.ArrowUp, Keys.ArrowUp, Keys.ArrowDown, Keys.ArrowDown, Keys.A, Keys.B],
@@ -149,7 +171,7 @@ export declare class HotKeys {
149
171
  id: string;
150
172
  description?: string;
151
173
  context?: string | null;
152
- type: "combination" | "sequence";
174
+ type: ShortcutTypes;
153
175
  }[];
154
176
  /**
155
177
  * Cleans up all active subscriptions and resources used by the Hotkeys instance.
@@ -1 +1 @@
1
- {"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAIxC,UAAU,kBAAkB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC5D,IAAI,EAAE;QACF;;;;;;;;;WASG;QACH,GAAG,EAAE,WAAW,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;CACL;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IACzD;;;;;;;;OAQG;IACH,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AA6BD;;;;GAIG;AACH,qBAAa,OAAO;IAChB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAa;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAc;IAEhD,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,SAAS,CAAU;IAE3B;;;;;OAKG;gBACS,cAAc,GAAE,MAAM,GAAG,IAAW,EAAE,SAAS,GAAE,OAAe;IAe5E;;;;;;OAMG;IACI,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAOnD;;;OAGG;IACI,UAAU,IAAI,MAAM,GAAG,IAAI;IAIlC;;;;OAIG;IACI,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAO1C;;;;OAIG;IACI,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIvC,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IAkBzB;;;;;;;;;;;;;;;;;;OAkBG;IACI,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS;IA+CvE;;;;;;;;;;;;;;;;;;;OAmBG;IACI,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS;IAkHjE;;;;;;OAMG;IACI,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAYlC;;;;;;OAMG;IACI,kBAAkB,IAAI;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAA;KAAC,EAAE;IAa5H;;;;;OAKG;IACI,OAAO,IAAI,IAAI;CAOzB"}
1
+ {"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAIxC,oBAAY,aAAa;IACrB,WAAW,gBAAgB;IAC3B,QAAQ,aAAa;CACxB;AAED,UAAU,kBAAkB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC5D;;;;;;;;;;OAUG;IACH,IAAI,EAAE;QACF;;;;;;;;;WASG;QACH,GAAG,EAAE,WAAW,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IACzD;;;;;;;;OAQG;IACH,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAyCD;;;;GAIG;AACH,qBAAa,OAAO;IAChB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAa;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAc;IAEhD,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,SAAS,CAAU;IAE3B;;;;;OAKG;gBACS,cAAc,GAAE,MAAM,GAAG,IAAW,EAAE,SAAS,GAAE,OAAe;IAe5E;;;;;;OAMG;IACI,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAOnD;;;OAGG;IACI,UAAU,IAAI,MAAM,GAAG,IAAI;IAIlC;;;;OAIG;IACI,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAO1C;;;;OAIG;IACI,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIvC,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IAkBzB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACI,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS;IA6EvE;;;;;;;;;;;;;;;;;;;OAmBG;IACI,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS;IAiHjE;;;;;;OAMG;IACI,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAYlC;;;;;;OAMG;IACI,kBAAkB,IAAI;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAC,EAAE;IAa/G;;;;;OAKG;IACI,OAAO,IAAI,IAAI;CAOzB"}
package/dist/hotkeys.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import { fromEvent, BehaviorSubject, EMPTY, filter, map, bufferCount, withLatestFrom, tap, catchError, scan, } from "rxjs";
2
+ // --- Enums, Interfaces and Types ---
3
+ export var ShortcutTypes;
4
+ (function (ShortcutTypes) {
5
+ ShortcutTypes["Combination"] = "combination";
6
+ ShortcutTypes["Sequence"] = "sequence";
7
+ })(ShortcutTypes || (ShortcutTypes = {}));
2
8
  // --- Helper function to compare keys ---
3
9
  /**
4
10
  * Compares a browser event's key with a configured key.
@@ -15,12 +21,18 @@ function compareKey(eventKey, configuredKey) {
15
21
  return eventKey === configuredKey;
16
22
  }
17
23
  // --- Hotkeys Library ---
24
+ var EmitStates;
25
+ (function (EmitStates) {
26
+ EmitStates[EmitStates["Emit"] = 0] = "Emit";
27
+ EmitStates[EmitStates["Ignore"] = 1] = "Ignore";
28
+ EmitStates[EmitStates["InProgress"] = 2] = "InProgress";
29
+ })(EmitStates || (EmitStates = {}));
18
30
  /**
19
31
  * Manages keyboard shortcuts for web applications.
20
32
  * Allows registration of single key combinations (e.g., Ctrl+S) and key sequences (e.g., g -> i).
21
33
  * Supports contexts to enable/disable shortcuts based on application state.
22
34
  */
23
- export class HotKeys {
35
+ export class Hotkeys {
24
36
  static KEYDOWN_EVENT = "keydown";
25
37
  static LOG_PREFIX = "Hotkeys:";
26
38
  keydown$;
@@ -36,13 +48,13 @@ export class HotKeys {
36
48
  constructor(initialContext = null, debugMode = false) {
37
49
  this.debugMode = debugMode;
38
50
  if (typeof document === "undefined" || typeof performance === "undefined") {
39
- throw new Error(`${HotKeys.LOG_PREFIX} Hotkeys can only be used in a browser environment with global 'document' and 'performance' objects.`);
51
+ throw new Error(`${Hotkeys.LOG_PREFIX} Hotkeys can only be used in a browser environment with global "document" and "performance" objects.`);
40
52
  }
41
- this.keydown$ = fromEvent(document, HotKeys.KEYDOWN_EVENT);
53
+ this.keydown$ = fromEvent(document, Hotkeys.KEYDOWN_EVENT);
42
54
  this.activeContext$ = new BehaviorSubject(initialContext);
43
55
  this.activeShortcuts = new Map();
44
56
  if (this.debugMode) {
45
- console.log(`${HotKeys.LOG_PREFIX} Library initialized. Initial context: "${initialContext}". Debug mode: ${debugMode}.`);
57
+ console.log(`${Hotkeys.LOG_PREFIX} Library initialized. Initial context: "${initialContext}". Debug mode: ${debugMode}.`);
46
58
  }
47
59
  }
48
60
  /**
@@ -54,7 +66,7 @@ export class HotKeys {
54
66
  */
55
67
  setContext(contextName) {
56
68
  if (this.debugMode) {
57
- console.log(`${HotKeys.LOG_PREFIX} Context changed to "${contextName}"`);
69
+ console.log(`${Hotkeys.LOG_PREFIX} Context changed to "${contextName}"`);
58
70
  }
59
71
  this.activeContext$.next(contextName);
60
72
  }
@@ -73,7 +85,7 @@ export class HotKeys {
73
85
  setDebugMode(enable) {
74
86
  this.debugMode = enable;
75
87
  if (this.debugMode) {
76
- console.log(`${HotKeys.LOG_PREFIX} Debug mode ${enable ? 'enabled' : 'disabled'}.`);
88
+ console.log(`${Hotkeys.LOG_PREFIX} Debug mode ${enable ? "enabled" : "disabled"}.`);
77
89
  }
78
90
  }
79
91
  /**
@@ -87,56 +99,103 @@ export class HotKeys {
87
99
  filterByContext(source$, context) {
88
100
  return source$.pipe(withLatestFrom(this.activeContext$), filter(([/* event */ , activeCtx]) => context == null || context === activeCtx), map(([event, /* _activeCtx */]) => event));
89
101
  }
90
- _registerShortcut(config, subscription, type, detailsForLog) {
102
+ _registerShortcut(config, subscription, type, // Changed to use Enum
103
+ detailsForLog) {
91
104
  const existingShortcut = this.activeShortcuts.get(config.id);
92
105
  if (existingShortcut) {
93
- console.warn(`${HotKeys.LOG_PREFIX} Shortcut with ID "${config.id}" already exists. It will be overwritten.`);
106
+ console.warn(`${Hotkeys.LOG_PREFIX} Shortcut with ID "${config.id}" already exists. It will be overwritten.`);
94
107
  existingShortcut.subscription.unsubscribe();
95
108
  }
96
109
  this.activeShortcuts.set(config.id, { id: config.id, config, subscription });
97
110
  if (this.debugMode) {
98
- console.log(`${HotKeys.LOG_PREFIX} ${type} shortcut "${config.id}" added. ${detailsForLog}, Context: ${config.context ?? "any"}`);
111
+ console.log(`${Hotkeys.LOG_PREFIX} ${type} shortcut "${config.id}" added. ${detailsForLog}, Context: ${config.context ?? "any"}`);
99
112
  }
100
113
  return config.id;
101
114
  }
102
115
  /**
103
- * Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter).
104
- * The callback is triggered when the specified key and modifier keys are pressed simultaneously.
116
+ * Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter, or a single key like Escape).
117
+ * The callback is triggered when the specified key and modifier keys (if any) are pressed.
105
118
  * @param config - Configuration object for the key combination.
106
119
  * See {@link KeyCombinationConfig} for details.
107
- * The `key` property within `config.keys` must be a value from the `Keys` object.
108
- * @returns The ID of the registered shortcut if successful, or `undefined` if the configuration is invalid (e.g., empty key).
120
+ * The `key` property (or the direct `StandardKey` if using shorthand) must be a value from the `Keys` object.
121
+ * @returns The ID of the registered shortcut if successful, or `undefined` if the configuration is invalid.
109
122
  * A warning is logged to the console if the configuration is invalid or if a shortcut with the same ID is overwritten.
110
123
  * @example
111
124
  * ```typescript
112
- * import { Keys } from './keys';
125
+ * import { Keys } from "./keys";
126
+ * // For Ctrl+S
113
127
  * keyManager.addCombination({
114
128
  * id: "saveFile",
115
129
  * keys: { key: Keys.S, ctrlKey: true },
116
130
  * callback: () => console.log("File saved!"),
117
131
  * context: "editor"
118
132
  * });
133
+ * // For just the Escape key
134
+ * keyManager.addCombination({
135
+ * id: "closeModal",
136
+ * keys: Keys.Escape, // Shorthand syntax
137
+ * callback: () => console.log("Modal closed!")
138
+ * });
119
139
  * ```
120
140
  */
121
141
  addCombination(config) {
122
142
  const { keys, callback, context, preventDefault = false, id } = config;
123
- if (!keys || !keys.key || typeof keys.key !== 'string' || keys.key.trim() === '') {
124
- console.warn(`${HotKeys.LOG_PREFIX} Invalid 'keys.key' for combination shortcut "${id}". Key must be a non-empty value from Keys. Shortcut not added.`);
125
- return undefined;
143
+ let configuredMainKey;
144
+ let ctrlKeyConfig;
145
+ let altKeyConfig;
146
+ let shiftKeyConfig;
147
+ let metaKeyConfig;
148
+ let keyDetailsForLog;
149
+ if (typeof keys === "string") {
150
+ // Shorthand: keys is StandardKey, implying no modifiers should be active
151
+ if (keys.trim() === "") {
152
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys" (shorthand) for combination shortcut "${id}". Key string must not be empty. Shortcut not added.`);
153
+ return undefined;
154
+ }
155
+ configuredMainKey = keys;
156
+ ctrlKeyConfig = false; // Shorthand implies no modifiers
157
+ altKeyConfig = false;
158
+ shiftKeyConfig = false;
159
+ metaKeyConfig = false;
160
+ keyDetailsForLog = `key: "${keys}" (no modifiers implied)`;
161
+ }
162
+ else {
163
+ // Object form
164
+ if (!keys || !keys.key || typeof keys.key !== "string" || keys.key.trim() === "") {
165
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys.key" for combination shortcut "${id}". Key must be a non-empty value from Keys. Shortcut not added.`);
166
+ return undefined;
167
+ }
168
+ configuredMainKey = keys.key;
169
+ ctrlKeyConfig = keys.ctrlKey;
170
+ altKeyConfig = keys.altKey;
171
+ shiftKeyConfig = keys.shiftKey;
172
+ metaKeyConfig = keys.metaKey;
173
+ keyDetailsForLog = `key: "${keys.key}"` +
174
+ (keys.ctrlKey !== undefined ? `, ctrlKey: ${keys.ctrlKey}` : "") +
175
+ (keys.altKey !== undefined ? `, altKey: ${keys.altKey}` : "") +
176
+ (keys.shiftKey !== undefined ? `, shiftKey: ${keys.shiftKey}` : "") +
177
+ (keys.metaKey !== undefined ? `, metaKey: ${keys.metaKey}` : "");
126
178
  }
127
- const configuredMainKey = keys.key;
128
- const shortcut$ = this.filterByContext(this.keydown$, context).pipe(filter(event => (keys.ctrlKey === undefined || event.ctrlKey === keys.ctrlKey) &&
129
- (keys.altKey === undefined || event.altKey === keys.altKey) &&
130
- (keys.shiftKey === undefined || event.shiftKey === keys.shiftKey) &&
131
- (keys.metaKey === undefined || event.metaKey === keys.metaKey)), filter(event => compareKey(event.key, configuredMainKey)), tap(event => {
179
+ const shortcut$ = this.filterByContext(this.keydown$, context).pipe(filter(event => {
180
+ // For shorthand (where modifiers are explicitly false), we want an exact match.
181
+ // For object form:
182
+ // - if modifier is true, event.modifier must be true.
183
+ // - if modifier is false, event.modifier must be false.
184
+ // - if modifier is undefined, we don't care about event.modifier.
185
+ const ctrlMatch = (ctrlKeyConfig === undefined) ? true : (event.ctrlKey === ctrlKeyConfig);
186
+ const altMatch = (altKeyConfig === undefined) ? true : (event.altKey === altKeyConfig);
187
+ const shiftMatch = (shiftKeyConfig === undefined) ? true : (event.shiftKey === shiftKeyConfig);
188
+ const metaMatch = (metaKeyConfig === undefined) ? true : (event.metaKey === metaKeyConfig);
189
+ return ctrlMatch && altMatch && shiftMatch && metaMatch;
190
+ }), filter(event => compareKey(event.key, configuredMainKey)), tap(event => {
132
191
  if (this.debugMode) {
133
192
  const preventAction = preventDefault ? ", preventing default" : "";
134
- console.log(`${HotKeys.LOG_PREFIX} Combination "${id}" triggered${preventAction}.`);
193
+ console.log(`${Hotkeys.LOG_PREFIX} Combination "${id}" triggered${preventAction}.`);
135
194
  }
136
195
  if (preventDefault)
137
196
  event.preventDefault();
138
197
  }), catchError(err => {
139
- console.error(`${HotKeys.LOG_PREFIX} Error in combination stream for shortcut "${id}":`, err);
198
+ console.error(`${Hotkeys.LOG_PREFIX} Error in combination stream for shortcut "${id}":`, err);
140
199
  return EMPTY;
141
200
  }));
142
201
  const subscription = shortcut$.subscribe(event => {
@@ -144,15 +203,10 @@ export class HotKeys {
144
203
  callback(event);
145
204
  }
146
205
  catch (e) {
147
- console.error(`${HotKeys.LOG_PREFIX} Error in user callback for combination shortcut "${id}":`, e);
206
+ console.error(`${Hotkeys.LOG_PREFIX} Error in user callback for combination shortcut "${id}":`, e);
148
207
  }
149
208
  });
150
- const keyDetails = `key: "${keys.key}"` +
151
- (keys.ctrlKey !== undefined ? `, ctrlKey: ${keys.ctrlKey}` : "") +
152
- (keys.altKey !== undefined ? `, altKey: ${keys.altKey}` : "") +
153
- (keys.shiftKey !== undefined ? `, shiftKey: ${keys.shiftKey}` : "") +
154
- (keys.metaKey !== undefined ? `, metaKey: ${keys.metaKey}` : "");
155
- return this._registerShortcut(config, subscription, "Combination", `Keys: { ${keyDetails} }`);
209
+ return this._registerShortcut(config, subscription, ShortcutTypes.Combination, `Keys: { ${keyDetailsForLog} }`); // Use Enum
156
210
  }
157
211
  /**
158
212
  * Registers a key sequence shortcut (e.g., g -> i, or ArrowUp -> ArrowUp -> ArrowDown).
@@ -165,7 +219,7 @@ export class HotKeys {
165
219
  * A warning is logged to the console if the configuration is invalid or if a shortcut with the same ID is overwritten.
166
220
  * @example
167
221
  * ```typescript
168
- * import { Keys } from './keys';
222
+ * import { Keys } from "./keys";
169
223
  * keyManager.addSequence({
170
224
  * id: "konamiCode",
171
225
  * sequence: [Keys.ArrowUp, Keys.ArrowUp, Keys.ArrowDown, Keys.ArrowDown, Keys.A, Keys.B],
@@ -177,11 +231,11 @@ export class HotKeys {
177
231
  addSequence(config) {
178
232
  const { sequence, callback, context, preventDefault = false, id, sequenceTimeoutMs } = config;
179
233
  if (!Array.isArray(sequence) || sequence.length === 0) {
180
- console.warn(`${HotKeys.LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
234
+ console.warn(`${Hotkeys.LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
181
235
  return undefined;
182
236
  }
183
- if (sequence.some(key => typeof key !== 'string' || key.trim() === '')) {
184
- console.warn(`${HotKeys.LOG_PREFIX} Invalid key in sequence for shortcut "${id}". All keys must be non-empty strings from Keys. Shortcut not added.`);
237
+ if (sequence.some(key => typeof key !== "string" || key.trim() === "")) {
238
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid key in sequence for shortcut "${id}". All keys must be non-empty strings from Keys. Shortcut not added.`);
185
239
  return undefined;
186
240
  }
187
241
  const configuredSequence = sequence;
@@ -192,48 +246,52 @@ export class HotKeys {
192
246
  shortcut$ = baseKeydownStream$.pipe(scan((acc, event) => {
193
247
  let { matchedEvents, lastEventTime } = acc;
194
248
  const currentTime = performance.now();
195
- if (acc.emitState === 'emit') {
249
+ if (acc.emitState === EmitStates.Emit) {
196
250
  matchedEvents = [];
197
251
  lastEventTime = 0;
198
252
  }
199
253
  if (matchedEvents.length > 0 && (currentTime - lastEventTime > sequenceTimeoutMs)) {
200
254
  if (this.debugMode) {
201
- console.log(`${HotKeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) attempt timed out. Matched: ${matchedEvents.map(e => e.key).join(',')}. Resetting.`);
255
+ console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) attempt timed out. Matched: ${matchedEvents.map(e => e.key).join(",")}. Resetting.`);
202
256
  }
203
257
  matchedEvents = [];
204
258
  }
205
259
  const nextExpectedKeyIndex = matchedEvents.length;
206
260
  if (nextExpectedKeyIndex >= sequenceLength) {
261
+ // Sequence was already emitted or buffer is too long (should not happen if reset correctly)
262
+ // Start new sequence if current key matches the first key of the sequence
207
263
  if (sequenceLength > 0 && compareKey(event.key, configuredSequence[0])) {
208
- return { matchedEvents: [event], lastEventTime: currentTime, emitState: 'in-progress' };
264
+ return { matchedEvents: [event], lastEventTime: currentTime, emitState: EmitStates.InProgress };
209
265
  }
210
- return { matchedEvents: [], lastEventTime: 0, emitState: 'ignore' };
266
+ return { matchedEvents: [], lastEventTime: 0, emitState: EmitStates.Ignore };
211
267
  }
212
268
  if (compareKey(event.key, configuredSequence[nextExpectedKeyIndex])) {
213
269
  const newMatchedEvents = [...matchedEvents, event];
214
270
  if (newMatchedEvents.length === sequenceLength) {
215
- if (this.debugMode)
216
- console.log(`${HotKeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) matched.`);
217
- return { matchedEvents: newMatchedEvents, lastEventTime: currentTime, emitState: 'emit' };
271
+ if (this.debugMode && !acc.emitState)
272
+ console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) matched.`);
273
+ return { matchedEvents: newMatchedEvents, lastEventTime: currentTime, emitState: EmitStates.Emit };
218
274
  }
219
275
  else {
220
- return { matchedEvents: newMatchedEvents, lastEventTime: currentTime, emitState: 'in-progress' };
276
+ return { matchedEvents: newMatchedEvents, lastEventTime: currentTime, emitState: EmitStates.InProgress };
221
277
  }
222
278
  }
223
279
  else {
280
+ // If current key breaks sequence, check if it starts a new sequence
224
281
  if (matchedEvents.length > 0 && this.debugMode) {
225
- console.log(`${HotKeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) broken by key "${event.key}". Matched: ${matchedEvents.map(e => e.key).join(',')}. Resetting.`);
282
+ console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) broken by key "${event.key}". Matched: ${matchedEvents.map(e => e.key).join(",")}. Resetting.`);
226
283
  }
227
284
  if (sequenceLength > 0 && compareKey(event.key, configuredSequence[0])) {
228
- return { matchedEvents: [event], lastEventTime: currentTime, emitState: 'in-progress' };
285
+ return { matchedEvents: [event], lastEventTime: currentTime, emitState: EmitStates.InProgress };
229
286
  }
230
287
  else {
231
- return { matchedEvents: [], lastEventTime: 0, emitState: 'ignore' };
288
+ return { matchedEvents: [], lastEventTime: 0, emitState: EmitStates.Ignore };
232
289
  }
233
290
  }
234
- }, { matchedEvents: [], lastEventTime: 0, emitState: 'ignore' }), filter(state => state.emitState === 'emit'), map(state => state.matchedEvents));
291
+ }, { matchedEvents: [], lastEventTime: 0, emitState: EmitStates.Ignore }), filter(state => state.emitState === EmitStates.Emit), map(state => state.matchedEvents));
235
292
  }
236
293
  else {
294
+ // No timeout logic: simple buffer-based matching
237
295
  shortcut$ = baseKeydownStream$.pipe(bufferCount(sequenceLength, 1), filter((events) => {
238
296
  if (events.length < sequenceLength)
239
297
  return false;
@@ -244,26 +302,27 @@ export class HotKeys {
244
302
  if (this.debugMode) {
245
303
  const timeoutInfo = (sequenceTimeoutMs && sequenceTimeoutMs > 0) ? ` (with timeout logic)` : ` (no timeout logic)`;
246
304
  const preventAction = preventDefault ? ", preventing default for last event" : "";
247
- console.log(`${HotKeys.LOG_PREFIX} Sequence "${id}" triggered${timeoutInfo}${preventAction}.`);
305
+ console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" triggered${timeoutInfo}${preventAction}.`);
248
306
  }
249
307
  if (preventDefault && events.length > 0) {
250
308
  events[events.length - 1].preventDefault();
251
309
  }
252
310
  }), catchError(err => {
253
- console.error(`${HotKeys.LOG_PREFIX} Error in sequence stream for shortcut "${id}":`, err);
311
+ console.error(`${Hotkeys.LOG_PREFIX} Error in sequence stream for shortcut "${id}":`, err);
254
312
  return EMPTY;
255
313
  }));
256
314
  const subscription = finalShortcut$.subscribe((events) => {
257
315
  try {
316
+ // Ensure callback receives the last event of the sequence, similar to combination.
258
317
  if (events.length > 0)
259
318
  callback(events[events.length - 1]);
260
319
  }
261
320
  catch (e) {
262
- console.error(`${HotKeys.LOG_PREFIX} Error in user callback for sequence shortcut "${id}":`, e);
321
+ console.error(`${Hotkeys.LOG_PREFIX} Error in user callback for sequence shortcut "${id}":`, e);
263
322
  }
264
323
  });
265
- const logDetails = `Sequence: ${sequence.join(" -> ")}${sequenceTimeoutMs && sequenceTimeoutMs > 0 ? ` (timeout: ${sequenceTimeoutMs}ms)` : ''}`;
266
- return this._registerShortcut(config, subscription, "Sequence", logDetails);
324
+ const logDetails = `Sequence: ${sequence.join(" -> ")}${sequenceTimeoutMs && sequenceTimeoutMs > 0 ? ` (timeout: ${sequenceTimeoutMs}ms)` : ""}`;
325
+ return this._registerShortcut(config, subscription, ShortcutTypes.Sequence, logDetails); // Use Enum
267
326
  }
268
327
  /**
269
328
  * Removes a registered shortcut by its ID.
@@ -278,10 +337,10 @@ export class HotKeys {
278
337
  shortcut.subscription.unsubscribe();
279
338
  this.activeShortcuts.delete(id);
280
339
  if (this.debugMode)
281
- console.log(`${HotKeys.LOG_PREFIX} Shortcut "${id}" removed.`);
340
+ console.log(`${Hotkeys.LOG_PREFIX} Shortcut "${id}" removed.`);
282
341
  return true;
283
342
  }
284
- console.warn(`${HotKeys.LOG_PREFIX} Shortcut with ID "${id}" not found for removal.`);
343
+ console.warn(`${Hotkeys.LOG_PREFIX} Shortcut with ID "${id}" not found for removal.`);
285
344
  return false;
286
345
  }
287
346
  /**
@@ -298,7 +357,7 @@ export class HotKeys {
298
357
  id,
299
358
  description: activeShortcut.config.description,
300
359
  context: activeShortcut.config.context,
301
- type: 'keys' in activeShortcut.config ? "combination" : "sequence"
360
+ type: ("sequence" in activeShortcut.config) ? ShortcutTypes.Sequence : ShortcutTypes.Combination // Use Enum values
302
361
  });
303
362
  }
304
363
  return shortcuts;
@@ -311,11 +370,11 @@ export class HotKeys {
311
370
  */
312
371
  destroy() {
313
372
  if (this.debugMode)
314
- console.log(`${HotKeys.LOG_PREFIX} Destroying library instance and unsubscribing all shortcuts.`);
373
+ console.log(`${Hotkeys.LOG_PREFIX} Destroying library instance and unsubscribing all shortcuts.`);
315
374
  this.activeShortcuts.forEach(shortcut => shortcut.subscription.unsubscribe());
316
375
  this.activeShortcuts.clear();
317
376
  this.activeContext$.complete(); // Complete the BehaviorSubject to release its resources
318
377
  if (this.debugMode)
319
- console.log(`${HotKeys.LOG_PREFIX} Library destroyed.`);
378
+ console.log(`${Hotkeys.LOG_PREFIX} Library destroyed.`);
320
379
  }
321
380
  }
@@ -1,8 +1,6 @@
1
1
  import { describe, it, before, beforeEach, afterEach, mock } from "node:test";
2
2
  import assert from "node:assert";
3
- // Importing main library components
4
- import { HotKeys } from "./hotkeys.js";
5
- // Importing Keys and StandardKey from the separate keys.js file
3
+ import { Hotkeys, ShortcutTypes } from "./hotkeys.js";
6
4
  import { Keys } from "./keys.js";
7
5
  import { fromEvent, BehaviorSubject } from "rxjs";
8
6
  import { createMockFn, dispatchKeyEvent } from "./testutils.js";
@@ -27,14 +25,14 @@ before(() => {
27
25
  // @ts-ignore
28
26
  global.fromEvent = fromEvent;
29
27
  // @ts-ignore
30
- if (typeof global.performance === 'undefined') {
28
+ if (typeof global.performance === "undefined") {
31
29
  // @ts-ignore
32
30
  global.performance = {};
33
31
  }
34
32
  // @ts-ignore
35
33
  originalPerformanceNow = global.performance.now;
36
34
  // @ts-ignore
37
- if (typeof global.performance.now !== 'function') {
35
+ if (typeof global.performance.now !== "function") {
38
36
  // @ts-ignore
39
37
  global.performance.now = (() => {
40
38
  const start = Date.now();
@@ -49,7 +47,7 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
49
47
  let consoleErrorMock;
50
48
  let performanceNowMock; // To mock global.performance.now specifically for sequence tests
51
49
  beforeEach(() => {
52
- keyManager = new HotKeys(null, false);
50
+ keyManager = new Hotkeys(null, false);
53
51
  mockCallback = createMockFn();
54
52
  consoleWarnMock = mock.method(console, "warn");
55
53
  consoleErrorMock = mock.method(console, "error");
@@ -70,15 +68,16 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
70
68
  global.performance.now = originalPerformanceNow;
71
69
  }
72
70
  });
71
+ // ... (Initialization and Basic Context tests remain the same) ...
73
72
  describe("Initialization and Basic Context", () => {
74
73
  it("should initialize without errors", () => {
75
- assert(keyManager instanceof HotKeys);
74
+ assert(keyManager instanceof Hotkeys);
76
75
  });
77
76
  it("should initialize with a null context by default", () => {
78
77
  assert.strictEqual(keyManager.getContext(), null);
79
78
  });
80
79
  it("should initialize with a given initial context", () => {
81
- const manager = new HotKeys("editor");
80
+ const manager = new Hotkeys("editor");
82
81
  assert.strictEqual(manager.getContext(), "editor");
83
82
  manager.destroy();
84
83
  });
@@ -92,11 +91,11 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
92
91
  const consoleLogMock = mock.method(console, "log");
93
92
  keyManager.setDebugMode(true);
94
93
  keyManager.setContext("debug_test");
95
- assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes('Context changed to "debug_test"')));
94
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed to "debug_test"`)));
96
95
  consoleLogMock.mock.resetCalls();
97
96
  keyManager.setDebugMode(false);
98
97
  keyManager.setContext("no_debug_test");
99
- assert.ok(!consoleLogMock.mock.calls.some(call => call.arguments[0].includes('Context changed to "no_debug_test"')));
98
+ assert.ok(!consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed to "no_debug_test"`)));
100
99
  consoleLogMock.mock.restore();
101
100
  });
102
101
  });
@@ -111,13 +110,12 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
111
110
  dispatchKeyEvent("A");
112
111
  assert.strictEqual(mockCallback.calledCount, 1, "Callback for 'A' not called");
113
112
  });
114
- it("should return undefined and warn if keys.key is null or undefined (runtime check)", () => {
115
- // This test checks runtime robustness if `any` is used to bypass StandardKey
113
+ it("should return undefined and warn if keys.key is null or undefined (runtime check in object form)", () => {
116
114
  const config = { id: "nullKey", keys: { key: null }, callback: mockCallback };
117
115
  const result = keyManager.addCombination(config);
118
116
  assert.strictEqual(result, undefined, "Should return undefined for null key");
119
117
  assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
120
- assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes('Invalid \'keys.key\' for combination shortcut "nullKey"'));
118
+ assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid "keys.key" for combination shortcut "nullKey"`));
121
119
  });
122
120
  it("should pass the KeyboardEvent to the callback", () => {
123
121
  const config = { id: "eventPass", keys: { key: Keys.E }, callback: mockCallback };
@@ -140,14 +138,14 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
140
138
  dispatchKeyEvent("a", { ctrlKey: false });
141
139
  assert.strictEqual(mockCallback.calledCount, 1);
142
140
  });
143
- it("should trigger for special keys like Escape", () => {
144
- const config = { id: "escapeKey", keys: { key: Keys.Escape }, callback: mockCallback };
141
+ it("should trigger for special keys like Escape (object form)", () => {
142
+ const config = { id: "escapeKeyObj", keys: { key: Keys.Escape }, callback: mockCallback };
145
143
  keyManager.addCombination(config);
146
144
  dispatchKeyEvent("Escape"); // Event key matches Keys.Escape
147
145
  assert.strictEqual(mockCallback.calledCount, 1);
148
146
  });
149
- it("should handle preventDefault correctly", () => {
150
- const config = { id: "preventA", keys: { key: Keys.A }, callback: mockCallback, preventDefault: true };
147
+ it("should handle preventDefault correctly (object form)", () => {
148
+ const config = { id: "preventAObj", keys: { key: Keys.A }, callback: mockCallback, preventDefault: true };
151
149
  keyManager.addCombination(config);
152
150
  const event = dispatchKeyEvent("a");
153
151
  assert.strictEqual(mockCallback.calledCount, 1);
@@ -173,25 +171,92 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
173
171
  keyManager.addCombination({ id: "workingCombo", keys: { key: Keys.W }, callback: workingCallback });
174
172
  dispatchKeyEvent("e");
175
173
  assert.strictEqual(consoleErrorMock.mock.calls.length, 1);
176
- assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes('Error in user callback for combination shortcut "errorCombo"'));
174
+ assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes(`Error in user callback for combination shortcut "errorCombo"`));
177
175
  dispatchKeyEvent("w");
178
176
  assert.strictEqual(workingCallback.calledCount, 1);
179
177
  });
178
+ describe("addCombination - Shorthand Syntax", () => {
179
+ it("should trigger callback for a simple key using shorthand (e.g., Keys.X)", () => {
180
+ const config = { id: "shorthandX", keys: Keys.X, callback: mockCallback };
181
+ keyManager.addCombination(config);
182
+ dispatchKeyEvent(Keys.X.toLowerCase());
183
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for 'x' (shorthand) not called");
184
+ mockCallback.mockClear();
185
+ dispatchKeyEvent(Keys.X);
186
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for 'X' (shorthand) not called");
187
+ });
188
+ it("should NOT trigger callback for shorthand if modifier is pressed", () => {
189
+ const config = { id: "shorthandY", keys: Keys.Y, callback: mockCallback };
190
+ keyManager.addCombination(config);
191
+ dispatchKeyEvent(Keys.Y, { ctrlKey: true });
192
+ assert.strictEqual(mockCallback.calledCount, 0, "Callback for 'y' (shorthand) should not be called with Ctrl");
193
+ mockCallback.mockClear();
194
+ dispatchKeyEvent(Keys.Y, { altKey: true });
195
+ assert.strictEqual(mockCallback.calledCount, 0, "Callback for 'y' (shorthand) should not be called with Alt");
196
+ mockCallback.mockClear();
197
+ dispatchKeyEvent(Keys.Y, { shiftKey: true });
198
+ assert.strictEqual(mockCallback.calledCount, 0, "Callback for 'y' (shorthand) should not be called with Shift");
199
+ mockCallback.mockClear();
200
+ dispatchKeyEvent(Keys.Y, { metaKey: true });
201
+ assert.strictEqual(mockCallback.calledCount, 0, "Callback for 'y' (shorthand) should not be called with Meta");
202
+ });
203
+ it("should trigger callback for shorthand if ONLY the key is pressed (no modifiers)", () => {
204
+ const config = { id: "shorthandZ", keys: Keys.Z, callback: mockCallback };
205
+ keyManager.addCombination(config);
206
+ dispatchKeyEvent(Keys.Z, { ctrlKey: false, altKey: false, shiftKey: false, metaKey: false });
207
+ assert.strictEqual(mockCallback.calledCount, 1);
208
+ });
209
+ it("should handle preventDefault correctly for shorthand", () => {
210
+ const config = { id: "shorthandPrevent", keys: Keys.P, callback: mockCallback, preventDefault: true };
211
+ keyManager.addCombination(config);
212
+ const event = dispatchKeyEvent(Keys.P.toLowerCase());
213
+ assert.strictEqual(mockCallback.calledCount, 1);
214
+ assert.strictEqual(event.defaultPrevented, true);
215
+ });
216
+ it("should respect context for shorthand", () => {
217
+ const config = { id: "shorthandContext", keys: Keys.C, callback: mockCallback, context: "editor" };
218
+ keyManager.addCombination(config);
219
+ keyManager.setContext("other");
220
+ dispatchKeyEvent(Keys.C.toLowerCase());
221
+ assert.strictEqual(mockCallback.calledCount, 0);
222
+ keyManager.setContext("editor");
223
+ dispatchKeyEvent(Keys.C.toLowerCase());
224
+ assert.strictEqual(mockCallback.calledCount, 1);
225
+ });
226
+ it("should return undefined and warn if shorthand key is an empty string", () => {
227
+ const config = { id: "emptyShorthand", keys: "", callback: mockCallback };
228
+ const result = keyManager.addCombination(config);
229
+ assert.strictEqual(result, undefined);
230
+ assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
231
+ assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid "keys" (shorthand) for combination shortcut "emptyShorthand"`));
232
+ });
233
+ it("should correctly log shorthand key details in debug mode", () => {
234
+ const consoleLogMock = mock.method(console, "log");
235
+ keyManager.setDebugMode(true);
236
+ const config = { id: "debugShorthand", keys: Keys.D, callback: mockCallback };
237
+ keyManager.addCombination(config);
238
+ const logMessage = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`combination shortcut "debugShorthand" added`));
239
+ assert.ok(logMessage, "Debug log for adding shortcut not found");
240
+ assert.ok(logMessage.arguments[0].includes(`Keys: { key: "D" (no modifiers implied) }`), `Log message content mismatch: ${logMessage.arguments[0]}`);
241
+ consoleLogMock.mock.restore();
242
+ keyManager.setDebugMode(false);
243
+ });
244
+ });
180
245
  });
181
246
  describe("addSequence", () => {
182
247
  it("should trigger callback for a simple key sequence", () => {
183
248
  const config = { id: "seqGI", sequence: [Keys.G, Keys.I], callback: mockCallback };
184
249
  const result = keyManager.addSequence(config);
185
250
  assert.strictEqual(result, "seqGI");
186
- dispatchKeyEvent("g"); // Dispatch 'g' (lowercase)
187
- dispatchKeyEvent("i"); // Dispatch 'i' (lowercase)
251
+ dispatchKeyEvent("g"); // Dispatch "g" (lowercase)
252
+ dispatchKeyEvent("i"); // Dispatch "i" (lowercase)
188
253
  assert.strictEqual(mockCallback.calledCount, 1);
189
254
  });
190
255
  it("should trigger callback for Konami code using Keys", () => {
191
256
  const konamiSequence = [
192
257
  Keys.ArrowUp, Keys.ArrowUp, Keys.ArrowDown, Keys.ArrowDown,
193
258
  Keys.ArrowLeft, Keys.ArrowRight, Keys.ArrowLeft, Keys.ArrowRight,
194
- Keys.B, Keys.A // Using 'B' and 'A' from Keys
259
+ Keys.B, Keys.A // Using "B" and "A" from Keys
195
260
  ];
196
261
  const config = { id: "konami", sequence: konamiSequence, callback: mockCallback };
197
262
  keyManager.addSequence(config);
@@ -236,7 +301,7 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
236
301
  dispatchKeyEvent("e");
237
302
  dispatchKeyEvent("s");
238
303
  assert.strictEqual(consoleErrorMock.mock.calls.length, 1);
239
- assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes('Error in user callback for sequence shortcut "errorSeq"'));
304
+ assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes(`Error in user callback for sequence shortcut "errorSeq"`));
240
305
  });
241
306
  describe("Sequence Contextual Triggering", () => {
242
307
  let editorSequenceConfig;
@@ -317,17 +382,17 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
317
382
  });
318
383
  });
319
384
  describe("getActiveShortcuts", () => {
320
- it("should return active combination and sequence shortcuts", () => {
385
+ it("should return active combination and sequence shortcuts with enum types", () => {
321
386
  keyManager.addCombination({ id: "combo1", keys: { key: Keys.A }, callback: createMockFn(), description: "Test A" });
322
387
  keyManager.addSequence({ id: "seq1", sequence: [Keys.B, Keys.C], callback: createMockFn(), context: "modal", description: "Test BC" });
323
388
  const active = keyManager.getActiveShortcuts();
324
389
  assert.strictEqual(active.length, 2);
325
390
  const combo = active.find(s => s.id === "combo1");
326
391
  assert.ok(combo);
327
- assert.strictEqual(combo.type, "combination");
392
+ assert.strictEqual(combo.type, ShortcutTypes.Combination); // Use Enum for comparison
328
393
  const seq = active.find(s => s.id === "seq1");
329
394
  assert.ok(seq);
330
- assert.strictEqual(seq.type, "sequence");
395
+ assert.strictEqual(seq.type, ShortcutTypes.Sequence); // Use Enum for comparison
331
396
  });
332
397
  });
333
398
  describe("hasShortcut", () => {
@@ -347,10 +412,10 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
347
412
  keyManager.addCombination({ id: "destroyTestCombo", keys: { key: Keys.D }, callback: mockCallback });
348
413
  keyManager.addSequence({ id: "destroyTestSeq", sequence: [Keys.X, Keys.Y], callback: mockCallback });
349
414
  // @ts-ignore
350
- assert.strictEqual(keyManager['activeShortcuts'].size, 2);
415
+ assert.strictEqual(keyManager["activeShortcuts"].size, 2);
351
416
  keyManager.destroy();
352
417
  // @ts-ignore
353
- assert.strictEqual(keyManager['activeShortcuts'].size, 0);
418
+ assert.strictEqual(keyManager["activeShortcuts"].size, 0);
354
419
  dispatchKeyEvent(Keys.D);
355
420
  dispatchKeyEvent(Keys.X);
356
421
  dispatchKeyEvent(Keys.Y);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { type StandardKey, Keys, } from "./keys.js";
2
- export { type KeyCombinationConfig, type KeySequenceConfig, HotKeys, } from "./hotkeys.js";
2
+ export { type KeyCombinationConfig, type KeySequenceConfig, Hotkeys, } from "./hotkeys.js";
3
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { Keys, } from "./keys.js";
2
- export { HotKeys, } from "./hotkeys.js";
2
+ export { Hotkeys, } from "./hotkeys.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rx-hotkeys",
3
- "version": "1.0.0",
3
+ "version": "2.1.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsc",
@@ -14,7 +14,12 @@
14
14
  "types": "./dist/index.d.ts"
15
15
  }
16
16
  },
17
- "keywords": ["rxjs", "hotkeys", "hotkey", "key"],
17
+ "keywords": [
18
+ "rxjs",
19
+ "hotkeys",
20
+ "hotkey",
21
+ "key"
22
+ ],
18
23
  "author": "Colin Cheng <zbinlin@outlook.com>",
19
24
  "repository": {
20
25
  "type": "git",