rx-hotkeys 2.1.1 → 2.2.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/dist/hotkeys.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Observable } from "rxjs";
1
2
  import { StandardKey } from "./keys.js";
2
3
  export declare enum ShortcutTypes {
3
4
  Combination = "combination",
@@ -96,6 +97,25 @@ export declare class Hotkeys {
96
97
  * @param enable - True to enable debug logs, false to disable.
97
98
  */
98
99
  setDebugMode(enable: boolean): void;
100
+ /**
101
+ * An Observable that emits the new context name (or null) whenever the active context changes.
102
+ * This allows external parts of the application to react to context transitions.
103
+ *
104
+ * Note: This observable benefits from the distinct check within the `setContext` method,
105
+ * meaning it will only emit when the context value actually changes.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const hotkeys = new Hotkeys();
110
+ * const subscription = hotkeys.onContextChange$.subscribe(newContext => {
111
+ * console.log("Hotkey context changed to:", newContext);
112
+ * // Update UI or perform other actions
113
+ * });
114
+ * // To unsubscribe when no longer needed:
115
+ * // subscription.unsubscribe();
116
+ * ```
117
+ */
118
+ get onContextChange$(): Observable<string | null>;
99
119
  /**
100
120
  * Checks if a shortcut with the given ID is currently registered and active.
101
121
  * @param id - The unique ID of the shortcut to check.
@@ -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,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"}
1
+ {"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"AAAA,OAAO,EACuC,UAAU,EAEvD,MAAM,MAAM,CAAC;AACd,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;IAiBnD;;;OAGG;IACI,UAAU,IAAI,MAAM,GAAG,IAAI;IAIlC;;;;OAIG;IACI,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAa1C;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAW,gBAAgB,IAAI,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,CAEvD;IAED;;;;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;IAgFvE;;;;;;;;;;;;;;;;;;;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,CAAA;KAAC,EAAE;IAa/G;;;;;OAKG;IACI,OAAO,IAAI,IAAI;CAOzB"}
package/dist/hotkeys.js CHANGED
@@ -65,8 +65,17 @@ export class Hotkeys {
65
65
  * Pass `null` to activate shortcuts with no context or to deactivate context-specific shortcuts.
66
66
  */
67
67
  setContext(contextName) {
68
+ if (this.activeContext$.getValue() === contextName) {
69
+ if (this.debugMode) {
70
+ // Optional: Log that no change is happening, or simply do nothing.
71
+ console.log(`${Hotkeys.LOG_PREFIX} setContext called with the same context "${contextName}". No change made.`);
72
+ }
73
+ return; // Context is the same, so no further action is needed.
74
+ }
75
+ // If we reach here, the context is actually changing.
76
+ const oldContext = this.activeContext$.getValue(); // For more informative logging
68
77
  if (this.debugMode) {
69
- console.log(`${Hotkeys.LOG_PREFIX} Context changed to "${contextName}"`);
78
+ console.log(`${Hotkeys.LOG_PREFIX} Context changed from "${oldContext}" to "${contextName}".`);
70
79
  }
71
80
  this.activeContext$.next(contextName);
72
81
  }
@@ -83,11 +92,38 @@ export class Hotkeys {
83
92
  * @param enable - True to enable debug logs, false to disable.
84
93
  */
85
94
  setDebugMode(enable) {
86
- this.debugMode = enable;
87
- if (this.debugMode) {
88
- console.log(`${Hotkeys.LOG_PREFIX} Debug mode ${enable ? "enabled" : "disabled"}.`);
95
+ if (this.debugMode === enable) { // Check if state is actually changing
96
+ return; // If no change, do nothing (no log)
97
+ }
98
+ this.debugMode = enable; // Set the new state
99
+ if (enable) { // Log based on the NEW state after a change
100
+ console.log(`${Hotkeys.LOG_PREFIX} Debug mode enabled.`);
101
+ }
102
+ else {
103
+ console.log(`${Hotkeys.LOG_PREFIX} Debug mode disabled.`);
89
104
  }
90
105
  }
106
+ /**
107
+ * An Observable that emits the new context name (or null) whenever the active context changes.
108
+ * This allows external parts of the application to react to context transitions.
109
+ *
110
+ * Note: This observable benefits from the distinct check within the `setContext` method,
111
+ * meaning it will only emit when the context value actually changes.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const hotkeys = new Hotkeys();
116
+ * const subscription = hotkeys.onContextChange$.subscribe(newContext => {
117
+ * console.log("Hotkey context changed to:", newContext);
118
+ * // Update UI or perform other actions
119
+ * });
120
+ * // To unsubscribe when no longer needed:
121
+ * // subscription.unsubscribe();
122
+ * ```
123
+ */
124
+ get onContextChange$() {
125
+ return this.activeContext$.asObservable();
126
+ }
91
127
  /**
92
128
  * Checks if a shortcut with the given ID is currently registered and active.
93
129
  * @param id - The unique ID of the shortcut to check.
@@ -148,8 +184,9 @@ export class Hotkeys {
148
184
  let keyDetailsForLog;
149
185
  if (typeof keys === "string") {
150
186
  // 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.`);
187
+ // Corrected validation: Check for actual empty string, not a string that trims to empty.
188
+ if (keys === "") { // StandardKey type should prevent this, but check for robustness.
189
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys" (shorthand) for combination shortcut "${id}". Key must not be an empty string. Shortcut not added.`);
153
190
  return undefined;
154
191
  }
155
192
  configuredMainKey = keys;
@@ -161,8 +198,9 @@ export class Hotkeys {
161
198
  }
162
199
  else {
163
200
  // 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.`);
201
+ // Corrected validation for keys.key
202
+ if (!keys || !keys.key || typeof keys.key !== "string" || keys.key === "") { // StandardKey type should prevent empty string for key.key
203
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys.key" for combination shortcut "${id}". Key must be a non-empty string value from Keys. Shortcut not added.`);
166
204
  return undefined;
167
205
  }
168
206
  configuredMainKey = keys.key;
@@ -234,8 +272,9 @@ export class Hotkeys {
234
272
  console.warn(`${Hotkeys.LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
235
273
  return undefined;
236
274
  }
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.`);
275
+ // Corrected validation: Check for actual empty string, not a string that trims to empty.
276
+ if (sequence.some(key => typeof key !== "string" || key === "")) { // StandardKey type should prevent empty strings.
277
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid key in sequence for shortcut "${id}". All keys must be non-empty string values from Keys. Shortcut not added.`);
239
278
  return undefined;
240
279
  }
241
280
  const configuredSequence = sequence;
@@ -69,35 +69,134 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
69
69
  }
70
70
  });
71
71
  // ... (Initialization and Basic Context tests remain the same) ...
72
- describe("Initialization and Basic Context", () => {
72
+ describe("Initialization and Context Management", () => {
73
73
  it("should initialize without errors", () => {
74
74
  assert(keyManager instanceof Hotkeys);
75
+ assert.strictEqual(keyManager.getContext(), null); // Default initial context
75
76
  });
76
77
  it("should initialize with a null context by default", () => {
77
78
  assert.strictEqual(keyManager.getContext(), null);
78
79
  });
79
- it("should initialize with a given initial context", () => {
80
- const manager = new Hotkeys("editor");
80
+ it("should initialize with a given initial context (debug off)", () => {
81
+ const manager = new Hotkeys("editor", false);
81
82
  assert.strictEqual(manager.getContext(), "editor");
82
83
  manager.destroy();
83
84
  });
84
- it("should set and get context", () => {
85
+ it("should log library initialization with context in debug mode", () => {
86
+ const consoleLogMock = mock.method(console, "log");
87
+ const manager = new Hotkeys("debugInitCtx", true);
88
+ const initLog = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`Library initialized. Initial context: "debugInitCtx"`));
89
+ assert.ok(initLog, "Library initialization log not found or incorrect.");
90
+ manager.destroy();
91
+ consoleLogMock.mock.restore();
92
+ });
93
+ it("should set and get context correctly", () => {
85
94
  keyManager.setContext("modal");
86
95
  assert.strictEqual(keyManager.getContext(), "modal");
87
96
  keyManager.setContext(null);
88
97
  assert.strictEqual(keyManager.getContext(), null);
89
98
  });
90
- it("should toggle debug mode and log appropriately", () => {
99
+ it("should log context change correctly when context is different (debug mode on)", () => {
91
100
  const consoleLogMock = mock.method(console, "log");
92
101
  keyManager.setDebugMode(true);
102
+ // Initial context is null for keyManager
103
+ consoleLogMock.mock.resetCalls(); // Clear "Debug mode enabled" log
93
104
  keyManager.setContext("debug_test");
94
- assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed to "debug_test"`)));
105
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed from "null" to "debug_test"`)), "Log for context change from null incorrect.");
106
+ assert.strictEqual(keyManager.getContext(), "debug_test");
107
+ consoleLogMock.mock.resetCalls();
108
+ keyManager.setContext("another_test");
109
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed from "debug_test" to "another_test"`)), "Log for context change between non-null incorrect.");
110
+ assert.strictEqual(keyManager.getContext(), "another_test");
111
+ consoleLogMock.mock.restore();
112
+ });
113
+ it("should log context change from non-null to null (debug mode on)", () => {
114
+ const consoleLogMock = mock.method(console, "log");
115
+ keyManager.setContext("fromCtx"); // Initial context
116
+ keyManager.setDebugMode(true);
117
+ consoleLogMock.mock.resetCalls();
118
+ keyManager.setContext(null);
119
+ const logCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`Context changed from "fromCtx" to "null"`));
120
+ assert.ok(logCall, "Context change to null log not found or incorrect.");
121
+ assert.strictEqual(keyManager.getContext(), null);
122
+ consoleLogMock.mock.restore();
123
+ });
124
+ it(`should not call activeContext$.next and log "no change" if context is set to the same value (debug mode on)`, () => {
125
+ keyManager.setContext("sameCtx"); // Set initial context
126
+ const consoleLogMock = mock.method(console, "log");
127
+ keyManager.setDebugMode(true); // Enable debug for this test
128
+ consoleLogMock.mock.resetCalls(); // Clear "Debug mode enabled" log
129
+ // @ts-ignore: Accessing private member for test
130
+ const activeContextNextSpy = mock.method(keyManager["activeContext$"], "next");
131
+ keyManager.setContext("sameCtx"); // Attempt to set the same context
132
+ const noChangeLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`setContext called with the same context "sameCtx". No change made.`));
133
+ assert.ok(noChangeLogCall, "No-change log not found or incorrect for same context.");
134
+ const changedLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`Context changed from`));
135
+ assert.strictEqual(changedLogCall, undefined, "Context changed log should not appear for same context.");
136
+ assert.strictEqual(keyManager.getContext(), "sameCtx");
137
+ assert.strictEqual(activeContextNextSpy.mock.callCount(), 0, "activeContext$.next should not have been called.");
138
+ activeContextNextSpy.mock.restore();
139
+ consoleLogMock.mock.restore();
140
+ });
141
+ it("should not log or call next if context is set to the same value (debug mode off)", () => {
142
+ keyManager.setContext("sameCtxNoDebug"); // Set initial context
143
+ keyManager.setDebugMode(false);
144
+ // Ensure debug is off
145
+ const consoleLogMock = mock.method(console, "log");
146
+ // @ts-ignore: Accessing private member for test
147
+ const activeContextNextSpy = mock.method(keyManager["activeContext$"], "next");
148
+ keyManager.setContext("sameCtxNoDebug"); // Attempt to set the same context
149
+ assert.strictEqual(consoleLogMock.mock.callCount(), 0, "Console.log should not have been called with debug mode off.");
150
+ assert.strictEqual(activeContextNextSpy.mock.callCount(), 0, "activeContext$.next should not have been called.");
151
+ assert.strictEqual(keyManager.getContext(), "sameCtxNoDebug");
152
+ activeContextNextSpy.mock.restore();
153
+ consoleLogMock.mock.restore();
154
+ });
155
+ it("should toggle debug mode and log its state", () => {
156
+ const consoleLogMock = mock.method(console, "log");
157
+ keyManager.setDebugMode(true);
158
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes("Debug mode enabled")));
95
159
  consoleLogMock.mock.resetCalls();
96
160
  keyManager.setDebugMode(false);
97
- keyManager.setContext("no_debug_test");
98
- assert.ok(!consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed to "no_debug_test"`)));
161
+ console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>", consoleLogMock.mock.calls);
162
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes("Debug mode disabled")));
99
163
  consoleLogMock.mock.restore();
100
164
  });
165
+ describe("onContextChange$ observable", () => {
166
+ it("should emit initial context to new subscriber", () => {
167
+ const manager = new Hotkeys("initial", false);
168
+ const spy = createMockFn();
169
+ const sub = manager.onContextChange$.subscribe(spy);
170
+ assert.strictEqual(spy.calledCount, 1);
171
+ assert.strictEqual(spy.lastArgs[0], "initial");
172
+ sub.unsubscribe();
173
+ manager.destroy();
174
+ });
175
+ it("should emit when context changes", () => {
176
+ const spy = createMockFn();
177
+ const sub = keyManager.onContextChange$.subscribe(spy); // Subscribes, gets initial null
178
+ spy.mockClear(); // Clear initial emission
179
+ keyManager.setContext("newContext");
180
+ assert.strictEqual(spy.calledCount, 1);
181
+ assert.strictEqual(spy.lastArgs[0], "newContext");
182
+ keyManager.setContext("anotherContext");
183
+ assert.strictEqual(spy.calledCount, 2);
184
+ assert.strictEqual(spy.lastArgs[0], "anotherContext");
185
+ keyManager.setContext(null);
186
+ assert.strictEqual(spy.calledCount, 3);
187
+ assert.strictEqual(spy.lastArgs[0], null);
188
+ sub.unsubscribe();
189
+ });
190
+ it("should not emit if context is set to the same value", () => {
191
+ keyManager.setContext("testContext");
192
+ const spy = createMockFn();
193
+ const sub = keyManager.onContextChange$.subscribe(spy); // Subscribes, gets "testContext"
194
+ spy.mockClear(); // Clear initial emission
195
+ keyManager.setContext("testContext"); // Set same context
196
+ assert.strictEqual(spy.calledCount, 0, "Observable should not emit if context value is the same.");
197
+ sub.unsubscribe();
198
+ });
199
+ });
101
200
  });
102
201
  describe("addCombination", () => {
103
202
  it("should trigger callback for a simple key combination (e.g., 'A')", () => {
@@ -241,6 +340,18 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
241
340
  consoleLogMock.mock.restore();
242
341
  keyManager.setDebugMode(false);
243
342
  });
343
+ it("should trigger callback for Keys.Space using shorthand", () => {
344
+ const config = { id: "spaceShorthand", keys: Keys.Space, callback: mockCallback };
345
+ keyManager.addCombination(config);
346
+ dispatchKeyEvent(" "); // Event key for space is " "
347
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for Keys.Space (shorthand) not called");
348
+ });
349
+ it("should trigger callback for Keys.Space using object form", () => {
350
+ const config = { id: "spaceObject", keys: { key: Keys.Space }, callback: mockCallback };
351
+ keyManager.addCombination(config);
352
+ dispatchKeyEvent(" ");
353
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for Keys.Space (object form) not called");
354
+ });
244
355
  });
245
356
  });
246
357
  describe("addSequence", () => {
@@ -303,6 +414,29 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
303
414
  assert.strictEqual(consoleErrorMock.mock.calls.length, 1);
304
415
  assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes(`Error in user callback for sequence shortcut "errorSeq"`));
305
416
  });
417
+ it("should trigger callback for a sequence including Keys.Space", () => {
418
+ const config = {
419
+ id: "seqWithSpace",
420
+ sequence: [Keys.G, Keys.Space, Keys.I],
421
+ callback: mockCallback
422
+ };
423
+ keyManager.addSequence(config);
424
+ dispatchKeyEvent(Keys.G);
425
+ dispatchKeyEvent(Keys.Space); // Dispatch " " for space
426
+ dispatchKeyEvent(Keys.I);
427
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for sequence with Keys.Space not called");
428
+ });
429
+ it("should trigger callback for a sequence starting or ending with Keys.Space", () => {
430
+ const config = {
431
+ id: "seqStartSpace",
432
+ sequence: [Keys.Space, Keys.A],
433
+ callback: mockCallback
434
+ };
435
+ keyManager.addSequence(config);
436
+ dispatchKeyEvent(Keys.Space);
437
+ dispatchKeyEvent(Keys.A);
438
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for sequence starting with Keys.Space not called");
439
+ });
306
440
  describe("Sequence Contextual Triggering", () => {
307
441
  let editorSequenceConfig;
308
442
  beforeEach(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rx-hotkeys",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsc",