@promptctl/rich-js 0.18.0 → 0.19.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 (244) hide show
  1. package/README.md +9 -7
  2. package/dist/core/anchor.d.ts +45 -0
  3. package/dist/core/anchor.js +39 -0
  4. package/dist/core/ansi.d.ts +65 -0
  5. package/dist/core/ansi.js +231 -0
  6. package/dist/core/box.d.ts +145 -0
  7. package/dist/core/box.js +372 -0
  8. package/dist/core/cells.d.ts +192 -0
  9. package/dist/core/cells.js +334 -0
  10. package/dist/core/clock.d.ts +48 -0
  11. package/dist/core/clock.js +42 -0
  12. package/dist/core/color.d.ts +326 -0
  13. package/dist/core/color.js +1225 -0
  14. package/dist/core/console.d.ts +196 -0
  15. package/dist/core/console.js +596 -0
  16. package/dist/core/easing.d.ts +86 -0
  17. package/dist/core/easing.js +171 -0
  18. package/dist/core/emoji.d.ts +24 -0
  19. package/dist/core/emoji.js +391 -0
  20. package/dist/core/env.d.ts +16 -0
  21. package/dist/core/env.js +16 -0
  22. package/dist/core/export-html.d.ts +59 -0
  23. package/dist/core/export-html.js +197 -0
  24. package/dist/core/export-lines.d.ts +130 -0
  25. package/dist/core/export-lines.js +159 -0
  26. package/dist/core/export-svg.d.ts +49 -0
  27. package/dist/core/export-svg.js +213 -0
  28. package/dist/core/fnv1a.d.ts +10 -0
  29. package/dist/core/fnv1a.js +17 -0
  30. package/dist/core/highlighter.d.ts +33 -0
  31. package/dist/core/highlighter.js +190 -0
  32. package/dist/core/json.d.ts +27 -0
  33. package/dist/core/json.js +53 -0
  34. package/dist/core/markup.d.ts +140 -0
  35. package/dist/core/markup.js +654 -0
  36. package/dist/core/measure.d.ts +27 -0
  37. package/dist/core/measure.js +67 -0
  38. package/dist/core/memo.d.ts +19 -0
  39. package/dist/core/memo.js +34 -0
  40. package/dist/core/oklch.d.ts +161 -0
  41. package/dist/core/oklch.js +312 -0
  42. package/dist/core/osc8.d.ts +68 -0
  43. package/dist/core/osc8.js +78 -0
  44. package/dist/core/paint.d.ts +107 -0
  45. package/dist/core/paint.js +150 -0
  46. package/dist/core/place.d.ts +30 -0
  47. package/dist/core/place.js +49 -0
  48. package/dist/core/pretty.d.ts +247 -0
  49. package/dist/core/pretty.js +906 -0
  50. package/dist/core/protocol.d.ts +305 -0
  51. package/dist/core/protocol.js +183 -0
  52. package/dist/core/render.d.ts +87 -0
  53. package/dist/core/render.js +105 -0
  54. package/dist/core/segment.d.ts +162 -0
  55. package/dist/core/segment.js +488 -0
  56. package/dist/core/spinnerData.d.ts +12 -0
  57. package/dist/core/spinnerData.js +117 -0
  58. package/dist/core/strip.d.ts +152 -0
  59. package/dist/core/strip.js +326 -0
  60. package/dist/core/style.d.ts +189 -0
  61. package/dist/core/style.js +758 -0
  62. package/dist/core/subscription.d.ts +12 -0
  63. package/dist/core/subscription.js +12 -0
  64. package/dist/core/text.d.ts +283 -0
  65. package/dist/core/text.js +1097 -0
  66. package/dist/core/wrap.d.ts +46 -0
  67. package/dist/core/wrap.js +140 -0
  68. package/dist/host/app.d.ts +133 -0
  69. package/dist/host/app.js +285 -0
  70. package/dist/host/host-environment.d.ts +26 -0
  71. package/dist/host/host-environment.js +44 -0
  72. package/dist/host/index.d.ts +6 -0
  73. package/dist/host/index.js +3 -0
  74. package/dist/host/terminal-host.d.ts +180 -0
  75. package/dist/host/terminal-host.js +186 -0
  76. package/dist/index.d.ts +98 -0
  77. package/dist/index.js +114 -0
  78. package/dist/node/prompt.d.ts +34 -0
  79. package/dist/node/prompt.js +51 -0
  80. package/dist/node/save.d.ts +18 -0
  81. package/dist/node/save.js +24 -0
  82. package/dist/node/terminal-host.d.ts +114 -0
  83. package/dist/node/terminal-host.js +227 -0
  84. package/dist/node/traceback.d.ts +38 -0
  85. package/dist/node/traceback.js +98 -0
  86. package/dist/renderables/align.d.ts +16 -0
  87. package/dist/renderables/align.js +26 -0
  88. package/dist/renderables/columns.d.ts +77 -0
  89. package/dist/renderables/columns.js +191 -0
  90. package/dist/renderables/constrain.d.ts +15 -0
  91. package/dist/renderables/constrain.js +31 -0
  92. package/dist/renderables/effect.d.ts +98 -0
  93. package/dist/renderables/effect.js +160 -0
  94. package/dist/renderables/embed.d.ts +107 -0
  95. package/dist/renderables/embed.js +160 -0
  96. package/dist/renderables/flexStrip.d.ts +41 -0
  97. package/dist/renderables/flexStrip.js +200 -0
  98. package/dist/renderables/group.d.ts +14 -0
  99. package/dist/renderables/group.js +22 -0
  100. package/dist/renderables/layout.d.ts +75 -0
  101. package/dist/renderables/layout.js +223 -0
  102. package/dist/renderables/live.d.ts +74 -0
  103. package/dist/renderables/live.js +188 -0
  104. package/dist/renderables/markdown.d.ts +38 -0
  105. package/dist/renderables/markdown.js +587 -0
  106. package/dist/renderables/padding.d.ts +59 -0
  107. package/dist/renderables/padding.js +147 -0
  108. package/dist/renderables/panel.d.ts +114 -0
  109. package/dist/renderables/panel.js +303 -0
  110. package/dist/renderables/progress.d.ts +178 -0
  111. package/dist/renderables/progress.js +362 -0
  112. package/dist/renderables/progressBar.d.ts +31 -0
  113. package/dist/renderables/progressBar.js +64 -0
  114. package/dist/renderables/prompt.d.ts +71 -0
  115. package/dist/renderables/prompt.js +165 -0
  116. package/dist/renderables/ratio.d.ts +123 -0
  117. package/dist/renderables/ratio.js +213 -0
  118. package/dist/renderables/rule.d.ts +26 -0
  119. package/dist/renderables/rule.js +86 -0
  120. package/dist/renderables/spinner.d.ts +57 -0
  121. package/dist/renderables/spinner.js +103 -0
  122. package/dist/renderables/status.d.ts +29 -0
  123. package/dist/renderables/status.js +46 -0
  124. package/dist/renderables/syntax.d.ts +65 -0
  125. package/dist/renderables/syntax.js +166 -0
  126. package/dist/renderables/table.d.ts +232 -0
  127. package/dist/renderables/table.js +973 -0
  128. package/dist/renderables/traceback.d.ts +32 -0
  129. package/dist/renderables/traceback.js +279 -0
  130. package/dist/renderables/tree.d.ts +76 -0
  131. package/dist/renderables/tree.js +177 -0
  132. package/dist/renderables/viewport.d.ts +114 -0
  133. package/dist/renderables/viewport.js +196 -0
  134. package/dist/template-bindings/color-funcs.d.ts +109 -0
  135. package/dist/template-bindings/color-funcs.js +243 -0
  136. package/dist/template-bindings/helpers.d.ts +26 -0
  137. package/dist/template-bindings/helpers.js +33 -0
  138. package/dist/template-bindings/index.d.ts +119 -0
  139. package/dist/template-bindings/index.js +162 -0
  140. package/dist/template-bindings/palette-funcs.d.ts +96 -0
  141. package/dist/template-bindings/palette-funcs.js +145 -0
  142. package/dist/template-bindings/style-funcs.d.ts +28 -0
  143. package/dist/template-bindings/style-funcs.js +157 -0
  144. package/dist/themes/buildPalette.d.ts +57 -0
  145. package/dist/themes/buildPalette.js +100 -0
  146. package/dist/themes/colorMath.d.ts +94 -0
  147. package/dist/themes/colorMath.js +280 -0
  148. package/dist/themes/colorRef.d.ts +36 -0
  149. package/dist/themes/colorRef.js +78 -0
  150. package/dist/themes/data/ansi-tables.d.ts +14 -0
  151. package/dist/themes/data/ansi-tables.js +49 -0
  152. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  153. package/dist/themes/data/atom-one-dark.js +177 -0
  154. package/dist/themes/data/atom-one-light.d.ts +3 -0
  155. package/dist/themes/data/atom-one-light.js +177 -0
  156. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  157. package/dist/themes/data/catppuccin-frappe.js +177 -0
  158. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  159. package/dist/themes/data/catppuccin-latte.js +177 -0
  160. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  161. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  162. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  163. package/dist/themes/data/catppuccin-mocha.js +177 -0
  164. package/dist/themes/data/cyberpunk.d.ts +17 -0
  165. package/dist/themes/data/cyberpunk.js +193 -0
  166. package/dist/themes/data/default.d.ts +3 -0
  167. package/dist/themes/data/default.js +160 -0
  168. package/dist/themes/data/dracula.d.ts +3 -0
  169. package/dist/themes/data/dracula.js +177 -0
  170. package/dist/themes/data/flexoki.d.ts +3 -0
  171. package/dist/themes/data/flexoki.js +177 -0
  172. package/dist/themes/data/gruvbox.d.ts +3 -0
  173. package/dist/themes/data/gruvbox.js +177 -0
  174. package/dist/themes/data/index.d.ts +35 -0
  175. package/dist/themes/data/index.js +64 -0
  176. package/dist/themes/data/monokai.d.ts +3 -0
  177. package/dist/themes/data/monokai.js +177 -0
  178. package/dist/themes/data/nord.d.ts +3 -0
  179. package/dist/themes/data/nord.js +177 -0
  180. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  181. package/dist/themes/data/rose-pine-dawn.js +177 -0
  182. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  183. package/dist/themes/data/rose-pine-moon.js +177 -0
  184. package/dist/themes/data/rose-pine.d.ts +3 -0
  185. package/dist/themes/data/rose-pine.js +177 -0
  186. package/dist/themes/data/solarized-dark.d.ts +3 -0
  187. package/dist/themes/data/solarized-dark.js +177 -0
  188. package/dist/themes/data/solarized-light.d.ts +3 -0
  189. package/dist/themes/data/solarized-light.js +177 -0
  190. package/dist/themes/data/svg-export.d.ts +3 -0
  191. package/dist/themes/data/svg-export.js +177 -0
  192. package/dist/themes/data/textual-ansi.d.ts +3 -0
  193. package/dist/themes/data/textual-ansi.js +160 -0
  194. package/dist/themes/data/textual-dark.d.ts +3 -0
  195. package/dist/themes/data/textual-dark.js +160 -0
  196. package/dist/themes/data/textual-light.d.ts +3 -0
  197. package/dist/themes/data/textual-light.js +160 -0
  198. package/dist/themes/data/tokyo-night.d.ts +3 -0
  199. package/dist/themes/data/tokyo-night.js +177 -0
  200. package/dist/themes/data/types.d.ts +31 -0
  201. package/dist/themes/data/types.js +10 -0
  202. package/dist/themes/palette.d.ts +45 -0
  203. package/dist/themes/palette.js +71 -0
  204. package/dist/themes/ramp.d.ts +70 -0
  205. package/dist/themes/ramp.js +103 -0
  206. package/dist/themes/registry.d.ts +44 -0
  207. package/dist/themes/registry.js +105 -0
  208. package/dist/themes/terminalThemes.d.ts +49 -0
  209. package/dist/themes/terminalThemes.js +63 -0
  210. package/dist/themes/transpose.d.ts +87 -0
  211. package/dist/themes/transpose.js +138 -0
  212. package/dist/widgets/button.d.ts +45 -0
  213. package/dist/widgets/button.js +177 -0
  214. package/dist/widgets/checkbox.d.ts +38 -0
  215. package/dist/widgets/checkbox.js +135 -0
  216. package/dist/widgets/dropdown.d.ts +64 -0
  217. package/dist/widgets/dropdown.js +416 -0
  218. package/dist/widgets/event-router.d.ts +71 -0
  219. package/dist/widgets/event-router.js +629 -0
  220. package/dist/widgets/focus-manager.d.ts +45 -0
  221. package/dist/widgets/focus-manager.js +191 -0
  222. package/dist/widgets/hit.d.ts +64 -0
  223. package/dist/widgets/hit.js +113 -0
  224. package/dist/widgets/index.d.ts +20 -0
  225. package/dist/widgets/index.js +14 -0
  226. package/dist/widgets/ink.d.ts +22 -0
  227. package/dist/widgets/ink.js +31 -0
  228. package/dist/widgets/slider.d.ts +54 -0
  229. package/dist/widgets/slider.js +247 -0
  230. package/dist/widgets/static-item.d.ts +34 -0
  231. package/dist/widgets/static-item.js +54 -0
  232. package/dist/widgets/text-input.d.ts +283 -0
  233. package/dist/widgets/text-input.js +1174 -0
  234. package/dist/widgets/themed-widget.d.ts +20 -0
  235. package/dist/widgets/themed-widget.js +89 -0
  236. package/dist/widgets/toggle.d.ts +41 -0
  237. package/dist/widgets/toggle.js +157 -0
  238. package/dist/widgets/types.d.ts +87 -0
  239. package/dist/widgets/types.js +30 -0
  240. package/dist/widgets/widget-app.d.ts +65 -0
  241. package/dist/widgets/widget-app.js +206 -0
  242. package/dist/widgets/widget-base.d.ts +42 -0
  243. package/dist/widgets/widget-base.js +172 -0
  244. package/package.json +16 -2
