@mariozechner/pi-tui 0.5.48 → 0.6.2

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.
Files changed (65) hide show
  1. package/README.md +166 -475
  2. package/dist/autocomplete.d.ts.map +1 -1
  3. package/dist/autocomplete.js +2 -0
  4. package/dist/autocomplete.js.map +1 -1
  5. package/dist/components/{text-editor.d.ts → editor.d.ts} +9 -5
  6. package/dist/components/editor.d.ts.map +1 -0
  7. package/dist/components/{text-editor.js → editor.js} +125 -70
  8. package/dist/components/editor.js.map +1 -0
  9. package/dist/components/input.d.ts +14 -0
  10. package/dist/components/input.d.ts.map +1 -0
  11. package/dist/components/input.js +120 -0
  12. package/dist/components/input.js.map +1 -0
  13. package/dist/components/{loading-animation.d.ts → loader.d.ts} +5 -5
  14. package/dist/components/loader.d.ts.map +1 -0
  15. package/dist/components/{loading-animation.js → loader.js} +13 -10
  16. package/dist/components/loader.js.map +1 -0
  17. package/dist/components/markdown.d.ts +46 -0
  18. package/dist/components/markdown.d.ts.map +1 -0
  19. package/dist/components/markdown.js +499 -0
  20. package/dist/components/markdown.js.map +1 -0
  21. package/dist/components/select-list.d.ts +3 -3
  22. package/dist/components/select-list.d.ts.map +1 -1
  23. package/dist/components/select-list.js +24 -16
  24. package/dist/components/select-list.js.map +1 -1
  25. package/dist/components/spacer.d.ts +11 -0
  26. package/dist/components/spacer.d.ts.map +1 -0
  27. package/dist/components/spacer.js +20 -0
  28. package/dist/components/spacer.js.map +1 -0
  29. package/dist/components/text.d.ts +26 -0
  30. package/dist/components/text.d.ts.map +1 -0
  31. package/dist/components/text.js +141 -0
  32. package/dist/components/text.js.map +1 -0
  33. package/dist/index.d.ts +8 -6
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +10 -12
  36. package/dist/index.js.map +1 -1
  37. package/dist/terminal.d.ts +12 -0
  38. package/dist/terminal.d.ts.map +1 -1
  39. package/dist/terminal.js +33 -3
  40. package/dist/terminal.js.map +1 -1
  41. package/dist/tui.d.ts +30 -52
  42. package/dist/tui.d.ts.map +1 -1
  43. package/dist/tui.js +131 -337
  44. package/dist/tui.js.map +1 -1
  45. package/dist/utils.d.ts +10 -0
  46. package/dist/utils.d.ts.map +1 -0
  47. package/dist/utils.js +15 -0
  48. package/dist/utils.js.map +1 -0
  49. package/package.json +6 -5
  50. package/dist/components/loading-animation.d.ts.map +0 -1
  51. package/dist/components/loading-animation.js.map +0 -1
  52. package/dist/components/markdown-component.d.ts +0 -15
  53. package/dist/components/markdown-component.d.ts.map +0 -1
  54. package/dist/components/markdown-component.js +0 -247
  55. package/dist/components/markdown-component.js.map +0 -1
  56. package/dist/components/text-component.d.ts +0 -14
  57. package/dist/components/text-component.d.ts.map +0 -1
  58. package/dist/components/text-component.js +0 -90
  59. package/dist/components/text-component.js.map +0 -1
  60. package/dist/components/text-editor.d.ts.map +0 -1
  61. package/dist/components/text-editor.js.map +0 -1
  62. package/dist/components/whitespace-component.d.ts +0 -13
  63. package/dist/components/whitespace-component.d.ts.map +0 -1
  64. package/dist/components/whitespace-component.js +0 -22
  65. package/dist/components/whitespace-component.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,uBAAuB;AACvB,OAAO,EAGN,4BAA4B,GAE5B,MAAM,mBAAmB,CAAC;AAC3B,8BAA8B;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,wBAAwB;AACxB,OAAO,EAAmB,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC1E,iBAAiB;AACjB,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,wBAAwB;AACxB,OAAO,EAAE,UAAU,EAAyB,MAAM,6BAA6B,CAAC;AAChF,uBAAuB;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,yCAAyC;AACzC,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAC;AAC/D,OAAO,EAGN,SAAS,EACT,kBAAkB,EAElB,GAAG,GACH,MAAM,UAAU,CAAC","sourcesContent":["// Core TUI interfaces and classes\n\n// Autocomplete support\nexport {\n\ttype AutocompleteItem,\n\ttype AutocompleteProvider,\n\tCombinedAutocompleteProvider,\n\ttype SlashCommand,\n} from \"./autocomplete.js\";\n// Loading animation component\nexport { LoadingAnimation } from \"./components/loading-animation.js\";\n// Markdown component\nexport { MarkdownComponent } from \"./components/markdown-component.js\";\n// Select list component\nexport { type SelectItem, SelectList } from \"./components/select-list.js\";\n// Text component\nexport { TextComponent } from \"./components/text-component.js\";\n// Text editor component\nexport { TextEditor, type TextEditorConfig } from \"./components/text-editor.js\";\n// Whitespace component\nexport { WhitespaceComponent } from \"./components/whitespace-component.js\";\n// Terminal interface and implementations\nexport { ProcessTerminal, type Terminal } from \"./terminal.js\";\nexport {\n\ttype Component,\n\ttype ComponentRenderResult,\n\tContainer,\n\tgetNextComponentId,\n\ttype Padding,\n\tTUI,\n} from \"./tui.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,uBAAuB;AACvB,OAAO,EAGN,4BAA4B,GAE5B,MAAM,mBAAmB,CAAC;AAC3B,aAAa;AACb,OAAO,EAAE,MAAM,EAAyB,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAmB,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,yCAAyC;AACzC,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAkB,SAAS,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY;AACZ,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Core TUI interfaces and classes\n\n// Autocomplete support\nexport {\n\ttype AutocompleteItem,\n\ttype AutocompleteProvider,\n\tCombinedAutocompleteProvider,\n\ttype SlashCommand,\n} from \"./autocomplete.js\";\n// Components\nexport { Editor, type TextEditorConfig } from \"./components/editor.js\";\nexport { Input } from \"./components/input.js\";\nexport { Loader } from \"./components/loader.js\";\nexport { Markdown } from \"./components/markdown.js\";\nexport { type SelectItem, SelectList } from \"./components/select-list.js\";\nexport { Spacer } from \"./components/spacer.js\";\nexport { Text } from \"./components/text.js\";\n// Terminal interface and implementations\nexport { ProcessTerminal, type Terminal } from \"./terminal.js\";\nexport { type Component, Container, TUI } from \"./tui.js\";\n// Utilities\nexport { visibleWidth } from \"./utils.js\";\n"]}
