take4-console 0.25.0 → 0.31.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +253 -0
  2. package/README.md +3 -2
  3. package/dist/Screen/ErrorHolder.d.mts +10 -0
  4. package/dist/Screen/ErrorHolder.d.mts.map +1 -0
  5. package/dist/Screen/ErrorHolder.mjs +14 -0
  6. package/dist/Screen/ErrorHolder.mjs.map +1 -0
  7. package/dist/Screen/InterfaceBuilder.d.mts.map +1 -1
  8. package/dist/Screen/InterfaceBuilder.mjs +7 -0
  9. package/dist/Screen/InterfaceBuilder.mjs.map +1 -1
  10. package/dist/Screen/Screen.d.mts +90 -1
  11. package/dist/Screen/Screen.d.mts.map +1 -1
  12. package/dist/Screen/Screen.mjs +300 -1
  13. package/dist/Screen/Screen.mjs.map +1 -1
  14. package/dist/Screen/StyleRegistry.d.mts.map +1 -1
  15. package/dist/Screen/StyleRegistry.mjs +3 -1
  16. package/dist/Screen/StyleRegistry.mjs.map +1 -1
  17. package/dist/Screen/VirtualCursor.d.mts +57 -0
  18. package/dist/Screen/VirtualCursor.d.mts.map +1 -0
  19. package/dist/Screen/VirtualCursor.mjs +148 -0
  20. package/dist/Screen/VirtualCursor.mjs.map +1 -0
  21. package/dist/Screen/Window.d.mts +116 -6
  22. package/dist/Screen/Window.d.mts.map +1 -1
  23. package/dist/Screen/Window.mjs +359 -34
  24. package/dist/Screen/Window.mjs.map +1 -1
  25. package/dist/Screen/WindowManager.d.mts +78 -2
  26. package/dist/Screen/WindowManager.d.mts.map +1 -1
  27. package/dist/Screen/WindowManager.mjs +197 -3
  28. package/dist/Screen/WindowManager.mjs.map +1 -1
  29. package/dist/Screen/controls/BarChart.d.mts.map +1 -1
  30. package/dist/Screen/controls/BarChart.mjs +3 -0
  31. package/dist/Screen/controls/BarChart.mjs.map +1 -1
  32. package/dist/Screen/controls/Checkbox.d.mts.map +1 -1
  33. package/dist/Screen/controls/Checkbox.mjs +4 -0
  34. package/dist/Screen/controls/Checkbox.mjs.map +1 -1
  35. package/dist/Screen/controls/LineChart.d.mts.map +1 -1
  36. package/dist/Screen/controls/LineChart.mjs +3 -0
  37. package/dist/Screen/controls/LineChart.mjs.map +1 -1
  38. package/dist/Screen/controls/ListBox.d.mts.map +1 -1
  39. package/dist/Screen/controls/ListBox.mjs +9 -0
  40. package/dist/Screen/controls/ListBox.mjs.map +1 -1
  41. package/dist/Screen/controls/ProgressBar.d.mts.map +1 -1
  42. package/dist/Screen/controls/ProgressBar.mjs +2 -0
  43. package/dist/Screen/controls/ProgressBar.mjs.map +1 -1
  44. package/dist/Screen/controls/ProgressBarV.d.mts.map +1 -1
  45. package/dist/Screen/controls/ProgressBarV.mjs +2 -0
  46. package/dist/Screen/controls/ProgressBarV.mjs.map +1 -1
  47. package/dist/Screen/controls/Radio.d.mts.map +1 -1
  48. package/dist/Screen/controls/Radio.mjs +7 -1
  49. package/dist/Screen/controls/Radio.mjs.map +1 -1
  50. package/dist/Screen/controls/Sparkline.d.mts.map +1 -1
  51. package/dist/Screen/controls/Sparkline.mjs +3 -0
  52. package/dist/Screen/controls/Sparkline.mjs.map +1 -1
  53. package/dist/Screen/controls/Spinner.d.mts.map +1 -1
  54. package/dist/Screen/controls/Spinner.mjs +8 -0
  55. package/dist/Screen/controls/Spinner.mjs.map +1 -1
  56. package/dist/Screen/controls/StatusLED.d.mts.map +1 -1
  57. package/dist/Screen/controls/StatusLED.mjs +3 -0
  58. package/dist/Screen/controls/StatusLED.mjs.map +1 -1
  59. package/dist/Screen/controls/Tabs.d.mts.map +1 -1
  60. package/dist/Screen/controls/Tabs.mjs +2 -0
  61. package/dist/Screen/controls/Tabs.mjs.map +1 -1
  62. package/dist/Screen/controls/TextArea.d.mts +68 -2
  63. package/dist/Screen/controls/TextArea.d.mts.map +1 -1
  64. package/dist/Screen/controls/TextArea.mjs +291 -46
  65. package/dist/Screen/controls/TextArea.mjs.map +1 -1
  66. package/dist/Screen/controls/TextBox.d.mts +52 -5
  67. package/dist/Screen/controls/TextBox.d.mts.map +1 -1
  68. package/dist/Screen/controls/TextBox.mjs +192 -10
  69. package/dist/Screen/controls/TextBox.mjs.map +1 -1
  70. package/dist/Screen/controls/Toast.d.mts +72 -0
  71. package/dist/Screen/controls/Toast.d.mts.map +1 -0
  72. package/dist/Screen/controls/Toast.mjs +112 -0
  73. package/dist/Screen/controls/Toast.mjs.map +1 -0
  74. package/dist/Screen/types.d.mts +169 -0
  75. package/dist/Screen/types.d.mts.map +1 -1
  76. package/dist/Screen/types.mjs +8 -0
  77. package/dist/Screen/types.mjs.map +1 -1
  78. package/dist/index.d.mts +4 -2
  79. package/dist/index.d.mts.map +1 -1
  80. package/dist/index.mjs +3 -1
  81. package/dist/index.mjs.map +1 -1
  82. package/package.json +1 -1
  83. package/src/Screen/ErrorHolder.mts +22 -0
  84. package/src/Screen/InterfaceBuilder.mts +12 -5
  85. package/src/Screen/Screen.mts +313 -2
  86. package/src/Screen/StyleRegistry.mts +4 -0
  87. package/src/Screen/VirtualCursor.mts +175 -0
  88. package/src/Screen/Window.mts +352 -34
  89. package/src/Screen/WindowManager.mts +203 -3
  90. package/src/Screen/controls/BarChart.mts +3 -0
  91. package/src/Screen/controls/Checkbox.mts +3 -0
  92. package/src/Screen/controls/LineChart.mts +3 -0
  93. package/src/Screen/controls/ListBox.mts +8 -0
  94. package/src/Screen/controls/ProgressBar.mts +2 -0
  95. package/src/Screen/controls/ProgressBarV.mts +2 -0
  96. package/src/Screen/controls/Radio.mts +6 -1
  97. package/src/Screen/controls/Sparkline.mts +3 -0
  98. package/src/Screen/controls/Spinner.mts +6 -0
  99. package/src/Screen/controls/StatusLED.mts +2 -0
  100. package/src/Screen/controls/Tabs.mts +2 -0
  101. package/src/Screen/controls/TextArea.mts +290 -41
  102. package/src/Screen/controls/TextBox.mts +193 -10
  103. package/src/Screen/controls/Toast.mts +138 -0
  104. package/src/Screen/types.mts +167 -0
  105. package/src/demo.mts +131 -0
  106. package/src/index.mts +13 -0
  107. package/src/layout.yaml +16 -0
