@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,267 @@
1
+ /**
2
+ * Rendering protocol interfaces — Renderable, Measurable, RenderOptions.
3
+ * [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
4
+ */
5
+ import type { Segment } from "./segment.js";
6
+ import { type Style, type StyleSyntaxError, type Theme } from "./style.js";
7
+ import type { ColorDepth } from "./color.js";
8
+ /**
9
+ * What becomes of a line still wider than its width once wrapping is done.
10
+ * `"fold"` breaks it across lines, `"crop"` cuts it at the edge, `"ellipsis"`
11
+ * cuts it and marks the cut. `"ignore"` is not a way of cutting: the line has
12
+ * no edge, so nothing wraps it and nothing cuts it, and it leaves at its
13
+ * natural width for whatever drew it to crop or not.
14
+ *
15
+ * `noWrap` is the other half of Rich's pair and is not `"ignore"`: it stops the
16
+ * wrapping and leaves the edge, so a line too wide is cut by the method here.
17
+ */
18
+ export type OverflowMethod = "fold" | "crop" | "ellipsis" | "ignore";
19
+ export interface RenderOptions {
20
+ /**
21
+ * The cells this renderable may occupy. A count of cells, so a non-negative
22
+ * integer — but the field is a plain `number` because it is public API and a
23
+ * caller can write anything into it. `withCellWidth` is where that becomes
24
+ * true rather than assumed; see its comment for why every renderable must
25
+ * call it rather than trusting this declaration.
26
+ */
27
+ maxWidth: number;
28
+ minWidth?: number;
29
+ /**
30
+ * The rows this renderable may occupy. Absent, nothing limits them.
31
+ */
32
+ height?: Height;
33
+ isTerminal?: boolean;
34
+ /**
35
+ * The output can draw only ASCII. Every glyph a renderable chooses — box
36
+ * lines, tree guides, a check mark — is resolved through `drawable`. Text
37
+ * the caller hands in is drawn as given. Absent means the output draws any
38
+ * glyph.
39
+ */
40
+ asciiOnly?: boolean;
41
+ justify?: "left" | "center" | "right" | "full";
42
+ overflow?: OverflowMethod;
43
+ noWrap?: boolean;
44
+ highlight?: unknown;
45
+ markup?: unknown;
46
+ /**
47
+ * The depth the output will be encoded at, `null` when it carries no colour.
48
+ * A renderable choosing between two ways of drawing something — the strip's
49
+ * arrow or its divider — decides on the colours the terminal will draw at
50
+ * this depth, which at 256 colours or fewer are not the colours it was
51
+ * handed. Absent means truecolor.
52
+ */
53
+ colorSystem?: ColorDepth | null;
54
+ /**
55
+ * The names a style string may use. A `Console` passes its own; absent, the
56
+ * built-in defaults apply. Read it through `getStyle` rather than directly.
57
+ */
58
+ theme?: Theme;
59
+ /**
60
+ * Called each time a render degrades a style string it cannot parse to
61
+ * unstyled, once per resolution — a style the render reads twice is reported
62
+ * twice. Absent, the failure passes silently. A handler that throws makes
63
+ * the render strict: the error leaves `render`.
64
+ */
65
+ onStyleError?: StyleErrorHandler;
66
+ /**
67
+ * Told of each owner that stamps its output (`core/anchor`) as it starts to
68
+ * render, so the calls arrive in document order: a container's children in
69
+ * the order it renders them, an owner before the owners nested in it. A
70
+ * container forwards it by passing its options on, which every container
71
+ * does. An owner rendered twice is told twice. A render made to measure is
72
+ * not drawing and is not told (`measuring`).
73
+ */
74
+ onDraw?: (owner: object) => void;
75
+ }
76
+ /**
77
+ * `chosen` as this output can draw it: `ascii` in its place when the output is
78
+ * ASCII-only and `chosen` spends a glyph outside ASCII, otherwise `chosen`
79
+ * itself. Choosing a glyph that is already ASCII (a rule of `=`, a MARKDOWN box)
80
+ * picks a frame the terminal can draw, so it is kept. A set of glyphs that must
81
+ * change together — a box grid, a spinner's frames, a scrollbar — passes
82
+ * `glyphs` to say which characters it spends, and is replaced whole.
83
+ *
84
+ * [LAW:single-enforcer] The one place that decides what `asciiOnly` replaces.
85
+ * `test/renderables/ascii-only.test.ts` renders every exported renderable to
86
+ * hold each of them to it.
87
+ */
88
+ export declare function drawable(options: {
89
+ readonly asciiOnly?: boolean;
90
+ }, chosen: string, ascii: string): string;
91
+ export declare function drawable<T>(options: {
92
+ readonly asciiOnly?: boolean;
93
+ }, chosen: T, ascii: T, glyphs: (value: T) => string): T;
94
+ /**
95
+ * A vertical budget: a count of rows, and whether they are a region the
96
+ * renderable stands in or a ceiling it stays under.
97
+ *
98
+ * `exact: true` is a region — a `Layout` pane, a full-screen frame. The output
99
+ * is exactly `rows` tall. A renderable may fill the region — a layout divides
100
+ * it, a frame can stretch to its last row — or ignore it and emit its natural
101
+ * height; either way, whoever set the region shapes what comes back to it
102
+ * (`fitHeight`). Setting a region is a promise to shape, so no renderable pads
103
+ * or crops itself to one.
104
+ *
105
+ * `exact: false` is a ceiling — the terminal an inline print lands on. Content
106
+ * keeps its natural height beneath it, and nothing pads up to the ceiling.
107
+ * Output taller than the ceiling is its setter's to handle: an inline `Live`
108
+ * applies its `verticalOverflow`, and a print lets the terminal scroll it.
109
+ *
110
+ * A renderable passing its whole space to one child must forward the budget
111
+ * less the rows it draws itself, and of the same kind (`insetHeight`). One
112
+ * stacking several children must hand each the rows as a ceiling
113
+ * (`stackedHeight`): any one of them may use all of it, and none may claim it
114
+ * as its region. Forwarded unchanged, a layout nested in a panel fills the
115
+ * panel's whole region and the region's crop takes the panel's bottom border.
116
+ *
117
+ * [LAW:types-are-the-program] One field, because the fact is one count and one
118
+ * bit about it. As two numbers, `height` and `maxHeight`, it admitted a region
119
+ * taller than its ceiling and a region with no ceiling at all, and the one
120
+ * reader in the library resolved them `maxHeight ?? height` — the ceiling ahead
121
+ * of the region it contains.
122
+ *
123
+ * There is no vertical `Measurable`. A parent chooses widths from its
124
+ * children's measurements before any of them renders; nothing chooses rows
125
+ * that way, because a renderable's height is a consequence of the width it
126
+ * renders at. Rendering and counting the lines is the measurement.
127
+ */
128
+ export interface Height {
129
+ readonly rows: number;
130
+ readonly exact: boolean;
131
+ }
132
+ /**
133
+ * The budget a renderable hands the one child filling its space: its own, less
134
+ * the `rows` it draws itself, and of the same kind.
135
+ */
136
+ export declare function insetHeight(height: Height | undefined, rows: number): Height | undefined;
137
+ /**
138
+ * The budget a renderable hands each of several children it stacks: its rows,
139
+ * as a ceiling.
140
+ */
141
+ export declare function stackedHeight(height: Height | undefined): Height | undefined;
142
+ /**
143
+ * The rows a region holds, parsed as a cell count, or `undefined` when there
144
+ * is no region to fill: a ceiling, no budget, or a region of `Infinity` rows,
145
+ * which names no count — as an unbounded width resolves to a natural one in
146
+ * `withBoundedWidth`.
147
+ */
148
+ export declare function regionRows(height: Height | undefined): number | undefined;
149
+ /**
150
+ * What came back from a child, held to the region `height` names — blank rows
151
+ * padded below, overflow cropped from the bottom — or left at its own height
152
+ * when `height` is no region. This is the shaping a region's setter owes.
153
+ */
154
+ export declare function fitHeight(lines: Segment[][], height: Height | undefined): Segment[][];
155
+ /**
156
+ * Receives a style error a render would otherwise absorb: the parse failure,
157
+ * and the whole style string it came from — the error names only the token
158
+ * that failed, and `"bold rd"` loses its `bold` too.
159
+ *
160
+ * [LAW:no-mode-explosion] Strict mode is not a second option beside this one.
161
+ * It is a handler that throws, so there is no "strict and a callback" state
162
+ * whose order anyone has to define.
163
+ */
164
+ export type StyleErrorHandler = (error: StyleSyntaxError, style: string) => void;
165
+ /**
166
+ * The style a `string | Style` stands for in this render: a name from the
167
+ * theme of whatever is drawing, otherwise a style definition. A string that is
168
+ * neither throws `StyleSyntaxError`. This is how a renderable that yields its
169
+ * own `Segment`s draws with a theme name, since a `Segment` takes a resolved
170
+ * `Style`. It is Rich's `console.get_style`, asked of the options because the
171
+ * options are where the theme travels.
172
+ *
173
+ * [LAW:single-enforcer] Every renderable resolves a style name here, at render
174
+ * time, because only the render knows whose theme it is drawing for. Resolved
175
+ * when a renderable was built instead, a name was fixed to the built-in
176
+ * defaults before any `Console` could offer its theme, and `new Console({
177
+ * theme })` changed nothing.
178
+ */
179
+ export declare function getStyle(options: RenderOptions, style: string | Style): Style;
180
+ /**
181
+ * The options a renderable should work from: the caller's, with `maxWidth`
182
+ * parsed into an actual count of cells.
183
+ *
184
+ * [LAW:parse-dont-validate] The stamp has to travel, which is the whole reason
185
+ * this returns options rather than a number. Parsing `options.maxWidth` into a
186
+ * local leaves the caller's raw value sitting in `options` for whatever the
187
+ * renderable forwards it to — and every renderable forwards it, to a child's
188
+ * `render`, to `Measurement.get`, to a nested layout. `Columns` was fixed that
189
+ * way first and still threw `Invalid array length` on a NaN width, because it
190
+ * handed the unparsed original to `Measurement.get` and got a NaN column count
191
+ * back. Replace the field, and nothing downstream can see the number the
192
+ * caller actually wrote.
193
+ *
194
+ * [LAW:single-enforcer] This is the width checkpoint for the render contract.
195
+ * There is no second one: a renderable that re-derives its own answer is how
196
+ * `Panel`, `Table`, `Tree`, `Columns` and `Layout` came to disagree about what
197
+ * a NaN width means — one collapsed to a cell of garbage, two threw, one
198
+ * ignored the request and emitted its full natural width.
199
+ */
200
+ export declare function withCellWidth(options: RenderOptions): RenderOptions;
201
+ /**
202
+ * The options a renderable should *lay out against*: `withCellWidth`, with an
203
+ * unbounded offer resolved to the renderable's own natural width.
204
+ *
205
+ * [LAW:parse-dont-validate] `cellCount` cannot finish the job alone. It floors
206
+ * a negative width and NaN to zero from the number alone, but `Infinity` is not
207
+ * a quantity it can floor — the only finite answer is "as wide as this
208
+ * renderable's content wants", which is a question about the renderable and not
209
+ * about the number. So the parse is completed here, where the renderable is in
210
+ * hand, and the stamped options travel exactly as `withCellWidth`'s do.
211
+ *
212
+ * [LAW:single-enforcer] One checkpoint for the whole rule, not one per
213
+ * renderable. `Panel`, `Padding`, `Columns` and `Layout` each expand into the
214
+ * width they are offered, and each independently reached `" ".repeat(Infinity)`
215
+ * — Columns twice over, since its column *count* is derived from the width too
216
+ * and `new Array(Infinity)` throws a different error again.
217
+ *
218
+ * The natural width comes from `measure`, which every one of them already
219
+ * implements, and the recursion terminates because `measure` reports content
220
+ * rather than the offer: `Panel.measure({maxWidth: Infinity})` is `{9, 9}` for
221
+ * nine cells of content and frame, not `{9, Infinity}`.
222
+ *
223
+ * So this belongs at the top of `render` and never at the top of `measure`:
224
+ * `measure` is the method being asked, and asking it through here would ask it
225
+ * with itself. `measure` parses with `withCellWidth` and reports a natural
226
+ * width of its own — that is the half of the contract that makes this half work.
227
+ *
228
+ * A `maximum` of `Infinity` means the renderable genuinely cannot answer. Two
229
+ * ways in: it wraps a `Renderable` with no `measure`, so nothing in the tree
230
+ * knows how wide the content wants to be; or something inside it asked for an
231
+ * unbounded width of its own, as a `Table` column declared
232
+ * `{ ratio: Infinity }` does. [LAW:no-silent-failure] Both are unanswerable
233
+ * rather than zero, and saying so names the cause — where `String.repeat` and
234
+ * `new Array` only ever name their own argument.
235
+ */
236
+ /**
237
+ * `options` as a measurement is asked with. A `measure` that renders to learn
238
+ * its shape draws nothing on the frame, so it reports no owner to `onDraw` —
239
+ * one that did would put a widget in document order at the moment a
240
+ * container measured it, and keep one the frame then cropped away.
241
+ *
242
+ * [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
243
+ * `withBoundedWidth` ask through it, and each `measure` passes it on.
244
+ */
245
+ export declare function measuring(options: RenderOptions): RenderOptions;
246
+ export declare function withBoundedWidth(options: RenderOptions, self: Measurable): RenderOptions;
247
+ export interface Renderable {
248
+ render(options: RenderOptions): Iterable<Segment>;
249
+ }
250
+ export interface Measurable {
251
+ measure(options: RenderOptions): {
252
+ minimum: number;
253
+ maximum: number;
254
+ };
255
+ }
256
+ /**
257
+ * What the wheel scrolls: an owner of anchors on the frame (`./anchor.ts`)
258
+ * that moves by lines. `canScrollBy` says whether `scrollBy` with the same
259
+ * `lines` would move it; one that would not passes the wheel outward.
260
+ */
261
+ export interface Scrollable {
262
+ canScrollBy(lines: number): boolean;
263
+ scrollBy(lines: number): void;
264
+ }
265
+ export declare function isRenderable(obj: unknown): obj is Renderable;
266
+ export declare function isMeasurable(obj: unknown): obj is Measurable;
267
+ export declare function isScrollable(obj: object): obj is Scrollable;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Rendering protocol interfaces — Renderable, Measurable, RenderOptions.
3
+ * [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
4
+ */
5
+ import { cellCount } from "./cells.js";
6
+ import { DEFAULT_THEME } from "./style.js";
7
+ const ASCII_TEXT = /^[\x00-\x7F]*$/;
8
+ export function drawable(options, chosen, ascii, glyphs = String) {
9
+ return options.asciiOnly === true && !ASCII_TEXT.test(glyphs(chosen)) ? ascii : chosen;
10
+ }
11
+ /**
12
+ * The budget a renderable hands the one child filling its space: its own, less
13
+ * the `rows` it draws itself, and of the same kind.
14
+ */
15
+ export function insetHeight(height, rows) {
16
+ return height && { rows: cellCount(height.rows - rows), exact: height.exact };
17
+ }
18
+ /**
19
+ * The budget a renderable hands each of several children it stacks: its rows,
20
+ * as a ceiling.
21
+ */
22
+ export function stackedHeight(height) {
23
+ return height && { rows: cellCount(height.rows), exact: false };
24
+ }
25
+ /**
26
+ * The rows a region holds, parsed as a cell count, or `undefined` when there
27
+ * is no region to fill: a ceiling, no budget, or a region of `Infinity` rows,
28
+ * which names no count — as an unbounded width resolves to a natural one in
29
+ * `withBoundedWidth`.
30
+ */
31
+ export function regionRows(height) {
32
+ return height?.exact && height.rows !== Infinity ? cellCount(height.rows) : undefined;
33
+ }
34
+ /**
35
+ * What came back from a child, held to the region `height` names — blank rows
36
+ * padded below, overflow cropped from the bottom — or left at its own height
37
+ * when `height` is no region. This is the shaping a region's setter owes.
38
+ */
39
+ export function fitHeight(lines, height) {
40
+ const rows = regionRows(height) ?? lines.length;
41
+ const fitted = lines.slice(0, rows);
42
+ while (fitted.length < rows)
43
+ fitted.push([]);
44
+ return fitted;
45
+ }
46
+ /**
47
+ * The style a `string | Style` stands for in this render: a name from the
48
+ * theme of whatever is drawing, otherwise a style definition. A string that is
49
+ * neither throws `StyleSyntaxError`. This is how a renderable that yields its
50
+ * own `Segment`s draws with a theme name, since a `Segment` takes a resolved
51
+ * `Style`. It is Rich's `console.get_style`, asked of the options because the
52
+ * options are where the theme travels.
53
+ *
54
+ * [LAW:single-enforcer] Every renderable resolves a style name here, at render
55
+ * time, because only the render knows whose theme it is drawing for. Resolved
56
+ * when a renderable was built instead, a name was fixed to the built-in
57
+ * defaults before any `Console` could offer its theme, and `new Console({
58
+ * theme })` changed nothing.
59
+ */
60
+ export function getStyle(options, style) {
61
+ return (options.theme ?? DEFAULT_THEME).resolve(style);
62
+ }
63
+ /**
64
+ * The options a renderable should work from: the caller's, with `maxWidth`
65
+ * parsed into an actual count of cells.
66
+ *
67
+ * [LAW:parse-dont-validate] The stamp has to travel, which is the whole reason
68
+ * this returns options rather than a number. Parsing `options.maxWidth` into a
69
+ * local leaves the caller's raw value sitting in `options` for whatever the
70
+ * renderable forwards it to — and every renderable forwards it, to a child's
71
+ * `render`, to `Measurement.get`, to a nested layout. `Columns` was fixed that
72
+ * way first and still threw `Invalid array length` on a NaN width, because it
73
+ * handed the unparsed original to `Measurement.get` and got a NaN column count
74
+ * back. Replace the field, and nothing downstream can see the number the
75
+ * caller actually wrote.
76
+ *
77
+ * [LAW:single-enforcer] This is the width checkpoint for the render contract.
78
+ * There is no second one: a renderable that re-derives its own answer is how
79
+ * `Panel`, `Table`, `Tree`, `Columns` and `Layout` came to disagree about what
80
+ * a NaN width means — one collapsed to a cell of garbage, two threw, one
81
+ * ignored the request and emitted its full natural width.
82
+ */
83
+ export function withCellWidth(options) {
84
+ return { ...options, maxWidth: cellCount(options.maxWidth) };
85
+ }
86
+ /**
87
+ * The options a renderable should *lay out against*: `withCellWidth`, with an
88
+ * unbounded offer resolved to the renderable's own natural width.
89
+ *
90
+ * [LAW:parse-dont-validate] `cellCount` cannot finish the job alone. It floors
91
+ * a negative width and NaN to zero from the number alone, but `Infinity` is not
92
+ * a quantity it can floor — the only finite answer is "as wide as this
93
+ * renderable's content wants", which is a question about the renderable and not
94
+ * about the number. So the parse is completed here, where the renderable is in
95
+ * hand, and the stamped options travel exactly as `withCellWidth`'s do.
96
+ *
97
+ * [LAW:single-enforcer] One checkpoint for the whole rule, not one per
98
+ * renderable. `Panel`, `Padding`, `Columns` and `Layout` each expand into the
99
+ * width they are offered, and each independently reached `" ".repeat(Infinity)`
100
+ * — Columns twice over, since its column *count* is derived from the width too
101
+ * and `new Array(Infinity)` throws a different error again.
102
+ *
103
+ * The natural width comes from `measure`, which every one of them already
104
+ * implements, and the recursion terminates because `measure` reports content
105
+ * rather than the offer: `Panel.measure({maxWidth: Infinity})` is `{9, 9}` for
106
+ * nine cells of content and frame, not `{9, Infinity}`.
107
+ *
108
+ * So this belongs at the top of `render` and never at the top of `measure`:
109
+ * `measure` is the method being asked, and asking it through here would ask it
110
+ * with itself. `measure` parses with `withCellWidth` and reports a natural
111
+ * width of its own — that is the half of the contract that makes this half work.
112
+ *
113
+ * A `maximum` of `Infinity` means the renderable genuinely cannot answer. Two
114
+ * ways in: it wraps a `Renderable` with no `measure`, so nothing in the tree
115
+ * knows how wide the content wants to be; or something inside it asked for an
116
+ * unbounded width of its own, as a `Table` column declared
117
+ * `{ ratio: Infinity }` does. [LAW:no-silent-failure] Both are unanswerable
118
+ * rather than zero, and saying so names the cause — where `String.repeat` and
119
+ * `new Array` only ever name their own argument.
120
+ */
121
+ /**
122
+ * `options` as a measurement is asked with. A `measure` that renders to learn
123
+ * its shape draws nothing on the frame, so it reports no owner to `onDraw` —
124
+ * one that did would put a widget in document order at the moment a
125
+ * container measured it, and keep one the frame then cropped away.
126
+ *
127
+ * [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
128
+ * `withBoundedWidth` ask through it, and each `measure` passes it on.
129
+ */
130
+ export function measuring(options) {
131
+ return { ...options, onDraw: undefined };
132
+ }
133
+ export function withBoundedWidth(options, self) {
134
+ const parsed = withCellWidth(options);
135
+ if (Number.isFinite(parsed.maxWidth))
136
+ return parsed;
137
+ const natural = self.measure(measuring(parsed)).maximum;
138
+ if (!Number.isFinite(natural)) {
139
+ throw new RangeError("maxWidth is unbounded and this renderable has no natural width to fall " +
140
+ "back on: its content does not implement measure(), or something inside " +
141
+ "it asked for an unbounded width of its own. Render it at a finite " +
142
+ "width, or give the content a measure() that reports a finite maximum.");
143
+ }
144
+ return { ...parsed, maxWidth: cellCount(natural) };
145
+ }
146
+ export function isRenderable(obj) {
147
+ return (typeof obj === "object" &&
148
+ obj !== null &&
149
+ "render" in obj &&
150
+ typeof obj.render === "function");
151
+ }
152
+ export function isMeasurable(obj) {
153
+ return (typeof obj === "object" &&
154
+ obj !== null &&
155
+ "measure" in obj &&
156
+ typeof obj.measure === "function");
157
+ }
158
+ export function isScrollable(obj) {
159
+ return ("canScrollBy" in obj &&
160
+ typeof obj.canScrollBy === "function" &&
161
+ "scrollBy" in obj &&
162
+ typeof obj.scrollBy === "function");
163
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * `renderToString` — stateless one-shot emission of a `Renderable` to a string
3
+ * of ANSI-encoded text. Pure function: same inputs produce byte-identical
4
+ * output. Does not write to `process.stdout` and does not require a `Console`
5
+ * instance.
6
+ *
7
+ * Note on `colorSystem: "auto"`: the `"auto"` spec resolves via
8
+ * `detectColorSystem`, which reads `process.env` and `process.stdout?.isTTY`
9
+ * by default. Callers that want a fully deterministic render must either pass
10
+ * an explicit `ColorDepth` enum / non-`"auto"` spec, or supply `env` and
11
+ * `isTTY` in the options so detection does not consult ambient process state.
12
+ *
13
+ * [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `segmentsToString`
14
+ * and is the single way segments become wire bytes. `Console._writeSegments`,
15
+ * `Live.refresh`, `Screen`'s frame paint, `renderToString`, and
16
+ * `segmentToString` all delegate here, so terminal output, live frames, widget
17
+ * frames, string export, and single-segment encoding agree by construction.
18
+ *
19
+ * [LAW:one-source-of-truth] What the encoding needs from where it writes
20
+ * arrives as one `Destination` value. Each caller resolves its destination
21
+ * once — spec, detection, and its own overrides — and hands it through whole,
22
+ * so a depth is never paired with a link setting taken from somewhere else.
23
+ *
24
+ * [LAW:dataflow-not-control-flow] The same pipeline runs every render: collect
25
+ * non-control non-empty pieces, partition by SGR-codes (SGR-runs), partition
26
+ * each run by link (link-runs), emit one SGR open/close per run with link
27
+ * open/close pairs sitting inside. Colour and hyperlinks are two facts about
28
+ * the destination: `colorSystem === null` empties every piece's SGR-codes, and
29
+ * `hyperlinks === false` empties every piece's link. A colour depth never
30
+ * removes a link — a hyperlink is not a colour, and a NO_COLOR terminal still
31
+ * follows OSC 8.
32
+ *
33
+ * [LAW:types-are-the-program] Adjacent same-style segments share an SGR wrap
34
+ * because the SGR-codes string is the same group key for both — the
35
+ * partitioning shape (data) encodes the byte structure; the emit walk is a
36
+ * mechanical fold over it.
37
+ */
38
+ import { ColorDepth } from "./color.js";
39
+ import type { Destination } from "./color.js";
40
+ import type { Segment } from "./segment.js";
41
+ import type { Renderable, RenderOptions } from "./protocol.js";
42
+ export interface RenderToStringOptions extends Pick<RenderOptions, "asciiOnly" | "onStyleError"> {
43
+ /** Cell width to render into. Default 80. */
44
+ width?: number;
45
+ /**
46
+ * Color encoding to emit. Accepts a string spec (`"auto"`, `"truecolor"`,
47
+ * `"256"`, `"ansi"`, `"none"`), a `ColorDepth` enum value, or `null` to
48
+ * strip SGR colors/attributes. Hyperlinks are `hyperlinks`' concern. Default
49
+ * truecolor.
50
+ */
51
+ colorSystem?: string | ColorDepth | null;
52
+ /**
53
+ * Environment to consult when `colorSystem` is `"auto"`. Defaults to
54
+ * `process.env`. Pass an explicit value to keep rendering deterministic.
55
+ */
56
+ env?: NodeJS.ProcessEnv;
57
+ /**
58
+ * Whether output is going to a TTY when `colorSystem` is `"auto"`. Defaults
59
+ * to `process.stdout?.isTTY`. Pass an explicit value to keep rendering
60
+ * deterministic.
61
+ */
62
+ isTTY?: boolean;
63
+ /** When true, forces `colorSystem` to `null` regardless of the explicit value. */
64
+ noColor?: boolean;
65
+ /**
66
+ * Whether OSC 8 hyperlinks are emitted. Independent of `colorSystem`: a
67
+ * null colour system strips SGR and keeps links. Default: what the
68
+ * destination takes — true for an explicit depth, detected under `"auto"`
69
+ * (no TTY or TERM=dumb: false). `false` with `colorSystem: null` is plain text.
70
+ */
71
+ hyperlinks?: boolean;
72
+ }
73
+ /**
74
+ * Encodes a single segment as ANSI bytes for `destination`. Equivalent to
75
+ * `segmentsToString([segment], destination)` — same SGR / OSC 8 layout.
76
+ */
77
+ export declare function segmentToString(segment: Segment, destination: Destination): string;
78
+ /**
79
+ * Encodes a sequence of segments as ANSI bytes for `destination`, coalescing
80
+ * adjacent same-SGR segments under a single SGR open/close pair, with OSC 8
81
+ * link pairs nested inside per same-link sub-run.
82
+ */
83
+ export declare function segmentsToString(segments: Iterable<Segment>, destination: Destination): string;
84
+ export declare function renderToString(renderable: Renderable, options?: RenderToStringOptions): string;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * `renderToString` — stateless one-shot emission of a `Renderable` to a string
3
+ * of ANSI-encoded text. Pure function: same inputs produce byte-identical
4
+ * output. Does not write to `process.stdout` and does not require a `Console`
5
+ * instance.
6
+ *
7
+ * Note on `colorSystem: "auto"`: the `"auto"` spec resolves via
8
+ * `detectColorSystem`, which reads `process.env` and `process.stdout?.isTTY`
9
+ * by default. Callers that want a fully deterministic render must either pass
10
+ * an explicit `ColorDepth` enum / non-`"auto"` spec, or supply `env` and
11
+ * `isTTY` in the options so detection does not consult ambient process state.
12
+ *
13
+ * [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `segmentsToString`
14
+ * and is the single way segments become wire bytes. `Console._writeSegments`,
15
+ * `Live.refresh`, `Screen`'s frame paint, `renderToString`, and
16
+ * `segmentToString` all delegate here, so terminal output, live frames, widget
17
+ * frames, string export, and single-segment encoding agree by construction.
18
+ *
19
+ * [LAW:one-source-of-truth] What the encoding needs from where it writes
20
+ * arrives as one `Destination` value. Each caller resolves its destination
21
+ * once — spec, detection, and its own overrides — and hands it through whole,
22
+ * so a depth is never paired with a link setting taken from somewhere else.
23
+ *
24
+ * [LAW:dataflow-not-control-flow] The same pipeline runs every render: collect
25
+ * non-control non-empty pieces, partition by SGR-codes (SGR-runs), partition
26
+ * each run by link (link-runs), emit one SGR open/close per run with link
27
+ * open/close pairs sitting inside. Colour and hyperlinks are two facts about
28
+ * the destination: `colorSystem === null` empties every piece's SGR-codes, and
29
+ * `hyperlinks === false` empties every piece's link. A colour depth never
30
+ * removes a link — a hyperlink is not a colour, and a NO_COLOR terminal still
31
+ * follows OSC 8.
32
+ *
33
+ * [LAW:types-are-the-program] Adjacent same-style segments share an SGR wrap
34
+ * because the SGR-codes string is the same group key for both — the
35
+ * partitioning shape (data) encodes the byte structure; the emit walk is a
36
+ * mechanical fold over it.
37
+ */
38
+ import { ColorDepth, resolveDestination } from "./color.js";
39
+ import { OSC8_CLOSE, osc8Open } from "./osc8.js";
40
+ const DEFAULT_WIDTH = 80;
41
+ function segmentToPiece(segment, destination) {
42
+ if (segment.isControl)
43
+ return undefined;
44
+ if (segment.text.length === 0)
45
+ return undefined;
46
+ const style = segment.style;
47
+ if (!style || style.isNull) {
48
+ return { text: segment.text, sgrCodes: "", link: undefined };
49
+ }
50
+ // [LAW:one-type-per-behavior] Colour depth governs SGR only; hyperlinks are
51
+ // their own fact. Coupling them let a colour setting delete every control.
52
+ return {
53
+ text: segment.text,
54
+ sgrCodes: destination.colorSystem === null ? "" : style.toSgrCodes(destination.colorSystem),
55
+ link: destination.hyperlinks ? style.link : undefined,
56
+ };
57
+ }
58
+ /**
59
+ * Encodes a single segment as ANSI bytes for `destination`. Equivalent to
60
+ * `segmentsToString([segment], destination)` — same SGR / OSC 8 layout.
61
+ */
62
+ export function segmentToString(segment, destination) {
63
+ return segmentsToString([segment], destination);
64
+ }
65
+ /**
66
+ * Encodes a sequence of segments as ANSI bytes for `destination`, coalescing
67
+ * adjacent same-SGR segments under a single SGR open/close pair, with OSC 8
68
+ * link pairs nested inside per same-link sub-run.
69
+ */
70
+ export function segmentsToString(segments, destination) {
71
+ const pieces = [];
72
+ for (const s of segments) {
73
+ const p = segmentToPiece(s, destination);
74
+ if (p)
75
+ pieces.push(p);
76
+ }
77
+ if (pieces.length === 0)
78
+ return "";
79
+ // [LAW:dataflow-not-control-flow] One linear chunk accumulator for the
80
+ // entire output; SGR / OSC 8 boundaries and piece texts all push into it
81
+ // in order. No per-link-run intermediate string, no quadratic `+=` chains.
82
+ const parts = [];
83
+ let i = 0;
84
+ while (i < pieces.length) {
85
+ const sgr = pieces[i].sgrCodes;
86
+ let j = i + 1;
87
+ while (j < pieces.length && pieces[j].sgrCodes === sgr)
88
+ j++;
89
+ if (sgr.length > 0)
90
+ parts.push(`\x1b[${sgr}m`);
91
+ let k = i;
92
+ while (k < j) {
93
+ const link = pieces[k].link;
94
+ let l = k + 1;
95
+ while (l < j && pieces[l].link === link)
96
+ l++;
97
+ if (link) {
98
+ parts.push(osc8Open(link));
99
+ for (let m = k; m < l; m++)
100
+ parts.push(pieces[m].text);
101
+ parts.push(OSC8_CLOSE);
102
+ }
103
+ else {
104
+ for (let m = k; m < l; m++)
105
+ parts.push(pieces[m].text);
106
+ }
107
+ k = l;
108
+ }
109
+ if (sgr.length > 0)
110
+ parts.push("\x1b[0m");
111
+ i = j;
112
+ }
113
+ return parts.join("");
114
+ }
115
+ export function renderToString(renderable, options) {
116
+ const width = options?.width ?? DEFAULT_WIDTH;
117
+ // [LAW:dataflow-not-control-flow] An absent spec is truecolor; an explicit
118
+ // `null` stays null (`??` would collapse it).
119
+ // [LAW:single-enforcer] Specs resolve through `resolveDestination`.
120
+ const detectOptions = {};
121
+ if (options?.env !== undefined)
122
+ detectOptions.env = options.env;
123
+ if (options?.isTTY !== undefined)
124
+ detectOptions.isTTY = options.isTTY;
125
+ const rawSpec = options?.colorSystem;
126
+ const resolved = resolveDestination(rawSpec === undefined ? ColorDepth.TRUECOLOR : rawSpec, detectOptions);
127
+ // [LAW:single-enforcer] This call's overrides are applied here, once, to
128
+ // make the one destination everything below is drawn for. `noColor` is a
129
+ // colour choice, so it overrides the depth and leaves hyperlinks to the
130
+ // destination; an explicit `hyperlinks` overrides what the destination takes.
131
+ const destination = {
132
+ colorSystem: options?.noColor ? null : resolved.colorSystem,
133
+ hyperlinks: options?.hyperlinks ?? resolved.hyperlinks,
134
+ };
135
+ const renderOptions = {
136
+ maxWidth: width,
137
+ isTerminal: false,
138
+ asciiOnly: options?.asciiOnly ?? false,
139
+ onStyleError: options?.onStyleError,
140
+ // [LAW:one-source-of-truth] The depth the segments below are encoded at,
141
+ // so a renderable measures what this very call will draw.
142
+ colorSystem: destination.colorSystem,
143
+ };
144
+ return segmentsToString(renderable.render(renderOptions), destination);
145
+ }