rx-hotkeys 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -128,7 +128,7 @@ Registers a key sequence shortcut.
128
128
  * `config`: The KeySequenceConfig object.
129
129
  * Returns the shortcut ID if successful, undefined otherwise.
130
130
 
131
- `setContext(contextName: string | null): void`
131
+ `setContext(contextName: string | null): boolean`
132
132
 
133
133
  Sets the active context. Only shortcuts matching this context or global shortcuts (no context) will trigger.
134
134
 
package/dist/hotkeys.d.ts CHANGED
@@ -84,8 +84,9 @@ export declare class Hotkeys {
84
84
  * will be active and can be triggered.
85
85
  * @param contextName - The name of the context (e.g., "modal", "editor", "global").
86
86
  * Pass `null` to activate shortcuts with no context or to deactivate context-specific shortcuts.
87
+ * @returns boolean
87
88
  */
88
- setContext(contextName: string | null): void;
89
+ setContext(contextName: string | null): boolean;
89
90
  /**
90
91
  * Gets the current active context.
91
92
  * @returns The current context name as a string, or `null` if no context is set.
@@ -1 +1 @@
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;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;;;;;;;OAOG;IACI,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO;IAkBtD;;;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
@@ -63,21 +63,23 @@ export class Hotkeys {
63
63
  * will be active and can be triggered.
64
64
  * @param contextName - The name of the context (e.g., "modal", "editor", "global").
65
65
  * Pass `null` to activate shortcuts with no context or to deactivate context-specific shortcuts.
66
+ * @returns boolean
66
67
  */
67
68
  setContext(contextName) {
68
- if (this.activeContext$.getValue() === contextName) {
69
+ const currentContext = this.activeContext$.getValue();
70
+ if (currentContext === contextName) {
69
71
  if (this.debugMode) {
70
72
  // Optional: Log that no change is happening, or simply do nothing.
71
73
  console.log(`${Hotkeys.LOG_PREFIX} setContext called with the same context "${contextName}". No change made.`);
72
74
  }
73
- return; // Context is the same, so no further action is needed.
75
+ return false; // Context is the same, so no further action is needed.
74
76
  }
75
77
  // If we reach here, the context is actually changing.
76
- const oldContext = this.activeContext$.getValue(); // For more informative logging
77
78
  if (this.debugMode) {
78
- console.log(`${Hotkeys.LOG_PREFIX} Context changed from "${oldContext}" to "${contextName}".`);
79
+ console.log(`${Hotkeys.LOG_PREFIX} Context changed from "${currentContext}" to "${contextName}".`);
79
80
  }
80
81
  this.activeContext$.next(contextName);
82
+ return true;
81
83
  }
82
84
  /**
83
85
  * Gets the current active context.
@@ -184,8 +186,9 @@ export class Hotkeys {
184
186
  let keyDetailsForLog;
185
187
  if (typeof keys === "string") {
186
188
  // Shorthand: keys is StandardKey, implying no modifiers should be active
187
- if (keys.trim() === "") {
188
- console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys" (shorthand) for combination shortcut "${id}". Key string must not be empty. Shortcut not added.`);
189
+ // Corrected validation: Check for actual empty string, not a string that trims to empty.
190
+ if (keys === "") { // StandardKey type should prevent this, but check for robustness.
191
+ console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys" (shorthand) for combination shortcut "${id}". Key must not be an empty string. Shortcut not added.`);
189
192
  return undefined;
190
193
  }
191
194
  configuredMainKey = keys;
@@ -197,8 +200,9 @@ export class Hotkeys {
197
200
  }
198
201
  else {
199
202
  // Object form
200
- if (!keys || !keys.key || typeof keys.key !== "string" || keys.key.trim() === "") {
201
- console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys.key" for combination shortcut "${id}". Key must be a non-empty value from Keys. Shortcut not added.`);
203
+ // Corrected validation for keys.key
204
+ if (!keys || !keys.key || typeof keys.key !== "string" || keys.key === "") { // StandardKey type should prevent empty string for key.key
205
+ 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.`);
202
206
  return undefined;
203
207
  }
204
208
  configuredMainKey = keys.key;
@@ -270,8 +274,9 @@ export class Hotkeys {
270
274
  console.warn(`${Hotkeys.LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
271
275
  return undefined;
272
276
  }
273
- if (sequence.some(key => typeof key !== "string" || key.trim() === "")) {
274
- console.warn(`${Hotkeys.LOG_PREFIX} Invalid key in sequence for shortcut "${id}". All keys must be non-empty strings from Keys. Shortcut not added.`);
277
+ // Corrected validation: Check for actual empty string, not a string that trims to empty.
278
+ if (sequence.some(key => typeof key !== "string" || key === "")) { // StandardKey type should prevent empty strings.
279
+ 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.`);
275
280
  return undefined;
276
281
  }
277
282
  const configuredSequence = sequence;
@@ -152,16 +152,59 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
152
152
  activeContextNextSpy.mock.restore();
153
153
  consoleLogMock.mock.restore();
154
154
  });
155
+ it(`should log "no change" if context is set to the same value (debug mode on)`, () => {
156
+ keyManager.setContext("sameCtx"); // Set initial context
157
+ const consoleLogMock = mock.method(console, "log");
158
+ keyManager.setDebugMode(true);
159
+ consoleLogMock.mock.resetCalls(); // Clear previous logs
160
+ keyManager.setContext("sameCtx"); // Attempt to set the same context
161
+ const noChangeLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`setContext called with the same context "sameCtx". No change made.`));
162
+ assert.ok(noChangeLogCall, "No-change log not found or incorrect for same context.");
163
+ const changedLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes("Context changed from"));
164
+ assert.strictEqual(changedLogCall, undefined, "Context changed log should not appear for same context.");
165
+ consoleLogMock.mock.restore();
166
+ });
155
167
  it("should toggle debug mode and log its state", () => {
156
168
  const consoleLogMock = mock.method(console, "log");
157
169
  keyManager.setDebugMode(true);
158
170
  assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes("Debug mode enabled")));
159
171
  consoleLogMock.mock.resetCalls();
160
172
  keyManager.setDebugMode(false);
161
- console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>", consoleLogMock.mock.calls);
162
173
  assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes("Debug mode disabled")));
163
174
  consoleLogMock.mock.restore();
164
175
  });
176
+ describe("setContext return value", () => {
177
+ it("should return true when context is changed from null to a new value", () => {
178
+ assert.strictEqual(keyManager.getContext(), null, "Initial context should be null");
179
+ const result = keyManager.setContext("newContext");
180
+ assert.strictEqual(result, true, "setContext should return true for a change from null");
181
+ assert.strictEqual(keyManager.getContext(), "newContext");
182
+ });
183
+ it("should return true when context is changed from one value to another", () => {
184
+ keyManager.setContext("initialContext"); // Set a non-null initial context
185
+ const result = keyManager.setContext("changedContext");
186
+ assert.strictEqual(result, true, "setContext should return true for a value-to-value change");
187
+ assert.strictEqual(keyManager.getContext(), "changedContext");
188
+ });
189
+ it("should return false when context is set to the same value (non-null)", () => {
190
+ keyManager.setContext("sameContext");
191
+ const result = keyManager.setContext("sameContext");
192
+ assert.strictEqual(result, false, "setContext should return false for no change (non-null)");
193
+ assert.strictEqual(keyManager.getContext(), "sameContext");
194
+ });
195
+ it("should return true when context is changed from a value to null", () => {
196
+ keyManager.setContext("initialContext");
197
+ const result = keyManager.setContext(null);
198
+ assert.strictEqual(result, true, "setContext should return true for a change to null");
199
+ assert.strictEqual(keyManager.getContext(), null);
200
+ });
201
+ it("should return false when context is set to null and already null", () => {
202
+ keyManager.setContext(null); // Ensure context is null
203
+ const result = keyManager.setContext(null);
204
+ assert.strictEqual(result, false, "setContext should return false for no change (already null)");
205
+ assert.strictEqual(keyManager.getContext(), null);
206
+ });
207
+ });
165
208
  describe("onContextChange$ observable", () => {
166
209
  it("should emit initial context to new subscriber", () => {
167
210
  const manager = new Hotkeys("initial", false);
@@ -176,14 +219,14 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
176
219
  const spy = createMockFn();
177
220
  const sub = keyManager.onContextChange$.subscribe(spy); // Subscribes, gets initial null
178
221
  spy.mockClear(); // Clear initial emission
179
- keyManager.setContext("newContext");
180
- assert.strictEqual(spy.calledCount, 1);
222
+ assert.ok(keyManager.setContext("newContext"), "setContext should have returned true for change");
223
+ assert.strictEqual(spy.calledCount, 1, "onContextChange$ did not emit on first change");
181
224
  assert.strictEqual(spy.lastArgs[0], "newContext");
182
- keyManager.setContext("anotherContext");
183
- assert.strictEqual(spy.calledCount, 2);
225
+ assert.ok(keyManager.setContext("anotherContext"), "setContext should have returned true for second change");
226
+ assert.strictEqual(spy.calledCount, 2, "onContextChange$ did not emit on second change");
184
227
  assert.strictEqual(spy.lastArgs[0], "anotherContext");
185
- keyManager.setContext(null);
186
- assert.strictEqual(spy.calledCount, 3);
228
+ assert.ok(keyManager.setContext(null), "setContext should have returned true for change to null");
229
+ assert.strictEqual(spy.calledCount, 3, "onContextChange$ did not emit on change to null");
187
230
  assert.strictEqual(spy.lastArgs[0], null);
188
231
  sub.unsubscribe();
189
232
  });
@@ -192,7 +235,7 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
192
235
  const spy = createMockFn();
193
236
  const sub = keyManager.onContextChange$.subscribe(spy); // Subscribes, gets "testContext"
194
237
  spy.mockClear(); // Clear initial emission
195
- keyManager.setContext("testContext"); // Set same context
238
+ assert.strictEqual(keyManager.setContext("testContext"), false, "setContext should have returned false for no change"); // Set same context
196
239
  assert.strictEqual(spy.calledCount, 0, "Observable should not emit if context value is the same.");
197
240
  sub.unsubscribe();
198
241
  });
@@ -340,6 +383,18 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
340
383
  consoleLogMock.mock.restore();
341
384
  keyManager.setDebugMode(false);
342
385
  });
386
+ it("should trigger callback for Keys.Space using shorthand", () => {
387
+ const config = { id: "spaceShorthand", keys: Keys.Space, callback: mockCallback };
388
+ keyManager.addCombination(config);
389
+ dispatchKeyEvent(" "); // Event key for space is " "
390
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for Keys.Space (shorthand) not called");
391
+ });
392
+ it("should trigger callback for Keys.Space using object form", () => {
393
+ const config = { id: "spaceObject", keys: { key: Keys.Space }, callback: mockCallback };
394
+ keyManager.addCombination(config);
395
+ dispatchKeyEvent(" ");
396
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for Keys.Space (object form) not called");
397
+ });
343
398
  });
344
399
  });
345
400
  describe("addSequence", () => {
@@ -402,6 +457,29 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
402
457
  assert.strictEqual(consoleErrorMock.mock.calls.length, 1);
403
458
  assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes(`Error in user callback for sequence shortcut "errorSeq"`));
404
459
  });
460
+ it("should trigger callback for a sequence including Keys.Space", () => {
461
+ const config = {
462
+ id: "seqWithSpace",
463
+ sequence: [Keys.G, Keys.Space, Keys.I],
464
+ callback: mockCallback
465
+ };
466
+ keyManager.addSequence(config);
467
+ dispatchKeyEvent(Keys.G);
468
+ dispatchKeyEvent(Keys.Space); // Dispatch " " for space
469
+ dispatchKeyEvent(Keys.I);
470
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for sequence with Keys.Space not called");
471
+ });
472
+ it("should trigger callback for a sequence starting or ending with Keys.Space", () => {
473
+ const config = {
474
+ id: "seqStartSpace",
475
+ sequence: [Keys.Space, Keys.A],
476
+ callback: mockCallback
477
+ };
478
+ keyManager.addSequence(config);
479
+ dispatchKeyEvent(Keys.Space);
480
+ dispatchKeyEvent(Keys.A);
481
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for sequence starting with Keys.Space not called");
482
+ });
405
483
  describe("Sequence Contextual Triggering", () => {
406
484
  let editorSequenceConfig;
407
485
  beforeEach(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rx-hotkeys",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsc",