@promptctl/rich-js 0.18.0 → 0.18.1

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 (221) hide show
  1. package/dist/core/anchor.d.ts +45 -0
  2. package/dist/core/anchor.js +39 -0
  3. package/dist/core/ansi.d.ts +65 -0
  4. package/dist/core/ansi.js +231 -0
  5. package/dist/core/box.d.ts +143 -0
  6. package/dist/core/box.js +379 -0
  7. package/dist/core/cells.d.ts +159 -0
  8. package/dist/core/cells.js +287 -0
  9. package/dist/core/color.d.ts +246 -0
  10. package/dist/core/color.js +1038 -0
  11. package/dist/core/console.d.ts +165 -0
  12. package/dist/core/console.js +527 -0
  13. package/dist/core/emoji.d.ts +24 -0
  14. package/dist/core/emoji.js +391 -0
  15. package/dist/core/export-html.d.ts +59 -0
  16. package/dist/core/export-html.js +200 -0
  17. package/dist/core/export-lines.d.ts +121 -0
  18. package/dist/core/export-lines.js +141 -0
  19. package/dist/core/highlighter.d.ts +33 -0
  20. package/dist/core/highlighter.js +124 -0
  21. package/dist/core/json.d.ts +27 -0
  22. package/dist/core/json.js +53 -0
  23. package/dist/core/markup.d.ts +119 -0
  24. package/dist/core/markup.js +731 -0
  25. package/dist/core/measure.d.ts +27 -0
  26. package/dist/core/measure.js +62 -0
  27. package/dist/core/oklch.d.ts +161 -0
  28. package/dist/core/oklch.js +312 -0
  29. package/dist/core/osc8.d.ts +68 -0
  30. package/dist/core/osc8.js +90 -0
  31. package/dist/core/place.d.ts +30 -0
  32. package/dist/core/place.js +51 -0
  33. package/dist/core/pretty.d.ts +144 -0
  34. package/dist/core/pretty.js +476 -0
  35. package/dist/core/protocol.d.ts +267 -0
  36. package/dist/core/protocol.js +163 -0
  37. package/dist/core/render.d.ts +84 -0
  38. package/dist/core/render.js +145 -0
  39. package/dist/core/segment.d.ts +161 -0
  40. package/dist/core/segment.js +492 -0
  41. package/dist/core/spinnerData.d.ts +12 -0
  42. package/dist/core/spinnerData.js +117 -0
  43. package/dist/core/strip.d.ts +141 -0
  44. package/dist/core/strip.js +305 -0
  45. package/dist/core/style.d.ts +169 -0
  46. package/dist/core/style.js +695 -0
  47. package/dist/core/subscription.d.ts +12 -0
  48. package/dist/core/subscription.js +12 -0
  49. package/dist/core/text.d.ts +256 -0
  50. package/dist/core/text.js +976 -0
  51. package/dist/core/wrap.d.ts +41 -0
  52. package/dist/core/wrap.js +127 -0
  53. package/dist/host/app.d.ts +121 -0
  54. package/dist/host/app.js +255 -0
  55. package/dist/host/host-environment.d.ts +26 -0
  56. package/dist/host/host-environment.js +44 -0
  57. package/dist/host/index.d.ts +5 -0
  58. package/dist/host/index.js +3 -0
  59. package/dist/host/terminal-host.d.ts +179 -0
  60. package/dist/host/terminal-host.js +186 -0
  61. package/dist/index.d.ts +91 -0
  62. package/dist/index.js +107 -0
  63. package/dist/node/prompt.d.ts +21 -0
  64. package/dist/node/prompt.js +30 -0
  65. package/dist/node/save.d.ts +17 -0
  66. package/dist/node/save.js +21 -0
  67. package/dist/node/terminal-host.d.ts +113 -0
  68. package/dist/node/terminal-host.js +227 -0
  69. package/dist/node/traceback.d.ts +37 -0
  70. package/dist/node/traceback.js +134 -0
  71. package/dist/renderables/align.d.ts +16 -0
  72. package/dist/renderables/align.js +33 -0
  73. package/dist/renderables/columns.d.ts +61 -0
  74. package/dist/renderables/columns.js +152 -0
  75. package/dist/renderables/constrain.d.ts +15 -0
  76. package/dist/renderables/constrain.js +37 -0
  77. package/dist/renderables/embed.d.ts +50 -0
  78. package/dist/renderables/embed.js +71 -0
  79. package/dist/renderables/flexStrip.d.ts +41 -0
  80. package/dist/renderables/flexStrip.js +200 -0
  81. package/dist/renderables/group.d.ts +11 -0
  82. package/dist/renderables/group.js +17 -0
  83. package/dist/renderables/layout.d.ts +92 -0
  84. package/dist/renderables/layout.js +312 -0
  85. package/dist/renderables/live.d.ts +50 -0
  86. package/dist/renderables/live.js +175 -0
  87. package/dist/renderables/markdown.d.ts +24 -0
  88. package/dist/renderables/markdown.js +184 -0
  89. package/dist/renderables/padding.d.ts +38 -0
  90. package/dist/renderables/padding.js +141 -0
  91. package/dist/renderables/panel.d.ts +116 -0
  92. package/dist/renderables/panel.js +307 -0
  93. package/dist/renderables/progress.d.ts +90 -0
  94. package/dist/renderables/progress.js +238 -0
  95. package/dist/renderables/progressBar.d.ts +31 -0
  96. package/dist/renderables/progressBar.js +53 -0
  97. package/dist/renderables/prompt.d.ts +49 -0
  98. package/dist/renderables/prompt.js +125 -0
  99. package/dist/renderables/rule.d.ts +25 -0
  100. package/dist/renderables/rule.js +90 -0
  101. package/dist/renderables/spinner.d.ts +31 -0
  102. package/dist/renderables/spinner.js +70 -0
  103. package/dist/renderables/status.d.ts +23 -0
  104. package/dist/renderables/status.js +60 -0
  105. package/dist/renderables/syntax.d.ts +35 -0
  106. package/dist/renderables/syntax.js +115 -0
  107. package/dist/renderables/table.d.ts +194 -0
  108. package/dist/renderables/table.js +795 -0
  109. package/dist/renderables/traceback.d.ts +31 -0
  110. package/dist/renderables/traceback.js +108 -0
  111. package/dist/renderables/tree.d.ts +68 -0
  112. package/dist/renderables/tree.js +150 -0
  113. package/dist/renderables/viewport.d.ts +114 -0
  114. package/dist/renderables/viewport.js +198 -0
  115. package/dist/template-bindings/color-funcs.d.ts +87 -0
  116. package/dist/template-bindings/color-funcs.js +217 -0
  117. package/dist/template-bindings/helpers.d.ts +24 -0
  118. package/dist/template-bindings/helpers.js +48 -0
  119. package/dist/template-bindings/index.d.ts +107 -0
  120. package/dist/template-bindings/index.js +146 -0
  121. package/dist/template-bindings/palette-funcs.d.ts +94 -0
  122. package/dist/template-bindings/palette-funcs.js +141 -0
  123. package/dist/template-bindings/style-funcs.d.ts +28 -0
  124. package/dist/template-bindings/style-funcs.js +155 -0
  125. package/dist/themes/buildPalette.d.ts +36 -0
  126. package/dist/themes/buildPalette.js +56 -0
  127. package/dist/themes/colorMath.d.ts +82 -0
  128. package/dist/themes/colorMath.js +254 -0
  129. package/dist/themes/colorRef.d.ts +51 -0
  130. package/dist/themes/colorRef.js +96 -0
  131. package/dist/themes/data/ansi-tables.d.ts +14 -0
  132. package/dist/themes/data/ansi-tables.js +49 -0
  133. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  134. package/dist/themes/data/atom-one-dark.js +177 -0
  135. package/dist/themes/data/atom-one-light.d.ts +3 -0
  136. package/dist/themes/data/atom-one-light.js +177 -0
  137. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  138. package/dist/themes/data/catppuccin-frappe.js +177 -0
  139. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  140. package/dist/themes/data/catppuccin-latte.js +177 -0
  141. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  142. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  143. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  144. package/dist/themes/data/catppuccin-mocha.js +177 -0
  145. package/dist/themes/data/cyberpunk.d.ts +17 -0
  146. package/dist/themes/data/cyberpunk.js +193 -0
  147. package/dist/themes/data/default.d.ts +3 -0
  148. package/dist/themes/data/default.js +160 -0
  149. package/dist/themes/data/dracula.d.ts +3 -0
  150. package/dist/themes/data/dracula.js +177 -0
  151. package/dist/themes/data/flexoki.d.ts +3 -0
  152. package/dist/themes/data/flexoki.js +177 -0
  153. package/dist/themes/data/gruvbox.d.ts +3 -0
  154. package/dist/themes/data/gruvbox.js +177 -0
  155. package/dist/themes/data/index.d.ts +35 -0
  156. package/dist/themes/data/index.js +64 -0
  157. package/dist/themes/data/monokai.d.ts +3 -0
  158. package/dist/themes/data/monokai.js +177 -0
  159. package/dist/themes/data/nord.d.ts +3 -0
  160. package/dist/themes/data/nord.js +177 -0
  161. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  162. package/dist/themes/data/rose-pine-dawn.js +177 -0
  163. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  164. package/dist/themes/data/rose-pine-moon.js +177 -0
  165. package/dist/themes/data/rose-pine.d.ts +3 -0
  166. package/dist/themes/data/rose-pine.js +177 -0
  167. package/dist/themes/data/solarized-dark.d.ts +3 -0
  168. package/dist/themes/data/solarized-dark.js +177 -0
  169. package/dist/themes/data/solarized-light.d.ts +3 -0
  170. package/dist/themes/data/solarized-light.js +177 -0
  171. package/dist/themes/data/svg-export.d.ts +3 -0
  172. package/dist/themes/data/svg-export.js +177 -0
  173. package/dist/themes/data/textual-ansi.d.ts +3 -0
  174. package/dist/themes/data/textual-ansi.js +160 -0
  175. package/dist/themes/data/textual-dark.d.ts +3 -0
  176. package/dist/themes/data/textual-dark.js +160 -0
  177. package/dist/themes/data/textual-light.d.ts +3 -0
  178. package/dist/themes/data/textual-light.js +160 -0
  179. package/dist/themes/data/tokyo-night.d.ts +3 -0
  180. package/dist/themes/data/tokyo-night.js +177 -0
  181. package/dist/themes/data/types.d.ts +31 -0
  182. package/dist/themes/data/types.js +10 -0
  183. package/dist/themes/palette.d.ts +21 -0
  184. package/dist/themes/palette.js +28 -0
  185. package/dist/themes/ramp.d.ts +83 -0
  186. package/dist/themes/ramp.js +122 -0
  187. package/dist/themes/registry.d.ts +44 -0
  188. package/dist/themes/registry.js +94 -0
  189. package/dist/themes/terminalThemes.d.ts +49 -0
  190. package/dist/themes/terminalThemes.js +63 -0
  191. package/dist/themes/transpose.d.ts +87 -0
  192. package/dist/themes/transpose.js +138 -0
  193. package/dist/widgets/button.d.ts +49 -0
  194. package/dist/widgets/button.js +208 -0
  195. package/dist/widgets/checkbox.d.ts +41 -0
  196. package/dist/widgets/checkbox.js +157 -0
  197. package/dist/widgets/dropdown.d.ts +67 -0
  198. package/dist/widgets/dropdown.js +450 -0
  199. package/dist/widgets/event-router.d.ts +71 -0
  200. package/dist/widgets/event-router.js +629 -0
  201. package/dist/widgets/focus-manager.d.ts +45 -0
  202. package/dist/widgets/focus-manager.js +191 -0
  203. package/dist/widgets/hit.d.ts +64 -0
  204. package/dist/widgets/hit.js +113 -0
  205. package/dist/widgets/index.d.ts +20 -0
  206. package/dist/widgets/index.js +14 -0
  207. package/dist/widgets/slider.d.ts +57 -0
  208. package/dist/widgets/slider.js +270 -0
  209. package/dist/widgets/static-item.d.ts +34 -0
  210. package/dist/widgets/static-item.js +54 -0
  211. package/dist/widgets/text-input.d.ts +274 -0
  212. package/dist/widgets/text-input.js +1165 -0
  213. package/dist/widgets/toggle.d.ts +44 -0
  214. package/dist/widgets/toggle.js +180 -0
  215. package/dist/widgets/types.d.ts +87 -0
  216. package/dist/widgets/types.js +30 -0
  217. package/dist/widgets/widget-app.d.ts +63 -0
  218. package/dist/widgets/widget-app.js +199 -0
  219. package/dist/widgets/widget-base.d.ts +40 -0
  220. package/dist/widgets/widget-base.js +165 -0
  221. package/package.json +6 -2
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Terminal cell width calculation.
3
+ * Handles ASCII, CJK (double-width), and emoji characters.
4
+ */
5
+ import stringWidth from "string-width";
6
+ // [LAW:one-source-of-truth] string-width is the single authority for cell width
7
+ const cellLenCache = new Map();
8
+ const CACHE_MAX = 4096;
9
+ /**
10
+ * Returns the terminal cell width of a string.
11
+ */
12
+ export function cellLen(text) {
13
+ if (text.length === 0)
14
+ return 0;
15
+ // Fast path for pure ASCII
16
+ if (text.length <= 64) {
17
+ const cached = cellLenCache.get(text);
18
+ if (cached !== undefined)
19
+ return cached;
20
+ }
21
+ const width = stringWidth(text);
22
+ if (text.length <= 64) {
23
+ if (cellLenCache.size >= CACHE_MAX)
24
+ cellLenCache.clear();
25
+ cellLenCache.set(text, width);
26
+ }
27
+ return width;
28
+ }
29
+ /** Brand a raw number as a CellCol. Use only at trust boundaries. */
30
+ export function asCellCol(n) { return n; }
31
+ /** Brand a raw number as a CodeUnit. Use only at trust boundaries. */
32
+ export function asCodeUnit(n) { return n; }
33
+ /** Brand a raw number as a CodePoint. Use only when the value is known to be
34
+ * on a Unicode code-point boundary. */
35
+ export function asCodePoint(n) { return n; }
36
+ /**
37
+ * [LAW:parse-dont-validate] A caller's number as a count of cells — the checked
38
+ * counterpart to `asCellCol`'s unchecked brand, and the one place the rule "a
39
+ * cell count is a non-negative integer" is written.
40
+ *
41
+ * [LAW:one-source-of-truth] Every renderable is handed `RenderOptions.maxWidth`
42
+ * as a plain `number`, so every renderable used to answer this for itself and
43
+ * they disagreed: at NaN, `Table` collapsed to one cell of garbage, `Columns`
44
+ * and `Layout` threw `Invalid array length`, and `RichText` ignored the request
45
+ * and emitted its full natural width. Parse at the entry to `render`/`measure`
46
+ * and nothing downstream re-asks.
47
+ *
48
+ * The comparison is the point, not `Math.max(0, n)`: NaN fails every comparison
49
+ * and so floors here, where `Math.max` would return it and let it poison every
50
+ * bounds check downstream. Integral because a cell is not divisible — a
51
+ * fractional budget reaches a largest-remainder division, which grants a whole
52
+ * cell against a fractional residue and hands out more width than it was given.
53
+ *
54
+ * `Infinity` is outside what the comparison floors, so it passes through
55
+ * unchanged, and no clamp belongs here: the finite answer to an unbounded offer
56
+ * is the renderable's own natural width, which this function cannot see from
57
+ * the number alone. `withBoundedWidth` in `protocol.ts` is the second half of
58
+ * the parse and the only place that value is resolved.
59
+ */
60
+ export function cellCount(n) {
61
+ return (n > 0 ? Math.floor(n) : 0);
62
+ }
63
+ // ── Cell-aware string utilities ──────────────────────────────────────────────
64
+ /**
65
+ * Pads or crops a string to exactly `totalWidth` terminal cells.
66
+ * Invariant: cellLen(setCellSize(text, n)) === n (unless n is 0)
67
+ */
68
+ export function setCellSize(text, totalWidth) {
69
+ if (totalWidth === 0)
70
+ return "";
71
+ const currentWidth = cellLen(text);
72
+ if (currentWidth === totalWidth)
73
+ return text;
74
+ if (currentWidth < totalWidth) {
75
+ return text + " ".repeat(totalWidth - currentWidth);
76
+ }
77
+ return cropToWidth(text, totalWidth);
78
+ }
79
+ /**
80
+ * Splits text at a cell position. Returns [left, right].
81
+ * When the position falls mid-wide-character, the left side is padded
82
+ * to reach exactly `position` cells. The wide char remains in the right side.
83
+ */
84
+ export function splitText(text, position) {
85
+ if (position <= 0)
86
+ return ["", text];
87
+ const totalWidth = cellLen(text);
88
+ if (position >= totalWidth)
89
+ return [text, ""];
90
+ // Walk characters tracking cell width and source char index
91
+ let width = 0;
92
+ let charIndex = 0;
93
+ for (const char of text) {
94
+ const charWidth = cellLen(char);
95
+ if (width + charWidth > position)
96
+ break;
97
+ width += charWidth;
98
+ charIndex += char.length;
99
+ }
100
+ const left = text.slice(0, charIndex);
101
+ const right = text.slice(charIndex);
102
+ // If we stopped short of position (mid-wide-char), pad left with spaces
103
+ if (width < position) {
104
+ return [left + " ".repeat(position - width), right];
105
+ }
106
+ return [left, right];
107
+ }
108
+ /**
109
+ * Wraps text into lines of at most `maxWidth` cells, preserving every code
110
+ * point: `chopCells(t, w).join("") === t`. Unlike `splitText` this never pads,
111
+ * so a line ending before a wide glyph is narrower than `maxWidth` rather than
112
+ * padded out to it.
113
+ *
114
+ * A line overflows exactly where the budget cannot be met: a glyph wider than
115
+ * `maxWidth` is force-taken whole rather than dropped, and a `maxWidth` of zero
116
+ * or less is no budget at all, so the text comes back as one line.
117
+ */
118
+ export function chopCells(text, maxWidth) {
119
+ if (maxWidth <= 0 || text.length === 0)
120
+ return [text];
121
+ const lines = [];
122
+ let start = asCodePoint(0);
123
+ while (start < text.length) {
124
+ const end = cellStepFrom(text, start, maxWidth);
125
+ lines.push(text.slice(start, end));
126
+ start = end;
127
+ }
128
+ return lines;
129
+ }
130
+ // [LAW:no-shared-mutable-globals] A private memo, written only by `graphemes`.
131
+ let segmenter;
132
+ /**
133
+ * The grapheme clusters of `text`, in order: the unit `string-width` measures,
134
+ * and so the unit `cellLen` of a whole string is the sum of. Summing `cellLen`
135
+ * over code points instead disagrees with it on every glyph built from several:
136
+ * `❤️` is 1 + 0 by code point and 2 as a cluster, `👨‍👩‍👧` is 6 and 2, and a cut
137
+ * between two of its code points leaves half a glyph.
138
+ */
139
+ export function graphemes(text) {
140
+ segmenter ??= new Intl.Segmenter(undefined, { granularity: "grapheme" });
141
+ return Array.from(segmenter.segment(text), (s) => s.segment);
142
+ }
143
+ /**
144
+ * Returns the largest prefix of `text` whose cell width fits within `cap` cells,
145
+ * cut between grapheme clusters. No padding — the returned string may be
146
+ * narrower than `cap` when the next glyph is wide and would overshoot. Never
147
+ * wider than `cap` cells.
148
+ *
149
+ * When the first glyph already exceeds `cap` cells, returns "" (the caller
150
+ * must decide whether to force-take the glyph or skip it).
151
+ */
152
+ export function cellFit(text, cap) {
153
+ return fitClusters(graphemes(text), cap).join("");
154
+ }
155
+ /** `cellFit` from the other end: the largest suffix of `text` within `cap` cells. */
156
+ export function cellFitEnd(text, cap) {
157
+ return fitClusters(graphemes(text).reverse(), cap).reverse().join("");
158
+ }
159
+ function fitClusters(clusters, cap) {
160
+ let w = 0;
161
+ let n = 0;
162
+ for (const cluster of clusters) {
163
+ const cw = cellLen(cluster);
164
+ if (w + cw > cap)
165
+ break;
166
+ w += cw;
167
+ n++;
168
+ }
169
+ return clusters.slice(0, n);
170
+ }
171
+ /**
172
+ * Returns the largest code-unit end offset starting from `startCU` whose
173
+ * prefix (from `startCU`) has cell width ≤ `cap`. Iterates from the given
174
+ * offset without slicing the tail, avoiding O(N²) allocation when called
175
+ * repeatedly across a long string.
176
+ *
177
+ * [LAW:types-are-the-program] returns CodePoint because for...of always
178
+ * stops on a code-point boundary.
179
+ */
180
+ export function cellFitFrom(text, startCU, cap) {
181
+ let w = 0;
182
+ let i = startCU;
183
+ while (i < text.length) {
184
+ const cp = text.codePointAt(i);
185
+ const ch = String.fromCodePoint(cp);
186
+ const cw = cellLen(ch);
187
+ if (w + cw > cap)
188
+ break;
189
+ w += cw;
190
+ i = asCodePoint(i + ch.length);
191
+ }
192
+ return i;
193
+ }
194
+ /**
195
+ * Like `cellFitFrom`, but for any `startCU` inside `text` the result is
196
+ * strictly past it: a glyph too wide for `cap` is force-taken whole and
197
+ * overflows `cap` by its own width. So a `while (i < text.length)` loop driven
198
+ * by this terminates by construction, and the re-brand is honest — both
199
+ * operands land on code-point boundaries, so their max does too.
200
+ *
201
+ * [LAW:single-enforcer] `cellFit` documents that its caller must choose between
202
+ * force-taking and skipping; this is that choice, made once. It was made twice
203
+ * before and the copies drifted — the textarea's soft wrap force-took,
204
+ * `chopCells` did neither and hung.
205
+ */
206
+ export function cellStepFrom(text, startCU, cap) {
207
+ return asCodePoint(Math.max(cellFitFrom(text, startCU, cap), nextCodePoint(text, startCU)));
208
+ }
209
+ /**
210
+ * Returns the largest code-unit offset into `content` whose prefix has
211
+ * cell width ≤ `cellCol`. When `cellCol` falls mid-wide-character the
212
+ * function stops before that character (never advances into it).
213
+ * Clamps to `content.length` if `cellCol` exceeds the string's total
214
+ * cell width.
215
+ *
216
+ * This is the inverse of `cellLen(content.slice(0, codeUnit))` — given a
217
+ * visual column, return the corresponding string index.
218
+ *
219
+ * Returns `CodePoint` because `for...of` iteration always stops on a
220
+ * code-point boundary.
221
+ */
222
+ export function cellColToCodeUnitOffset(content, cellCol) {
223
+ let w = 0;
224
+ let i = 0;
225
+ for (const ch of content) {
226
+ if (w >= cellCol)
227
+ break;
228
+ const cw = cellLen(ch);
229
+ if (w + cw > cellCol)
230
+ break;
231
+ w += cw;
232
+ i += ch.length;
233
+ }
234
+ return asCodePoint(i);
235
+ }
236
+ /**
237
+ * Advance one full Unicode code point from `cu`, returning the code-unit
238
+ * offset of the start of the *next* code point. Returns `s.length` when
239
+ * already at or past the end.
240
+ *
241
+ * Handles surrogate pairs: when the code point at `cu` is a supplementary
242
+ * character (U+10000…U+10FFFF) it occupies 2 UTF-16 code units, so the
243
+ * returned offset advances by 2.
244
+ */
245
+ export function nextCodePoint(s, cu) {
246
+ if (cu >= s.length)
247
+ return asCodePoint(s.length);
248
+ const cp = s.codePointAt(cu);
249
+ return asCodePoint(cu + (cp > 0xFFFF ? 2 : 1));
250
+ }
251
+ /**
252
+ * Step back one full Unicode code point from `cu`, returning the code-unit
253
+ * offset of the start of the *previous* code point. Returns 0 when already
254
+ * at the start.
255
+ *
256
+ * Handles surrogate pairs: when the code unit at `cu - 1` is a low surrogate
257
+ * AND the code unit at `cu - 2` is a high surrogate, steps back 2 code units.
258
+ * Unpaired surrogates are treated as 1-CU characters.
259
+ */
260
+ export function prevCodePoint(s, cu) {
261
+ if (cu <= 0)
262
+ return asCodePoint(0);
263
+ const low = s.charCodeAt(cu - 1);
264
+ if (low >= 0xDC00 && low <= 0xDFFF && cu >= 2) {
265
+ const high = s.charCodeAt(cu - 2);
266
+ if (high >= 0xD800 && high <= 0xDBFF)
267
+ return asCodePoint(cu - 2);
268
+ }
269
+ return asCodePoint(cu - 1);
270
+ }
271
+ // --- internal ---
272
+ function cropToWidth(text, targetWidth) {
273
+ let width = 0;
274
+ let i = 0;
275
+ // Use the string's code point iterator to handle surrogate pairs
276
+ for (const char of text) {
277
+ const charWidth = cellLen(char);
278
+ if (width + charWidth > targetWidth)
279
+ break;
280
+ width += charWidth;
281
+ i += char.length;
282
+ }
283
+ const cropped = text.slice(0, i);
284
+ // Pad if we couldn't hit the exact width (wide char boundary)
285
+ const diff = targetWidth - width;
286
+ return diff > 0 ? cropped + " ".repeat(diff) : cropped;
287
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Terminal color representation, parsing, downgrading, and ANSI code generation.
3
+ */
4
+ /**
5
+ * Immutable RGBA color value. Alpha defaults to 1 (fully opaque).
6
+ *
7
+ * [LAW:single-enforcer] The constructor is the sole validation site for
8
+ * channel/alpha invariants. Float-arithmetic callers (HSL roundtrips, blends,
9
+ * lerps) are responsible for `Math.round` + clamp before construction; the
10
+ * constructor throws rather than silently masking out-of-range input.
11
+ */
12
+ export declare class ColorRgba {
13
+ readonly red: number;
14
+ readonly green: number;
15
+ readonly blue: number;
16
+ readonly alpha: number;
17
+ constructor(red: number, green: number, blue: number, alpha?: number);
18
+ /** 6-char `#RRGGBB` when fully opaque, 8-char `#RRGGBBAA` otherwise. */
19
+ get hex(): string;
20
+ /** `rgb(r,g,b)` when fully opaque, `rgba(r,g,b,a)` otherwise. */
21
+ get rgb(): string;
22
+ /** [r/255, g/255, b/255, alpha] — alpha is already 0..1, no division. */
23
+ get normalized(): [number, number, number, number];
24
+ /**
25
+ * Composite this color over an opaque background. Returns a fully opaque
26
+ * ColorRgba (alpha=1). Per-channel linear interpolation by alpha.
27
+ *
28
+ * [LAW:dataflow-not-control-flow] alpha=1 short-circuits to `this`, so
29
+ * callers can invoke this unconditionally; the data decides whether work
30
+ * happens.
31
+ */
32
+ compositeOver(bg: ColorRgba): ColorRgba;
33
+ }
34
+ /**
35
+ * The surface a terminal draws a translucent colour over. A terminal cannot
36
+ * know what lies under its cells, so the SGR writer (`Style.toSgrCodes`), the
37
+ * strip's seam test and the contrast choosers (`themes/colorMath`) all
38
+ * composite over this one colour. [LAW:one-source-of-truth] One constant, so
39
+ * the colour text is chosen against is the colour the writer draws.
40
+ */
41
+ export declare const SURFACE_BLACK: ColorRgba;
42
+ /**
43
+ * WCAG 2.x relative luminance (0..1) of an opaque color. The single
44
+ * luminance function in the codebase — `contrastFor`, `contrastRatio`, and
45
+ * any caller that needs to reason about readability all funnel through it.
46
+ * [LAW:one-source-of-truth]
47
+ */
48
+ export declare function relativeLuminance(c: ColorRgba): number;
49
+ /**
50
+ * WCAG 2.x contrast ratio between two colors, in [1, 21]. Symmetric — the
51
+ * order of arguments does not matter. 4.5 is the AA threshold for normal
52
+ * text, 3.0 for large text.
53
+ *
54
+ * Assumes opaque inputs: alpha is ignored, since the displayed contrast of a
55
+ * translucent color depends on what it composites over. For a translucent
56
+ * foreground, flatten it first (or use `ensureContrast`, which does).
57
+ */
58
+ export declare function contrastRatio(a: ColorRgba, b: ColorRgba): number;
59
+ export declare class ColorTable {
60
+ private readonly colors;
61
+ private readonly firstIndex;
62
+ private readonly matchCache;
63
+ private readonly readableCache;
64
+ constructor(colors: ColorRgba[], firstIndex?: number);
65
+ get(index: number): ColorRgba;
66
+ private luminanceCache;
67
+ /** Each entry's relative luminance, computed once per table. */
68
+ private luminances;
69
+ /** How many entries the table holds (terminal indices `firstIndex`…). */
70
+ get size(): number;
71
+ /**
72
+ * Finds the nearest table index to the given color (Euclidean RGB distance, cached).
73
+ * Alpha is part of the cache key so two values that differ only in alpha
74
+ * don't collide, but is not used in the distance metric.
75
+ */
76
+ match(value: ColorRgba): number;
77
+ /**
78
+ * The nearest entry to `value` (the distance `match` uses) among those that
79
+ * clear `minRatio` against `on`; when none does, the entry with the most
80
+ * contrast against `on`. Text downgraded on its own background: `match`
81
+ * moves text and background independently, and two independent roundings
82
+ * can meet in the middle, so a pair that read at 4.5:1 in truecolor can
83
+ * draw at 2:1. [LAW:dataflow-not-control-flow] One scan scores every entry;
84
+ * the ratio decides which one wins.
85
+ */
86
+ matchReadable(value: ColorRgba, on: ColorRgba, minRatio: number): number;
87
+ /**
88
+ * The nearest entry to `value` (the distance `match` uses) among those
89
+ * `accept` takes, given each entry's colour and terminal index; `undefined`
90
+ * when it takes none. Entries are offered nearest-first (ties to the lower
91
+ * index) and the first taken wins, so an expensive predicate runs only as
92
+ * far out as the answer. Uncached: the predicate is the caller's, and a
93
+ * closure has no key.
94
+ */
95
+ matchWhere(value: ColorRgba, accept: (entry: ColorRgba, index: number) => boolean): number | undefined;
96
+ }
97
+ export declare enum ColorDepth {
98
+ DEFAULT = 0,
99
+ STANDARD = 1,
100
+ EIGHT_BIT = 2,
101
+ TRUECOLOR = 3,
102
+ WINDOWS = 4
103
+ }
104
+ export interface DetectColorOptions {
105
+ /** Environment to probe. Defaults to `process.env`. */
106
+ env?: NodeJS.ProcessEnv;
107
+ /** Whether output is going to a real terminal. Defaults to `process.stdout?.isTTY`. */
108
+ isTTY?: boolean;
109
+ }
110
+ /**
111
+ * Detect the terminal's color capability from env + TTY state.
112
+ *
113
+ * Probes (in priority order): NO_COLOR (per https://no-color.org —
114
+ * any non-empty value disables color), FORCE_COLOR (numeric/boolean override),
115
+ * TTY presence, TERM=dumb/unknown, COLORTERM, known terminal/TERM_PROGRAM
116
+ * names, and TERM regex patterns. Returns `null` for "no color".
117
+ *
118
+ * [LAW:dataflow-not-control-flow] Same probes run every call; variability is
119
+ * in the env values, not in which checks execute.
120
+ */
121
+ export declare function detectColorSystem(options?: DetectColorOptions): ColorDepth | null;
122
+ /**
123
+ * Where segments are written, as everything the serializer (`segmentsToString`)
124
+ * needs to know about it: the colour depth SGR is drawn at, and whether OSC 8
125
+ * hyperlinks are emitted. The serializer takes this value whole, so a fact the
126
+ * encoding comes to need joins it here rather than the serializer's signature.
127
+ */
128
+ export interface Destination {
129
+ readonly colorSystem: ColorDepth | null;
130
+ readonly hyperlinks: boolean;
131
+ }
132
+ /**
133
+ * Resolve a colour spec into a `Destination`.
134
+ *
135
+ * [LAW:single-enforcer] The one place a spec becomes both facts. A hyperlink is
136
+ * not a colour: an explicit depth — `"none"` and `null` included — states a
137
+ * colour choice and keeps links, and under `"auto"` links follow what the
138
+ * terminal can take (no TTY, TERM=dumb: none) while NO_COLOR and FORCE_COLOR=0,
139
+ * colour preferences, leave them alone. A FORCE_COLOR that forces colour on
140
+ * declares the destination takes escapes, so it keeps links on a pipe too.
141
+ */
142
+ export declare function resolveDestination(spec: string | ColorDepth | null, options?: DetectColorOptions): Destination;
143
+ /**
144
+ * Resolve a string spec into a `ColorDepth` (or `null` for no color).
145
+ *
146
+ * `"auto"` triggers env-based detection; all other recognized specs are
147
+ * direct table lookups against `STRING_TO_DEPTH`. Throws on unknown specs
148
+ * — silent fallback would mask user typos.
149
+ *
150
+ * [LAW:single-enforcer] All string→ColorDepth resolution flows through here.
151
+ */
152
+ export declare function resolveColorSystem(spec: string, options?: DetectColorOptions): ColorDepth | null;
153
+ export declare class ColorParseError extends Error {
154
+ constructor(message: string);
155
+ }
156
+ export declare class ColorSpec {
157
+ readonly name: string;
158
+ readonly type: ColorDepth;
159
+ readonly number: number | undefined;
160
+ readonly value: ColorRgba | undefined;
161
+ private downgradeCache;
162
+ constructor(name: string, type: ColorDepth, number?: number, value?: ColorRgba);
163
+ get isDefault(): boolean;
164
+ /**
165
+ * The colour every terminal draws this spec as, where that is fixed — before
166
+ * any alpha is flattened, so a translucent value is drawn composited over
167
+ * its ground (`flattenAlpha`) and should be measured that way: a
168
+ * truecolor value, or a 256-colour cube or grey-ramp entry (xterm fixes
169
+ * indices 16–255, and `fromAnsi` types only those as EIGHT_BIT). ANSI 0–15
170
+ * and the default colour are the terminal theme's own, so they have none.
171
+ */
172
+ get fixedValue(): ColorRgba | undefined;
173
+ get isSystemDefined(): boolean;
174
+ /**
175
+ * Generates SGR parameter strings for this color.
176
+ */
177
+ getAnsiCodes(foreground?: boolean): string[];
178
+ /**
179
+ * Composite this color's alpha (if any) against an opaque background,
180
+ * returning a fully-opaque ColorSpec. Non-truecolor specs (palette
181
+ * indices, default) have no alpha and pass through unchanged.
182
+ *
183
+ * [LAW:single-enforcer] Sole alpha-flattening site for the render
184
+ * pipeline. Run *before* downgrade — otherwise palette matching on the
185
+ * still-translucent RGB silently drops alpha (the "flatten then lose
186
+ * alpha" path the unification ticket forbids).
187
+ *
188
+ * [LAW:dataflow-not-control-flow] `ColorRgba.compositeOver` is a no-op
189
+ * when alpha=1, so callers invoke this unconditionally; the data
190
+ * (alpha value) decides whether work happens.
191
+ */
192
+ flattenAlpha(bg: ColorRgba): ColorSpec;
193
+ /**
194
+ * Downgrade to a lower-fidelity color depth. Cached.
195
+ */
196
+ downgrade(targetSystem: ColorDepth): ColorSpec;
197
+ /**
198
+ * Resolves to an actual RGB(A) value for any color type.
199
+ */
200
+ getTruecolor(theme?: TerminalTheme, foreground?: boolean): ColorRgba;
201
+ static default(): ColorSpec;
202
+ static fromAnsi(n: number): ColorSpec;
203
+ static fromRgba(value: ColorRgba): ColorSpec;
204
+ static fromRgb(r: number, g: number, b: number): ColorSpec;
205
+ /**
206
+ * Parse a color string. Cached — identical strings return the same instance.
207
+ */
208
+ static parse(colorString: string): ColorSpec;
209
+ private performDowngrade;
210
+ }
211
+ export declare function parseRgbHex(hex: string): ColorRgba;
212
+ export declare function parseRgbaHex(hex: string): ColorRgba;
213
+ export declare function blendRgb(color1: ColorRgba, color2: ColorRgba, crossFade?: number): ColorRgba;
214
+ /**
215
+ * A terminal theme — surface/foreground baseline, the ANSI 16/256 LUT, and a
216
+ * semantic palette.
217
+ *
218
+ * **`ansiColors` is the theme's own sixteen colours**, the shades a terminal
219
+ * showing this theme draws `red`, `blue` and the rest in. `ColorSpec.parse("red")`
220
+ * is ANSI colour 1 under every theme; which red that is, is the theme's to say,
221
+ * as it is in Rich, where each `TerminalTheme` carries its own table.
222
+ */
223
+ export declare class TerminalTheme {
224
+ readonly backgroundColor: ColorRgba;
225
+ readonly foregroundColor: ColorRgba;
226
+ readonly ansiColors: ColorTable;
227
+ readonly palette: import("../themes/palette.js").Palette;
228
+ constructor(backgroundColor: ColorRgba, foregroundColor: ColorRgba, ansiColors: ColorTable, palette: import("../themes/palette.js").Palette);
229
+ }
230
+ export declare const STANDARD_TABLE: ColorTable;
231
+ export declare const EIGHT_BIT_TABLE: ColorTable;
232
+ /**
233
+ * What a downgrade to 256 colours may choose: the cube and the grey ramp,
234
+ * indices 16–255. Indices 0–15 are the terminal's own ANSI colours, which
235
+ * every theme redefines, so their RGB is unknown and a match against them is
236
+ * a guess; Python Rich never picks them either.
237
+ */
238
+ export declare const EIGHT_BIT_DOWNGRADE_TABLE: ColorTable;
239
+ export declare const WINDOWS_TABLE: ColorTable;
240
+ export declare const ANSI_COLOR_NAMES: Record<string, number>;
241
+ /**
242
+ * Every word `ColorSpec.parse` accepts as a name rather than a `#hex`,
243
+ * `rgb()` or `color(N)` form: `default` and the named palette entries, which
244
+ * are exactly the two name checks `parseSingle` makes.
245
+ */
246
+ export declare const COLOR_NAMES: readonly string[];