@f5-sales-demo/pi-tui 19.51.3 → 19.52.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": "19.51.3",
4
+ "version": "19.52.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": "19.51.3",
41
- "@f5-sales-demo/pi-utils": "19.51.3",
40
+ "@f5-sales-demo/pi-natives": "19.52.0",
41
+ "@f5-sales-demo/pi-utils": "19.52.0",
42
42
  "marked": "^17.0"
43
43
  },
44
44
  "devDependencies": {
@@ -16,6 +16,9 @@ import {
16
16
 
17
17
  const segmenter = getSegmenter();
18
18
 
19
+ /** Glyph shown in place of each grapheme when an Input is masked (tokens/passwords). */
20
+ const MASK_CHAR = "•";
21
+
19
22
  interface InputState {
20
23
  value: string;
21
24
  cursor: number;
@@ -27,6 +30,7 @@ interface InputState {
27
30
  export class Input implements Component, Focusable {
28
31
  #value: string = "";
29
32
  #cursor: number = 0; // Cursor position in the value
33
+ #masked: boolean = false; // When true, render obscures each grapheme with MASK_CHAR
30
34
  onSubmit?: (value: string) => void;
31
35
  onEscape?: () => void;
32
36
 
@@ -52,6 +56,31 @@ export class Input implements Component, Focusable {
52
56
  this.#cursor = Math.min(this.#cursor, value.length);
53
57
  }
54
58
 
59
+ /**
60
+ * Obscure the rendered text — for secret entry such as API tokens and
61
+ * passwords. `getValue()` still returns the real text; only the on-screen
62
+ * display (and horizontal-scroll/cursor math) operate on the masked form.
63
+ */
64
+ setMasked(masked: boolean): void {
65
+ this.#masked = masked;
66
+ }
67
+
68
+ /** The string to render: the real value, or one MASK_CHAR per grapheme when masked. */
69
+ #displayValue(): string {
70
+ if (!this.#masked) return this.#value;
71
+ let count = 0;
72
+ for (const _seg of segmenter.segment(this.#value)) count++;
73
+ return MASK_CHAR.repeat(count);
74
+ }
75
+
76
+ /** Cursor offset within #displayValue(): grapheme count before the real cursor when masked. */
77
+ #displayCursor(): number {
78
+ if (!this.#masked) return this.#cursor;
79
+ let count = 0;
80
+ for (const _seg of segmenter.segment(this.#value.slice(0, this.#cursor))) count++;
81
+ return count;
82
+ }
83
+
55
84
  handleInput(data: string): void {
56
85
  // Handle bracketed paste mode
57
86
  const paste = this.#pasteHandler.process(data);
@@ -378,9 +407,10 @@ export class Input implements Component, Focusable {
378
407
  return [prompt];
379
408
  }
380
409
 
381
- const cursorIndex = this.#cursor;
410
+ const value = this.#displayValue();
411
+ const cursorIndex = this.#displayCursor();
382
412
  // Ensure we always have a grapheme to invert at the cursor (space at end).
383
- const displayValue = cursorIndex >= this.#value.length ? `${this.#value} ` : this.#value;
413
+ const displayValue = cursorIndex >= value.length ? `${value} ` : value;
384
414
 
385
415
  const totalCols = visibleWidth(displayValue);
386
416
  const cursorCols = visibleWidth(displayValue.slice(0, cursorIndex));