@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,77 @@
1
+ /**
2
+ * Columns — arranges renderables in a multi-column layout.
3
+ *
4
+ * A port of Rich's `Columns`. What it decides is how many columns, and which
5
+ * item goes in which cell. Everything after that — how wide each column is, how
6
+ * far apart, what `expand` does, how rows are separated — is the grid's: the
7
+ * items are laid out as a `Table.grid`, as the reference lays them out.
8
+ * [LAW:one-source-of-truth] A second copy of that geometry here is how the two
9
+ * drifted: an empty item's column was drawn with no width where the grid draws one cell.
10
+ */
11
+ import type { Segment } from "../core/segment.js";
12
+ import type { PaddingDimensions } from "./padding.js";
13
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
14
+ export interface ColumnsOptions {
15
+ expand?: boolean;
16
+ equal?: boolean;
17
+ width?: number;
18
+ padding?: PaddingDimensions;
19
+ columnFirst?: boolean;
20
+ }
21
+ export declare class Columns implements Renderable, Measurable {
22
+ renderables: (Renderable & Partial<Measurable>)[];
23
+ readonly expand: boolean;
24
+ readonly equal: boolean;
25
+ readonly colWidth: number | undefined;
26
+ readonly columnFirst: boolean;
27
+ private readonly padding;
28
+ constructor(items?: Iterable<unknown>, options?: ColumnsOptions);
29
+ render(rawOptions: RenderOptions): Iterable<Segment>;
30
+ measure(rawOptions: RenderOptions): {
31
+ minimum: number;
32
+ maximum: number;
33
+ };
34
+ /**
35
+ * The items at the width offered, as the reference's grid: `_layout`'s
36
+ * cells in a `Table.grid`.
37
+ *
38
+ * [LAW:single-enforcer] `render` draws this and `measure` reports its width,
39
+ * so the two cannot disagree about how wide a Columns is.
40
+ *
41
+ * A declared `width` is each column's natural width rather than a Table
42
+ * column `width`, which this port never stretches: the reference's grid
43
+ * stretches a Columns' declared width under `expand` like any other.
44
+ * `equal` holds every item to the widest, Rich's `Constrain`; without it
45
+ * the cap is `undefined` and holds nothing.
46
+ */
47
+ private _grid;
48
+ /**
49
+ * How many columns fit the width offered, and which item goes in each cell,
50
+ * decided the way Rich's `Columns` decides them.
51
+ *
52
+ * Every item carries a width. It is what the item measures, or the declared
53
+ * `width` for all of them. `equal` counts every item as the widest when
54
+ * choosing how many columns fit, and the grid still sizes each column by
55
+ * what is in it — which is why an `equal` layout is not a grid of equal
56
+ * columns, in the reference or here.
57
+ *
58
+ * The count starts at one column per item and drops whenever the columns
59
+ * filled so far, and the gaps between them, pass the width offered. Two
60
+ * columns stand the wider of the padding's left and right sides apart, which
61
+ * is what the collapsed grid leaves between them. One departure from the
62
+ * reference: a declared `width` is searched the same way rather than by
63
+ * `max_width // (width + gap)`, which laid out empty trailing columns and
64
+ * charged the last column a gap it does not have.
65
+ */
66
+ private _layout;
67
+ /**
68
+ * The declared column width, bounded by the width offered.
69
+ *
70
+ * A declared width is what a column asks for, not what it takes — the
71
+ * contract `Table._outerWidth` keeps for a declared table width. Laid out at
72
+ * the raw declared width, a six-cell column offered three emitted six-cell
73
+ * lines while `measure` reported three, and the terminal's soft wrap took the
74
+ * frame of everything printed after it.
75
+ */
76
+ private _declaredWidth;
77
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Columns — arranges renderables in a multi-column layout.
3
+ *
4
+ * A port of Rich's `Columns`. What it decides is how many columns, and which
5
+ * item goes in which cell. Everything after that — how wide each column is, how
6
+ * far apart, what `expand` does, how rows are separated — is the grid's: the
7
+ * items are laid out as a `Table.grid`, as the reference lays them out.
8
+ * [LAW:one-source-of-truth] A second copy of that geometry here is how the two
9
+ * drifted: an empty item's column was drawn with no width where the grid draws one cell.
10
+ */
11
+ import { Measurement } from "../core/measure.js";
12
+ import { normalizePadding } from "./padding.js";
13
+ import { withBoundedWidth, withCellWidth } from "../core/protocol.js";
14
+ import { cellCount } from "../core/cells.js";
15
+ import { EmbeddedText, embed } from "./embed.js";
16
+ import { Constrain } from "./constrain.js";
17
+ import { Table } from "./table.js";
18
+ /**
19
+ * The item in slot `slot` of the grid for `count` columns, counting row by row;
20
+ * `undefined` is a slot no item fills. Constant time, so a search that stops at
21
+ * the first slot that overflows pays for no slot after it.
22
+ */
23
+ function slotItem(slot, items, count, columnFirst) {
24
+ const row = Math.floor(slot / count);
25
+ const col = slot % count;
26
+ // Column-first fills each column top to bottom, and the columns that take one
27
+ // item more than the rest are the leading ones — Rich's `column_lengths`.
28
+ const base = Math.floor(items / count);
29
+ const extra = items % count;
30
+ const length = base + (col < extra ? 1 : 0);
31
+ const index = !columnFirst ? slot : row < length ? col * base + Math.min(col, extra) + row : items;
32
+ return index < items ? index : undefined;
33
+ }
34
+ /** How many slots the grid for `count` columns has: every row full, the last one padded. */
35
+ function slotCount(items, count) {
36
+ return Math.ceil(items / count) * count;
37
+ }
38
+ export class Columns {
39
+ renderables;
40
+ expand;
41
+ equal;
42
+ colWidth;
43
+ columnFirst;
44
+ padding;
45
+ constructor(items, options) {
46
+ this.renderables = items ? [...items].map(embed) : [];
47
+ this.expand = options?.expand ?? false;
48
+ this.equal = options?.equal ?? false;
49
+ // A declared column width is a cell count like any other, and it reaches
50
+ // the layout without passing through `withCellWidth`, which parses only
51
+ // what the *caller of render* supplied. Left raw, `width: NaN` made
52
+ // `Math.max(1, NaN)` a NaN column count and `new Array` threw `Invalid
53
+ // array length` before a single column was laid out. Absence is preserved
54
+ // rather than parsed: `undefined` selects auto-fit, and `cellCount` would
55
+ // read it as a declared zero.
56
+ this.colWidth = options?.width === undefined ? undefined : cellCount(options.width);
57
+ this.columnFirst = options?.columnFirst ?? false;
58
+ // [LAW:parse-dont-validate] Rich's default is `(0, 1)`: one cell between
59
+ // columns and none between rows.
60
+ this.padding = normalizePadding(options?.padding ?? [0, 1]);
61
+ }
62
+ *render(rawOptions) {
63
+ if (this.renderables.length === 0)
64
+ return;
65
+ // The parsed options, not just a parsed local: `_layout` hands them to
66
+ // `Measurement.get`, and a raw NaN reaching it comes back as a NaN item
67
+ // width, then a NaN column count, then `Invalid array length` before a
68
+ // single column is laid out. An unbounded width reaches the same place by
69
+ // the same route, which is why the parse here is the bounded one.
70
+ const options = withBoundedWidth(rawOptions, this);
71
+ yield* this._grid(options).render(options);
72
+ }
73
+ measure(rawOptions) {
74
+ // The maximum is the grid's, which reports its width before `expand`: a
75
+ // stretch is what a Columns does with a width it is given, not a width it
76
+ // asks for. Reported as the offer instead, `Panel` in fit mode drew a
77
+ // 40-cell frame around five cells of content and an unbounded offer came
78
+ // back unbounded.
79
+ //
80
+ // A floor of one cell is what Columns asks for, since it can always fall
81
+ // back to one column, and the ceiling wins: a bare `minimum: 1` measured
82
+ // into no width at all reported the range 1..0 — a floor above its own
83
+ // ceiling, which the parent that asked cannot divide.
84
+ const parsed = withCellWidth(rawOptions);
85
+ if (this.renderables.length === 0)
86
+ return { minimum: 0, maximum: 0 };
87
+ const { maximum } = this._grid(parsed).measure(parsed);
88
+ return { minimum: Math.min(1, maximum), maximum };
89
+ }
90
+ /**
91
+ * The items at the width offered, as the reference's grid: `_layout`'s
92
+ * cells in a `Table.grid`.
93
+ *
94
+ * [LAW:single-enforcer] `render` draws this and `measure` reports its width,
95
+ * so the two cannot disagree about how wide a Columns is.
96
+ *
97
+ * A declared `width` is each column's natural width rather than a Table
98
+ * column `width`, which this port never stretches: the reference's grid
99
+ * stretches a Columns' declared width under `expand` like any other.
100
+ * `equal` holds every item to the widest, Rich's `Constrain`; without it
101
+ * the cap is `undefined` and holds nothing.
102
+ */
103
+ _grid(options) {
104
+ const { columns, rows, widest } = this._layout(options);
105
+ const grid = Table.grid({ padding: this.padding, expand: this.expand });
106
+ const declared = this._declaredWidth(options);
107
+ for (let col = 0; col < columns; col++) {
108
+ grid.addColumn(undefined, { minWidth: declared, maxWidth: declared });
109
+ }
110
+ const cap = this.equal ? widest : undefined;
111
+ // A string item is read here, under the options Columns is drawn with,
112
+ // before the grid gets it: Rich's `Columns` reads its strings through
113
+ // `render_str`, so they are highlighted where a table cell's are not.
114
+ const items = this.renderables.map((item) => (item instanceof EmbeddedText ? item.text(options) : item));
115
+ for (const row of rows) {
116
+ grid.addRow(...row.map((index) => (index === undefined ? "" : new Constrain(items[index], cap))));
117
+ }
118
+ return grid;
119
+ }
120
+ /**
121
+ * How many columns fit the width offered, and which item goes in each cell,
122
+ * decided the way Rich's `Columns` decides them.
123
+ *
124
+ * Every item carries a width. It is what the item measures, or the declared
125
+ * `width` for all of them. `equal` counts every item as the widest when
126
+ * choosing how many columns fit, and the grid still sizes each column by
127
+ * what is in it — which is why an `equal` layout is not a grid of equal
128
+ * columns, in the reference or here.
129
+ *
130
+ * The count starts at one column per item and drops whenever the columns
131
+ * filled so far, and the gaps between them, pass the width offered. Two
132
+ * columns stand the wider of the padding's left and right sides apart, which
133
+ * is what the collapsed grid leaves between them. One departure from the
134
+ * reference: a declared `width` is searched the same way rather than by
135
+ * `max_width // (width + gap)`, which laid out empty trailing columns and
136
+ * charged the last column a gap it does not have.
137
+ */
138
+ _layout(options) {
139
+ const count = this.renderables.length;
140
+ const maxWidth = options.maxWidth;
141
+ const [, right, , left] = this.padding;
142
+ const gap = Math.max(left, right);
143
+ const declared = this._declaredWidth(options);
144
+ // An item that cannot measure itself wants the whole offer. Counting it as one
145
+ // cell instead resolved an unbounded offer to a single column and cropped
146
+ // forty cells of content down to `"x"` with no error at all.
147
+ const sizes = this.renderables.map((item) => declared ?? Measurement.get(options, item).maximum);
148
+ // A fold, not `Math.max(...sizes)`: spreading one argument per item
149
+ // overflows the call stack at a few hundred thousand items.
150
+ const widest = sizes.reduce((w, size) => Math.max(w, size), 0);
151
+ const fits = this.equal ? sizes.map(() => widest) : sizes;
152
+ // One attempt either fits in the offer or names the smaller count to try
153
+ // next. `total` is the columns so far and the gaps between them, kept as
154
+ // the widths grow so that a slot costs the same however many columns came
155
+ // before it.
156
+ const attempt = (columns) => {
157
+ const widths = [];
158
+ let total = 0;
159
+ for (let slot = 0; slot < slotCount(count, columns); slot++) {
160
+ const col = slot % columns;
161
+ const index = slotItem(slot, count, columns, this.columnFirst);
162
+ const held = widths[col];
163
+ const width = Math.max(held ?? 0, index === undefined ? 0 : fits[index]);
164
+ total += width - (held ?? 0) + (held === undefined && col > 0 ? gap : 0);
165
+ widths[col] = width;
166
+ // Never zero: no item measures wider than the offer, so the first
167
+ // column alone always fits.
168
+ if (total > maxWidth)
169
+ return widths.length - 1;
170
+ }
171
+ return columns;
172
+ };
173
+ let columns = count;
174
+ for (let next = attempt(columns); next < columns; next = attempt(columns))
175
+ columns = next;
176
+ const rows = Array.from({ length: slotCount(count, columns) / columns }, (_, row) => Array.from({ length: columns }, (_, col) => slotItem(row * columns + col, count, columns, this.columnFirst)));
177
+ return { columns, rows, widest };
178
+ }
179
+ /**
180
+ * The declared column width, bounded by the width offered.
181
+ *
182
+ * A declared width is what a column asks for, not what it takes — the
183
+ * contract `Table._outerWidth` keeps for a declared table width. Laid out at
184
+ * the raw declared width, a six-cell column offered three emitted six-cell
185
+ * lines while `measure` reported three, and the terminal's soft wrap took the
186
+ * frame of everything printed after it.
187
+ */
188
+ _declaredWidth(options) {
189
+ return this.colWidth === undefined ? undefined : Math.min(this.colWidth, options.maxWidth);
190
+ }
191
+ }
@@ -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,31 @@
1
+ /**
2
+ * Constrain — wraps a renderable and constrains its maximum width.
3
+ */
4
+ import { Measurement } from "../core/measure.js";
5
+ export class Constrain {
6
+ renderable;
7
+ width;
8
+ constructor(renderable, width) {
9
+ this.renderable = renderable;
10
+ this.width = width;
11
+ }
12
+ *render(options) {
13
+ // [LAW:dataflow-not-control-flow] Always compute constrainedWidth; undefined means no constraint
14
+ const constrainedWidth = this.width !== undefined
15
+ ? Math.min(this.width, options.maxWidth)
16
+ : options.maxWidth;
17
+ const innerOptions = {
18
+ ...options,
19
+ maxWidth: constrainedWidth,
20
+ };
21
+ yield* this.renderable.render(innerOptions);
22
+ }
23
+ measure(options) {
24
+ // Measured at the width it will be drawn at, as Rich's `update_width` does;
25
+ // `Measurement.get` then holds the answer inside that offer.
26
+ const maxWidth = this.width !== undefined
27
+ ? Math.min(this.width, options.maxWidth)
28
+ : options.maxWidth;
29
+ return Measurement.get({ ...options, maxWidth }, this.renderable);
30
+ }
31
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Effected — a renderable whose finished cells' colours pass through an
3
+ * effect at a moment `t`.
4
+ *
5
+ * An effect is one pure function: a cell's colours, where the cell is, and a
6
+ * time in, the cell's new colours out. Shimmer, pulse, fade and dissolve are
7
+ * all this one shape, differing by what the function does with its inputs
8
+ * [LAW:one-type-per-behavior]; two effects stack by nesting one wrapper in
9
+ * another, which is the same as calling one after the other.
10
+ *
11
+ * [LAW:no-ambient-temporal-coupling] `t` is a constructor argument, not a
12
+ * clock read. Whoever draws frames builds the view for each frame's `t`, so
13
+ * the same `t` always draws the same bytes, at 30 frames a second or one
14
+ * every two seconds.
15
+ *
16
+ * What an effect may not do is the contract this module holds:
17
+ *
18
+ * - It changes how cells look, never which cells exist or who drew them. The
19
+ * child's text is walked by grapheme cluster, so a wide glyph is one cell
20
+ * to the effect and is never cut, and a run cut out of a segment keeps that
21
+ * segment's link and meta and carries its anchor shifted to the run's first
22
+ * cell (`Style.shiftedBy`), so hit testing still finds the owner.
23
+ * - A cell whose colours come back unchanged keeps its original `ColorSpec`,
24
+ * and a segment whose every cell is unchanged is yielded as it was, so the
25
+ * identity effect is byte-identical. Neighbouring cells given equal colours
26
+ * stay one segment: a segment is cut only where the colours change.
27
+ * - New colours are built with `ColorSpec.fromRgba`, never parsed from a
28
+ * string, so no frame adds an entry to a parse cache.
29
+ * - With no colour output (`colorSystem: null`) there is nothing for an
30
+ * effect to change, and the child's segments pass through untouched.
31
+ *
32
+ * Positions are the child's own: row and column count from the top-left of
33
+ * what the child drew, so a sweep crosses the element, not the screen.
34
+ */
35
+ import { type ColorRgba, type TerminalTheme } from "../core/color.js";
36
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
37
+ import { Segment } from "../core/segment.js";
38
+ /**
39
+ * A cell's foreground and background as they appear on screen — the glyph's
40
+ * colour and the colour behind it — which are the colours an effect moves.
41
+ * Under `reverse` they are the style's two colours swapped.
42
+ */
43
+ export interface CellColors {
44
+ readonly fg: ColorRgba;
45
+ readonly bg: ColorRgba;
46
+ }
47
+ /**
48
+ * Where a cell is in the output of the renderable an effect wraps, and its
49
+ * seed: a number in [0, 1) that is the same for that cell on every frame, for
50
+ * an effect that varies cell by cell (a per-cell offset in time, a sparkle).
51
+ */
52
+ export interface EffectCell {
53
+ readonly row: number;
54
+ readonly col: number;
55
+ readonly seed: number;
56
+ }
57
+ /** A cell's colours at time `t`, in seconds. Return `colors` to leave it be. */
58
+ export type Effect = (colors: CellColors, cell: EffectCell, t: number) => CellColors;
59
+ export interface EffectedOptions {
60
+ /** The moment to draw, in seconds. */
61
+ readonly t: number;
62
+ /**
63
+ * Names the element for its seeds, so two elements showing the same text do
64
+ * not twinkle in lockstep. The same key gives the same seeds on every frame.
65
+ */
66
+ readonly key: string;
67
+ /**
68
+ * The terminal the frame is drawn on: the colours a cell that sets none is
69
+ * drawn in, and the sixteen an ANSI colour names. The terminal's own palette
70
+ * is never queried, so the caller says what it is.
71
+ */
72
+ readonly theme: TerminalTheme;
73
+ }
74
+ export declare class Effected implements Renderable, Measurable {
75
+ readonly renderable: Renderable;
76
+ readonly effect: Effect;
77
+ readonly t: number;
78
+ readonly key: string;
79
+ readonly theme: TerminalTheme;
80
+ constructor(renderable: Renderable, effect: Effect, options: EffectedOptions);
81
+ render(options: RenderOptions): Iterable<Segment>;
82
+ measure(options: RenderOptions): {
83
+ minimum: number;
84
+ maximum: number;
85
+ };
86
+ /**
87
+ * The style that draws a run's slot colours `to` where the segment drew
88
+ * `from` with `wire`: a slot the effect left alone keeps its written spec,
89
+ * and a moved one is laid on what is beneath it — the ground on
90
+ * `SURFACE_BLACK`, the glyph on that ground, as the writer lays any
91
+ * translucent colour (`Style.drawnColors`) — then drawn in the colour nearest it at `depth`
92
+ * that the terminal shows (`ColorSpec.matchOn`), so a colour moved a little
93
+ * off a theme slot or the default colour is still drawn as that one.
94
+ */
95
+ private respec;
96
+ /** A cell's seed: FNV-1a of the key and the position, scaled into [0, 1). */
97
+ private seed;
98
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Effected — a renderable whose finished cells' colours pass through an
3
+ * effect at a moment `t`.
4
+ *
5
+ * An effect is one pure function: a cell's colours, where the cell is, and a
6
+ * time in, the cell's new colours out. Shimmer, pulse, fade and dissolve are
7
+ * all this one shape, differing by what the function does with its inputs
8
+ * [LAW:one-type-per-behavior]; two effects stack by nesting one wrapper in
9
+ * another, which is the same as calling one after the other.
10
+ *
11
+ * [LAW:no-ambient-temporal-coupling] `t` is a constructor argument, not a
12
+ * clock read. Whoever draws frames builds the view for each frame's `t`, so
13
+ * the same `t` always draws the same bytes, at 30 frames a second or one
14
+ * every two seconds.
15
+ *
16
+ * What an effect may not do is the contract this module holds:
17
+ *
18
+ * - It changes how cells look, never which cells exist or who drew them. The
19
+ * child's text is walked by grapheme cluster, so a wide glyph is one cell
20
+ * to the effect and is never cut, and a run cut out of a segment keeps that
21
+ * segment's link and meta and carries its anchor shifted to the run's first
22
+ * cell (`Style.shiftedBy`), so hit testing still finds the owner.
23
+ * - A cell whose colours come back unchanged keeps its original `ColorSpec`,
24
+ * and a segment whose every cell is unchanged is yielded as it was, so the
25
+ * identity effect is byte-identical. Neighbouring cells given equal colours
26
+ * stay one segment: a segment is cut only where the colours change.
27
+ * - New colours are built with `ColorSpec.fromRgba`, never parsed from a
28
+ * string, so no frame adds an entry to a parse cache.
29
+ * - With no colour output (`colorSystem: null`) there is nothing for an
30
+ * effect to change, and the child's segments pass through untouched.
31
+ *
32
+ * Positions are the child's own: row and column count from the top-left of
33
+ * what the child drew, so a sweep crosses the element, not the screen.
34
+ */
35
+ import { cellLen, graphemes } from "../core/cells.js";
36
+ import { ColorDepth, ColorSpec, SURFACE_BLACK } from "../core/color.js";
37
+ import { fnv1a } from "../core/fnv1a.js";
38
+ import { Measurement } from "../core/measure.js";
39
+ import { Segment } from "../core/segment.js";
40
+ import { Style } from "../core/style.js";
41
+ export class Effected {
42
+ renderable;
43
+ effect;
44
+ t;
45
+ key;
46
+ theme;
47
+ constructor(renderable, effect, options) {
48
+ this.renderable = renderable;
49
+ this.effect = effect;
50
+ if (!Number.isFinite(options.t)) {
51
+ throw new RangeError(`Effected needs a finite t, got ${options.t}`);
52
+ }
53
+ this.t = options.t;
54
+ this.key = options.key;
55
+ this.theme = options.theme;
56
+ }
57
+ *render(options) {
58
+ const segments = this.renderable.render(options);
59
+ if (options.colorSystem === null) {
60
+ yield* segments;
61
+ return;
62
+ }
63
+ // No depth named is no downgrade, as `drawnColors` reads it.
64
+ const depth = options.colorSystem ?? ColorDepth.TRUECOLOR;
65
+ let row = 0;
66
+ let col = 0;
67
+ for (const segment of segments) {
68
+ if (segment.isControl) {
69
+ yield segment;
70
+ continue;
71
+ }
72
+ const base = segment.style ?? Style.null();
73
+ // [LAW:single-enforcer] `drawnColors` is what the wire writes at this
74
+ // depth — alpha flattened, downgraded — and the theme resolves what it
75
+ // leaves to the terminal. A slot the effect leaves alone keeps the
76
+ // written spec, so it stays what the screen showed whatever moves
77
+ // beside it.
78
+ const wire = base.drawnColors(options.colorSystem ?? undefined);
79
+ const ink = (wire.color ?? DEFAULT).getTruecolor(this.theme, true);
80
+ const paper = (wire.bgcolor ?? DEFAULT).getTruecolor(this.theme, false);
81
+ const from = onScreen({ fg: ink, bg: paper }, base);
82
+ // Each grapheme's colours, merged into runs of equal colours as they
83
+ // come; `undefined` is a run left as the segment drew it.
84
+ const runs = [];
85
+ for (const glyph of graphemes(segment.text)) {
86
+ let colors;
87
+ if (glyph === "\n") {
88
+ row++;
89
+ col = 0;
90
+ }
91
+ else {
92
+ const to = this.effect(from, { row, col, seed: this.seed(row, col) }, this.t);
93
+ colors = sameColors(to, from) ? undefined : to;
94
+ col += cellLen(glyph);
95
+ }
96
+ const last = runs[runs.length - 1];
97
+ if (last !== undefined && sameRun(last.colors, colors)) {
98
+ last.text += glyph;
99
+ last.cells += cellLen(glyph);
100
+ }
101
+ else {
102
+ runs.push({ text: glyph, cells: cellLen(glyph), colors });
103
+ }
104
+ }
105
+ if (runs.every((run) => run.colors === undefined)) {
106
+ yield segment;
107
+ continue;
108
+ }
109
+ let offset = 0;
110
+ for (const run of runs) {
111
+ const shifted = base.shiftedBy(offset);
112
+ // Back from the screen to the style's slots: the same swap undoes itself.
113
+ const slots = run.colors === undefined ? undefined : onScreen(run.colors, base);
114
+ const style = slots === undefined ? shifted : shifted.add(this.respec(wire, { fg: ink, bg: paper }, slots, depth));
115
+ yield new Segment(run.text, style);
116
+ offset += run.cells;
117
+ }
118
+ }
119
+ }
120
+ measure(options) {
121
+ return Measurement.get(options, this.renderable);
122
+ }
123
+ /**
124
+ * The style that draws a run's slot colours `to` where the segment drew
125
+ * `from` with `wire`: a slot the effect left alone keeps its written spec,
126
+ * and a moved one is laid on what is beneath it — the ground on
127
+ * `SURFACE_BLACK`, the glyph on that ground, as the writer lays any
128
+ * translucent colour (`Style.drawnColors`) — then drawn in the colour nearest it at `depth`
129
+ * that the terminal shows (`ColorSpec.matchOn`), so a colour moved a little
130
+ * off a theme slot or the default colour is still drawn as that one.
131
+ */
132
+ respec(wire, from, to, depth) {
133
+ // [LAW:one-source-of-truth] The writer's surface, so one translucent colour draws one way in a frame.
134
+ const ground = to.bg.compositeOver(SURFACE_BLACK);
135
+ const glyph = to.fg.compositeOver(ground);
136
+ const drawn = (spec, was, now, foreground) => sameColor(was, now) ? spec : ColorSpec.matchOn(now, depth, this.theme, foreground);
137
+ return Style.fromColor(drawn(wire.color, from.fg, glyph, true), drawn(wire.bgcolor, from.bg, ground, false));
138
+ }
139
+ /** A cell's seed: FNV-1a of the key and the position, scaled into [0, 1). */
140
+ seed(row, col) {
141
+ return Number.parseInt(fnv1a(`${this.key}:${row}:${col}`), 16) / 0x1_0000_0000;
142
+ }
143
+ }
144
+ const DEFAULT = ColorSpec.default();
145
+ /**
146
+ * A style's colour slots as the screen shows them, or the reverse: `reverse`
147
+ * swaps glyph and ground, and swapping twice is where you started.
148
+ */
149
+ function onScreen(colors, style) {
150
+ return style.reverse === true ? { fg: colors.bg, bg: colors.fg } : colors;
151
+ }
152
+ function sameColor(a, b) {
153
+ return a.red === b.red && a.green === b.green && a.blue === b.blue && a.alpha === b.alpha;
154
+ }
155
+ function sameColors(a, b) {
156
+ return sameColor(a.fg, b.fg) && sameColor(a.bg, b.bg);
157
+ }
158
+ function sameRun(a, b) {
159
+ return a === undefined || b === undefined ? a === b : sameColors(a, b);
160
+ }