@@ -7,6 +7,12 @@ export interface Terminal {
7
7
  write(data: string): void;
8
8
  get columns(): number;
9
9
  get rows(): number;
10
+ moveBy(lines: number): void;
11
+ hideCursor(): void;
12
+ showCursor(): void;
13
+ clearLine(): void;
14
+ clearFromCursor(): void;
15
+ clearScreen(): void;
10
16
  }
11
17
  /**
12
18
  * Real terminal using process.stdin/stdout
@@ -20,5 +26,11 @@ export declare class ProcessTerminal implements Terminal {
20
26
  write(data: string): void;
21
27
  get columns(): number;
22
28
  get rows(): number;
29
+ moveBy(lines: number): void;
30
+ hideCursor(): void;
31
+ showCursor(): void;
32
+ clearLine(): void;
33
+ clearFromCursor(): void;
34
+ clearScreen(): void;
23
35
  }
24
36
  //# sourceMappingURL=terminal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IAExB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAGnE,IAAI,IAAI,IAAI,CAAC;IAGb,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,IAAI,OAAO,IAAI,MAAM,CAAC;IACtB,IAAI,IAAI,IAAI,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAa;IAEnC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAiBlE,IAAI,IAAI,IAAI;IAiBZ,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzB,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;CACD"}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IAExB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAGnE,IAAI,IAAI,IAAI,CAAC;IAGb,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,IAAI,OAAO,IAAI,MAAM,CAAC;IACtB,IAAI,IAAI,IAAI,MAAM,CAAC;IAGnB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG5B,UAAU,IAAI,IAAI,CAAC;IACnB,UAAU,IAAI,IAAI,CAAC;IAGnB,SAAS,IAAI,IAAI,CAAC;IAClB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,IAAI,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAa;IAEnC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAkBjE;IAED,IAAI,IAAI,IAAI,CAkBX;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAS1B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,SAAS,IAAI,IAAI,CAEhB;IAED,eAAe,IAAI,IAAI,CAEtB;IAED,WAAW,IAAI,IAAI,CAElB;CACD","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n}\n"]}
package/dist/terminal.js CHANGED
@@ -2,9 +2,9 @@
2
2
  * Real terminal using process.stdin/stdout
3
3
  */