@@ -0,0 +1,1174 @@
1
+ /**
2
+ * TextInput widget — editable text field with cursor.
3
+ *
4
+ * Two modes:
5
+ * - single-line (default): Enter submits; newlines in value are rendered
6
+ * as a literal `↵` glyph if they ever appear.
7
+ * - multi-line (`multiline: true`): Enter inserts a `\n`; `value` may
8
+ * contain logical line breaks; Up/Down navigate
9
+ * between logical lines preserving column intent.
10
+ *
11
+ * [LAW:dataflow-not-control-flow] cursor and value are observable data; the
12
+ * keymap dispatches to small `@action` mutators that all flow through the
13
+ * same value/cursor update path. There is no per-key bespoke branch in the
14
+ * render loop or the change-emit path.
15
+ *
16
+ * [LAW:one-source-of-truth] The cursor's logical line — and therefore "what
17
+ * Home/End/Up/Down mean" — is derived from `value` and `cursorPosition` via
18
+ * `_lineStart()` / `_lineEnd()`. Multi-line behavior is the same code path
19
+ * as single-line; with no `\n` in `value`, line bounds collapse to value
20
+ * bounds, so single-line semantics are recovered without a special case.
21
+ *
22
+ * [LAW:types-are-the-program] Three integer spaces coexist: `CellCol` (terminal
23
+ * cell columns), `CodeUnit` (JS UTF-16 indices), and `CodePoint` (code-unit
24
+ * offsets that are additionally on a Unicode code-point boundary — a subtype of
25
+ * `CodeUnit`). `cursorPosition` is `CodePoint`, and stronger: its setter
26
+ * parses any offset to a grapheme-cluster boundary. Visual positions are `CellCol`. The three are never
27
+ * interchangeable — the type system enforces this at every crossing point.
28
+ *
29
+ * [LAW:one-source-of-truth] The cursor moves, deletes and draws one grapheme
30
+ * cluster at a time (`nextGrapheme` / `prevGrapheme`) — the unit `cellLen`
31
+ * measures — so `👍🏽` is one step and one cursor cell, never a glyph and a
32
+ * stray skin-tone swatch.
33
+ *
34
+ * Keymap (readline / emacs compatible):
35
+ *
36
+ * ─── motion ───
37
+ * left | Ctrl+B char left
38
+ * right | Ctrl+F char right
39
+ * up | Ctrl+P line up (preserves preferred column)
40
+ * down | Ctrl+N line down (preserves preferred column)
41
+ * home | Ctrl+A line start
42
+ * end | Ctrl+E line end
43
+ * Ctrl+Home document start
44
+ * Ctrl+End document end
45
+ * Ctrl+Left | Alt+Left | Alt+B word left
46
+ * Ctrl+Right | Alt+Right | Alt+F word right
47
+ *
48
+ * ─── editing ───
49
+ * backspace | Ctrl+H delete char back
50
+ * delete | Ctrl+D delete char forward
51
+ * Ctrl+W | Alt+Backspace delete word back (whitespace-bounded; readline parity)
52
+ * Alt+D delete word forward
53
+ * Ctrl+U kill to line start (stores in kill buffer)
54
+ * Ctrl+K kill to line end (stores in kill buffer)
55
+ * Ctrl+Y yank kill buffer at cursor
56
+ * Ctrl+T transpose chars (swap pre-cursor/at-cursor, advance)
57
+ * enter submit (single-line) or insert `\n` (multiline)
58
+ *
59
+ * Motion and editing primitives are also exposed as public methods
60
+ * (`moveCharLeft`, `killLineForward`, etc.) so a host can bind custom keys
61
+ * or invoke them programmatically.
62
+ */
63
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
64
+ var useValue = arguments.length > 2;
65
+ for (var i = 0; i < initializers.length; i++) {
66
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
67
+ }
68
+ return useValue ? value : void 0;
69
+ };
70
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
71
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
72
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
73
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
74
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
75
+ var _, done = false;
76
+ for (var i = decorators.length - 1; i >= 0; i--) {
77
+ var context = {};
78
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
79
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
80
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
81
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
82
+ if (kind === "accessor") {
83
+ if (result === void 0) continue;
84
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
85
+ if (_ = accept(result.get)) descriptor.get = _;
86
+ if (_ = accept(result.set)) descriptor.set = _;
87
+ if (_ = accept(result.init)) initializers.unshift(_);
88
+ }
89
+ else if (_ = accept(result)) {
90
+ if (kind === "field") initializers.unshift(_);
91
+ else descriptor[key] = _;
92
+ }
93
+ }
94
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
95
+ done = true;
96
+ };
97
+ import { observable, action, observableRef, runInAction } from "mobx";
98
+ import { Segment } from "../core/segment.js";
99
+ import { Style } from "../core/style.js";
100
+ import { cellLen, setCellSize, splitText, cellStepFrom, cellColToCodeUnitOffset, asCellCol, asCodePoint, asCodeUnit, graphemeBoundary, graphemes, nextGrapheme, prevGrapheme, } from "../core/cells.js";
101
+ import { drawable } from "../core/protocol.js";
102
+ import { ThemedWidget } from "./themed-widget.js";
103
+ import { ink } from "./ink.js";
104
+ /**
105
+ * Character-greedy soft wrap. Breaks at any character once `firstWidth`
106
+ * (or `continuationWidth` for continuation rows) cells are exhausted. The
107
+ * textarea default when a consumer says "wrap, I don't care how" — no syntax
108
+ * awareness, just fits the line to the width.
109
+ *
110
+ * Wide characters (CJK, emoji) are treated as atomic: a char that would
111
+ * straddle the budget is moved to the next row rather than split, and one too
112
+ * wide for the whole budget is force-taken — see `cellStepFrom`.
113
+ */
114
+ export const charGreedyWrap = (line, { firstWidth, continuationWidth }) => {
115
+ if (line.length === 0)
116
+ return [{ content: "", start: asCodePoint(0) }];
117
+ const rows = [];
118
+ let pos = asCodePoint(0);
119
+ let isFirst = true;
120
+ while (pos < line.length) {
121
+ const cap = isFirst ? firstWidth : continuationWidth;
122
+ if (cap <= 0)
123
+ break;
124
+ const end = cellStepFrom(line, pos, cap);
125
+ rows.push({ content: line.slice(pos, end), start: pos });
126
+ pos = end;
127
+ isFirst = false;
128
+ }
129
+ return rows;
130
+ };
131
+ const MIN_CONTENT_WIDTH = 8;
132
+ // Each glyph and its ASCII stand-in are one code unit and one cell, so the
133
+ // display string maps 1:1 onto `value` and hit-testing needs no render options.
134
+ const NEWLINE_GLYPH = "↵";
135
+ const ASCII_NEWLINE_GLYPH = "~";
136
+ const MASK_GLYPH = "•";
137
+ const ASCII_MASK_GLYPH = "*";
138
+ // Text a key may insert: anything but the C0 controls, DEL and the C1
139
+ // controls, which a terminal reads as escape sequences (U+009B is CSI).
140
+ const PRINTABLE = /^[^\x00-\x1f\x7f-\x9f]+$/u;
141
+ // Word-character regex used by Alt+B/F and Ctrl+Left/Right. Matches the
142
+ // readline default (alphanumeric + underscore), which is what users typing
143
+ // shell commands or code identifiers expect.
144
+ const WORD_CHAR_RE = /[A-Za-z0-9_]/;
145
+ const WHITESPACE_RE = /\s/;
146
+ /** CRLF and lone CR as LF, the line break `value` holds. */
147
+ function toLf(text) {
148
+ return text.replace(/\r\n?/g, "\n");
149
+ }
150
+ function isWordChar(c) {
151
+ return c !== undefined && WORD_CHAR_RE.test(c);
152
+ }
153
+ function isWhitespace(c) {
154
+ return c !== undefined && WHITESPACE_RE.test(c);
155
+ }
156
+ let TextInput = (() => {
157
+ let _classSuper = ThemedWidget;
158
+ let _instanceExtraInitializers = [];
159
+ let __value_decorators;
160
+ let __value_initializers = [];
161
+ let __value_extraInitializers = [];
162
+ let __cursor_decorators;
163
+ let __cursor_initializers = [];
164
+ let __cursor_extraInitializers = [];
165
+ let _placeholder_decorators;
166
+ let _placeholder_initializers = [];
167
+ let _placeholder_extraInitializers = [];
168
+ let _indicatorStyleOverride_decorators;
169
+ let _indicatorStyleOverride_initializers = [];
170
+ let _indicatorStyleOverride_extraInitializers = [];
171
+ let _cursorStyleOverride_decorators;
172
+ let _cursorStyleOverride_initializers = [];
173
+ let _cursorStyleOverride_extraInitializers = [];
174
+ let _contentStyleOverride_decorators;
175
+ let _contentStyleOverride_initializers = [];
176
+ let _contentStyleOverride_extraInitializers = [];
177
+ let _handleKey_decorators;
178
+ let _handleMouse_decorators;
179
+ let _setHovered_decorators;
180
+ let _moveCharLeft_decorators;
181
+ let _moveCharRight_decorators;
182
+ let _moveLineUp_decorators;
183
+ let _moveLineDown_decorators;
184
+ let _moveLineStart_decorators;
185
+ let _moveLineEnd_decorators;
186
+ let _moveDocStart_decorators;
187
+ let _moveDocEnd_decorators;
188
+ let _moveWordLeft_decorators;
189
+ let _moveWordRight_decorators;
190
+ let _deleteCharBack_decorators;
191
+ let _deleteCharForward_decorators;
192
+ let _deleteWordBack_decorators;
193
+ let _deleteWordForward_decorators;
194
+ let _killLineBack_decorators;
195
+ let _killLineForward_decorators;
196
+ let _yank_decorators;
197
+ let _transposeChars_decorators;
198
+ let __insertText_decorators;
199
+ return class TextInput extends _classSuper {
200
+ static {
201
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
202
+ __value_decorators = [observable];
203
+ __cursor_decorators = [observable];
204
+ _placeholder_decorators = [observableRef];
205
+ _indicatorStyleOverride_decorators = [observableRef];
206
+ _cursorStyleOverride_decorators = [observableRef];
207
+ _contentStyleOverride_decorators = [observableRef];
208
+ _handleKey_decorators = [action];
209
+ _handleMouse_decorators = [action];
210
+ _setHovered_decorators = [action];
211
+ _moveCharLeft_decorators = [action];
212
+ _moveCharRight_decorators = [action];
213
+ _moveLineUp_decorators = [action];
214
+ _moveLineDown_decorators = [action];
215
+ _moveLineStart_decorators = [action];
216
+ _moveLineEnd_decorators = [action];
217
+ _moveDocStart_decorators = [action];
218
+ _moveDocEnd_decorators = [action];
219
+ _moveWordLeft_decorators = [action];
220
+ _moveWordRight_decorators = [action];
221
+ _deleteCharBack_decorators = [action];
222
+ _deleteCharForward_decorators = [action];
223
+ _deleteWordBack_decorators = [action];
224
+ _deleteWordForward_decorators = [action];
225
+ _killLineBack_decorators = [action];
226
+ _killLineForward_decorators = [action];
227
+ _yank_decorators = [action];
228
+ _transposeChars_decorators = [action];
229
+ __insertText_decorators = [action];
230
+ __esDecorate(this, null, __value_decorators, { kind: "accessor", name: "_value", static: false, private: false, access: { has: obj => "_value" in obj, get: obj => obj._value, set: (obj, value) => { obj._value = value; } }, metadata: _metadata }, __value_initializers, __value_extraInitializers);
231
+ __esDecorate(this, null, __cursor_decorators, { kind: "accessor", name: "_cursor", static: false, private: false, access: { has: obj => "_cursor" in obj, get: obj => obj._cursor, set: (obj, value) => { obj._cursor = value; } }, metadata: _metadata }, __cursor_initializers, __cursor_extraInitializers);
232
+ __esDecorate(this, null, _placeholder_decorators, { kind: "accessor", name: "placeholder", static: false, private: false, access: { has: obj => "placeholder" in obj, get: obj => obj.placeholder, set: (obj, value) => { obj.placeholder = value; } }, metadata: _metadata }, _placeholder_initializers, _placeholder_extraInitializers);
233
+ __esDecorate(this, null, _indicatorStyleOverride_decorators, { kind: "accessor", name: "indicatorStyleOverride", static: false, private: false, access: { has: obj => "indicatorStyleOverride" in obj, get: obj => obj.indicatorStyleOverride, set: (obj, value) => { obj.indicatorStyleOverride = value; } }, metadata: _metadata }, _indicatorStyleOverride_initializers, _indicatorStyleOverride_extraInitializers);
234
+ __esDecorate(this, null, _cursorStyleOverride_decorators, { kind: "accessor", name: "cursorStyleOverride", static: false, private: false, access: { has: obj => "cursorStyleOverride" in obj, get: obj => obj.cursorStyleOverride, set: (obj, value) => { obj.cursorStyleOverride = value; } }, metadata: _metadata }, _cursorStyleOverride_initializers, _cursorStyleOverride_extraInitializers);
235
+ __esDecorate(this, null, _contentStyleOverride_decorators, { kind: "accessor", name: "contentStyleOverride", static: false, private: false, access: { has: obj => "contentStyleOverride" in obj, get: obj => obj.contentStyleOverride, set: (obj, value) => { obj.contentStyleOverride = value; } }, metadata: _metadata }, _contentStyleOverride_initializers, _contentStyleOverride_extraInitializers);
236
+ __esDecorate(this, null, _handleKey_decorators, { kind: "method", name: "handleKey", static: false, private: false, access: { has: obj => "handleKey" in obj, get: obj => obj.handleKey }, metadata: _metadata }, null, _instanceExtraInitializers);
237
+ __esDecorate(this, null, _handleMouse_decorators, { kind: "method", name: "handleMouse", static: false, private: false, access: { has: obj => "handleMouse" in obj, get: obj => obj.handleMouse }, metadata: _metadata }, null, _instanceExtraInitializers);
238
+ __esDecorate(this, null, _setHovered_decorators, { kind: "method", name: "setHovered", static: false, private: false, access: { has: obj => "setHovered" in obj, get: obj => obj.setHovered }, metadata: _metadata }, null, _instanceExtraInitializers);
239
+ __esDecorate(this, null, _moveCharLeft_decorators, { kind: "method", name: "moveCharLeft", static: false, private: false, access: { has: obj => "moveCharLeft" in obj, get: obj => obj.moveCharLeft }, metadata: _metadata }, null, _instanceExtraInitializers);
240
+ __esDecorate(this, null, _moveCharRight_decorators, { kind: "method", name: "moveCharRight", static: false, private: false, access: { has: obj => "moveCharRight" in obj, get: obj => obj.moveCharRight }, metadata: _metadata }, null, _instanceExtraInitializers);
241
+ __esDecorate(this, null, _moveLineUp_decorators, { kind: "method", name: "moveLineUp", static: false, private: false, access: { has: obj => "moveLineUp" in obj, get: obj => obj.moveLineUp }, metadata: _metadata }, null, _instanceExtraInitializers);
242
+ __esDecorate(this, null, _moveLineDown_decorators, { kind: "method", name: "moveLineDown", static: false, private: false, access: { has: obj => "moveLineDown" in obj, get: obj => obj.moveLineDown }, metadata: _metadata }, null, _instanceExtraInitializers);
243
+ __esDecorate(this, null, _moveLineStart_decorators, { kind: "method", name: "moveLineStart", static: false, private: false, access: { has: obj => "moveLineStart" in obj, get: obj => obj.moveLineStart }, metadata: _metadata }, null, _instanceExtraInitializers);
244
+ __esDecorate(this, null, _moveLineEnd_decorators, { kind: "method", name: "moveLineEnd", static: false, private: false, access: { has: obj => "moveLineEnd" in obj, get: obj => obj.moveLineEnd }, metadata: _metadata }, null, _instanceExtraInitializers);
245
+ __esDecorate(this, null, _moveDocStart_decorators, { kind: "method", name: "moveDocStart", static: false, private: false, access: { has: obj => "moveDocStart" in obj, get: obj => obj.moveDocStart }, metadata: _metadata }, null, _instanceExtraInitializers);
246
+ __esDecorate(this, null, _moveDocEnd_decorators, { kind: "method", name: "moveDocEnd", static: false, private: false, access: { has: obj => "moveDocEnd" in obj, get: obj => obj.moveDocEnd }, metadata: _metadata }, null, _instanceExtraInitializers);
247
+ __esDecorate(this, null, _moveWordLeft_decorators, { kind: "method", name: "moveWordLeft", static: false, private: false, access: { has: obj => "moveWordLeft" in obj, get: obj => obj.moveWordLeft }, metadata: _metadata }, null, _instanceExtraInitializers);
248
+ __esDecorate(this, null, _moveWordRight_decorators, { kind: "method", name: "moveWordRight", static: false, private: false, access: { has: obj => "moveWordRight" in obj, get: obj => obj.moveWordRight }, metadata: _metadata }, null, _instanceExtraInitializers);
249
+ __esDecorate(this, null, _deleteCharBack_decorators, { kind: "method", name: "deleteCharBack", static: false, private: false, access: { has: obj => "deleteCharBack" in obj, get: obj => obj.deleteCharBack }, metadata: _metadata }, null, _instanceExtraInitializers);
250
+ __esDecorate(this, null, _deleteCharForward_decorators, { kind: "method", name: "deleteCharForward", static: false, private: false, access: { has: obj => "deleteCharForward" in obj, get: obj => obj.deleteCharForward }, metadata: _metadata }, null, _instanceExtraInitializers);
251
+ __esDecorate(this, null, _deleteWordBack_decorators, { kind: "method", name: "deleteWordBack", static: false, private: false, access: { has: obj => "deleteWordBack" in obj, get: obj => obj.deleteWordBack }, metadata: _metadata }, null, _instanceExtraInitializers);
252
+ __esDecorate(this, null, _deleteWordForward_decorators, { kind: "method", name: "deleteWordForward", static: false, private: false, access: { has: obj => "deleteWordForward" in obj, get: obj => obj.deleteWordForward }, metadata: _metadata }, null, _instanceExtraInitializers);
253
+ __esDecorate(this, null, _killLineBack_decorators, { kind: "method", name: "killLineBack", static: false, private: false, access: { has: obj => "killLineBack" in obj, get: obj => obj.killLineBack }, metadata: _metadata }, null, _instanceExtraInitializers);
254
+ __esDecorate(this, null, _killLineForward_decorators, { kind: "method", name: "killLineForward", static: false, private: false, access: { has: obj => "killLineForward" in obj, get: obj => obj.killLineForward }, metadata: _metadata }, null, _instanceExtraInitializers);
255
+ __esDecorate(this, null, _yank_decorators, { kind: "method", name: "yank", static: false, private: false, access: { has: obj => "yank" in obj, get: obj => obj.yank }, metadata: _metadata }, null, _instanceExtraInitializers);
256
+ __esDecorate(this, null, _transposeChars_decorators, { kind: "method", name: "transposeChars", static: false, private: false, access: { has: obj => "transposeChars" in obj, get: obj => obj.transposeChars }, metadata: _metadata }, null, _instanceExtraInitializers);
257
+ __esDecorate(this, null, __insertText_decorators, { kind: "method", name: "_insertText", static: false, private: false, access: { has: obj => "_insertText" in obj, get: obj => obj._insertText }, metadata: _metadata }, null, _instanceExtraInitializers);
258
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
259
+ }
260
+ id = __runInitializers(this, _instanceExtraInitializers);
261
+ focusable = true;
262
+ #_value_accessor_storage = __runInitializers(this, __value_initializers, void 0);
263
+ get _value() { return this.#_value_accessor_storage; }
264
+ set _value(value) { this.#_value_accessor_storage = value; }
265
+ #_cursor_accessor_storage = (__runInitializers(this, __value_extraInitializers), __runInitializers(this, __cursor_initializers, void 0));
266
+ get _cursor() { return this.#_cursor_accessor_storage; }
267
+ set _cursor(value) { this.#_cursor_accessor_storage = value; }
268
+ // [LAW:single-enforcer] Every write to either half of the pair passes
269
+ // `graphemeBoundary`, so the cursor is a cluster boundary inside `value`
270
+ // whoever set it last — a caller replacing `value`, a click, an insert that
271
+ // grew the cluster before it. A line break is `\n`, as in an HTML
272
+ // textarea's value: `\r\n` is one cluster, and a cursor could not rest at
273
+ // its line's end.
274
+ get value() { return this._value; }
275
+ set value(v) {
276
+ runInAction(() => {
277
+ this._value = toLf(v);
278
+ this._cursor = graphemeBoundary(this._value, this._cursor);
279
+ });
280
+ }
281
+ get cursorPosition() { return this._cursor; }
282
+ set cursorPosition(p) { this._cursor = graphemeBoundary(this._value, p); }
283
+ #placeholder_accessor_storage = (__runInitializers(this, __cursor_extraInitializers), __runInitializers(this, _placeholder_initializers, void 0));
284
+ get placeholder() { return this.#placeholder_accessor_storage; }
285
+ set placeholder(value) { this.#placeholder_accessor_storage = value; }
286
+ _maxLength = __runInitializers(this, _placeholder_extraInitializers);
287
+ _password;
288
+ _multiline;
289
+ _wrap;
290
+ _continuationMarker;
291
+ _markerWidth;
292
+ _maxRows;
293
+ _minRows;
294
+ _scrollIndicator;
295
+ #indicatorStyleOverride_accessor_storage = __runInitializers(this, _indicatorStyleOverride_initializers, void 0);
296
+ get indicatorStyleOverride() { return this.#indicatorStyleOverride_accessor_storage; }
297
+ set indicatorStyleOverride(value) { this.#indicatorStyleOverride_accessor_storage = value; }
298
+ #cursorStyleOverride_accessor_storage = (__runInitializers(this, _indicatorStyleOverride_extraInitializers), __runInitializers(this, _cursorStyleOverride_initializers, void 0));
299
+ get cursorStyleOverride() { return this.#cursorStyleOverride_accessor_storage; }
300
+ set cursorStyleOverride(value) { this.#cursorStyleOverride_accessor_storage = value; }
301
+ #contentStyleOverride_accessor_storage = (__runInitializers(this, _cursorStyleOverride_extraInitializers), __runInitializers(this, _contentStyleOverride_initializers, void 0));
302
+ get contentStyleOverride() { return this.#contentStyleOverride_accessor_storage; }
303
+ set contentStyleOverride(value) { this.#contentStyleOverride_accessor_storage = value; }
304
+ /**
305
+ * Last computed visual-row decomposition. Cached at the end of `render()`
306
+ * so vertical motion (Up/Down) can step row-by-row without re-running the
307
+ * wrap strategy. Null before the first render — vertical motion falls
308
+ * back to logical-line motion in that case.
309
+ *
310
+ * [LAW:dataflow-not-control-flow] One source of truth for "what does Up
311
+ * mean right now": the row table the renderer just produced. No parallel
312
+ * "where would the cursor go" math; both the renderer and the keymap
313
+ * read from the same array.
314
+ */
315
+ _visualRows = (__runInitializers(this, _contentStyleOverride_extraInitializers), null);
316
+ // [LAW:types-are-the-program] `_preferredColumn` is a cell column (not a
317
+ // code-unit index) — vertical motion navigates the visual surface, not the
318
+ // string's byte layout. Reset to null on every non-vertical motion or edit
319
+ // so the next Up/Down captures a fresh intent. Without this, Down through a
320
+ // short line then back Up would land at the short-line column instead of the
321
+ // original.
322
+ _preferredColumn = null;
323
+ // Single-slot kill buffer used by Ctrl+U / Ctrl+K → Ctrl+Y. Not a full
324
+ // kill ring (no Alt+Y rotation) — sufficient for the common cut/paste
325
+ // pattern that's the readline 90% case.
326
+ _killBuffer = "";
327
+ // [LAW:one-source-of-truth] Top visual row of the scroll viewport.
328
+ // Persisted across renders so the cursor can move *within* the viewport
329
+ // without dragging it along — the viewport scrolls only when the cursor
330
+ // would leave it. Without this, recomputing scroll from cursor position
331
+ // alone pins the cursor to the viewport's bottom edge whenever
332
+ // `cursorRow >= maxRows`, making every subsequent Up/Down scroll in
333
+ // lockstep with the cursor.
334
+ _scrollStart = 0;
335
+ // [LAW:one-source-of-truth] Viewport scroll offset in cells for the
336
+ // single-line renderer. Captured by _renderSingleLine and read by
337
+ // handleMouse so the hit-test uses the same visual origin the user sees.
338
+ _singleLineViewportStart = asCellCol(0);
339
+ _singleLineAsciiOnly = false;
340
+ multiline;
341
+ constructor(options = {}) {
342
+ super(options.theme);
343
+ this.id = options.id ?? `text-input-${Math.random().toString(36).slice(2, 8)}`;
344
+ this._value = toLf(options.value ?? "");
345
+ this.placeholder = options.placeholder ?? "";
346
+ // [LAW:dataflow-not-control-flow] Initial cursor position is data, not
347
+ // a render-time branch. Single-line inputs follow the pre-filled-form
348
+ // convention (cursor at end — typing continues the value). Multiline
349
+ // inputs follow the textarea convention (cursor at start — pre-loaded
350
+ // content is read top-to-bottom, and the viewport's "scroll into view"
351
+ // logic does nothing because row 0 is already inside the initial window).
352
+ this._cursor = graphemeBoundary(this._value, asCodeUnit((options.multiline ?? false) ? 0 : this._value.length));
353
+ this.disabled = options.disabled ?? false;
354
+ this._maxLength = options.maxLength;
355
+ this._password = options.password ?? false;
356
+ this._multiline = options.multiline ?? false;
357
+ this._wrap = options.wrap;
358
+ this._continuationMarker = options.continuationMarker ?? "↳ ";
359
+ this._markerWidth = asCellCol(cellLen(this._continuationMarker));
360
+ this._maxRows = options.maxRows;
361
+ this._minRows = options.minRows;
362
+ this._scrollIndicator = options.scrollIndicator ?? "arrows";
363
+ this.indicatorStyleOverride = options.indicatorStyle;
364
+ this.cursorStyleOverride = options.cursorStyle;
365
+ this.contentStyleOverride = options.contentStyle;
366
+ this.multiline = this._multiline;
367
+ }
368
+ // --- Event handlers ---
369
+ handleKey(event) {
370
+ if (this.disabled)
371
+ return;
372
+ // ─── editing keys (modifier-conscious) ───
373
+ if (event.key === "backspace") {
374
+ if (event.meta || event.ctrl)
375
+ this.deleteWordBack();
376
+ else
377
+ this.deleteCharBack();
378
+ event.stop();
379
+ return;
380
+ }
381
+ if (event.key === "delete") {
382
+ this.deleteCharForward();
383
+ event.stop();
384
+ return;
385
+ }
386
+ if (event.key === "enter") {
387
+ if (this._multiline && !event.ctrl)
388
+ this._insertText("\n");
389
+ else
390
+ this.emitSubmit();
391
+ event.stop();
392
+ return;
393
+ }
394
+ // Escape is intentionally ignored — text-input does not stop it so
395
+ // higher-level UI (dialogs, modals) can react to it.
396
+ if (event.key === "escape")
397
+ return;
398
+ // ─── unmodified motion ───
399
+ if (!event.ctrl && !event.meta) {
400
+ switch (event.key) {
401
+ case "left":
402
+ this.moveCharLeft();
403
+ event.stop();
404
+ return;
405
+ case "right":
406
+ this.moveCharRight();
407
+ event.stop();
408
+ return;
409
+ case "up":
410
+ this.moveLineUp();
411
+ event.stop();
412
+ return;
413
+ case "down":
414
+ this.moveLineDown();
415
+ event.stop();
416
+ return;
417
+ case "home":
418
+ this.moveLineStart();
419
+ event.stop();
420
+ return;
421
+ case "end":
422
+ this.moveLineEnd();
423
+ event.stop();
424
+ return;
425
+ }
426
+ }
427
+ // ─── Ctrl-modified motion + readline editing ───
428
+ if (event.ctrl && !event.meta) {
429
+ switch (event.key) {
430
+ case "left":
431
+ this.moveWordLeft();
432
+ event.stop();
433
+ return;
434
+ case "right":
435
+ this.moveWordRight();
436
+ event.stop();
437
+ return;
438
+ case "home":
439
+ this.moveDocStart();
440
+ event.stop();
441
+ return;
442
+ case "end":
443
+ this.moveDocEnd();
444
+ event.stop();
445
+ return;
446
+ case "a":
447
+ this.moveLineStart();
448
+ event.stop();
449
+ return;
450
+ case "e":
451
+ this.moveLineEnd();
452
+ event.stop();
453
+ return;
454
+ case "b":
455
+ this.moveCharLeft();
456
+ event.stop();
457
+ return;
458
+ case "f":
459
+ this.moveCharRight();
460
+ event.stop();
461
+ return;
462
+ case "p":
463
+ this.moveLineUp();
464
+ event.stop();
465
+ return;
466
+ case "n":
467
+ this.moveLineDown();
468
+ event.stop();
469
+ return;
470
+ case "d":
471
+ this.deleteCharForward();
472
+ event.stop();
473
+ return;
474
+ case "h":
475
+ this.deleteCharBack();
476
+ event.stop();
477
+ return;
478
+ case "w":
479
+ this.deleteWordBack();
480
+ event.stop();
481
+ return;
482
+ case "u":
483
+ this.killLineBack();
484
+ event.stop();
485
+ return;
486
+ case "k":
487
+ this.killLineForward();
488
+ event.stop();
489
+ return;
490
+ case "y":
491
+ this.yank();
492
+ event.stop();
493
+ return;
494
+ case "t":
495
+ this.transposeChars();
496
+ event.stop();
497
+ return;
498
+ }
499
+ }
500
+ // ─── Alt-modified (meta) motion + editing ───
501
+ if (event.meta && !event.ctrl) {
502
+ switch (event.key) {
503
+ case "left":
504
+ this.moveWordLeft();
505
+ event.stop();
506
+ return;
507
+ case "right":
508
+ this.moveWordRight();
509
+ event.stop();
510
+ return;
511
+ case "b":
512
+ this.moveWordLeft();
513
+ event.stop();
514
+ return;
515
+ case "f":
516
+ this.moveWordRight();
517
+ event.stop();
518
+ return;
519
+ case "d":
520
+ this.deleteWordForward();
521
+ event.stop();
522
+ return;
523
+ }
524
+ }
525
+ // ─── printable insertion ───
526
+ // A non-control `event.character` with no command modifiers is text to
527
+ // insert. Ctrl/meta-modified keys produce empty `character` from the
528
+ // router (see event-router.ts) so they never reach this branch. The router
529
+ // sends one code point per key, so `👍🏽` arrives as `👍` then `🏽`, and an
530
+ // astral code point is two UTF-16 units — a `.length === 1` test drops it.
531
+ if (PRINTABLE.test(event.character) && !event.ctrl && !event.meta) {
532
+ this._insertText(event.character);
533
+ event.stop();
534
+ }
535
+ }
536
+ handleMouse(event) {
537
+ if (this.disabled)
538
+ return;
539
+ if (event.type !== "mouse_down")
540
+ return;
541
+ if (this._multiline) {
542
+ // Multiline has no brackets; rows may have a leading continuation marker.
543
+ // Use event.y to pick the visual row, then project event.x into its content.
544
+ const rows = this._visualRows;
545
+ if (!rows || rows.length === 0)
546
+ return;
547
+ const rawRowIdx = this._scrollStart + event.y;
548
+ // Clicks on minRows-padded empty rows (beyond the last real row) snap to end.
549
+ if (rawRowIdx >= rows.length) {
550
+ this.cursorPosition = asCodeUnit(this.value.length);
551
+ this._preferredColumn = null;
552
+ return;
553
+ }
554
+ const rowIdx = Math.max(0, rawRowIdx);
555
+ const row = rows[rowIdx];
556
+ // Continuation rows start after the marker; non-continuation rows start at column 0.
557
+ const contentXOff = asCellCol(row.isContinuation ? this._markerWidth : 0);
558
+ const relX = asCellCol(Math.max(0, event.x - contentXOff));
559
+ this.cursorPosition = asCodeUnit(row.valueStart + cellColToCodeUnitOffset(row.content, relX));
560
+ }
561
+ else {
562
+ // [LAW:types-are-the-program] relX is a cell-column offset into the full
563
+ // display string. Add _singleLineViewportStart (the scroll offset captured
564
+ // by _renderSingleLine) so a click on a scrolled viewport maps to the
565
+ // correct code-unit in the underlying value.
566
+ const relX = asCellCol(Math.max(0, event.x - 1) + this._singleLineViewportStart);
567
+ // The glyphs of `value` whose shown cells lie wholly left of the click,
568
+ // shown as the last render showed them.
569
+ const drawn = { asciiOnly: this._singleLineAsciiOnly };
570
+ let at = 0;
571
+ let col = 0;
572
+ for (const g of graphemes(this.value)) {
573
+ const w = cellLen(this._shown(g, drawn));
574
+ if (col + w > relX)
575
+ break;
576
+ col += w;
577
+ at += g.length;
578
+ }
579
+ this.cursorPosition = asCodeUnit(at);
580
+ }
581
+ this._preferredColumn = null;
582
+ }
583
+ // --- Hover mutator (router fast-path) ---
584
+ setHovered(value) { this.hovered = value; }
585
+ // ─── Public motion primitives ───────────────────────────────────────────
586
+ moveCharLeft() {
587
+ this.cursorPosition = prevGrapheme(this.value, this.cursorPosition);
588
+ this._preferredColumn = null;
589
+ }
590
+ moveCharRight() {
591
+ this.cursorPosition = nextGrapheme(this.value, this.cursorPosition);
592
+ this._preferredColumn = null;
593
+ }
594
+ moveLineUp() {
595
+ // Prefer the on-screen visual decomposition when it exists — wraps,
596
+ // marker offsets, and scroll alignment are all already accounted for.
597
+ // Fall back to logical-line motion only when no render has occurred yet
598
+ // (e.g. cursor motion before mount).
599
+ if (this._visualRows !== null && this._visualRows.length > 1) {
600
+ const rowIdx = this._cursorVisualRow();
601
+ if (rowIdx === 0)
602
+ return;
603
+ const col = this._preferredColumn ?? this._cursorVisualCol();
604
+ const target = this._visualRows[rowIdx - 1];
605
+ this.cursorPosition = asCodeUnit(target.valueStart + this._clampColForRow(col, rowIdx - 1));
606
+ this._preferredColumn = col;
607
+ return;
608
+ }
609
+ const lineStart = this._lineStart();
610
+ if (lineStart === 0)
611
+ return;
612
+ // Fallback: no render cache yet; use cell-space arithmetic for wide-char correctness.
613
+ const col = this._preferredColumn ?? asCellCol(cellLen(this.value.slice(lineStart, this.cursorPosition)));
614
+ const prevLineEnd = lineStart - 1;
615
+ let prevLineStart = prevLineEnd;
616
+ while (prevLineStart > 0 && this.value[prevLineStart - 1] !== "\n")
617
+ prevLineStart--;
618
+ const prevLineContent = this.value.slice(prevLineStart, prevLineEnd);
619
+ this.cursorPosition = asCodeUnit(prevLineStart + cellColToCodeUnitOffset(prevLineContent, asCellCol(Math.min(col, cellLen(prevLineContent)))));
620
+ this._preferredColumn = col;
621
+ }
622
+ moveLineDown() {
623
+ if (this._visualRows !== null && this._visualRows.length > 1) {
624
+ const rowIdx = this._cursorVisualRow();
625
+ if (rowIdx === this._visualRows.length - 1)
626
+ return;
627
+ const col = this._preferredColumn ?? this._cursorVisualCol();
628
+ this.cursorPosition = asCodeUnit(this._visualRows[rowIdx + 1].valueStart + this._clampColForRow(col, rowIdx + 1));
629
+ this._preferredColumn = col;
630
+ return;
631
+ }
632
+ const lineEnd = this._lineEnd();
633
+ if (lineEnd === this.value.length)
634
+ return;
635
+ const lineStart = this._lineStart();
636
+ // Fallback: no render cache yet; use cell-space arithmetic for wide-char correctness.
637
+ const col = this._preferredColumn ?? asCellCol(cellLen(this.value.slice(lineStart, this.cursorPosition)));
638
+ const nextLineStart = lineEnd + 1;
639
+ let nextLineEnd = nextLineStart;
640
+ while (nextLineEnd < this.value.length && this.value[nextLineEnd] !== "\n")
641
+ nextLineEnd++;
642
+ const nextLineContent = this.value.slice(nextLineStart, nextLineEnd);
643
+ this.cursorPosition = asCodeUnit(nextLineStart + cellColToCodeUnitOffset(nextLineContent, asCellCol(Math.min(col, cellLen(nextLineContent)))));
644
+ this._preferredColumn = col;
645
+ }
646
+ // Clamp `col` (a cell column) to the target visual row's content, returning
647
+ // the code-unit offset within that row. When the row IS followed by a
648
+ // continuation of the same logical line, we clamp to the last valid
649
+ // cluster boundary strictly inside the row (which may be more than
650
+ // one cell before the visual end when the last glyph is wide) so the
651
+ // cursor stays strictly inside the target row — landing at the boundary
652
+ // would cause `_cursorVisualRow` to resolve to the *later* row, making
653
+ // every subsequent Up/Down stick at the boundary.
654
+ //
655
+ // For rows NOT followed by a continuation (last wrap row, or any unwrapped
656
+ // logical line), the end-of-line position is valid — there is no adjacent
657
+ // continuation row to collide with.
658
+ _clampColForRow(col, targetIdx) {
659
+ const rows = this._visualRows;
660
+ const target = rows[targetIdx];
661
+ const nextIsContinuation = targetIdx + 1 < rows.length && rows[targetIdx + 1].isContinuation;
662
+ const contentCellWidth = asCellCol(cellLen(target.content));
663
+ const capCells = nextIsContinuation
664
+ ? asCellCol(Math.max(0, contentCellWidth - 1))
665
+ : contentCellWidth;
666
+ return cellColToCodeUnitOffset(target.content, asCellCol(Math.min(col, capCells)));
667
+ }
668
+ // Locate which cached visual row the cursor sits on. Returns the row index
669
+ // for use in `moveLineUp`/`moveLineDown`; caller must guard `_visualRows`.
670
+ _cursorVisualRow() {
671
+ const rows = this._visualRows;
672
+ let idx = 0;
673
+ for (let i = rows.length - 1; i >= 0; i--) {
674
+ if (rows[i].valueStart <= this.cursorPosition) {
675
+ idx = i;
676
+ break;
677
+ }
678
+ }
679
+ return idx;
680
+ }
681
+ // Returns the cursor's cell column within its current visual row.
682
+ _cursorVisualCol() {
683
+ const rows = this._visualRows;
684
+ const idx = this._cursorVisualRow();
685
+ const row = rows[idx];
686
+ return asCellCol(cellLen(row.content.slice(0, this.cursorPosition - row.valueStart)));
687
+ }
688
+ moveLineStart() {
689
+ this.cursorPosition = this._lineStart();
690
+ this._preferredColumn = null;
691
+ }
692
+ moveLineEnd() {
693
+ this.cursorPosition = this._lineEnd();
694
+ this._preferredColumn = null;
695
+ }
696
+ moveDocStart() {
697
+ this.cursorPosition = asCodeUnit(0);
698
+ this._preferredColumn = null;
699
+ }
700
+ moveDocEnd() {
701
+ this.cursorPosition = asCodeUnit(this.value.length);
702
+ this._preferredColumn = null;
703
+ }
704
+ moveWordLeft() {
705
+ let p = this.cursorPosition;
706
+ while (p > 0) {
707
+ const prev = prevGrapheme(this.value, p);
708
+ if (isWordChar(this.value.slice(prev, p)))
709
+ break;
710
+ p = prev;
711
+ }
712
+ while (p > 0) {
713
+ const prev = prevGrapheme(this.value, p);
714
+ if (!isWordChar(this.value.slice(prev, p)))
715
+ break;
716
+ p = prev;
717
+ }
718
+ this.cursorPosition = p;
719
+ this._preferredColumn = null;
720
+ }
721
+ moveWordRight() {
722
+ let p = this.cursorPosition;
723
+ while (p < this.value.length) {
724
+ const next = nextGrapheme(this.value, p);
725
+ if (isWordChar(this.value.slice(p, next)))
726
+ break;
727
+ p = next;
728
+ }
729
+ while (p < this.value.length) {
730
+ const next = nextGrapheme(this.value, p);
731
+ if (!isWordChar(this.value.slice(p, next)))
732
+ break;
733
+ p = next;
734
+ }
735
+ this.cursorPosition = p;
736
+ this._preferredColumn = null;
737
+ }
738
+ // ─── Public editing primitives ──────────────────────────────────────────
739
+ deleteCharBack() {
740
+ if (this.cursorPosition === 0)
741
+ return;
742
+ const newPos = prevGrapheme(this.value, this.cursorPosition);
743
+ this.value = this.value.slice(0, newPos) + this.value.slice(this.cursorPosition);
744
+ this.cursorPosition = newPos;
745
+ this._preferredColumn = null;
746
+ this.emitChange();
747
+ }
748
+ deleteCharForward() {
749
+ if (this.cursorPosition >= this.value.length)
750
+ return;
751
+ const nextPos = nextGrapheme(this.value, this.cursorPosition);
752
+ this.value = this.value.slice(0, this.cursorPosition) + this.value.slice(nextPos);
753
+ this._preferredColumn = null;
754
+ this.emitChange();
755
+ }
756
+ deleteWordBack() {
757
+ // Readline `unix-word-rubout` semantics: delete back to nearest whitespace,
758
+ // skipping trailing whitespace first so successive Ctrl+W at "foo bar |"
759
+ // → "foo |" → "|" rather than getting stuck on the trailing space.
760
+ let p = this.cursorPosition;
761
+ while (p > 0) {
762
+ const prev = prevGrapheme(this.value, p);
763
+ if (!isWhitespace(this.value.slice(prev, p)))
764
+ break;
765
+ p = prev;
766
+ }
767
+ while (p > 0) {
768
+ const prev = prevGrapheme(this.value, p);
769
+ if (isWhitespace(this.value.slice(prev, p)))
770
+ break;
771
+ p = prev;
772
+ }
773
+ if (p === this.cursorPosition)
774
+ return;
775
+ this._killBuffer = this.value.slice(p, this.cursorPosition);
776
+ this.value = this.value.slice(0, p) + this.value.slice(this.cursorPosition);
777
+ this.cursorPosition = p;
778
+ this._preferredColumn = null;
779
+ this.emitChange();
780
+ }
781
+ deleteWordForward() {
782
+ let p = this.cursorPosition;
783
+ while (p < this.value.length) {
784
+ const next = nextGrapheme(this.value, p);
785
+ if (isWordChar(this.value.slice(p, next)))
786
+ break;
787
+ p = next;
788
+ }
789
+ while (p < this.value.length) {
790
+ const next = nextGrapheme(this.value, p);
791
+ if (!isWordChar(this.value.slice(p, next)))
792
+ break;
793
+ p = next;
794
+ }
795
+ if (p === this.cursorPosition)
796
+ return;
797
+ this._killBuffer = this.value.slice(this.cursorPosition, p);
798
+ this.value = this.value.slice(0, this.cursorPosition) + this.value.slice(p);
799
+ this._preferredColumn = null;
800
+ this.emitChange();
801
+ }
802
+ killLineBack() {
803
+ const start = this._lineStart();
804
+ if (start === this.cursorPosition)
805
+ return;
806
+ this._killBuffer = this.value.slice(start, this.cursorPosition);
807
+ this.value = this.value.slice(0, start) + this.value.slice(this.cursorPosition);
808
+ this.cursorPosition = start;
809
+ this._preferredColumn = null;
810
+ this.emitChange();
811
+ }
812
+ killLineForward() {
813
+ const end = this._lineEnd();
814
+ if (end > this.cursorPosition) {
815
+ this._killBuffer = this.value.slice(this.cursorPosition, end);
816
+ this.value = this.value.slice(0, this.cursorPosition) + this.value.slice(end);
817
+ this._preferredColumn = null;
818
+ this.emitChange();
819
+ return;
820
+ }
821
+ // Already at line end → kill the trailing `\n` (join next line).
822
+ if (this.cursorPosition < this.value.length) {
823
+ this._killBuffer = "\n";
824
+ this.value = this.value.slice(0, this.cursorPosition) + this.value.slice(this.cursorPosition + 1);
825
+ this._preferredColumn = null;
826
+ this.emitChange();
827
+ }
828
+ }
829
+ yank() {
830
+ if (this._killBuffer.length === 0)
831
+ return;
832
+ this._insertText(this._killBuffer);
833
+ }
834
+ transposeChars() {
835
+ // Readline `transpose-chars`: swap the char before the cursor with the
836
+ // char at the cursor and advance. At end-of-value, swap the trailing
837
+ // two chars without advancing. At position 0, no-op.
838
+ if (this.cursorPosition === 0)
839
+ return;
840
+ const p = this.cursorPosition;
841
+ if (p === this.value.length) {
842
+ // End-of-value: swap the trailing two glyphs; cursor stays. One glyph
843
+ // has nothing to swap with.
844
+ const cp2 = prevGrapheme(this.value, p);
845
+ if (cp2 === 0)
846
+ return;
847
+ const cp1 = prevGrapheme(this.value, cp2);
848
+ this.value = this.value.slice(0, cp1) + this.value.slice(cp2) + this.value.slice(cp1, cp2);
849
+ this._preferredColumn = null;
850
+ this.emitChange();
851
+ return;
852
+ }
853
+ // Normal case: the whole glyphs before and at the cursor.
854
+ const cpBefore = prevGrapheme(this.value, p);
855
+ const cpAfter = nextGrapheme(this.value, p);
856
+ const charBefore = this.value.slice(cpBefore, p);
857
+ const charAt = this.value.slice(p, cpAfter);
858
+ this.value = this.value.slice(0, cpBefore) + charAt + charBefore + this.value.slice(cpAfter);
859
+ this.cursorPosition = cpAfter;
860
+ this._preferredColumn = null;
861
+ this.emitChange();
862
+ }
863
+ // ─── Internal helpers ───────────────────────────────────────────────────
864
+ _lineStart() {
865
+ let p = this.cursorPosition;
866
+ while (p > 0 && this.value[p - 1] !== "\n")
867
+ p--;
868
+ return asCodePoint(p);
869
+ }
870
+ _lineEnd() {
871
+ let p = this.cursorPosition;
872
+ while (p < this.value.length && this.value[p] !== "\n")
873
+ p++;
874
+ return asCodePoint(p);
875
+ }
876
+ _insertText(text) {
877
+ let toInsert = text;
878
+ if (this._maxLength !== undefined) {
879
+ const room = this._maxLength - this.value.length;
880
+ // Cut between glyphs: the longest run of whole clusters within `room`.
881
+ let fit = 0;
882
+ for (const g of graphemes(toInsert)) {
883
+ if (fit + g.length > room)
884
+ break;
885
+ fit += g.length;
886
+ }
887
+ toInsert = toInsert.slice(0, fit);
888
+ }
889
+ if (toInsert.length === 0)
890
+ return;
891
+ // The end is taken before the write: the value setter has already moved
892
+ // the cursor past a cluster the insert grew.
893
+ const end = asCodeUnit(this.cursorPosition + toInsert.length);
894
+ this.value =
895
+ this.value.slice(0, this.cursorPosition) +
896
+ toInsert +
897
+ this.value.slice(this.cursorPosition);
898
+ this.cursorPosition = end;
899
+ this._preferredColumn = null;
900
+ this.emitChange();
901
+ }
902
+ // --- Rendering ---
903
+ // [LAW:one-source-of-truth] The style a value is drawn in is also what a
904
+ // short row is padded with, so a caller's `contentStyle` fills the row.
905
+ get ground() {
906
+ return this.disabled
907
+ ? new Style(this.disabledInk)
908
+ : this.contentStyleOverride ?? super.ground;
909
+ }
910
+ draw(options) {
911
+ if (this._multiline)
912
+ return this._renderMultiline(options);
913
+ return this._renderSingleLine(options);
914
+ }
915
+ // [LAW:dataflow-not-control-flow] How a stretch of `value` is shown in one
916
+ // line derives from mode: one mask cell per glyph, or `\n` as a visible
917
+ // glyph. The render and the click hit-test both read it.
918
+ _shown(text, options) {
919
+ return this._password
920
+ ? drawable(options, MASK_GLYPH, ASCII_MASK_GLYPH).repeat(graphemes(text).length)
921
+ : text.replace(/\n/g, drawable(options, NEWLINE_GLYPH, ASCII_NEWLINE_GLYPH));
922
+ }
923
+ _renderSingleLine(options) {
924
+ const showPlaceholder = this.focused && this.value.length === 0 && this.placeholder.length > 0;
925
+ // The cursor's column is the width of the shown text before it, whatever
926
+ // the mode — a placeholder is shown only over an empty value.
927
+ this._singleLineAsciiOnly = options.asciiOnly ?? false;
928
+ const shown = (text) => this._shown(text, options);
929
+ const rawDisplay = showPlaceholder ? this.placeholder : shown(this.value);
930
+ const rawCellWidth = asCellCol(cellLen(rawDisplay));
931
+ const cursorCellCol = asCellCol(cellLen(shown(this.value.slice(0, this.cursorPosition))));
932
+ const maxAvailable = asCellCol(Math.max(MIN_CONTENT_WIDTH, options.maxWidth - 2));
933
+ const desiredWidth = asCellCol(Math.max(MIN_CONTENT_WIDTH, rawCellWidth, cursorCellCol + 1));
934
+ const contentWidth = asCellCol(Math.min(maxAvailable, desiredWidth));
935
+ // Slide viewport (in cells) to keep cursor visible when content overflows.
936
+ const startCell = asCellCol(Math.max(0, Math.min(rawCellWidth - contentWidth, cursorCellCol - contentWidth + 1)));
937
+ const [, afterStart] = splitText(rawDisplay, startCell);
938
+ // splitText snaps backward when startCell falls mid-wide-char, so afterStart
939
+ // may start 1 cell earlier than requested. actualStartCell is the real offset.
940
+ const actualStartCell = asCellCol(rawCellWidth - cellLen(afterStart));
941
+ this._singleLineViewportStart = actualStartCell;
942
+ const [visible] = splitText(afterStart, contentWidth);
943
+ const display = setCellSize(visible, contentWidth);
944
+ const cursorDisplayCellCol = asCellCol(cursorCellCol - actualStartCell);
945
+ const bracketStyle = this.disabled
946
+ ? new Style(this.disabledInk)
947
+ : new Style(ink(this.theme, "foreground", "background"));
948
+ const contentStyle = showPlaceholder && !this.disabled
949
+ ? new Style({ ...ink(this.theme, "foreground", "background"), dim: true })
950
+ : this.ground;
951
+ const cursorStyle = this.cursorStyleOverride ?? new Style(ink(this.theme, "on-primary", "primary"));
952
+ const segments = [new Segment("[", bracketStyle)];
953
+ if (this.focused && !this.disabled && cursorDisplayCellCol >= 0 && cursorDisplayCellCol < contentWidth) {
954
+ const [before, rest] = splitText(display, cursorDisplayCellCol);
955
+ const firstCh = rest.slice(0, nextGrapheme(rest, asCodePoint(0)));
956
+ const at = firstCh || " ";
957
+ const after = rest.slice(firstCh.length);
958
+ if (before.length > 0)
959
+ segments.push(new Segment(before, contentStyle));
960
+ segments.push(new Segment(at, cursorStyle));
961
+ if (after.length > 0)
962
+ segments.push(new Segment(after, contentStyle));
963
+ }
964
+ else {
965
+ segments.push(new Segment(display, contentStyle));
966
+ }
967
+ segments.push(new Segment("]", bracketStyle));
968
+ return segments;
969
+ }
970
+ _renderMultiline(options) {
971
+ // [LAW:dataflow-not-control-flow] Decompose `value` into visual rows
972
+ // exactly once per render, cache the result, then drive both display
973
+ // *and* cursor projection from the same array. Vertical motion reads
974
+ // from the cache so Up/Down step through whatever the user actually
975
+ // sees, including soft-wrap continuations.
976
+ const visualRows = this._computeVisualRows(options.maxWidth);
977
+ this._visualRows = visualRows;
978
+ // Scroll window: keep `_scrollStart` (the viewport's top row) stable
979
+ // across renders, adjusting only when the cursor would leave the
980
+ // viewport. This is the textarea-conventional "viewport follows cursor
981
+ // when it has to" behavior — cursor moves freely *within* the visible
982
+ // rows; the window scrolls only at the edges. When `maxRows` is unset,
983
+ // render every row.
984
+ const total = visualRows.length;
985
+ const cursorRow = this._cursorVisualRow();
986
+ let scrollStart = 0;
987
+ let visibleCount = total;
988
+ if (this._maxRows !== undefined && total > this._maxRows) {
989
+ const maxStart = total - this._maxRows;
990
+ if (cursorRow < this._scrollStart) {
991
+ this._scrollStart = cursorRow;
992
+ }
993
+ else if (cursorRow >= this._scrollStart + this._maxRows) {
994
+ this._scrollStart = cursorRow - this._maxRows + 1;
995
+ }
996
+ this._scrollStart = Math.max(0, Math.min(maxStart, this._scrollStart));
997
+ scrollStart = this._scrollStart;
998
+ visibleCount = this._maxRows;
999
+ }
1000
+ else {
1001
+ this._scrollStart = 0;
1002
+ }
1003
+ // Pad with empty rows when the value is shorter than `minRows`.
1004
+ let padRows = 0;
1005
+ if (this._minRows !== undefined && total < this._minRows && this._maxRows === undefined) {
1006
+ padRows = this._minRows - total;
1007
+ }
1008
+ const contentStyle = this.ground;
1009
+ const markerStyle = new Style({ ...ink(this.theme, "foreground", "background"), dim: true });
1010
+ const cursorStyle = this.cursorStyleOverride ?? new Style(ink(this.theme, "on-primary", "primary"));
1011
+ // Scroll-direction arrows appear in the rightmost cell of the first/last
1012
+ // visible row, *only* when scroll is actually possible in that direction.
1013
+ // _emitRowContent reserves the indicator's cells so content never
1014
+ // collides with the arrow. Only emitted in `"arrows"` mode; other modes
1015
+ // leave the content area untouched and rely on the host to surface scroll
1016
+ // state externally (e.g. Panel border accessory).
1017
+ const scrollable = this._maxRows !== undefined && total > this._maxRows;
1018
+ const arrowsMode = this._scrollIndicator === "arrows";
1019
+ const canScrollUp = arrowsMode && scrollable && this._scrollStart > 0;
1020
+ const canScrollDown = arrowsMode && scrollable && this._scrollStart + this._maxRows < total;
1021
+ const indicatorStyle = this.indicatorStyleOverride ?? new Style(ink(this.theme, "text-primary", "background"));
1022
+ const segments = [];
1023
+ const showCursor = this.focused && !this.disabled;
1024
+ for (let i = 0; i < visibleCount; i++) {
1025
+ const rowIdx = scrollStart + i;
1026
+ const row = visualRows[rowIdx];
1027
+ if (i > 0)
1028
+ segments.push(new Segment("\n"));
1029
+ if (row.isContinuation) {
1030
+ // An ASCII stand-in as wide as the marker, so the rows keep their geometry.
1031
+ segments.push(new Segment(drawable(options, this._continuationMarker, ">".padEnd(this._markerWidth)), markerStyle));
1032
+ }
1033
+ let indicator;
1034
+ if (i === 0 && canScrollUp) {
1035
+ indicator = { ch: drawable(options, "▲", "^"), style: indicatorStyle };
1036
+ }
1037
+ else if (i === visibleCount - 1 && canScrollDown) {
1038
+ indicator = { ch: drawable(options, "▼", "v"), style: indicatorStyle };
1039
+ }
1040
+ // When a continuation marker was emitted, the row's printable width is
1041
+ // reduced by the marker width — the indicator column is still at
1042
+ // `options.maxWidth - 1` in the panel's view, which is
1043
+ // `options.maxWidth - markerWidth - 1` columns past the marker.
1044
+ const rowPrintWidth = row.isContinuation
1045
+ ? asCellCol(Math.max(0, options.maxWidth - this._markerWidth))
1046
+ : asCellCol(options.maxWidth);
1047
+ this._emitRowContent(segments, row, rowIdx === cursorRow && showCursor, contentStyle, cursorStyle, indicator, rowPrintWidth);
1048
+ }
1049
+ // Trailing empty rows for minRows padding (no cursor, no marker).
1050
+ // Emit a whitespace segment of the full content width so Segment.splitLines
1051
+ // does not drop the row — an empty segment before a newline would produce
1052
+ // an invisible row that the renderer discards.
1053
+ for (let i = 0; i < padRows; i++) {
1054
+ segments.push(new Segment("\n"));
1055
+ segments.push(new Segment(setCellSize("", asCellCol(options.maxWidth)), contentStyle));
1056
+ }
1057
+ return segments;
1058
+ }
1059
+ _emitRowContent(out, row, cursorOnRow, contentStyle, cursorStyle, indicator, rowPrintWidth) {
1060
+ const content = row.content;
1061
+ // cursorCol is a code-unit offset into row.content; both cursorPosition
1062
+ // and valueStart are CodePoint, so their difference lands on a code-point
1063
+ // boundary within content and is safe for string slicing.
1064
+ const cursorCol = cursorOnRow ? this.cursorPosition - row.valueStart : -1;
1065
+ // Fast path: no indicator on this row. Let the wrapper pad to width.
1066
+ if (indicator === undefined) {
1067
+ if (!cursorOnRow) {
1068
+ if (content.length > 0)
1069
+ out.push(new Segment(content, contentStyle));
1070
+ return;
1071
+ }
1072
+ const before = content.slice(0, cursorCol);
1073
+ const nextCp = nextGrapheme(content, asCodePoint(cursorCol));
1074
+ const at = content.slice(cursorCol, nextCp) || " ";
1075
+ const after = content.slice(nextCp);
1076
+ if (before.length > 0)
1077
+ out.push(new Segment(before, contentStyle));
1078
+ out.push(new Segment(at, cursorStyle));
1079
+ if (after.length > 0)
1080
+ out.push(new Segment(after, contentStyle));
1081
+ return;
1082
+ }
1083
+ // Indicator path: reserve the indicator's cells at the end of the row so
1084
+ // wide characters never collide with it. Content fills [0, contentCellWidth)
1085
+ // cells; the indicator occupies the remaining cells.
1086
+ const indicatorWidth = asCellCol(cellLen(indicator.ch));
1087
+ const contentCellWidth = asCellCol(Math.max(0, rowPrintWidth - indicatorWidth));
1088
+ // Clip row content to the non-indicator region and pad to exact cell width.
1089
+ const [visibleContent] = splitText(content, contentCellWidth);
1090
+ const paddedContent = setCellSize(visibleContent, contentCellWidth);
1091
+ // Map the code-unit cursor offset to a cell column within this row.
1092
+ const cursorCellColInRow = cursorOnRow && cursorCol >= 0
1093
+ ? cellLen(content.slice(0, cursorCol))
1094
+ : -1;
1095
+ if (cursorCellColInRow >= 0 && cursorCellColInRow < contentCellWidth) {
1096
+ const [before, rest] = splitText(paddedContent, asCellCol(cursorCellColInRow));
1097
+ const firstCh = rest.slice(0, nextGrapheme(rest, asCodePoint(0)));
1098
+ const at = firstCh || " ";
1099
+ const after = rest.slice(firstCh.length);
1100
+ if (before.length > 0)
1101
+ out.push(new Segment(before, contentStyle));
1102
+ out.push(new Segment(at, cursorStyle));
1103
+ if (after.length > 0)
1104
+ out.push(new Segment(after, contentStyle));
1105
+ }
1106
+ else {
1107
+ if (paddedContent.length > 0)
1108
+ out.push(new Segment(paddedContent, contentStyle));
1109
+ }
1110
+ out.push(new Segment(indicator.ch, indicator.style));
1111
+ }
1112
+ _computeVisualRows(maxWidth) {
1113
+ const firstWidth = asCellCol(Math.max(1, maxWidth));
1114
+ const continuationWidth = asCellCol(Math.max(1, firstWidth - this._markerWidth));
1115
+ const rows = [];
1116
+ const lines = this.value.split("\n");
1117
+ let pos = 0;
1118
+ for (let li = 0; li < lines.length; li++) {
1119
+ const line = lines[li];
1120
+ if (this._wrap !== undefined) {
1121
+ const wrapRows = this._wrap(line, { firstWidth, continuationWidth });
1122
+ if (wrapRows.length === 0) {
1123
+ rows.push({ content: "", valueStart: asCodePoint(pos), isContinuation: false });
1124
+ }
1125
+ else {
1126
+ for (let ri = 0; ri < wrapRows.length; ri++) {
1127
+ const wr = wrapRows[ri];
1128
+ rows.push({
1129
+ content: wr.content,
1130
+ valueStart: asCodePoint(pos + wr.start),
1131
+ isContinuation: ri > 0,
1132
+ });
1133
+ }
1134
+ }
1135
+ }
1136
+ else {
1137
+ rows.push({ content: line, valueStart: asCodePoint(pos), isContinuation: false });
1138
+ }
1139
+ pos += line.length + 1; // +1 for the \n separator
1140
+ }
1141
+ return rows;
1142
+ }
1143
+ measure(_options) {
1144
+ const minimum = MIN_CONTENT_WIDTH + 2;
1145
+ const maximum = Math.max(minimum, Math.max(cellLen(this.value), cellLen(this.placeholder)) + 2);
1146
+ return { minimum, maximum };
1147
+ }
1148
+ /**
1149
+ * `[X/Y]` scroll-position string when there's actually something to
1150
+ * scroll, else `undefined`. X is the cursor's 1-indexed visual row; Y is
1151
+ * the total visual row count. Reads cached state populated by the most
1152
+ * recent `render()` — call from a `Panel.bottomRightAccessory` thunk so
1153
+ * it evaluates after content has been rendered for the current frame.
1154
+ *
1155
+ * Returns `undefined` when:
1156
+ * - `scrollIndicator` is not `"indices"`, or
1157
+ * - `maxRows` is unset, or total visual rows ≤ maxRows (nothing to
1158
+ * scroll), or
1159
+ * - no render has happened yet (cache is empty).
1160
+ */
1161
+ get scrollIndicatorText() {
1162
+ if (this._scrollIndicator !== "indices")
1163
+ return undefined;
1164
+ if (this._maxRows === undefined)
1165
+ return undefined;
1166
+ const rows = this._visualRows;
1167
+ if (rows === null || rows.length <= this._maxRows)
1168
+ return undefined;
1169
+ const cursorRow1 = this._cursorVisualRow() + 1;
1170
+ return `[${cursorRow1}/${rows.length}]`;
1171
+ }
1172
+ };
1173
+ })();
1174
+ export { TextInput };