rx-hotkeys 2.1.1 → 2.2.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/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;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
@@ -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.
@@ -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);
95
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
96
143
  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"`)));
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();
99
153
  consoleLogMock.mock.restore();
100
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")));
159
+ consoleLogMock.mock.resetCalls();
160
+ keyManager.setDebugMode(false);
161
+ console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>", consoleLogMock.mock.calls);
162
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes("Debug mode disabled")));
163
+ consoleLogMock.mock.restore();
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')", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rx-hotkeys",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsc",