@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,107 @@
1
+ /**
2
+ * The one crossing where content a caller hands a renderable — a table cell, a
3
+ * panel's body, title or subtitle, a padding's body, a tree label, a column
4
+ * item, a layout pane, a rule's title — becomes something that renderable can
5
+ * lay out.
6
+ * [LAW:single-enforcer]
7
+ *
8
+ * A string is kept as written and read when it is drawn, through `readStr`,
9
+ * under the options it is drawn with. That is where a console's settings
10
+ * arrive: its `markup` decides whether the string is markup, and its
11
+ * highlighter highlights it, exactly as they do for a string handed to `print`.
12
+ * Parsed when the renderable was built, a string was markup under a console
13
+ * that had turned markup off, never met the console's highlighter, and an
14
+ * unmatched closing tag threw from a constructor rather than from the render
15
+ * that would draw it, where Rich's does (rich-markup-3sw).
16
+ *
17
+ * Text keeps its own `end`, as a body does in Rich: `RichText("foo\n")` draws
18
+ * the blank row its default `"\n"` end makes, and a string reads with that
19
+ * default end as `render_str` gives it. Only a label, set into a line it
20
+ * shares, clears it. A `RichText` is copied when it is handed over, since the
21
+ * caller still holds it, and every text drawn from it is a copy again, since
22
+ * the drawing site may change it.
23
+ */
24
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
25
+ import { Segment } from "../core/segment.js";
26
+ import type { Style } from "../core/style.js";
27
+ import { RichText } from "../core/text.js";
28
+ /**
29
+ * Caller content as text an embedding site lays out. A string is the only kind
30
+ * of content that can contain markup, so it is the only kind whose reading
31
+ * waits for the options; any other value is its `String` form as written, so an
32
+ * object's `[object Object]` is not eaten as a tag.
33
+ */
34
+ export declare class EmbeddedText implements Renderable, Measurable {
35
+ private readonly _source;
36
+ private readonly _readings;
37
+ constructor(content: unknown);
38
+ /**
39
+ * The text this content draws under `options`. A string reads with `base` as
40
+ * its style, as Rich's `render_str(style=)` gives one; text handed over
41
+ * keeps its own, since the caller already said how it looks — so a table's
42
+ * `Text` title draws without the title style, as Rich's does.
43
+ */
44
+ text(options: RenderOptions, base?: Style): RichText;
45
+ render(options: RenderOptions): Iterable<Segment>;
46
+ /**
47
+ * Measured unhighlighted, as Rich's `Measurement.get` reads a string with
48
+ * `highlight=False`: so a container that clears the highlighter for what it
49
+ * draws measures what it draws, whether or not it clears it for what it
50
+ * measures, and no highlighter runs just to be thrown away.
51
+ */
52
+ measure(options: RenderOptions): {
53
+ minimum: number;
54
+ maximum: number;
55
+ };
56
+ }
57
+ /**
58
+ * Caller content as something an embedding site can render. A non-text
59
+ * `Renderable` (a nested `Panel` or `Table`) passes through: it has no markup
60
+ * to parse. Everything else is `EmbeddedText`.
61
+ */
62
+ export declare function embed(content: unknown): Renderable & Partial<Measurable>;
63
+ /**
64
+ * Caller content set into a line it shares with other drawing — a panel's
65
+ * title or subtitle in its border, a rule's title — read as Rich's `_title`
66
+ * reads it: newlines become spaces, so the label stays on its line, and tabs
67
+ * are widened to their stops. Whatever sets it off from the line around it —
68
+ * a panel's space either side, a rule's gaps — is the embedding site's.
69
+ *
70
+ * Held once by the renderable that owns it, so a string's markup is read once
71
+ * and every measure and render after reuses that reading.
72
+ */
73
+ export declare class InlineLabel {
74
+ private readonly _content;
75
+ constructor(content: string | RichText);
76
+ /** The label drawn under `options`: one line, its own overflow kept for the caller's cut. */
77
+ text(options: RenderOptions): RichText;
78
+ }
79
+ /**
80
+ * A label cut to `width` cells as Rich's `Text.truncate` cuts one: its plain
81
+ * text set to the width less `marker` — a wide character the cut splits leaves
82
+ * a space in its cell rather than a cell of the line around it — then the
83
+ * marker, with every span that ran past the cut trimmed to the new end, so the
84
+ * pad and the marker take the styling of what they replace. A label that fits
85
+ * is left as it is.
86
+ */
87
+ export declare function cutLabel(text: RichText, width: number, marker: string): void;
88
+ /**
89
+ * Whether caller content is there at all, decided on the content as given, as
90
+ * Rich's `if self.title` decides it: absent, an empty string and an empty
91
+ * `Text` are not, and markup that styles nothing is — it reads as empty text,
92
+ * but the string that holds it is not empty.
93
+ */
94
+ export declare function present(content: string | RichText | undefined): content is string | RichText;
95
+ /**
96
+ * A label's text as one line of segments at its natural width, its own styles
97
+ * laid over `base`. The caller has already cut it to its room, so it leaves as
98
+ * it stands: `"ignore"` also leaves it unjustified, its spaces where `pad` put
99
+ * them.
100
+ */
101
+ export declare function drawLabel(text: RichText, options: RenderOptions, base: Style | undefined): Segment[];
102
+ /**
103
+ * Caller content as a label, or no label at all: content that is not
104
+ * `present` draws the plain rule, and markup that styles nothing is an empty
105
+ * label, which still opens a gap in the rule, as Rich's does.
106
+ */
107
+ export declare function inlineLabel(content: string | RichText | undefined): InlineLabel | undefined;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * The one crossing where content a caller hands a renderable — a table cell, a
3
+ * panel's body, title or subtitle, a padding's body, a tree label, a column
4
+ * item, a layout pane, a rule's title — becomes something that renderable can
5
+ * lay out.
6
+ * [LAW:single-enforcer]
7
+ *
8
+ * A string is kept as written and read when it is drawn, through `readStr`,
9
+ * under the options it is drawn with. That is where a console's settings
10
+ * arrive: its `markup` decides whether the string is markup, and its
11
+ * highlighter highlights it, exactly as they do for a string handed to `print`.
12
+ * Parsed when the renderable was built, a string was markup under a console
13
+ * that had turned markup off, never met the console's highlighter, and an
14
+ * unmatched closing tag threw from a constructor rather than from the render
15
+ * that would draw it, where Rich's does (rich-markup-3sw).
16
+ *
17
+ * Text keeps its own `end`, as a body does in Rich: `RichText("foo\n")` draws
18
+ * the blank row its default `"\n"` end makes, and a string reads with that
19
+ * default end as `render_str` gives it. Only a label, set into a line it
20
+ * shares, clears it. A `RichText` is copied when it is handed over, since the
21
+ * caller still holds it, and every text drawn from it is a copy again, since
22
+ * the drawing site may change it.
23
+ */
24
+ import { cellCount, cellLen, setCellSize } from "../core/cells.js";
25
+ import { activeHighlighter, readStr } from "../core/markup.js";
26
+ import { Segment } from "../core/segment.js";
27
+ import { RichText } from "../core/text.js";
28
+ /**
29
+ * Caller content as text an embedding site lays out. A string is the only kind
30
+ * of content that can contain markup, so it is the only kind whose reading
31
+ * waits for the options; any other value is its `String` form as written, so an
32
+ * object's `[object Object]` is not eaten as a tag.
33
+ */
34
+ export class EmbeddedText {
35
+ // [LAW:types-are-the-program] Two kinds, and the kind is the discriminator: a
36
+ // string still to be read, or text whose reading is already fixed.
37
+ _source;
38
+ // A string's reading under each markup setting, made the first time one is
39
+ // asked for. Parsing is the costly half of drawing a string, and a table
40
+ // measures every cell more than once per print; read on every call, a
41
+ // 500-row table of markup printed 75% slower.
42
+ _readings = new Map();
43
+ constructor(content) {
44
+ if (typeof content === "string") {
45
+ this._source = content;
46
+ }
47
+ else {
48
+ this._source = content instanceof RichText ? content.copy() : new RichText(String(content ?? ""));
49
+ }
50
+ }
51
+ /**
52
+ * The text this content draws under `options`. A string reads with `base` as
53
+ * its style, as Rich's `render_str(style=)` gives one; text handed over
54
+ * keeps its own, since the caller already said how it looks — so a table's
55
+ * `Text` title draws without the title style, as Rich's does.
56
+ */
57
+ text(options, base) {
58
+ if (typeof this._source !== "string")
59
+ return this._source.copy();
60
+ const markup = options.markup !== false;
61
+ const reading = this._readings.get(markup) ?? readStr(this._source, markup);
62
+ this._readings.set(markup, reading);
63
+ const text = reading.copy();
64
+ if (base)
65
+ text.style = base;
66
+ activeHighlighter(options)?.highlight(text);
67
+ return text;
68
+ }
69
+ render(options) {
70
+ return this.text(options).render(options);
71
+ }
72
+ /**
73
+ * Measured unhighlighted, as Rich's `Measurement.get` reads a string with
74
+ * `highlight=False`: so a container that clears the highlighter for what it
75
+ * draws measures what it draws, whether or not it clears it for what it
76
+ * measures, and no highlighter runs just to be thrown away.
77
+ */
78
+ measure(options) {
79
+ return this.text({ ...options, highlight: false }).measure(options);
80
+ }
81
+ }
82
+ /**
83
+ * Caller content as something an embedding site can render. A non-text
84
+ * `Renderable` (a nested `Panel` or `Table`) passes through: it has no markup
85
+ * to parse. Everything else is `EmbeddedText`.
86
+ */
87
+ export function embed(content) {
88
+ if (!(content instanceof RichText) && typeof content === "object" && content !== null && "render" in content) {
89
+ return content;
90
+ }
91
+ return new EmbeddedText(content);
92
+ }
93
+ /**
94
+ * Caller content set into a line it shares with other drawing — a panel's
95
+ * title or subtitle in its border, a rule's title — read as Rich's `_title`
96
+ * reads it: newlines become spaces, so the label stays on its line, and tabs
97
+ * are widened to their stops. Whatever sets it off from the line around it —
98
+ * a panel's space either side, a rule's gaps — is the embedding site's.
99
+ *
100
+ * Held once by the renderable that owns it, so a string's markup is read once
101
+ * and every measure and render after reuses that reading.
102
+ */
103
+ export class InlineLabel {
104
+ _content;
105
+ constructor(content) {
106
+ this._content = new EmbeddedText(content);
107
+ }
108
+ /** The label drawn under `options`: one line, its own overflow kept for the caller's cut. */
109
+ text(options) {
110
+ const text = this._content.text(options);
111
+ // A fragment of the line, so its own end is not drawn, as Rich's `_title` clears it.
112
+ text.end = "";
113
+ text.plain = text.plain.replaceAll("\n", " ");
114
+ // Tabs widened before anything measures it, so a cut to the border and
115
+ // the width a title asks for count the cells the label will draw.
116
+ return text.expandTabs();
117
+ }
118
+ }
119
+ /**
120
+ * A label cut to `width` cells as Rich's `Text.truncate` cuts one: its plain
121
+ * text set to the width less `marker` — a wide character the cut splits leaves
122
+ * a space in its cell rather than a cell of the line around it — then the
123
+ * marker, with every span that ran past the cut trimmed to the new end, so the
124
+ * pad and the marker take the styling of what they replace. A label that fits
125
+ * is left as it is.
126
+ */
127
+ export function cutLabel(text, width, marker) {
128
+ if (text.cellLength <= width)
129
+ return;
130
+ text.plain = setCellSize(text.plain, cellCount(width - cellLen(marker))) + marker;
131
+ }
132
+ /**
133
+ * Whether caller content is there at all, decided on the content as given, as
134
+ * Rich's `if self.title` decides it: absent, an empty string and an empty
135
+ * `Text` are not, and markup that styles nothing is — it reads as empty text,
136
+ * but the string that holds it is not empty.
137
+ */
138
+ export function present(content) {
139
+ // Asked of what the content is rather than of what it is not, so a `null`
140
+ // from untyped JS is no content, as Rich's `None` is.
141
+ return typeof content === "string" ? content !== "" : content instanceof RichText && content.plain !== "";
142
+ }
143
+ /**
144
+ * A label's text as one line of segments at its natural width, its own styles
145
+ * laid over `base`. The caller has already cut it to its room, so it leaves as
146
+ * it stands: `"ignore"` also leaves it unjustified, its spaces where `pad` put
147
+ * them.
148
+ */
149
+ export function drawLabel(text, options, base) {
150
+ text.overflow = "ignore";
151
+ return [...Segment.applyStyle(text.render(options), base)];
152
+ }
153
+ /**
154
+ * Caller content as a label, or no label at all: content that is not
155
+ * `present` draws the plain rule, and markup that styles nothing is an empty
156
+ * label, which still opens a gap in the rule, as Rich's does.
157
+ */
158
+ export function inlineLabel(content) {
159
+ return present(content) ? new InlineLabel(content) : undefined;
160
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * FlexStrip — wrap-to-width horizontal layout for `StyledRenderable` items.
3
+ *
4
+ * Pack as many items as fit on a line, then break and continue. Each line is
5
+ * an independent sub-strip: optional `Joiner` end-caps fire at every line
6
+ * boundary (not just the strip's first/last position), so a line break looks
7
+ * the same as an endpoint to the joiner. Composes with the Strip + Joiner
8
+ * primitive in `core/strip` — same `Joiner` protocol, no new join semantics.
9
+ *
10
+ * [LAW:dataflow-not-control-flow] The pack walk is the same shape every
11
+ * render: measure each item, render every joiner form (start-cap, end-cap,
12
+ * mid-join) up-front, then sweep items into lines. Line breaks are decided by
13
+ * width data, not by control-flow special cases.
14
+ *
15
+ * [LAW:one-source-of-truth] The Strip primitive owns the join protocol;
16
+ * FlexStrip reuses it verbatim. There is no second concept of "join."
17
+ */
18
+ import { Segment } from "../core/segment.js";
19
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
20
+ import type { Joiner, StyledRenderable } from "../core/strip.js";
21
+ export type FlexAlign = "left" | "center" | "right" | "justify";
22
+ export interface FlexStripOptions<T extends StyledRenderable> {
23
+ /** Joiner used between items within a line, and at line endpoints. */
24
+ joiner?: Joiner<T>;
25
+ /** Cells of horizontal space inserted on each side of an inter-item joiner (default 0). */
26
+ gap?: number;
27
+ /** Line alignment when packed items don't fill the available width (default "left"). */
28
+ align?: FlexAlign;
29
+ }
30
+ export declare class FlexStrip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable {
31
+ readonly items: readonly T[];
32
+ readonly joiner: Joiner<T> | undefined;
33
+ readonly gap: number;
34
+ readonly align: FlexAlign;
35
+ constructor(items: readonly T[], options?: FlexStripOptions<T>);
36
+ render(options: RenderOptions): Iterable<Segment>;
37
+ measure(options: RenderOptions): {
38
+ minimum: number;
39
+ maximum: number;
40
+ };
41
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * FlexStrip — wrap-to-width horizontal layout for `StyledRenderable` items.
3
+ *
4
+ * Pack as many items as fit on a line, then break and continue. Each line is
5
+ * an independent sub-strip: optional `Joiner` end-caps fire at every line
6
+ * boundary (not just the strip's first/last position), so a line break looks
7
+ * the same as an endpoint to the joiner. Composes with the Strip + Joiner
8
+ * primitive in `core/strip` — same `Joiner` protocol, no new join semantics.
9
+ *
10
+ * [LAW:dataflow-not-control-flow] The pack walk is the same shape every
11
+ * render: measure each item, render every joiner form (start-cap, end-cap,
12
+ * mid-join) up-front, then sweep items into lines. Line breaks are decided by
13
+ * width data, not by control-flow special cases.
14
+ *
15
+ * [LAW:one-source-of-truth] The Strip primitive owns the join protocol;
16
+ * FlexStrip reuses it verbatim. There is no second concept of "join."
17
+ */
18
+ import { Segment } from "../core/segment.js";
19
+ function renderToBlock(r, options) {
20
+ const segments = [...r.render(options)];
21
+ let width = 0;
22
+ for (const s of segments)
23
+ width += s.cellLength;
24
+ return { segments, width };
25
+ }
26
+ const EMPTY_BLOCK = { segments: [], width: 0 };
27
+ const PAD_LEFT_BY_ALIGN = {
28
+ left: () => 0,
29
+ justify: () => 0,
30
+ center: (spare) => Math.floor(spare / 2),
31
+ right: (spare) => spare,
32
+ };
33
+ function computePadLeft(align, spare) {
34
+ return PAD_LEFT_BY_ALIGN[align](spare);
35
+ }
36
+ // Return per-slot fill widths (length = itemCount - 1, possibly empty).
37
+ // Non-justify cases produce all zeros — the emit loop runs the same way.
38
+ function computeSlotFills(align, isFinalLine, spare, itemCount) {
39
+ const slots = Math.max(0, itemCount - 1);
40
+ const distribute = align === "justify" && !isFinalLine && slots > 0 && spare > 0;
41
+ const totalFill = distribute ? spare : 0;
42
+ const base = slots > 0 ? Math.floor(totalFill / slots) : 0;
43
+ const extra = totalFill - base * slots;
44
+ return Array.from({ length: slots }, (_, k) => base + (k < extra ? 1 : 0));
45
+ }
46
+ export class FlexStrip {
47
+ items;
48
+ joiner;
49
+ gap;
50
+ align;
51
+ constructor(items, options) {
52
+ this.items = items;
53
+ this.joiner = options?.joiner;
54
+ this.gap = options?.gap ?? 0;
55
+ this.align = options?.align ?? "left";
56
+ }
57
+ *render(options) {
58
+ const items = this.items;
59
+ if (items.length === 0)
60
+ return;
61
+ const maxWidth = Math.max(1, options.maxWidth);
62
+ const gap = this.gap;
63
+ const joiner = this.joiner;
64
+ // Pre-render every item and every joiner form we might need. Doing this
65
+ // up-front turns packing into a pure data computation over widths.
66
+ const itemBlocks = items.map((it) => renderToBlock(it, options));
67
+ const startCap = (i) => joiner ? renderToBlock(joiner.join(null, items[i]), options) : EMPTY_BLOCK;
68
+ const endCap = (i) => joiner ? renderToBlock(joiner.join(items[i], null), options) : EMPTY_BLOCK;
69
+ const midJoin = (i, j) => joiner ? renderToBlock(joiner.join(items[i], items[j]), options) : EMPTY_BLOCK;
70
+ const lines = [];
71
+ let line = null;
72
+ const gapBlock = gap > 0
73
+ ? { segments: [new Segment(" ".repeat(gap))], width: gap }
74
+ : EMPTY_BLOCK;
75
+ for (let i = 0; i < items.length; i++) {
76
+ const item = itemBlocks[i];
77
+ if (line === null) {
78
+ const sc = startCap(i);
79
+ const ec = endCap(i);
80
+ const blocks = sc.width > 0 ? [sc, item] : [item];
81
+ const itemBlockIndices = [blocks.length - 1];
82
+ line = { blocks, width: sc.width + item.width, lastIndex: i, itemBlockIndices };
83
+ // Tentatively reserve space for the line's eventual end-cap by
84
+ // tracking it separately below — `line.width` is the open width
85
+ // (without end-cap). We test fit using width + endCap on each step.
86
+ // Always start a new line with at least the first item (overflow ok
87
+ // per ticket: graceful fallback when an item is wider than terminal).
88
+ // Stash end-cap for the close step.
89
+ line.endCap = ec;
90
+ continue;
91
+ }
92
+ const prev = line.lastIndex;
93
+ const mid = midJoin(prev, i);
94
+ const newEndCap = endCap(i);
95
+ const addCost = (gap > 0 ? gap : 0) + mid.width + (gap > 0 ? gap : 0) + item.width;
96
+ const closedWidthIfAdded = line.width + addCost + newEndCap.width;
97
+ if (closedWidthIfAdded <= maxWidth) {
98
+ if (gap > 0)
99
+ line.blocks.push(gapBlock);
100
+ if (mid.width > 0)
101
+ line.blocks.push(mid);
102
+ if (gap > 0)
103
+ line.blocks.push(gapBlock);
104
+ line.blocks.push(item);
105
+ line.itemBlockIndices.push(line.blocks.length - 1);
106
+ line.width += addCost;
107
+ line.lastIndex = i;
108
+ line.endCap = newEndCap;
109
+ }
110
+ else {
111
+ // Close current line and start a new one with this item.
112
+ const closing = line.endCap;
113
+ if (closing.width > 0)
114
+ line.blocks.push(closing);
115
+ line.width += closing.width;
116
+ lines.push(line);
117
+ const sc = startCap(i);
118
+ const ec = endCap(i);
119
+ const blocks = sc.width > 0 ? [sc, item] : [item];
120
+ const itemBlockIndices = [blocks.length - 1];
121
+ line = { blocks, width: sc.width + item.width, lastIndex: i, itemBlockIndices };
122
+ line.endCap = ec;
123
+ }
124
+ }
125
+ // Close final line. `line` is non-null here: the loop runs at least once
126
+ // (items.length > 0 was handled above).
127
+ const finalLine = line;
128
+ {
129
+ const closing = finalLine.endCap;
130
+ if (closing.width > 0)
131
+ finalLine.blocks.push(closing);
132
+ finalLine.width += closing.width;
133
+ lines.push(finalLine);
134
+ }
135
+ // [LAW:dataflow-not-control-flow] Single emission path for every line:
136
+ // emit pad, walk every block, optionally emit a per-slot fill before each
137
+ // non-first item. Variability lives in two precomputed value sequences
138
+ // (`padLeft` and `slotFills`); the emit loop has no per-mode branch.
139
+ //
140
+ // - `padLeft` pushes the line right by 0 (left/justify), spare/2 (center),
141
+ // or full spare (right). Justify-on-final-line follows the left rule.
142
+ // - `slotFills[k]` is the cells of fill emitted before the (k+1)-th item.
143
+ // Always 0 unless align=justify on a non-final line with ≥2 items.
144
+ //
145
+ // Putting the variability in data means the emit loop is the same shape
146
+ // every render, and "what happens between two items" can never accidentally
147
+ // become "what happens between joiner halves" — the data decides.
148
+ const align = this.align;
149
+ for (let li = 0; li < lines.length; li++) {
150
+ const ln = lines[li];
151
+ const isLast = li === lines.length - 1;
152
+ const spare = Math.max(0, maxWidth - ln.width);
153
+ const padLeft = computePadLeft(align, spare);
154
+ const slotFills = computeSlotFills(align, isLast, spare, ln.itemBlockIndices.length);
155
+ const itemRankOf = new Map();
156
+ ln.itemBlockIndices.forEach((blockIdx, rank) => itemRankOf.set(blockIdx, rank));
157
+ if (padLeft > 0)
158
+ yield new Segment(" ".repeat(padLeft));
159
+ for (let bi = 0; bi < ln.blocks.length; bi++) {
160
+ const rank = itemRankOf.get(bi);
161
+ if (rank !== undefined && rank > 0) {
162
+ const fill = slotFills[rank - 1];
163
+ if (fill > 0)
164
+ yield new Segment(" ".repeat(fill));
165
+ }
166
+ yield* ln.blocks[bi].segments;
167
+ }
168
+ yield Segment.line();
169
+ }
170
+ }
171
+ measure(options) {
172
+ const items = this.items;
173
+ if (items.length === 0)
174
+ return { minimum: 0, maximum: 0 };
175
+ let minimum = 0;
176
+ let maximum = 0;
177
+ for (let i = 0; i < items.length; i++) {
178
+ const itemWidth = renderToBlock(items[i], options).width;
179
+ const sc = this.joiner
180
+ ? renderToBlock(this.joiner.join(null, items[i]), options).width
181
+ : 0;
182
+ const ec = this.joiner
183
+ ? renderToBlock(this.joiner.join(items[i], null), options).width
184
+ : 0;
185
+ minimum = Math.max(minimum, itemWidth + sc + ec);
186
+ maximum += itemWidth;
187
+ if (i > 0) {
188
+ maximum += this.gap * 2;
189
+ if (this.joiner) {
190
+ maximum += renderToBlock(this.joiner.join(items[i - 1], items[i]), options).width;
191
+ }
192
+ }
193
+ }
194
+ if (this.joiner) {
195
+ maximum += renderToBlock(this.joiner.join(null, items[0]), options).width;
196
+ maximum += renderToBlock(this.joiner.join(items[items.length - 1], null), options).width;
197
+ }
198
+ return { minimum, maximum };
199
+ }
200
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Group — renders multiple renderables in sequence.
3
+ * No visual chrome — purely a container.
4
+ */
5
+ import type { Segment } from "../core/segment.js";
6
+ import { type Measurement } from "../core/measure.js";
7
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
8
+ export declare class Group implements Renderable, Measurable {
9
+ readonly renderables: Renderable[];
10
+ constructor(...renderables: Renderable[]);
11
+ render(options: RenderOptions): Iterable<Segment>;
12
+ /** As wide as its widest member: the reference's `measure_renderables`. */
13
+ measure(options: RenderOptions): Measurement;
14
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Group — renders multiple renderables in sequence.
3
+ * No visual chrome — purely a container.
4
+ */
5
+ import { measureRenderables } from "../core/measure.js";
6
+ import { stackedHeight } from "../core/protocol.js";
7
+ export class Group {
8
+ renderables;
9
+ constructor(...renderables) {
10
+ this.renderables = renderables;
11
+ }
12
+ *render(options) {
13
+ const memberOptions = { ...options, height: stackedHeight(options.height) };
14
+ for (const renderable of this.renderables) {
15
+ yield* renderable.render(memberOptions);
16
+ }
17
+ }
18
+ /** As wide as its widest member: the reference's `measure_renderables`. */
19
+ measure(options) {
20
+ return measureRenderables(options, this.renderables);
21
+ }
22
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Layout — divides the screen into rectangular regions.
3
+ */
4
+ import { Segment } from "../core/segment.js";
5
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
6
+ import type { CellCol } from "../core/cells.js";
7
+ import type { Weight } from "./ratio.js";
8
+ export interface LayoutOptions {
9
+ name?: string;
10
+ ratio?: number;
11
+ size?: number;
12
+ minimumSize?: number;
13
+ visible?: boolean;
14
+ }
15
+ export declare class Layout implements Renderable, Measurable {
16
+ name: string | undefined;
17
+ visible: boolean;
18
+ private _ratio;
19
+ private _size;
20
+ private _minimumSize;
21
+ private _renderable;
22
+ private _children;
23
+ private _splitDirection;
24
+ constructor(renderable?: Renderable | string, options?: LayoutOptions);
25
+ /**
26
+ * The three declared numbers, parsed on assignment rather than at the
27
+ * constructor. All three are public and a caller reaches them long after
28
+ * construction — `layout.getByName("pane")!.ratio = -1` walked straight past a
29
+ * constructor-only parse and put a negative weight back into the division that
30
+ * `ratioResolve` and `ratioBudget` are written to trust.
31
+ *
32
+ * [LAW:parse-dont-validate] The setter is the border, so the guarantee holds
33
+ * for the object's whole lifetime and nothing downstream re-checks. `size` and
34
+ * `minimumSize` are cell counts; `ratio` is a share weight and keeps its
35
+ * fractions. Absence is preserved rather than parsed: an undefined `size`
36
+ * selects a flex pane, and `cellCount` would read it as a declared zero.
37
+ */
38
+ get ratio(): Weight;
39
+ set ratio(value: number);
40
+ get size(): CellCol | undefined;
41
+ set size(value: number | undefined);
42
+ get minimumSize(): CellCol;
43
+ set minimumSize(value: number);
44
+ get children(): Layout[];
45
+ /**
46
+ * Whether this layout draws `_renderable` or its children.
47
+ *
48
+ * [LAW:one-source-of-truth] `render` and `_naturalWidth` must answer this the
49
+ * same way. Asked separately they did not: `_naturalWidth` read it off the
50
+ * *visible* children, so a layout holding content and a single hidden child
51
+ * reported that content's width while `render` emitted nothing at all, and a
52
+ * fit-mode `Panel` framed twelve cells of air.
53
+ */
54
+ private get _isLeaf();
55
+ splitColumn(...layouts: Layout[]): void;
56
+ splitRow(...layouts: Layout[]): void;
57
+ update(renderable: Renderable | string): void;
58
+ getByName(name: string): Layout | undefined;
59
+ render(rawOptions: RenderOptions): Iterable<Segment>;
60
+ private _renderColumn;
61
+ private _renderRow;
62
+ /**
63
+ * The width this layout would take if nothing constrained it: its content's
64
+ * for a leaf, its children's laid out the way the split lays them out.
65
+ *
66
+ * A `size` counts only across a row, which is the one direction in which it
67
+ * is a width — down a column the same field is a height, and reading it as a
68
+ * width there would report a two-line pane as two cells wide.
69
+ */
70
+ private _naturalWidth;
71
+ measure(rawOptions: RenderOptions): {
72
+ minimum: number;
73
+ maximum: number;
74
+ };
75
+ }