@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,152 @@
1
+ /**
2
+ * Columns — arranges renderables in a multi-column layout.
3
+ */
4
+ import { Segment } from "../core/segment.js";
5
+ import { Measurement } from "../core/measure.js";
6
+ import { isMeasurable, stackedHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
7
+ import { cellCount } from "../core/cells.js";
8
+ import { embed } from "./embed.js";
9
+ export class Columns {
10
+ renderables;
11
+ expand;
12
+ equal;
13
+ colWidth;
14
+ columnFirst;
15
+ gutterWidth;
16
+ constructor(items, options) {
17
+ this.renderables = items ? [...items].map(embed) : [];
18
+ this.expand = options?.expand ?? false;
19
+ this.equal = options?.equal ?? false;
20
+ // A declared column width is a cell count like any other, and it reaches
21
+ // the layout without passing through `withCellWidth`, which parses only
22
+ // what the *caller of render* supplied. Left raw, `width: NaN` made
23
+ // `Math.max(1, NaN)` a NaN column count and `new Array` threw `Invalid
24
+ // array length` before a single column was laid out. Absence is preserved
25
+ // rather than parsed: `undefined` selects auto-fit, and `cellCount` would
26
+ // read it as a declared zero.
27
+ this.colWidth = options?.width === undefined ? undefined : cellCount(options.width);
28
+ this.columnFirst = options?.columnFirst ?? false;
29
+ this.gutterWidth = 2; // default gutter between columns
30
+ }
31
+ *render(rawOptions) {
32
+ const items = this.renderables;
33
+ if (items.length === 0)
34
+ return;
35
+ // The parsed options, not just a parsed local: `_divide` hands them to
36
+ // `Measurement.get`, and a raw NaN reaching it comes back as a NaN item
37
+ // width, then a NaN column count, then `Invalid array length` out of
38
+ // `new Array(numCols)` before a single column is laid out. An unbounded
39
+ // width reaches the same `new Array` by the same route, which is why the
40
+ // parse here is the bounded one.
41
+ const options = withBoundedWidth(rawOptions, this);
42
+ const { numCols, colWidths } = this._divide(options);
43
+ // Layout items into rows
44
+ const numRows = Math.ceil(items.length / numCols);
45
+ // [LAW:types-are-the-program] The element type is Renderable — any number
46
+ // of lines. Each grid cell renders to its full set of lines; an out-of-range
47
+ // slot (the last row is rarely full) is an empty cell. mergeHorizontal then
48
+ // stacks every row of every cell line-by-line, so multi-line children
49
+ // (Panels, Tables) compose instead of being truncated to their first row.
50
+ for (let row = 0; row < numRows; row++) {
51
+ const cells = colWidths.map((width, col) => {
52
+ const idx = this.columnFirst ? col * numRows + row : row * numCols + col;
53
+ const item = items[idx];
54
+ const lines = item === undefined
55
+ ? []
56
+ : Segment.splitLines([
57
+ ...item.render({ ...options, maxWidth: width, height: stackedHeight(options.height) }),
58
+ ]);
59
+ return { lines, width };
60
+ });
61
+ yield* Segment.mergeHorizontal(cells, this.gutterWidth);
62
+ }
63
+ }
64
+ /**
65
+ * The requested width divided into columns, once.
66
+ *
67
+ * [LAW:single-enforcer] The three modes disagree only about how many columns
68
+ * there are and how wide each one is, so they disagree in one place. `render`
69
+ * lays out against this division and `_naturalWidth` is the width at which it
70
+ * comes out as one row of every item.
71
+ */
72
+ _divide(options) {
73
+ const maxWidth = options.maxWidth;
74
+ const gutter = this.gutterWidth;
75
+ const declared = this._declaredWidth(options);
76
+ if (declared !== undefined) {
77
+ const numCols = Math.max(1, Math.floor((maxWidth + gutter) / (declared + gutter)));
78
+ return { numCols, colWidths: new Array(numCols).fill(declared) };
79
+ }
80
+ if (this.equal) {
81
+ const itemWidth = this._itemWidth(options);
82
+ const numCols = Math.max(1, Math.floor((maxWidth + gutter) / (itemWidth + gutter)));
83
+ const equalWidth = Math.floor((maxWidth - gutter * (numCols - 1)) / numCols);
84
+ return { numCols, colWidths: new Array(numCols).fill(equalWidth) };
85
+ }
86
+ // Auto: as many columns as the widest item fits into, capped at the item
87
+ // count. Sized from the content rather than from a flat four cells per
88
+ // column, because `_naturalWidth` below is the inverse of *this* expression
89
+ // and a constant has no inverse: six two-cell items reported a natural
90
+ // width of 22 and then wrapped into two rows at 22, since `floor(22 / 4)`
91
+ // is five columns no matter how narrow the items are.
92
+ const itemWidth = this._itemWidth(options);
93
+ const numCols = Math.min(this.renderables.length, Math.max(1, Math.floor((maxWidth + gutter) / (itemWidth + gutter))));
94
+ const colW = Math.floor((maxWidth - gutter * (numCols - 1)) / numCols);
95
+ return { numCols, colWidths: new Array(numCols).fill(colW) };
96
+ }
97
+ /**
98
+ * The widest any one item wants to be.
99
+ *
100
+ * An item that cannot measure itself wants the offer, which is what `Panel`,
101
+ * `Padding`, `Layout` and `Tree` all answer for the same case — and under an
102
+ * unbounded offer that is `Infinity`, so `withBoundedWidth` throws and says
103
+ * the request was unanswerable. Counting it as one cell instead reported a
104
+ * natural width of 1, which resolved an unbounded offer to a single column and
105
+ * cropped forty cells of content down to `"x"` with no error at all.
106
+ */
107
+ _itemWidth(options) {
108
+ let widest = 1;
109
+ for (const item of this.renderables) {
110
+ widest = Math.max(widest, isMeasurable(item)
111
+ ? Measurement.get(options, item).maximum
112
+ : options.maxWidth);
113
+ }
114
+ return widest;
115
+ }
116
+ /**
117
+ * The declared column width, bounded by the width offered.
118
+ *
119
+ * [LAW:single-enforcer] A declared width is what a column asks for, not what
120
+ * it takes — the contract `Table._outerWidth` keeps for a declared table
121
+ * width. Read by `_divide` for render and by `_naturalWidth` for measure, so
122
+ * the two answer from one number: laid out at the raw declared width, a
123
+ * six-cell column offered three emitted six-cell lines while `measure`
124
+ * reported three, and the terminal's soft wrap took the frame of everything
125
+ * printed after it.
126
+ */
127
+ _declaredWidth(options) {
128
+ return this.colWidth === undefined ? undefined : Math.min(this.colWidth, options.maxWidth);
129
+ }
130
+ /** The width at which every item sits on one row: n columns and the gutters between them. */
131
+ _naturalWidth(options) {
132
+ const count = this.renderables.length;
133
+ if (count === 0)
134
+ return 0;
135
+ const width = this._declaredWidth(options) ?? this._itemWidth(options);
136
+ return count * width + this.gutterWidth * (count - 1);
137
+ }
138
+ measure(rawOptions) {
139
+ // A floor of one cell is what Columns asks for; the ceiling is what it was
140
+ // offered, and the ceiling wins. Stated as a bare `minimum: 1`, a Columns
141
+ // measured into no width at all reported the range 1..0 — a floor above
142
+ // its own ceiling, which the parent that asked cannot divide.
143
+ //
144
+ // The ceiling is not the whole answer, though: reported as the offer alone,
145
+ // a Columns claimed every cell it was shown, so `Panel` in fit mode drew a
146
+ // 40-cell frame around five cells of content and an unbounded offer came
147
+ // back unbounded.
148
+ const parsed = withCellWidth(rawOptions);
149
+ const maximum = Math.min(this._naturalWidth(parsed), parsed.maxWidth);
150
+ return { minimum: Math.min(1, maximum), maximum };
151
+ }
152
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Constrain — wraps a renderable and constrains its maximum width.
3
+ */
4
+ import { Segment } from "../core/segment.js";
5
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
6
+ export declare class Constrain implements Renderable, Measurable {
7
+ readonly renderable: Renderable;
8
+ readonly width: number | undefined;
9
+ constructor(renderable: Renderable, width?: number);
10
+ render(options: RenderOptions): Iterable<Segment>;
11
+ measure(options: RenderOptions): {
12
+ minimum: number;
13
+ maximum: number;
14
+ };
15
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Constrain — wraps a renderable and constrains its maximum width.
3
+ */
4
+ import { Measurement } from "../core/measure.js";
5
+ import { isMeasurable } from "../core/protocol.js";
6
+ export class Constrain {
7
+ renderable;
8
+ width;
9
+ constructor(renderable, width) {
10
+ this.renderable = renderable;
11
+ this.width = width;
12
+ }
13
+ *render(options) {
14
+ // [LAW:dataflow-not-control-flow] Always compute constrainedWidth; undefined means no constraint
15
+ const constrainedWidth = this.width !== undefined
16
+ ? Math.min(this.width, options.maxWidth)
17
+ : options.maxWidth;
18
+ const innerOptions = {
19
+ ...options,
20
+ maxWidth: constrainedWidth,
21
+ };
22
+ yield* this.renderable.render(innerOptions);
23
+ }
24
+ measure(options) {
25
+ if (isMeasurable(this.renderable)) {
26
+ const measurement = Measurement.get(options, this.renderable);
27
+ const maxWidth = this.width !== undefined
28
+ ? Math.min(this.width, options.maxWidth)
29
+ : options.maxWidth;
30
+ return {
31
+ minimum: measurement.minimum,
32
+ maximum: Math.min(measurement.maximum, maxWidth),
33
+ };
34
+ }
35
+ return { minimum: 1, maximum: options.maxWidth };
36
+ }
37
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The one crossing where content a caller hands a renderable — a table cell, a
3
+ * panel's body, title or subtitle, a tree label, a column item, a layout pane,
4
+ * a rule's title — becomes something that renderable can lay out.
5
+ * [LAW:single-enforcer]
6
+ *
7
+ * Each of those sites used to build its own `RichText` straight from the
8
+ * constructor, which does not parse markup, so `[red]Solo[/red]` reached the
9
+ * terminal with its tags intact. Table cells were fixed on their own first; the
10
+ * rest kept the defect until the rule moved here.
11
+ *
12
+ * Markup is parsed because Rich parses it: every one of these positions
13
+ * reaches the wire through Rich's `render_str`, and a console's markup is on by
14
+ * default. Rich's `render_str` also honours `Console(markup=False)` and runs the
15
+ * console's highlighter; this crossing does neither, because the console's
16
+ * settings do not reach it (rich-markup-3sw).
17
+ *
18
+ * `end` is cleared because embedded text is a fragment rather than a printed
19
+ * line; left at the default `"\n"` it draws a trailing blank row. A `RichText`
20
+ * is copied first, since clearing in place would reach back into the caller's
21
+ * object.
22
+ */
23
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
24
+ import { Segment } from "../core/segment.js";
25
+ import type { Style } from "../core/style.js";
26
+ import { RichText } from "../core/text.js";
27
+ /**
28
+ * Caller content as the text an embedding site lays out, `end` cleared. A
29
+ * string is the only kind of content that can contain markup, so it is the only
30
+ * kind parsed; any other value is its `String` form as written, so an object's
31
+ * `[object Object]` is not eaten as a tag.
32
+ */
33
+ export declare function embeddedText(content: unknown): RichText;
34
+ /**
35
+ * Caller content as something an embedding site can render. A non-text
36
+ * `Renderable` (a nested `Panel` or `Table`) passes through: it carries no
37
+ * `end` to clear and no markup to parse. Everything else is `embeddedText`.
38
+ */
39
+ export declare function embed(content: unknown): Renderable & Partial<Measurable>;
40
+ /**
41
+ * Caller content set into a line it shares with other drawing — a panel's
42
+ * title or subtitle in its border, a rule's title — as one line of segments:
43
+ * a space either side, its own styles over `base`. The caller cuts it to the
44
+ * room it has with `Segment.adjustLineLength`, which is cell-aware.
45
+ *
46
+ * Content with no text is no label at all, not two spaces: an absent title, an
47
+ * empty string, an empty `RichText` and markup that styles nothing all draw
48
+ * the plain line, as Rich's do (an empty `Text` is falsy there).
49
+ */
50
+ export declare function inlineLabel(content: unknown, options: RenderOptions, base: Style | undefined): Segment[];
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The one crossing where content a caller hands a renderable — a table cell, a
3
+ * panel's body, title or subtitle, a tree label, a column item, a layout pane,
4
+ * a rule's title — becomes something that renderable can lay out.
5
+ * [LAW:single-enforcer]
6
+ *
7
+ * Each of those sites used to build its own `RichText` straight from the
8
+ * constructor, which does not parse markup, so `[red]Solo[/red]` reached the
9
+ * terminal with its tags intact. Table cells were fixed on their own first; the
10
+ * rest kept the defect until the rule moved here.
11
+ *
12
+ * Markup is parsed because Rich parses it: every one of these positions
13
+ * reaches the wire through Rich's `render_str`, and a console's markup is on by
14
+ * default. Rich's `render_str` also honours `Console(markup=False)` and runs the
15
+ * console's highlighter; this crossing does neither, because the console's
16
+ * settings do not reach it (rich-markup-3sw).
17
+ *
18
+ * `end` is cleared because embedded text is a fragment rather than a printed
19
+ * line; left at the default `"\n"` it draws a trailing blank row. A `RichText`
20
+ * is copied first, since clearing in place would reach back into the caller's
21
+ * object.
22
+ */
23
+ import { renderMarkup } from "../core/markup.js";
24
+ import { Segment } from "../core/segment.js";
25
+ import { RichText } from "../core/text.js";
26
+ /**
27
+ * Caller content as the text an embedding site lays out, `end` cleared. A
28
+ * string is the only kind of content that can contain markup, so it is the only
29
+ * kind parsed; any other value is its `String` form as written, so an object's
30
+ * `[object Object]` is not eaten as a tag.
31
+ */
32
+ export function embeddedText(content) {
33
+ const text = content instanceof RichText
34
+ ? content.copy()
35
+ : typeof content === "string"
36
+ ? renderMarkup(content)
37
+ : new RichText(String(content ?? ""));
38
+ text.end = "";
39
+ return text;
40
+ }
41
+ /**
42
+ * Caller content as something an embedding site can render. A non-text
43
+ * `Renderable` (a nested `Panel` or `Table`) passes through: it carries no
44
+ * `end` to clear and no markup to parse. Everything else is `embeddedText`.
45
+ */
46
+ export function embed(content) {
47
+ if (!(content instanceof RichText) && typeof content === "object" && content !== null && "render" in content) {
48
+ return content;
49
+ }
50
+ return embeddedText(content);
51
+ }
52
+ /**
53
+ * Caller content set into a line it shares with other drawing — a panel's
54
+ * title or subtitle in its border, a rule's title — as one line of segments:
55
+ * a space either side, its own styles over `base`. The caller cuts it to the
56
+ * room it has with `Segment.adjustLineLength`, which is cell-aware.
57
+ *
58
+ * Content with no text is no label at all, not two spaces: an absent title, an
59
+ * empty string, an empty `RichText` and markup that styles nothing all draw
60
+ * the plain line, as Rich's do (an empty `Text` is falsy there).
61
+ */
62
+ export function inlineLabel(content, options, base) {
63
+ // The label leaves at its natural width, `"ignore"`, because the caller does
64
+ // the cutting; the label's own overflow method would cut it first, and to a
65
+ // width no one measured. `"ignore"` also leaves it unjustified, so its spaces
66
+ // stay where `pad` put them.
67
+ const bare = embeddedText(content);
68
+ const text = bare.plain === "" ? bare : bare.pad(1);
69
+ text.overflow = "ignore";
70
+ return [...Segment.applyStyle(text.render(options), base)];
71
+ }
@@ -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,11 @@
1
+ /**
2
+ * Group — renders multiple renderables in sequence.
3
+ * No visual chrome — purely a container.
4
+ */
5
+ import { Segment } from "../core/segment.js";
6
+ import type { Renderable, RenderOptions } from "../core/protocol.js";
7
+ export declare class Group implements Renderable {
8
+ readonly renderables: Renderable[];
9
+ constructor(...renderables: Renderable[]);
10
+ render(options: RenderOptions): Iterable<Segment>;
11
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Group — renders multiple renderables in sequence.
3
+ * No visual chrome — purely a container.
4
+ */
5
+ import { stackedHeight } from "../core/protocol.js";
6
+ export class Group {
7
+ renderables;
8
+ constructor(...renderables) {
9
+ this.renderables = renderables;
10
+ }
11
+ *render(options) {
12
+ const memberOptions = { ...options, height: stackedHeight(options.height) };
13
+ for (const renderable of this.renderables) {
14
+ yield* renderable.render(memberOptions);
15
+ }
16
+ }
17
+ }