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.
- package/CHANGELOG.md +253 -0
- package/README.md +3 -2
- package/dist/Screen/ErrorHolder.d.mts +10 -0
- package/dist/Screen/ErrorHolder.d.mts.map +1 -0
- package/dist/Screen/ErrorHolder.mjs +14 -0
- package/dist/Screen/ErrorHolder.mjs.map +1 -0
- package/dist/Screen/InterfaceBuilder.d.mts.map +1 -1
- package/dist/Screen/InterfaceBuilder.mjs +7 -0
- package/dist/Screen/InterfaceBuilder.mjs.map +1 -1
- package/dist/Screen/Screen.d.mts +90 -1
- package/dist/Screen/Screen.d.mts.map +1 -1
- package/dist/Screen/Screen.mjs +300 -1
- package/dist/Screen/Screen.mjs.map +1 -1
- package/dist/Screen/StyleRegistry.d.mts.map +1 -1
- package/dist/Screen/StyleRegistry.mjs +3 -1
- package/dist/Screen/StyleRegistry.mjs.map +1 -1
- package/dist/Screen/VirtualCursor.d.mts +57 -0
- package/dist/Screen/VirtualCursor.d.mts.map +1 -0
- package/dist/Screen/VirtualCursor.mjs +148 -0
- package/dist/Screen/VirtualCursor.mjs.map +1 -0
- package/dist/Screen/Window.d.mts +116 -6
- package/dist/Screen/Window.d.mts.map +1 -1
- package/dist/Screen/Window.mjs +359 -34
- package/dist/Screen/Window.mjs.map +1 -1
- package/dist/Screen/WindowManager.d.mts +78 -2
- package/dist/Screen/WindowManager.d.mts.map +1 -1
- package/dist/Screen/WindowManager.mjs +197 -3
- package/dist/Screen/WindowManager.mjs.map +1 -1
- package/dist/Screen/controls/BarChart.d.mts.map +1 -1
- package/dist/Screen/controls/BarChart.mjs +3 -0
- package/dist/Screen/controls/BarChart.mjs.map +1 -1
- package/dist/Screen/controls/Checkbox.d.mts.map +1 -1
- package/dist/Screen/controls/Checkbox.mjs +4 -0
- package/dist/Screen/controls/Checkbox.mjs.map +1 -1
- package/dist/Screen/controls/LineChart.d.mts.map +1 -1
- package/dist/Screen/controls/LineChart.mjs +3 -0
- package/dist/Screen/controls/LineChart.mjs.map +1 -1
- package/dist/Screen/controls/ListBox.d.mts.map +1 -1
- package/dist/Screen/controls/ListBox.mjs +9 -0
- package/dist/Screen/controls/ListBox.mjs.map +1 -1
- package/dist/Screen/controls/ProgressBar.d.mts.map +1 -1
- package/dist/Screen/controls/ProgressBar.mjs +2 -0
- package/dist/Screen/controls/ProgressBar.mjs.map +1 -1
- package/dist/Screen/controls/ProgressBarV.d.mts.map +1 -1
- package/dist/Screen/controls/ProgressBarV.mjs +2 -0
- package/dist/Screen/controls/ProgressBarV.mjs.map +1 -1
- package/dist/Screen/controls/Radio.d.mts.map +1 -1
- package/dist/Screen/controls/Radio.mjs +7 -1
- package/dist/Screen/controls/Radio.mjs.map +1 -1
- package/dist/Screen/controls/Sparkline.d.mts.map +1 -1
- package/dist/Screen/controls/Sparkline.mjs +3 -0
- package/dist/Screen/controls/Sparkline.mjs.map +1 -1
- package/dist/Screen/controls/Spinner.d.mts.map +1 -1
- package/dist/Screen/controls/Spinner.mjs +8 -0
- package/dist/Screen/controls/Spinner.mjs.map +1 -1
- package/dist/Screen/controls/StatusLED.d.mts.map +1 -1
- package/dist/Screen/controls/StatusLED.mjs +3 -0
- package/dist/Screen/controls/StatusLED.mjs.map +1 -1
- package/dist/Screen/controls/Tabs.d.mts.map +1 -1
- package/dist/Screen/controls/Tabs.mjs +2 -0
- package/dist/Screen/controls/Tabs.mjs.map +1 -1
- package/dist/Screen/controls/TextArea.d.mts +68 -2
- package/dist/Screen/controls/TextArea.d.mts.map +1 -1
- package/dist/Screen/controls/TextArea.mjs +291 -46
- package/dist/Screen/controls/TextArea.mjs.map +1 -1
- package/dist/Screen/controls/TextBox.d.mts +52 -5
- package/dist/Screen/controls/TextBox.d.mts.map +1 -1
- package/dist/Screen/controls/TextBox.mjs +192 -10
- package/dist/Screen/controls/TextBox.mjs.map +1 -1
- package/dist/Screen/controls/Toast.d.mts +72 -0
- package/dist/Screen/controls/Toast.d.mts.map +1 -0
- package/dist/Screen/controls/Toast.mjs +112 -0
- package/dist/Screen/controls/Toast.mjs.map +1 -0
- package/dist/Screen/types.d.mts +169 -0
- package/dist/Screen/types.d.mts.map +1 -1
- package/dist/Screen/types.mjs +8 -0
- package/dist/Screen/types.mjs.map +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Screen/ErrorHolder.mts +22 -0
- package/src/Screen/InterfaceBuilder.mts +12 -5
- package/src/Screen/Screen.mts +313 -2
- package/src/Screen/StyleRegistry.mts +4 -0
- package/src/Screen/VirtualCursor.mts +175 -0
- package/src/Screen/Window.mts +352 -34
- package/src/Screen/WindowManager.mts +203 -3
- package/src/Screen/controls/BarChart.mts +3 -0
- package/src/Screen/controls/Checkbox.mts +3 -0
- package/src/Screen/controls/LineChart.mts +3 -0
- package/src/Screen/controls/ListBox.mts +8 -0
- package/src/Screen/controls/ProgressBar.mts +2 -0
- package/src/Screen/controls/ProgressBarV.mts +2 -0
- package/src/Screen/controls/Radio.mts +6 -1
- package/src/Screen/controls/Sparkline.mts +3 -0
- package/src/Screen/controls/Spinner.mts +6 -0
- package/src/Screen/controls/StatusLED.mts +2 -0
- package/src/Screen/controls/Tabs.mts +2 -0
- package/src/Screen/controls/TextArea.mts +290 -41
- package/src/Screen/controls/TextBox.mts +193 -10
- package/src/Screen/controls/Toast.mts +138 -0
- package/src/Screen/types.mts +167 -0
- package/src/demo.mts +131 -0
- package/src/index.mts +13 -0
- 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;
|
|
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;
|
|
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":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD;;;;;;6EAM6E;AAC7E,MAAM,OAAO,IAAK,SAAQ,MAAM;IACvB,MAAM,CAAW;IACjB,WAAW,CAAS;IACpB,QAAQ,CAA0C;IAC1D,qFAAqF;IAC7E,QAAQ,CAAsB;IAC9B,iBAAiB,CAAU;IAC3B,gBAAgB,CAAU;IAElC;uEACmE;IACnE,YAAmB,EAAoB,EAAE,EAAmB;QAC3D,KAAK,CAAC;YACL,GAAG,EAAE;YACL,aAAa,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;SACpF,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,GAAQ,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC;QACvF,IAAI,CAAC,QAAQ,GAAM,EAAE,EAAE,QAAQ,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAM,IAAI,GAAG,EAAE,CAAC;QAE7B,qFAAqF;QACrF,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;QAC1B,IAAI,CAAC,iBAAiB,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACxF,IAAI,CAAC,gBAAgB,GAAI,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED;sEACkE;IAC3D,aAAa,CAAC,QAAgB,EAAE,KAAa;QACnD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,sEAAsE;IAC/D,SAAS,CAAC,MAAgB;QAChC,IAAI,CAAC,MAAM,GAAQ,MAAM,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;
|
|
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
|
-
/**
|
|
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;
|
|
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;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;8EAC8E;AAC9E,qBAAa,QAAS,SAAQ,MAAM;IACnC,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,MAAM,CAA2B;IACzC;;2CAEuC;IACvC,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,gBAAgB,CAAU;IAClC,kDAAkD;IAClD,OAAO,CAAC,aAAa,CAAgB;IAErC,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;IAsChE,oFAAoF;IAC7E,gBAAgB,IAAI,aAAa;IAIxC;;+BAE2B;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IASnC;;+BAE2B;IACpB,YAAY,IAAI;QAAE,KAAK,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,GAAG,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,IAAI;IAWhG;4CACwC;IACjC,eAAe,IAAI,MAAM;IAWhC;;qBAEiB;IACV,YAAY,CAAC,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAS7F;6BACyB;IAClB,SAAS,IAAI,IAAI;IAaxB,4DAA4D;IACrD,cAAc,IAAI,IAAI;IAM7B,uEAAuE;IACvE,OAAO,CAAC,aAAa;IAMrB,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;;0CAEsC;IAC/B,SAAS,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAQrD,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;IAoKnC;oDACgD;IAChD,OAAO,CAAC,SAAS;IAUjB,kEAAkE;IAClE,OAAO,CAAC,YAAY;IAIpB;gEAC4D;IAC5D,OAAO,CAAC,eAAe;IAiBvB;8DAC0D;IAC1D,OAAO,CAAC,iBAAiB;IAQzB,yEAAyE;IACzE,OAAO,CAAC,cAAc;IAStB,wEAAwE;IACxE,OAAO,CAAC,eAAe;IAUvB,4EAA4E;IAC5E,OAAO,CAAC,YAAY;IAOpB,8EAA8E;IAC9E,OAAO,CAAC,cAAc;IAOtB,kEAAkE;IAClD,MAAM,IAAI,IAAI;IA2D9B,6EAA6E;IAC7E,OAAO,CAAC,WAAW;CAanB"}
|
|
@@ -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
|
-
/**
|
|
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.
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.
|
|
121
|
-
|
|
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.
|
|
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.
|
|
174
|
-
|
|
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.
|
|
184
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
//
|
|
242
|
-
|
|
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
|
|
247
|
-
const
|
|
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
|
}
|