4
4
  export class ProcessTerminal {
5
- constructor() {
6
- this.wasRaw = false;
7
- }
5
+ wasRaw = false;
6
+ inputHandler;
7
+ resizeHandler;
8
8
  start(onInput, onResize) {
9
9
  this.inputHandler = onInput;
10
10
  this.resizeHandler = onResize;
@@ -15,11 +15,15 @@ export class ProcessTerminal {
15
15
  }
16
16
  process.stdin.setEncoding("utf8");
17
17
  process.stdin.resume();
18
+ // Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
19
+ process.stdout.write("\x1b[?2004h");
18
20
  // Set up event handlers
19
21
  process.stdin.on("data", this.inputHandler);
20
22
  process.stdout.on("resize", this.resizeHandler);
21
23
  }
22
24
  stop() {
25
+ // Disable bracketed paste mode
26
+ process.stdout.write("\x1b[?2004l");
23
27
  // Remove event handlers
24
28
  if (this.inputHandler) {
25
29
  process.stdin.removeListener("data", this.inputHandler);
@@ -43,5 +47,31 @@ export class ProcessTerminal {
43
47
  get rows() {
44
48
  return process.stdout.rows || 24;
45
49
  }
50
+ moveBy(lines) {
51
+ if (lines > 0) {
52
+ // Move down
53
+ process.stdout.write(`\x1b[${lines}B`);
54
+ }
55
+ else if (lines < 0) {
56
+ // Move up
57
+ process.stdout.write(`\x1b[${-lines}A`);
58
+ }
59
+ // lines === 0: no movement
60
+ }
61
+ hideCursor() {
62
+ process.stdout.write("\x1b[?25l");
63
+ }
64
+ showCursor() {
65
+ process.stdout.write("\x1b[?25h");
66
+ }
67
+ clearLine() {
68
+ process.stdout.write("\x1b[K");
69
+ }
70
+ clearFromCursor() {
71
+ process.stdout.write("\x1b[J");
72
+ }
73
+ clearScreen() {
74
+ process.stdout.write("\x1b[2J\x1b[H"); // Clear screen and move to home (1,1)
75
+ }
46
76
  }
47
77
  //# sourceMappingURL=terminal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,MAAM,OAAO,eAAe;IAA5B;QACS,WAAM,GAAG,KAAK,CAAC;IAiDxB,CAAC;IA7CA,KAAK,CAAC,OAA+B,EAAE,QAAoB;QAC1D,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;QAE9B,0CAA0C;QAC1C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC;QAC3C,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAEvB,wBAAwB;QACxB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5C,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IACjD,CAAC;IAED,IAAI;QACH,wBAAwB;QACxB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YACxD,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC/B,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5D,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAChC,CAAC;QAED,yBAAyB;QACzB,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;IACF,CAAC;IAED,KAAK,CAAC,IAAY;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,OAAO;QACV,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IACrC,CAAC;IAED,IAAI,IAAI;QACP,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;IAClC,CAAC;CACD","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n}\n"]}
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n}\n"]}
package/dist/tui.d.ts CHANGED
@@ -1,72 +1,50 @@
1
- import { type Terminal } from "./terminal.js";
2
1
  /**
3
- * Result of rendering a component
2
+ * Minimal TUI implementation with differential rendering
4
3
  */
5
- export interface ComponentRenderResult {
6
- lines: string[];
7
- changed: boolean;
8
- }
4
+ import type { Terminal } from "./terminal.js";
5
+ import { visibleWidth } from "./utils.js";
9
6
  /**
10
- * Component interface
7
+ * Component interface - all components must implement this
11
8
  */
12
9
  export interface Component {
13
- readonly id: number;
14
- render(width: number): ComponentRenderResult;
15
- handleInput?(keyData: string): void;
16
- }
17
- export declare function getNextComponentId(): number;
18
- export interface Padding {
19
- top?: number;
20
- bottom?: number;
21
- left?: number;
22
- right?: number;
10
+ /**
11
+ * Render the component to lines for the given viewport width
12
+ * @param width - Current viewport width
13
+ * @returns Array of strings, each representing a line
14
+ */
15
+ render(width: number): string[];
16
+ /**
17
+ * Optional handler for keyboard input when component has focus
18
+ */
19
+ handleInput?(data: string): void;
23
20
  }
21
+ export { visibleWidth };
24
22
  /**
25
- * Container for managing child components
23
+ * Container - a component that contains other components
26
24
  */
27
25
  export declare class Container implements Component {
28
- readonly id: number;
29
- children: (Component | Container)[];
30
- private tui?;
31
- private previousChildCount;
32
- constructor();
33
- setTui(tui: TUI | undefined): void;
34
- addChild(component: Component | Container): void;
35
- removeChild(component: Component | Container): void;
36
- removeChildAt(index: number): void;
26
+ children: Component[];
27
+ addChild(component: Component): void;
28
+ removeChild(component: Component): void;
37
29
  clear(): void;
38
- getChild(index: number): (Component | Container) | undefined;
39
- getChildCount(): number;
40
- render(width: number): ComponentRenderResult;
30
+ render(width: number): string[];
41
31
  }
42
32
  /**
43
- * TUI - Smart differential rendering TUI implementation.
33
+ * TUI - Main class for managing terminal UI with differential rendering
44
34
  */
45
35
  export declare class TUI extends Container {
46
- private focusedComponent;
47
- private needsRender;
48
- private isFirstRender;
49
- private isStarted;
50
- onGlobalKeyPress?: (data: string) => boolean;
51
36
  private terminal;
52
- private previousRenderCommands;
53
37
  private previousLines;
54
- private totalLinesRedrawn;
55
- private renderCount;
56
- getLinesRedrawn(): number;
57
- getAverageLinesRedrawn(): number;
58
- constructor(terminal?: Terminal);
59
- setFocus(component: Component): void;
60
- private findComponent;
61
- private findInContainer;
62
- requestRender(): void;
38
+ private previousWidth;
39
+ private focusedComponent;
40
+ private renderRequested;
41
+ private cursorRow;
42
+ constructor(terminal: Terminal);
43
+ setFocus(component: Component | null): void;
63
44
  start(): void;
64
45
  stop(): void;
65
- private renderToScreen;
66
- private collectRenderCommands;
67
- private renderInitial;
68
- private renderLineBased;
69
- private handleResize;
70
- private handleKeypress;
46
+ requestRender(): void;
47
+ private handleInput;
48
+ private doRender;
71
49
  }
72
50
  //# sourceMappingURL=tui.d.ts.map
package/dist/tui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAAC;IAC7C,WAAW,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAMD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAGD,MAAM,WAAW,OAAO;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IAC1C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,CAAM;IAChD,OAAO,CAAC,GAAG,CAAC,CAAM;IAClB,OAAO,CAAC,kBAAkB,CAAa;;IAMvC,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,SAAS,GAAG,IAAI;IASlC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IAQhD,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IAWnD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWlC,KAAK,IAAI,IAAI;IAUb,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS;IAI5D,aAAa,IAAI,MAAM;IAIvB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB;CAoB5C;AAWD;;GAEG;AACH,qBAAa,GAAI,SAAQ,SAAS;IACjC,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAS;IACnB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACpD,OAAO,CAAC,QAAQ,CAAW;IAE3B,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,aAAa,CAAgB;IAGrC,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,WAAW,CAAK;IACjB,eAAe,IAAI,MAAM;IAGzB,sBAAsB,IAAI,MAAM;gBAI3B,QAAQ,CAAC,EAAE,QAAQ;IAU/B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAMpC,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,eAAe;IAkBvB,aAAa,IAAI,IAAI;IAerB,KAAK,IAAI,IAAI;IAmBb,IAAI,IAAI,IAAI;IAUZ,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,eAAe;IA6IvB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,cAAc;CActB"}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IAC1C,QAAQ,EAAE,SAAS,EAAE,CAAM;IAE3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAEnC;IAED,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAKtC;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAM9B;CACD;AAED;;GAEG;AACH,qBAAa,GAAI,SAAQ,SAAS;IACjC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,SAAS,CAAK;IAEtB,YAAY,QAAQ,EAAE,QAAQ,EAG7B;IAED,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAE1C;IAED,KAAK,IAAI,IAAI,CAOZ;IAED,IAAI,IAAI,IAAI,CAGX;IAED,aAAa,IAAI,IAAI,CAOpB;IAED,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;CAwHhB","sourcesContent":["/**\n * Minimal TUI implementation with differential rendering\n */\n\nimport type { Terminal } from \"./terminal.js\";\nimport { visibleWidth } from \"./utils.js\";\n\n/**\n * Component interface - all components must implement this\n */\nexport interface Component {\n\t/**\n\t * Render the component to lines for the given viewport width\n\t * @param width - Current viewport width\n\t * @returns Array of strings, each representing a line\n\t */\n\trender(width: number): string[];\n\n\t/**\n\t * Optional handler for keyboard input when component has focus\n\t */\n\thandleInput?(data: string): void;\n}\n\nexport { visibleWidth };\n\n/**\n * Container - a component that contains other components\n */\nexport class Container implements Component {\n\tchildren: Component[] = [];\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tlines.push(...child.render(width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n\n/**\n * TUI - Main class for managing terminal UI with differential rendering\n */\nexport class TUI extends Container {\n\tprivate terminal: Terminal;\n\tprivate previousLines: string[] = [];\n\tprivate previousWidth = 0;\n\tprivate focusedComponent: Component | null = null;\n\tprivate renderRequested = false;\n\tprivate cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)\n\n\tconstructor(terminal: Terminal) {\n\t\tsuper();\n\t\tthis.terminal = terminal;\n\t}\n\n\tsetFocus(component: Component | null): void {\n\t\tthis.focusedComponent = component;\n\t}\n\n\tstart(): void {\n\t\tthis.terminal.start(\n\t\t\t(data) => this.handleInput(data),\n\t\t\t() => this.requestRender(),\n\t\t);\n\t\tthis.terminal.hideCursor();\n\t\tthis.requestRender();\n\t}\n\n\tstop(): void {\n\t\tthis.terminal.showCursor();\n\t\tthis.terminal.stop();\n\t}\n\n\trequestRender(): void {\n\t\tif (this.renderRequested) return;\n\t\tthis.renderRequested = true;\n\t\tprocess.nextTick(() => {\n\t\t\tthis.renderRequested = false;\n\t\t\tthis.doRender();\n\t\t});\n\t}\n\n\tprivate handleInput(data: string): void {\n\t\t// Pass input to focused component (including Ctrl+C)\n\t\t// The focused component can decide how to handle Ctrl+C\n\t\tif (this.focusedComponent?.handleInput) {\n\t\t\tthis.focusedComponent.handleInput(data);\n\t\t\tthis.requestRender();\n\t\t}\n\t}\n\n\tprivate doRender(): void {\n\t\tconst width = this.terminal.columns;\n\t\tconst height = this.terminal.rows;\n\n\t\t// Render all components to get new lines\n\t\tconst newLines = this.render(width);\n\n\t\t// Width changed - need full re-render\n\t\tconst widthChanged = this.previousWidth !== 0 && this.previousWidth !== width;\n\n\t\t// First render - just output everything without clearing\n\t\tif (this.previousLines.length === 0) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\t// After rendering N lines, cursor is at end of last line (line N-1)\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Width changed - full re-render\n\t\tif (widthChanged) {\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find first and last changed lines\n\t\tlet firstChanged = -1;\n\t\tlet lastChanged = -1;\n\n\t\tconst maxLines = Math.max(newLines.length, this.previousLines.length);\n\t\tfor (let i = 0; i < maxLines; i++) {\n\t\t\tconst oldLine = i < this.previousLines.length ? this.previousLines[i] : \"\";\n\t\t\tconst newLine = i < newLines.length ? newLines[i] : \"\";\n\n\t\t\tif (oldLine !== newLine) {\n\t\t\t\tif (firstChanged === -1) {\n\t\t\t\t\tfirstChanged = i;\n\t\t\t\t}\n\t\t\t\tlastChanged = i;\n\t\t\t}\n\t\t}\n\n\t\t// No changes\n\t\tif (firstChanged === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if firstChanged is outside the viewport\n\t\t// cursorRow is the line where cursor is (0-indexed)\n\t\t// Viewport shows lines from (cursorRow - height + 1) to cursorRow\n\t\t// If firstChanged < viewportTop, we need full re-render\n\t\tconst viewportTop = this.cursorRow - height + 1;\n\t\tif (firstChanged < viewportTop) {\n\t\t\t// First change is above viewport - need full re-render\n\t\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\t\t\tbuffer += \"\\x1b[3J\\x1b[2J\\x1b[H\"; // Clear scrollback, screen, and home\n\t\t\tfor (let i = 0; i < newLines.length; i++) {\n\t\t\t\tif (i > 0) buffer += \"\\r\\n\";\n\t\t\t\tbuffer += newLines[i];\n\t\t\t}\n\t\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\t\t\tthis.terminal.write(buffer);\n\t\t\tthis.cursorRow = newLines.length - 1;\n\t\t\tthis.previousLines = newLines;\n\t\t\tthis.previousWidth = width;\n\t\t\treturn;\n\t\t}\n\n\t\t// Render from first changed line to end\n\t\t// Build buffer with all updates wrapped in synchronized output\n\t\tlet buffer = \"\\x1b[?2026h\"; // Begin synchronized output\n\n\t\t// Move cursor to first changed line\n\t\tconst lineDiff = firstChanged - this.cursorRow;\n\t\tif (lineDiff > 0) {\n\t\t\tbuffer += `\\x1b[${lineDiff}B`; // Move down\n\t\t} else if (lineDiff < 0) {\n\t\t\tbuffer += `\\x1b[${-lineDiff}A`; // Move up\n\t\t}\n\n\t\tbuffer += \"\\r\"; // Move to column 0\n\t\tbuffer += \"\\x1b[J\"; // Clear from cursor to end of screen\n\n\t\t// Render from first changed line to end\n\t\tfor (let i = firstChanged; i < newLines.length; i++) {\n\t\t\tif (i > firstChanged) buffer += \"\\r\\n\";\n\t\t\tif (visibleWidth(newLines[i]) > width) {\n\t\t\t\tthrow new Error(`Rendered line ${i} exceeds terminal width\\n\\n${newLines[i]}`);\n\t\t\t}\n\t\t\tbuffer += newLines[i];\n\t\t}\n\n\t\tbuffer += \"\\x1b[?2026l\"; // End synchronized output\n\n\t\t// Write entire buffer at once\n\t\tthis.terminal.write(buffer);\n\n\t\t// Cursor is now at end of last line\n\t\tthis.cursorRow = newLines.length - 1;\n\n\t\tthis.previousLines = newLines;\n\t\tthis.previousWidth = width;\n\t}\n}\n"]}