@@ -29,7 +29,10 @@ export class StatusLED extends Window {
29
29
  }
30
30
  /** Sets the current LED state. Call render() afterwards to update the display. */
31
31
  setState(state) {
32
+ if (this.state === state)
33
+ return;
32
34
  this.state = state;
35
+ this.markDirty();
33
36
  }
34
37
  /** Returns the current LED state. */
35
38
  getState() {
@@ -1 +1 @@
1
- {"version":3,"file":"StatusLED.mjs","sourceRoot":"","sources":["../../../src/Screen/controls/StatusLED.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,4DAA4D;AAC5D,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;mFACmF;AACnF,MAAM,OAAO,SAAU,SAAQ,MAAM;IAC5B,KAAK,CAAmC;IAExC,UAAU,CAAY;IACtB,SAAS,CAAa;IACtB,WAAW,CAAW;IACtB,YAAY,CAAU;IACtB,WAAW,CAAW;IAE9B;;uEAEmE;IACnE,YAAmB,EAAoB,EAAE,EAAwB;QAChE,MAAM,GAAG,GAAK,WAAW,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAI,EAAE,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAE/D,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvB,IAAI,CAAC,KAAK,GAAG,EAAE,EAAE,KAAK,IAAI,KAAK,CAAC;QAEhC,IAAI,CAAC,UAAU,GAAK,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,GAAM,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,EAAE,EAAG,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,GAAI,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,GAAI,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,kFAAkF;IAC3E,QAAQ,CAAC,KAAsC;QACrD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,qCAAqC;IAC9B,QAAQ;QACd,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAEe,MAAM;QACrB,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAI,CAAC,CAAC,IAAI,CAAC,SAAS;YACzC,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,CAAE,CAAC,CAAC,IAAI,CAAC,WAAW;gBAC3C,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY;oBAC5C,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAEjC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAErD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,KAAK,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;CACD"}
1
+ {"version":3,"file":"StatusLED.mjs","sourceRoot":"","sources":["../../../src/Screen/controls/StatusLED.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,4DAA4D;AAC5D,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;mFACmF;AACnF,MAAM,OAAO,SAAU,SAAQ,MAAM;IAC5B,KAAK,CAAmC;IAExC,UAAU,CAAY;IACtB,SAAS,CAAa;IACtB,WAAW,CAAW;IACtB,YAAY,CAAU;IACtB,WAAW,CAAW;IAE9B;;uEAEmE;IACnE,YAAmB,EAAoB,EAAE,EAAwB;QAChE,MAAM,GAAG,GAAK,WAAW,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAI,EAAE,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAE/D,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvB,IAAI,CAAC,KAAK,GAAG,EAAE,EAAE,KAAK,IAAI,KAAK,CAAC;QAEhC,IAAI,CAAC,UAAU,GAAK,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,GAAM,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,EAAE,EAAG,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,GAAI,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,GAAI,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,kFAAkF;IAC3E,QAAQ,CAAC,KAAsC;QACrD,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,EAAE,CAAC;IAClB,CAAC;IAED,qCAAqC;IAC9B,QAAQ;QACd,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAEe,MAAM;QACrB,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAI,CAAC,CAAC,IAAI,CAAC,SAAS;YACzC,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,CAAE,CAAC,CAAC,IAAI,CAAC,WAAW;gBAC3C,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY;oBAC5C,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAEjC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAErD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,KAAK,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;CACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.d.mts","sourceRoot":"","sources":["../../../src/Screen/controls/Tabs.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAW,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvC;;;;;;6EAM6E;AAC7E,qBAAa,IAAK,SAAQ,MAAM;IAC/B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,CAAyC;IAC1D,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,gBAAgB,CAAU;IAElC;uEACmE;gBAChD,EAAE,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,cAAc;IAiB5D;sEACkE;IAC3D,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAK3D,sEAAsE;IAC/D,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAKxC,sCAAsC;IAC/B,SAAS,IAAI,MAAM,EAAE;IAI5B,2FAA2F;IACpF,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAY1C,8CAA8C;IACvC,cAAc,IAAI,MAAM;IAI/B,oFAAoF;IAC7E,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IASnC;;mGAE+F;IAC/E,MAAM,IAAI,IAAI;IAiB9B;0FACsF;IACtF,OAAO,CAAC,UAAU;CAgClB"}
1
+ {"version":3,"file":"Tabs.d.mts","sourceRoot":"","sources":["../../../src/Screen/controls/Tabs.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAW,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvC;;;;;;6EAM6E;AAC7E,qBAAa,IAAK,SAAQ,MAAM;IAC/B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,CAAyC;IAC1D,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,gBAAgB,CAAU;IAElC;uEACmE;gBAChD,EAAE,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,cAAc;IAiB5D;sEACkE;IAC3D,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAK3D,sEAAsE;IAC/D,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAMxC,sCAAsC;IAC/B,SAAS,IAAI,MAAM,EAAE;IAI5B,2FAA2F;IACpF,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAa1C,8CAA8C;IACvC,cAAc,IAAI,MAAM;IAI/B,oFAAoF;IAC7E,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IASnC;;mGAE+F;IAC/E,MAAM,IAAI,IAAI;IAiB9B;0FACsF;IACtF,OAAO,CAAC,UAAU;CAgClB"}
@@ -41,6 +41,7 @@ export class Tabs extends Window {
41
41
  setTitles(titles) {
42
42
  this.titles = titles;
43
43
  this.activeIndex = Math.max(0, Math.min(titles.length - 1, this.activeIndex));
44
+ this.markDirty();
44
45
  }
45
46
  /** Returns the current tab titles. */
46
47
  getTitles() {
@@ -55,6 +56,7 @@ export class Tabs extends Window {
55
56
  const clamped = Math.max(0, Math.min(this.titles.length - 1, index));
56
57
  if (clamped !== this.activeIndex) {
57
58
  this.activeIndex = clamped;
59
+ this.markDirty();
58
60
  this.onChange?.(clamped, this.titles[clamped]);
59
61
  }
60
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Tabs.mjs","sourceRoot":"","sources":["../../../src/Screen/controls/Tabs.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Tabs.mjs","sourceRoot":"","sources":["../../../src/Screen/controls/Tabs.mts"],"names":[],"mappings":"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"}
@@ -1,15 +1,23 @@
1
1
  import type { TextAreaProperties, WindowProperties } from '../types.mjs';
2
2
  import { Window } from '../Window.mjs';
3
+ import { VirtualCursor } from '../VirtualCursor.mjs';
3
4
  /** A multi-line text-input widget with 2-D cursor, scrolling, and placeholder support.
4
5
  * Call handleKey() to feed raw terminal key strings from your input loop. */
5
6
  export declare class TextArea extends Window {
6
7
  private lines;
7
8
  private cursor;
9
+ /** Anchor of the active selection in 2-D coordinates, or null when no
10
+ * selection is active. The selection spans from `selectionAnchor` to
11
+ * `cursor` regardless of ordering. */
12
+ private selectionAnchor;
8
13
  private scrollX;
9
14
  private scrollY;
10
15
  private placeholder;
11
16
  private placeholderStyleId;
12
17
  private cursorStyleId;
18
+ private selectionStyleId;
19
+ /** Software-cursor model: blink state + glyph. */
20
+ private virtualCursor;
13
21
  private onChange?;
14
22
  private onSubmit?;
15
23
  private onKeyDown?;
@@ -18,8 +26,45 @@ export declare class TextArea extends Window {
18
26
  /** Creates a TextArea from window properties and optional control-specific properties.
19
27
  * Uses the global StyleRegistry set by the Screen constructor. */
20
28
  constructor(wp: WindowProperties, cp?: TextAreaProperties);
21
- /** Replaces the current value; cursor and scroll are clamped to fit. Does NOT fire onChange. */
29
+ /** Returns the VirtualCursor model so callers can tweak symbol/blink at runtime. */
30
+ getVirtualCursor(): VirtualCursor;
31
+ /** Replaces the current value; cursor and scroll are clamped to fit. Does NOT fire onChange.
32
+ * Any active selection is cleared because the old anchor no longer maps
33
+ * onto the new buffer. */
22
34
  setValue(text: string): void;
35
+ /** Returns the normalized selection range `{ start, end }` in 2-D
36
+ * coordinates (start always ≤ end in document order) or `null` when no
37
+ * selection is active. */
38
+ getSelection(): {
39
+ start: {
40
+ x: number;
41
+ y: number;
42
+ };
43
+ end: {
44
+ x: number;
45
+ y: number;
46
+ };
47
+ } | null;
48
+ /** Returns the currently selected substring (with embedded newlines), or
49
+ * `''` when no selection is active. */
50
+ getSelectedText(): string;
51
+ /** Replaces the current selection: sets anchor and cursor, clamping both
52
+ * positions into valid lines/columns. Identical positions clear the
53
+ * selection. */
54
+ setSelection(anchor: {
55
+ x: number;
56
+ y: number;
57
+ }, cursor: {
58
+ x: number;
59
+ y: number;
60
+ }): void;
61
+ /** Selects every character in the buffer. No-op when the buffer holds a
62
+ * single empty line. */
63
+ selectAll(): void;
64
+ /** Drops any active selection without moving the cursor. */
65
+ clearSelection(): void;
66
+ /** Clamps a 2-D position so both components land inside the buffer. */
67
+ private clampPosition;
23
68
  /** Replaces the onChange callback. */
24
69
  setOnChange(fn?: (value: string) => void): void;
25
70
  /** Replaces the onSubmit callback. */
@@ -31,7 +76,9 @@ export declare class TextArea extends Window {
31
76
  capturesTab(): boolean;
32
77
  /** Returns the current text value (lines joined with '\n'). */
33
78
  getValue(): string;
34
- /** Sets the cursor to the given position (clamped to valid range). */
79
+ /** Sets the cursor to the given position (clamped to valid range).
80
+ * Any active selection is dropped — use `setSelection()` to move the
81
+ * cursor while keeping an anchor. */
35
82
  setCursor(pos: {
36
83
  x: number;
37
84
  y: number;
@@ -47,6 +94,25 @@ export declare class TextArea extends Window {
47
94
  * Human-readable aliases: 'backspace', 'delete', 'left', 'right', 'up', 'down', 'home', 'end', 'enter'.
48
95
  * Any single printable character is inserted at the cursor. */
49
96
  handleKey(key: string): void;
97
+ /** Shared post-key housekeeping: clamp scroll, reset blink phase, fire
98
+ * onChange when the value actually changed. */
99
+ private finishKey;
100
+ /** Starts a selection at the current cursor if none is active. */
101
+ private ensureAnchor;
102
+ /** Removes the selected text and places the cursor at the start of the
103
+ * deleted range. Returns true when a deletion happened. */
104
+ private deleteSelection;
105
+ /** Drops an active selection, moving the cursor to the selection's
106
+ * `start` or `end` edge. Returns true when it did so. */
107
+ private collapseSelection;
108
+ /** Moves the cursor one cell left, wrapping to the previous line end. */
109
+ private moveCursorLeft;
110
+ /** Moves the cursor one cell right, wrapping to the next line start. */
111
+ private moveCursorRight;
112
+ /** Moves the cursor up one line, clamping column to the new line length. */
113
+ private moveCursorUp;
114
+ /** Moves the cursor down one line, clamping column to the new line length. */
115
+ private moveCursorDown;
50
116
  /** Rebuilds the TextArea: renders visible lines, draws cursor. */
51
117
  render(): void;
52
118
  /** Adjusts scrollX/scrollY so the cursor remains within the visible area. */
@@ -1 +1 @@
1
- {"version":3,"file":"TextArea.d.mts","sourceRoot":"","sources":["../../../src/Screen/controls/TextArea.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAW,MAAM,cAAc,CAAC;AAElF,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvC;8EAC8E;AAC9E,qBAAa,QAAS,SAAQ,MAAM;IACnC,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,aAAa,CAAU;IAE/B,OAAO,CAAC,QAAQ,CAAC,CAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAC,CAA0B;IAC3C,OAAO,CAAC,SAAS,CAAC,CAAkC;IACpD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,mBAAmB,CAAU;IAErC;uEACmE;gBAChD,EAAE,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,kBAAkB;IA+BhE,gGAAgG;IACzF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOnC,sCAAsC;IAC/B,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAItD,sCAAsC;IAC/B,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAItD,gDAAgD;IACzC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI;IAI/D;8EAC0E;IACnE,WAAW,IAAI,OAAO;IAI7B,+DAA+D;IACxD,QAAQ,IAAI,MAAM;IAIzB,sEAAsE;IAC/D,SAAS,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAMrD,qDAAqD;IAC9C,SAAS,IAAI;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAI5C;;;;oEAIgE;IACzD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAyHnC,kEAAkE;IAClD,MAAM,IAAI,IAAI;IAgC9B,6EAA6E;IAC7E,OAAO,CAAC,WAAW;CAanB"}
1
+ {"version":3,"file":"TextArea.d.mts","sourceRoot":"","sources":["../../../src/Screen/controls/TextArea.mts"],"names":[],"mappings":"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"}
@@ -1,16 +1,24 @@
1
- import { BUILTIN_TEXT_PLACEHOLDER, BUILTIN_CURSOR } from '../types.mjs';
1
+ import { BUILTIN_TEXT_PLACEHOLDER, BUILTIN_CURSOR, BUILTIN_TEXT_SELECTION } from '../types.mjs';
2
2
  import { Window } from '../Window.mjs';
3
3
  import { getRegistry } from '../RegistryHolder.mjs';
4
+ import { VirtualCursor } from '../VirtualCursor.mjs';
4
5
  /** A multi-line text-input widget with 2-D cursor, scrolling, and placeholder support.
5
6
  * Call handleKey() to feed raw terminal key strings from your input loop. */
6
7
  export class TextArea extends Window {
7
8
  lines;
8
9
  cursor;
10
+ /** Anchor of the active selection in 2-D coordinates, or null when no
11
+ * selection is active. The selection spans from `selectionAnchor` to
12
+ * `cursor` regardless of ordering. */
13
+ selectionAnchor;
9
14
  scrollX;
10
15
  scrollY;
11
16
  placeholder;
12
17
  placeholderStyleId;
13
18
  cursorStyleId;
19
+ selectionStyleId;
20
+ /** Software-cursor model: blink state + glyph. */
21
+ virtualCursor;
14
22
  onChange;
15
23
  onSubmit;
16
24
  onKeyDown;
@@ -33,6 +41,7 @@ export class TextArea extends Window {
33
41
  x: 0,
34
42
  };
35
43
  this.cursor.x = Math.max(0, Math.min(rawCursor.x, this.lines[this.cursor.y].length));
44
+ this.selectionAnchor = null;
36
45
  this.onChange = cp?.onChange;
37
46
  this.onSubmit = cp?.onSubmit;
38
47
  this.onKeyDown = cp?.onKeyDown;
@@ -41,14 +50,95 @@ export class TextArea extends Window {
41
50
  const reg = getRegistry();
42
51
  this.placeholderStyleId = reg.getNamed(BUILTIN_TEXT_PLACEHOLDER);
43
52
  this.cursorStyleId = reg.getNamed(BUILTIN_CURSOR);
53
+ this.selectionStyleId = reg.getNamed(BUILTIN_TEXT_SELECTION);
54
+ this.virtualCursor = new VirtualCursor({
55
+ symbol: cp?.cursorSymbol,
56
+ blink: cp?.cursorBlink,
57
+ });
44
58
  this.clampScroll();
45
59
  }
46
- /** Replaces the current value; cursor and scroll are clamped to fit. Does NOT fire onChange. */
60
+ /** Returns the VirtualCursor model so callers can tweak symbol/blink at runtime. */
61
+ getVirtualCursor() {
62
+ return this.virtualCursor;
63
+ }
64
+ /** Replaces the current value; cursor and scroll are clamped to fit. Does NOT fire onChange.
65
+ * Any active selection is cleared because the old anchor no longer maps
66
+ * onto the new buffer. */
47
67
  setValue(text) {
48
68
  this.lines = text.split('\n');
49
69
  this.cursor.y = Math.min(this.cursor.y, this.lines.length - 1);
50
70
  this.cursor.x = Math.min(this.cursor.x, this.lines[this.cursor.y].length);
71
+ this.selectionAnchor = null;
72
+ this.clampScroll();
73
+ this.markDirty();
74
+ }
75
+ /** Returns the normalized selection range `{ start, end }` in 2-D
76
+ * coordinates (start always ≤ end in document order) or `null` when no
77
+ * selection is active. */
78
+ getSelection() {
79
+ if (this.selectionAnchor === null)
80
+ return null;
81
+ const a = this.selectionAnchor;
82
+ const b = this.cursor;
83
+ if (a.x === b.x && a.y === b.y)
84
+ return null;
85
+ const aBeforeB = a.y < b.y || (a.y === b.y && a.x < b.x);
86
+ return aBeforeB
87
+ ? { start: { ...a }, end: { ...b } }
88
+ : { start: { ...b }, end: { ...a } };
89
+ }
90
+ /** Returns the currently selected substring (with embedded newlines), or
91
+ * `''` when no selection is active. */
92
+ getSelectedText() {
93
+ const sel = this.getSelection();
94
+ if (sel === null)
95
+ return '';
96
+ const { start, end } = sel;
97
+ if (start.y === end.y)
98
+ return this.lines[start.y].slice(start.x, end.x);
99
+ const parts = [this.lines[start.y].slice(start.x)];
100
+ for (let y = start.y + 1; y < end.y; y++)
101
+ parts.push(this.lines[y]);
102
+ parts.push(this.lines[end.y].slice(0, end.x));
103
+ return parts.join('\n');
104
+ }
105
+ /** Replaces the current selection: sets anchor and cursor, clamping both
106
+ * positions into valid lines/columns. Identical positions clear the
107
+ * selection. */
108
+ setSelection(anchor, cursor) {
109
+ const a = this.clampPosition(anchor);
110
+ const c = this.clampPosition(cursor);
111
+ this.selectionAnchor = (a.x === c.x && a.y === c.y) ? null : a;
112
+ this.cursor = c;
113
+ this.clampScroll();
114
+ this.markDirty();
115
+ }
116
+ /** Selects every character in the buffer. No-op when the buffer holds a
117
+ * single empty line. */
118
+ selectAll() {
119
+ const lastY = this.lines.length - 1;
120
+ const lastX = this.lines[lastY].length;
121
+ if (lastY === 0 && lastX === 0) {
122
+ this.selectionAnchor = null;
123
+ return;
124
+ }
125
+ this.selectionAnchor = { x: 0, y: 0 };
126
+ this.cursor = { x: lastX, y: lastY };
51
127
  this.clampScroll();
128
+ this.markDirty();
129
+ }
130
+ /** Drops any active selection without moving the cursor. */
131
+ clearSelection() {
132
+ if (this.selectionAnchor === null)
133
+ return;
134
+ this.selectionAnchor = null;
135
+ this.markDirty();
136
+ }
137
+ /** Clamps a 2-D position so both components land inside the buffer. */
138
+ clampPosition(p) {
139
+ const y = Math.max(0, Math.min(p.y, this.lines.length - 1));
140
+ const x = Math.max(0, Math.min(p.x, this.lines[y].length));
141
+ return { x, y };
52
142
  }
53
143
  /** Replaces the onChange callback. */
54
144
  setOnChange(fn) {
@@ -71,11 +161,15 @@ export class TextArea extends Window {
71
161
  getValue() {
72
162
  return this.lines.join('\n');
73
163
  }
74
- /** Sets the cursor to the given position (clamped to valid range). */
164
+ /** Sets the cursor to the given position (clamped to valid range).
165
+ * Any active selection is dropped — use `setSelection()` to move the
166
+ * cursor while keeping an anchor. */
75
167
  setCursor(pos) {
76
168
  this.cursor.y = Math.max(0, Math.min(pos.y, this.lines.length - 1));
77
169
  this.cursor.x = Math.max(0, Math.min(pos.x, this.lines[this.cursor.y].length));
170
+ this.selectionAnchor = null;
78
171
  this.clampScroll();
172
+ this.markDirty();
79
173
  }
80
174
  /** Returns a copy of the current cursor position. */
81
175
  getCursor() {
@@ -95,15 +189,56 @@ export class TextArea extends Window {
95
189
  return;
96
190
  }
97
191
  const before = this.getValue();
192
+ // ── Selection-extending motion (shift + arrow / home / end) ─────────
193
+ if (key === '\x1b[1;2D' || key === 'shift+left') {
194
+ this.ensureAnchor();
195
+ this.moveCursorLeft();
196
+ this.finishKey(before);
197
+ return;
198
+ }
199
+ if (key === '\x1b[1;2C' || key === 'shift+right') {
200
+ this.ensureAnchor();
201
+ this.moveCursorRight();
202
+ this.finishKey(before);
203
+ return;
204
+ }
205
+ if (key === '\x1b[1;2A' || key === 'shift+up') {
206
+ this.ensureAnchor();
207
+ this.moveCursorUp();
208
+ this.finishKey(before);
209
+ return;
210
+ }
211
+ if (key === '\x1b[1;2B' || key === 'shift+down') {
212
+ this.ensureAnchor();
213
+ this.moveCursorDown();
214
+ this.finishKey(before);
215
+ return;
216
+ }
217
+ if (key === '\x1b[1;2H' || key === 'shift+home') {
218
+ this.ensureAnchor();
219
+ this.cursor.x = 0;
220
+ this.finishKey(before);
221
+ return;
222
+ }
223
+ if (key === '\x1b[1;2F' || key === 'shift+end') {
224
+ this.ensureAnchor();
225
+ this.cursor.x = this.lines[this.cursor.y].length;
226
+ this.finishKey(before);
227
+ return;
228
+ }
229
+ if (key === '\x01' || key === 'ctrl+a') {
230
+ this.selectAll();
231
+ this.finishKey(before);
232
+ return;
233
+ }
98
234
  // Tab → soft-tab insert when configured, otherwise pass-through.
99
235
  if ((key === '\t' || key === 'tab') && this.insertTabAsSpaces > 0) {
236
+ this.deleteSelection();
100
237
  const spaces = ' '.repeat(this.insertTabAsSpaces);
101
238
  const lineTab = this.lines[this.cursor.y];
102
239
  this.lines[this.cursor.y] = lineTab.slice(0, this.cursor.x) + spaces + lineTab.slice(this.cursor.x);
103
240
  this.cursor.x += spaces.length;
104
- this.clampScroll();
105
- if (this.getValue() !== before)
106
- this.onChange?.(this.getValue());
241
+ this.finishKey(before);
107
242
  return;
108
243
  }
109
244
  if (key === '\t' || key === 'tab') {
@@ -112,17 +247,17 @@ export class TextArea extends Window {
112
247
  }
113
248
  // Ctrl+D forward-delete when enabled — same behaviour as \x1b[3~.
114
249
  if (key === '\x04' && this.ctrlDDeletesForward) {
115
- const lineD = this.lines[this.cursor.y];
116
- if (this.cursor.x < lineD.length) {
117
- this.lines[this.cursor.y] = lineD.slice(0, this.cursor.x) + lineD.slice(this.cursor.x + 1);
118
- }
119
- else if (this.cursor.y < this.lines.length - 1) {
120
- this.lines[this.cursor.y] = lineD + this.lines[this.cursor.y + 1];
121
- this.lines.splice(this.cursor.y + 1, 1);
250
+ if (!this.deleteSelection()) {
251
+ const lineD = this.lines[this.cursor.y];
252
+ if (this.cursor.x < lineD.length) {
253
+ this.lines[this.cursor.y] = lineD.slice(0, this.cursor.x) + lineD.slice(this.cursor.x + 1);
254
+ }
255
+ else if (this.cursor.y < this.lines.length - 1) {
256
+ this.lines[this.cursor.y] = lineD + this.lines[this.cursor.y + 1];
257
+ this.lines.splice(this.cursor.y + 1, 1);
258
+ }
122
259
  }
123
- this.clampScroll();
124
- if (this.getValue() !== before)
125
- this.onChange?.(this.getValue());
260
+ this.finishKey(before);
126
261
  return;
127
262
  }
128
263
  // Ctrl+Enter submits without inserting a newline. In xterm this arrives
@@ -133,11 +268,13 @@ export class TextArea extends Window {
133
268
  this.onSubmit?.(this.getValue());
134
269
  return;
135
270
  }
136
- const line = this.lines[this.cursor.y];
137
271
  switch (key) {
138
272
  case '\x7f':
139
273
  case '\b':
140
- case 'backspace':
274
+ case 'backspace': {
275
+ if (this.deleteSelection())
276
+ break;
277
+ const line = this.lines[this.cursor.y];
141
278
  if (this.cursor.x > 0) {
142
279
  this.lines[this.cursor.y] = line.slice(0, this.cursor.x - 1) + line.slice(this.cursor.x);
143
280
  this.cursor.x--;
@@ -150,8 +287,12 @@ export class TextArea extends Window {
150
287
  this.cursor.y--;
151
288
  }
152
289
  break;
290
+ }
153
291
  case '\x1b[3~':
154
- case 'delete':
292
+ case 'delete': {
293
+ if (this.deleteSelection())
294
+ break;
295
+ const line = this.lines[this.cursor.y];
155
296
  if (this.cursor.x < line.length) {
156
297
  this.lines[this.cursor.y] = line.slice(0, this.cursor.x) + line.slice(this.cursor.x + 1);
157
298
  }
@@ -160,66 +301,142 @@ export class TextArea extends Window {
160
301
  this.lines.splice(this.cursor.y + 1, 1);
161
302
  }
162
303
  break;
304
+ }
163
305
  case '\r':
164
306
  case '\n':
165
- case 'enter':
307
+ case 'enter': {
308
+ this.deleteSelection();
309
+ const line = this.lines[this.cursor.y];
166
310
  this.lines.splice(this.cursor.y + 1, 0, line.slice(this.cursor.x));
167
311
  this.lines[this.cursor.y] = line.slice(0, this.cursor.x);
168
312
  this.cursor.y++;
169
313
  this.cursor.x = 0;
170
314
  break;
315
+ }
171
316
  case '\x1b[D':
172
317
  case 'left':
173
- if (this.cursor.x > 0) {
174
- this.cursor.x--;
175
- }
176
- else if (this.cursor.y > 0) {
177
- this.cursor.y--;
178
- this.cursor.x = this.lines[this.cursor.y].length;
179
- }
318
+ if (this.collapseSelection('start'))
319
+ break;
320
+ this.moveCursorLeft();
180
321
  break;
181
322
  case '\x1b[C':
182
323
  case 'right':
183
- if (this.cursor.x < line.length) {
184
- this.cursor.x++;
185
- }
186
- else if (this.cursor.y < this.lines.length - 1) {
187
- this.cursor.y++;
188
- this.cursor.x = 0;
189
- }
324
+ if (this.collapseSelection('end'))
325
+ break;
326
+ this.moveCursorRight();
190
327
  break;
191
328
  case '\x1b[A':
192
329
  case 'up':
193
- if (this.cursor.y > 0) {
194
- this.cursor.y--;
195
- this.cursor.x = Math.min(this.cursor.x, this.lines[this.cursor.y].length);
196
- }
330
+ this.selectionAnchor = null;
331
+ this.moveCursorUp();
197
332
  break;
198
333
  case '\x1b[B':
199
334
  case 'down':
200
- if (this.cursor.y < this.lines.length - 1) {
201
- this.cursor.y++;
202
- this.cursor.x = Math.min(this.cursor.x, this.lines[this.cursor.y].length);
203
- }
335
+ this.selectionAnchor = null;
336
+ this.moveCursorDown();
204
337
  break;
205
338
  case '\x1b[H':
206
339
  case 'home':
340
+ this.selectionAnchor = null;
207
341
  this.cursor.x = 0;
208
342
  break;
209
343
  case '\x1b[F':
210
344
  case 'end':
345
+ this.selectionAnchor = null;
211
346
  this.cursor.x = this.lines[this.cursor.y].length;
212
347
  break;
213
348
  default:
214
349
  if (key.length === 1 && key >= ' ') {
350
+ this.deleteSelection();
351
+ const line = this.lines[this.cursor.y];
215
352
  this.lines[this.cursor.y] = line.slice(0, this.cursor.x) + key + line.slice(this.cursor.x);
216
353
  this.cursor.x++;
217
354
  }
218
355
  }
356
+ this.finishKey(before);
357
+ }
358
+ /** Shared post-key housekeeping: clamp scroll, reset blink phase, fire
359
+ * onChange when the value actually changed. */
360
+ finishKey(before) {
219
361
  this.clampScroll();
362
+ this.virtualCursor.resetPhase();
363
+ // Every key dispatch may have moved the cursor, changed the
364
+ // selection, or edited the buffer — flag the window dirty so the
365
+ // next Screen.render() re-emits the composed state.
366
+ this.markDirty();
220
367
  if (this.getValue() !== before)
221
368
  this.onChange?.(this.getValue());
222
369
  }
370
+ /** Starts a selection at the current cursor if none is active. */
371
+ ensureAnchor() {
372
+ if (this.selectionAnchor === null)
373
+ this.selectionAnchor = { ...this.cursor };
374
+ }
375
+ /** Removes the selected text and places the cursor at the start of the
376
+ * deleted range. Returns true when a deletion happened. */
377
+ deleteSelection() {
378
+ const sel = this.getSelection();
379
+ if (sel === null)
380
+ return false;
381
+ const { start, end } = sel;
382
+ if (start.y === end.y) {
383
+ const line = this.lines[start.y];
384
+ this.lines[start.y] = line.slice(0, start.x) + line.slice(end.x);
385
+ }
386
+ else {
387
+ const head = this.lines[start.y].slice(0, start.x);
388
+ const tail = this.lines[end.y].slice(end.x);
389
+ this.lines.splice(start.y, end.y - start.y + 1, head + tail);
390
+ }
391
+ this.cursor = { ...start };
392
+ this.selectionAnchor = null;
393
+ return true;
394
+ }
395
+ /** Drops an active selection, moving the cursor to the selection's
396
+ * `start` or `end` edge. Returns true when it did so. */
397
+ collapseSelection(edge) {
398
+ const sel = this.getSelection();
399
+ if (sel === null)
400
+ return false;
401
+ this.cursor = { ...(edge === 'start' ? sel.start : sel.end) };
402
+ this.selectionAnchor = null;
403
+ return true;
404
+ }
405
+ /** Moves the cursor one cell left, wrapping to the previous line end. */
406
+ moveCursorLeft() {
407
+ if (this.cursor.x > 0) {
408
+ this.cursor.x--;
409
+ }
410
+ else if (this.cursor.y > 0) {
411
+ this.cursor.y--;
412
+ this.cursor.x = this.lines[this.cursor.y].length;
413
+ }
414
+ }
415
+ /** Moves the cursor one cell right, wrapping to the next line start. */
416
+ moveCursorRight() {
417
+ const line = this.lines[this.cursor.y];
418
+ if (this.cursor.x < line.length) {
419
+ this.cursor.x++;
420
+ }
421
+ else if (this.cursor.y < this.lines.length - 1) {
422
+ this.cursor.y++;
423
+ this.cursor.x = 0;
424
+ }
425
+ }
426
+ /** Moves the cursor up one line, clamping column to the new line length. */
427
+ moveCursorUp() {
428
+ if (this.cursor.y > 0) {
429
+ this.cursor.y--;
430
+ this.cursor.x = Math.min(this.cursor.x, this.lines[this.cursor.y].length);
431
+ }
432
+ }
433
+ /** Moves the cursor down one line, clamping column to the new line length. */
434
+ moveCursorDown() {
435
+ if (this.cursor.y < this.lines.length - 1) {
436
+ this.cursor.y++;
437
+ this.cursor.x = Math.min(this.cursor.x, this.lines[this.cursor.y].length);
438
+ }
439
+ }
223
440
  /** Rebuilds the TextArea: renders visible lines, draws cursor. */
224
441
  render() {
225
442
  this.clear();
@@ -238,13 +455,41 @@ export class TextArea extends Window {
238
455
  this.writeText(visible, { x: 0, y: row, style: this.disabled ? undefined : this.normalStyleId });
239
456
  }
240
457
  }
241
- // Draw cursor when focused.
242
- if (this.focused && !this.disabled) {
458
+ // Paint the selection highlight over any cells that fall inside the
459
+ // active selection range. Runs before the cursor so the caret still
460
+ // shows on top.
461
+ const sel = this.focused && !this.disabled ? this.getSelection() : null;
462
+ if (sel !== null) {
463
+ const base = this.normalStyleId;
464
+ const merged = this.registry.merge(base, this.selectionStyleId);
465
+ for (let y = sel.start.y; y <= sel.end.y; y++) {
466
+ const row = y - this.scrollY;
467
+ if (row < 0 || row >= height)
468
+ continue;
469
+ const lineText = this.lines[y];
470
+ const fromX = y === sel.start.y ? sel.start.x : 0;
471
+ const toX = y === sel.end.y ? sel.end.x : lineText.length + 1; // +1 to paint a trailing newline cell
472
+ for (let x = fromX; x < toX; x++) {
473
+ const col = x - this.scrollX;
474
+ if (col < 0 || col >= width)
475
+ continue;
476
+ const ch = lineText[x] ?? ' ';
477
+ this.writeText(ch, { x: col, y: row, style: merged });
478
+ }
479
+ }
480
+ }
481
+ // Draw cursor when focused and the virtual-cursor blink says "on".
482
+ if (this.focused && !this.disabled && this.virtualCursor.isVisible()) {
243
483
  const screenX = this.cursor.x - this.scrollX;
244
484
  const screenY = this.cursor.y - this.scrollY;
245
485
  if (screenX >= 0 && screenX < width && screenY >= 0 && screenY < height) {
246
- const cursorChar = this.lines[this.cursor.y][this.cursor.x] ?? ' ';
247
- const cursorStyle = this.registry.merge(this.normalStyleId, this.cursorStyleId);
486
+ const useSymbol = this.virtualCursor.hasCustomSymbol();
487
+ const cursorChar = useSymbol
488
+ ? this.virtualCursor.getSymbol()
489
+ : (this.lines[this.cursor.y][this.cursor.x] ?? ' ');
490
+ const cursorStyle = useSymbol
491
+ ? this.normalStyleId
492
+ : this.registry.merge(this.normalStyleId, this.cursorStyleId);
248
493
  this.writeText(cursorChar, { x: screenX, y: screenY, style: cursorStyle });
249
494
  }
250
495
  }