@f5-sales-demo/pi-tui 21.24.4 → 21.25.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-tui",
4
- "version": "21.24.4",
4
+ "version": "21.25.0",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@f5-sales-demo/pi-natives": "21.24.4",
41
- "@f5-sales-demo/pi-utils": "21.24.4",
40
+ "@f5-sales-demo/pi-natives": "21.25.0",
41
+ "@f5-sales-demo/pi-utils": "21.25.0",
42
42
  "marked": "18.0.11"
43
43
  },
44
44
  "devDependencies": {
@@ -1,22 +1,23 @@
1
- import { getKeybindings } from "../keybindings";
1
+ import { getKeybindings, type Keybinding } from "../keybindings";
2
+ import { matchesKey } from "../keys";
2
3
  import { Loader } from "./loader";
3
4
 
4
5
  /**
5
- * Loader that can be cancelled with Escape.
6
+ * Loader that requests interruption with the application's interrupt binding (Ctrl+C by default).
6
7
  * Extends Loader with an AbortSignal for cancelling async operations.
7
8
  *
8
9
  * @example
9
10
  * const loader = new CancellableLoader(tui, cyan, dim, "Working...");
10
- * loader.onAbort = () => done(null);
11
+ * // Keep tracking work until it acknowledges loader.signal; onAbort is only a request.
11
12
  * doWork(loader.signal).then(done);
12
13
  */
13
14
  export class CancellableLoader extends Loader {
14
15
  #abortController = new AbortController();
15
16
 
16
- /** Called when user presses Escape */
17
+ /** Called once when interruption is requested, not when work has necessarily stopped. */
17
18
  onAbort?: () => void;
18
19
 
19
- /** AbortSignal that is aborted when user presses Escape */
20
+ /** AbortSignal that is aborted when interruption is requested. */
20
21
  get signal(): AbortSignal {
21
22
  return this.#abortController.signal;
22
23
  }
@@ -28,7 +29,10 @@ export class CancellableLoader extends Loader {
28
29
 
29
30
  handleInput(data: string): void {
30
31
  const kb = getKeybindings();
31
- if (kb.matches(data, "tui.select.cancel")) {
32
+ // The optional application binding is supplied by downstream registry augmentation.
33
+ const interrupt = "app.interrupt" as Keybinding;
34
+ const requested = kb.getDefinition(interrupt) ? kb.matches(data, interrupt) : matchesKey(data, "ctrl+c");
35
+ if (requested && !this.aborted) {
32
36
  this.#abortController.abort();
33
37
  this.onAbort?.();
34
38
  }
@@ -139,7 +139,7 @@ export const TUI_KEYBINDINGS = {
139
139
  },
140
140
  "tui.select.confirm": { defaultKeys: "enter", description: "Confirm selection" },
141
141
  "tui.select.cancel": {
142
- defaultKeys: ["escape", "ctrl+c"],
142
+ defaultKeys: "escape",
143
143
  description: "Cancel selection",
144
144
  },
145
145
  } as const satisfies KeybindingDefinitions;
@@ -211,6 +211,17 @@ function extractCompleteSequences(buffer: string): { sequences: string[]; remain
211
211
 
212
212
  // Try to extract a sequence starting at this position
213
213
  if (remaining.startsWith(ESC)) {
214
+ // Two adjacent ESC introducers are two independent sequences. This occurs
215
+ // when a user's Escape key races a terminal capability response such as
216
+ // DA1 (`ESC ESC[?1;2c`). Treating the pair as one Meta-Escape event strips
217
+ // the response introducer and leaks `[?1;2c` into the focused editor.
218
+ // Emitting the first Escape independently lets the next pass retain the
219
+ // complete response for the terminal-level filter.
220
+ if (remaining.startsWith(`${ESC}${ESC}`)) {
221
+ sequences.push(ESC);
222
+ pos++;
223
+ continue;
224
+ }
214
225
  // Find the end of this escape sequence
215
226
  let seqEnd = 1;
216
227
  while (seqEnd <= remaining.length) {