@promptctl/rich-js 0.7.0 → 0.8.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 (137) hide show
  1. package/README.md +31 -30
  2. package/dist/core/box.d.ts +3 -3
  3. package/dist/core/box.d.ts.map +1 -1
  4. package/dist/core/box.js.map +1 -1
  5. package/dist/core/cells.d.ts +46 -1
  6. package/dist/core/cells.d.ts.map +1 -1
  7. package/dist/core/cells.js +55 -14
  8. package/dist/core/cells.js.map +1 -1
  9. package/dist/core/console.d.ts +28 -10
  10. package/dist/core/console.d.ts.map +1 -1
  11. package/dist/core/console.js +141 -80
  12. package/dist/core/console.js.map +1 -1
  13. package/dist/core/markup.d.ts +0 -2
  14. package/dist/core/markup.d.ts.map +1 -1
  15. package/dist/core/markup.js +31 -17
  16. package/dist/core/markup.js.map +1 -1
  17. package/dist/core/measure.d.ts +11 -0
  18. package/dist/core/measure.d.ts.map +1 -1
  19. package/dist/core/measure.js +15 -3
  20. package/dist/core/measure.js.map +1 -1
  21. package/dist/core/oklch.d.ts +22 -0
  22. package/dist/core/oklch.d.ts.map +1 -1
  23. package/dist/core/oklch.js +62 -18
  24. package/dist/core/oklch.js.map +1 -1
  25. package/dist/core/pretty.d.ts +87 -0
  26. package/dist/core/pretty.d.ts.map +1 -0
  27. package/dist/core/pretty.js +304 -0
  28. package/dist/core/pretty.js.map +1 -0
  29. package/dist/core/protocol.d.ts +64 -0
  30. package/dist/core/protocol.d.ts.map +1 -1
  31. package/dist/core/protocol.js +72 -0
  32. package/dist/core/protocol.js.map +1 -1
  33. package/dist/core/segment.d.ts +21 -0
  34. package/dist/core/segment.d.ts.map +1 -1
  35. package/dist/core/segment.js +40 -1
  36. package/dist/core/segment.js.map +1 -1
  37. package/dist/core/subscription.d.ts +13 -0
  38. package/dist/core/subscription.d.ts.map +1 -0
  39. package/dist/core/subscription.js +13 -0
  40. package/dist/core/subscription.js.map +1 -0
  41. package/dist/core/text.d.ts.map +1 -1
  42. package/dist/core/text.js +45 -13
  43. package/dist/core/text.js.map +1 -1
  44. package/dist/host/host-stream.d.ts +31 -0
  45. package/dist/host/host-stream.d.ts.map +1 -0
  46. package/dist/host/host-stream.js +36 -0
  47. package/dist/host/host-stream.js.map +1 -0
  48. package/dist/host/index.d.ts +4 -0
  49. package/dist/host/index.d.ts.map +1 -0
  50. package/dist/host/index.js +3 -0
  51. package/dist/host/index.js.map +1 -0
  52. package/dist/host/terminal-host.d.ts +144 -0
  53. package/dist/host/terminal-host.d.ts.map +1 -0
  54. package/dist/host/terminal-host.js +133 -0
  55. package/dist/host/terminal-host.js.map +1 -0
  56. package/dist/index.d.ts +10 -33
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +33 -21
  59. package/dist/index.js.map +1 -1
  60. package/dist/node/terminal-host.d.ts +85 -0
  61. package/dist/node/terminal-host.d.ts.map +1 -0
  62. package/dist/node/terminal-host.js +161 -0
  63. package/dist/node/terminal-host.js.map +1 -0
  64. package/dist/node/traceback.d.ts +28 -0
  65. package/dist/node/traceback.d.ts.map +1 -0
  66. package/dist/node/traceback.js +113 -0
  67. package/dist/node/traceback.js.map +1 -0
  68. package/dist/renderables/columns.d.ts +24 -2
  69. package/dist/renderables/columns.d.ts.map +1 -1
  70. package/dist/renderables/columns.js +91 -32
  71. package/dist/renderables/columns.js.map +1 -1
  72. package/dist/renderables/layout.d.ts +61 -5
  73. package/dist/renderables/layout.d.ts.map +1 -1
  74. package/dist/renderables/layout.js +191 -19
  75. package/dist/renderables/layout.js.map +1 -1
  76. package/dist/renderables/padding.d.ts +15 -2
  77. package/dist/renderables/padding.d.ts.map +1 -1
  78. package/dist/renderables/padding.js +92 -37
  79. package/dist/renderables/padding.js.map +1 -1
  80. package/dist/renderables/panel.d.ts +44 -3
  81. package/dist/renderables/panel.d.ts.map +1 -1
  82. package/dist/renderables/panel.js +149 -80
  83. package/dist/renderables/panel.js.map +1 -1
  84. package/dist/renderables/table.d.ts +40 -4
  85. package/dist/renderables/table.d.ts.map +1 -1
  86. package/dist/renderables/table.js +276 -158
  87. package/dist/renderables/table.js.map +1 -1
  88. package/dist/renderables/traceback.d.ts +6 -2
  89. package/dist/renderables/traceback.d.ts.map +1 -1
  90. package/dist/renderables/traceback.js +6 -10
  91. package/dist/renderables/traceback.js.map +1 -1
  92. package/dist/renderables/tree.d.ts +27 -2
  93. package/dist/renderables/tree.d.ts.map +1 -1
  94. package/dist/renderables/tree.js +82 -23
  95. package/dist/renderables/tree.js.map +1 -1
  96. package/dist/template-bindings/color-funcs.d.ts +3 -3
  97. package/dist/template-bindings/color-funcs.js +3 -3
  98. package/dist/template-bindings/index.d.ts +6 -7
  99. package/dist/template-bindings/index.d.ts.map +1 -1
  100. package/dist/template-bindings/index.js +6 -7
  101. package/dist/template-bindings/index.js.map +1 -1
  102. package/dist/template-bindings/palette-funcs.d.ts +17 -5
  103. package/dist/template-bindings/palette-funcs.d.ts.map +1 -1
  104. package/dist/template-bindings/palette-funcs.js +69 -6
  105. package/dist/template-bindings/palette-funcs.js.map +1 -1
  106. package/dist/template-bindings/style-funcs.d.ts +1 -1
  107. package/dist/template-bindings/style-funcs.js +1 -1
  108. package/dist/themes/ramp.d.ts +84 -0
  109. package/dist/themes/ramp.d.ts.map +1 -0
  110. package/dist/themes/ramp.js +123 -0
  111. package/dist/themes/ramp.js.map +1 -0
  112. package/dist/widgets/dropdown.d.ts +1 -1
  113. package/dist/widgets/dropdown.js +1 -1
  114. package/dist/widgets/event-router.d.ts +3 -2
  115. package/dist/widgets/event-router.d.ts.map +1 -1
  116. package/dist/widgets/focus-manager.d.ts +2 -1
  117. package/dist/widgets/focus-manager.d.ts.map +1 -1
  118. package/dist/widgets/index.d.ts +3 -6
  119. package/dist/widgets/index.d.ts.map +1 -1
  120. package/dist/widgets/index.js +4 -3
  121. package/dist/widgets/index.js.map +1 -1
  122. package/dist/widgets/screen.d.ts +1 -1
  123. package/dist/widgets/screen.d.ts.map +1 -1
  124. package/dist/widgets/terminal-host.d.ts +13 -63
  125. package/dist/widgets/terminal-host.d.ts.map +1 -1
  126. package/dist/widgets/terminal-host.js +11 -142
  127. package/dist/widgets/terminal-host.js.map +1 -1
  128. package/dist/widgets/text-input.d.ts +2 -3
  129. package/dist/widgets/text-input.d.ts.map +1 -1
  130. package/dist/widgets/text-input.js +4 -12
  131. package/dist/widgets/text-input.js.map +1 -1
  132. package/dist/widgets/types.d.ts +1 -1
  133. package/dist/widgets/types.d.ts.map +1 -1
  134. package/dist/widgets/types.js.map +1 -1
  135. package/dist/widgets/widget-base.d.ts +2 -1
  136. package/dist/widgets/widget-base.d.ts.map +1 -1
  137. package/package.json +20 -4
@@ -95,14 +95,50 @@ export declare class Table implements Renderable, Measurable {
95
95
  addColumn(header?: string | RichText, options?: ColumnOptions): this;
96
96
  addRow(...cells: unknown[]): this;
97
97
  addSection(): this;
98
- render(options: RenderOptions): Iterable<Segment>;
99
- measure(options: RenderOptions): {
98
+ render(rawOptions: RenderOptions): Iterable<Segment>;
99
+ /**
100
+ * [LAW:one-source-of-truth] Both ends of the range are widths the geometry
101
+ * actually produced — the maximum from the demands as they stand, the
102
+ * minimum from the same layout with every column asking for a single cell.
103
+ * Neither can exceed the width offered and the tighter request cannot exceed
104
+ * the looser one, so the range cannot invert. Deriving the minimum from raw
105
+ * column and padding counts instead is what used to return
106
+ * `{minimum: 6, maximum: 1}` at `maxWidth: 1` — a floor above its own ceiling.
107
+ */
108
+ measure(rawOptions: RenderOptions): {
100
109
  minimum: number;
101
110
  maximum: number;
102
111
  };
103
112
  static grid(options?: Omit<TableOptions, "box" | "showHeader" | "showEdge">): Table;
104
- private _calculateWidths;
105
- private _totalTableWidth;
113
+ private _frame;
114
+ /**
115
+ * The width this table lays itself out against: what it was told to be, as a
116
+ * count of cells, never wider than what it was offered.
117
+ *
118
+ * [LAW:one-source-of-truth] `render` and `measure` divide the same two fields
119
+ * and once did it in two places. `measure` bounded the declared width by the
120
+ * offer and `render` preferred it outright, walking past the value
121
+ * `withBoundedWidth` had just resolved — so `new Table({width: Infinity})`
122
+ * with a `{ratio: 1}` column granted that column `UNBOUNDED` cells and threw
123
+ * `RangeError: Invalid string length` out of the top border, at a perfectly
124
+ * ordinary 80-cell offer.
125
+ */
126
+ private _outerWidth;
127
+ private _geometry;
128
+ /**
129
+ * [LAW:dataflow-not-control-flow] The three ways a column can be sized —
130
+ * declared width, ratio, natural content — differ only in the `want` and
131
+ * `weight` they produce. They are resolved once, here, into uniform data, so
132
+ * `layoutTable` runs the same apportionment for every table and no sizing
133
+ * mode gets its own path through the width division.
134
+ */
135
+ private _columnDemands;
136
+ /**
137
+ * The widest cell in a column, bounded by its own `minWidth`/`maxWidth`.
138
+ * Zero for a column with no header and no content — a gutter asks for its
139
+ * padding and nothing else.
140
+ */
141
+ private _naturalWidth;
106
142
  private _renderRow;
107
143
  private _renderTitle;
108
144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/renderables/table.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/renderables/table.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Table — tabular data with headers, borders, auto-sizing, and alignment.
3
3
  */
4
- import { cellLen } from "../core/cells.js";
4
+ import { cellLen, setCellSize, asCellCol, cellCount as cells } from "../core/cells.js";
5
5
  import { Segment } from "../core/segment.js";
6
6
  import { Style, NULL_STYLE } from "../core/style.js";
7
7
  import { HEAVY_HEAD } from "../core/box.js";
8
8
  import { RichText } from "../core/text.js";
9
+ import { normalizePadding } from "./padding.js";
10
+ import { withBoundedWidth, withCellWidth } from "../core/protocol.js";
9
11
  function resolveStyle(style) {
10
12
  if (style === undefined)
11
13
  return NULL_STYLE;
@@ -21,14 +23,143 @@ function toRenderable(content) {
21
23
  }
22
24
  return new RichText(String(content ?? ""), { end: "" });
23
25
  }
24
- function normalizePadding(padding) {
25
- if (padding === undefined)
26
- return [0, 1, 0, 1];
27
- if (typeof padding === "number")
28
- return [padding, padding, padding, padding];
29
- if (padding.length === 2)
30
- return [padding[0], padding[1], padding[0], padding[1]];
31
- return padding;
26
+ /** A `want` no budget can satisfy: the column takes every cell its weight earns. */
27
+ const UNBOUNDED = Number.MAX_SAFE_INTEGER;
28
+ /**
29
+ * A column's `want` or `weight`. Finite, because `UNBOUNDED` is this model's
30
+ * own infinity: a literal `Infinity` reaching `distribute` makes a column's
31
+ * weighted share `Infinity / Infinity`, which is NaN, and the NaN then skews
32
+ * every other elastic column in the table.
33
+ */
34
+ const demandCells = (n) => Math.min(cells(n), UNBOUNDED);
35
+ /**
36
+ * Hand out `total` cells across `demands`, weighted, and never past a demand's
37
+ * `want`.
38
+ *
39
+ * A column whose proportional share would overshoot its cap is granted its
40
+ * whole want and dropped, and the cells it could not use are reopened to the
41
+ * columns still under their caps. That repeats until everyone still open fits
42
+ * within their share; one largest-remainder pass then places the cells the
43
+ * shares left as fractions, so the granted widths sum to `total` exactly — or
44
+ * to the point where every column is capped, which is how a table stays
45
+ * narrower than a width it was offered.
46
+ *
47
+ * Each round caps at least one column or is the last, so the work is bounded by
48
+ * the number of columns and never by the width. That bound is the point rather
49
+ * than an optimization: handing cells out one at a time made the iteration
50
+ * count the width itself, which stalled on a very wide table and — since
51
+ * `Infinity - 1 === Infinity` — never terminated at all for a `maxWidth` of
52
+ * `Infinity` held open by a ratio column. Capping by want reaches that case in
53
+ * one round.
54
+ */
55
+ function distribute(total, demands) {
56
+ const granted = demands.map(() => 0);
57
+ let open = demands
58
+ .map((_, index) => index)
59
+ .filter((index) => demands[index].weight > 0 && demands[index].want > 0);
60
+ let remaining = Math.max(0, total);
61
+ const weightOf = (indices) => indices.reduce((sum, index) => sum + demands[index].weight, 0);
62
+ while (open.length > 0 && remaining > 0) {
63
+ const weightSum = weightOf(open);
64
+ const capped = open.filter((index) => (remaining * demands[index].weight) / weightSum >= demands[index].want);
65
+ if (capped.length === 0)
66
+ break;
67
+ for (const index of capped) {
68
+ granted[index] = demands[index].want;
69
+ remaining -= demands[index].want;
70
+ }
71
+ open = open.filter((index) => !capped.includes(index));
72
+ }
73
+ if (open.length > 0 && remaining > 0) {
74
+ const weightSum = weightOf(open);
75
+ const shares = open.map((index) => (remaining * demands[index].weight) / weightSum);
76
+ const whole = shares.map(Math.floor);
77
+ let residue = remaining - whole.reduce((sum, cells) => sum + cells, 0);
78
+ // The cells the shares left as fractions go to the largest fraction first,
79
+ // ties to the leftmost column, so the total lands exactly on `remaining`.
80
+ const byFraction = open
81
+ .map((_, slot) => slot)
82
+ .sort((a, b) => shares[b] - whole[b] - (shares[a] - whole[a]) || a - b);
83
+ for (const slot of byFraction) {
84
+ if (residue <= 0)
85
+ break;
86
+ whole[slot]++;
87
+ residue--;
88
+ }
89
+ open.forEach((index, slot) => {
90
+ granted[index] = whole[slot];
91
+ });
92
+ }
93
+ return granted;
94
+ }
95
+ function layoutTable(outerWidth, demands, padding, frame) {
96
+ const [, padRightWanted, , padLeftWanted] = padding;
97
+ // Plain `number`, not the `CellCol` `cells` hands back: this budget is spent
98
+ // down by arithmetic, and arithmetic on a branded type produces a number.
99
+ let budget = cells(outerWidth);
100
+ const take = (want) => {
101
+ const got = Math.min(Math.max(0, want), budget);
102
+ budget -= got;
103
+ return got;
104
+ };
105
+ // Both edge columns or neither: `Box.getTop` and its siblings take a single
106
+ // flag for the pair, so half a frame is not a shape this renderer can emit.
107
+ const edge = budget >= frame.edge * 2 ? frame.edge : 0;
108
+ take(edge * 2);
109
+ // A first content cell per column, in column order, each paying for the
110
+ // divider that precedes it. The first column the budget cannot seat is where
111
+ // the table ends; the rest are dropped. A column that wants no cells is
112
+ // seated at zero — the queue exists to stop one column taking a second cell
113
+ // before another has its first, and a spacer never joins it.
114
+ const seats = [];
115
+ while (seats.length < demands.length) {
116
+ const seat = Math.min(1, demands[seats.length].want);
117
+ const cost = (seats.length > 0 ? frame.divider : 0) + seat;
118
+ if (budget < cost)
119
+ break;
120
+ take(cost);
121
+ seats.push(seat);
122
+ }
123
+ const seated = seats.length;
124
+ // Padding is uniform across columns or it is not padding, so it is bought
125
+ // for every seated column at once and skipped entirely when only some could
126
+ // afford it.
127
+ const takePerColumn = (want) => {
128
+ const per = seated === 0
129
+ ? 0
130
+ : Math.min(Math.max(0, want), Math.floor(budget / seated));
131
+ budget -= per * seated;
132
+ return per;
133
+ };
134
+ const padLeft = takePerColumn(padLeftWanted);
135
+ const padRight = takePerColumn(padRightWanted);
136
+ // Reservations are paid in column order, each already holding the cell the
137
+ // seating pass gave it. A budget too small to cover them all runs out
138
+ // partway, which costs the trailing columns their width but never costs the
139
+ // table its frame.
140
+ const seatedDemands = demands.slice(0, seated);
141
+ const reserved = seatedDemands.map((demand, index) => take(demand.reserved - seats[index]));
142
+ // What is left to apportion is the rest of what each column wanted. A table
143
+ // whose columns all fit leaves this budget partly unspent, which is how it
144
+ // stays narrower than the width it was offered.
145
+ const extra = distribute(budget, seatedDemands.map((demand, index) => ({
146
+ reserved: 0,
147
+ want: Math.max(0, demand.want - seats[index] - reserved[index]),
148
+ weight: demand.weight,
149
+ })));
150
+ const columns = extra.map((cells, index) => seats[index] + reserved[index] + cells);
151
+ const cellWidths = columns.map((width) => padLeft + width + padRight);
152
+ return {
153
+ edge,
154
+ divider: frame.divider,
155
+ padLeft,
156
+ padRight,
157
+ columns,
158
+ cellWidths,
159
+ totalWidth: edge * 2 +
160
+ Math.max(0, seated - 1) * frame.divider +
161
+ cellWidths.reduce((sum, width) => sum + width, 0),
162
+ };
32
163
  }
33
164
  export class Column {
34
165
  header;
@@ -134,7 +265,7 @@ export class Table {
134
265
  this.showFooter = options?.showFooter ?? false;
135
266
  this.showLines = options?.showLines ?? false;
136
267
  this.showEdge = options?.showEdge !== false;
137
- this.padding = normalizePadding(options?.padding);
268
+ this.padding = normalizePadding(options?.padding ?? [0, 1, 0, 1]);
138
269
  this.style = resolveStyle(options?.style);
139
270
  this.headerStyle = resolveStyle(options?.headerStyle ?? "table.header");
140
271
  this.footerStyle = resolveStyle(options?.footerStyle ?? "table.footer");
@@ -183,78 +314,96 @@ export class Table {
183
314
  }
184
315
  return this;
185
316
  }
186
- *render(options) {
317
+ *render(rawOptions) {
187
318
  if (this._columns.length === 0) {
188
319
  yield Segment.line();
189
320
  return;
190
321
  }
322
+ const options = withBoundedWidth(rawOptions, this);
191
323
  const box = this.box
192
324
  ? (options.asciiOnly ? this.box.substitute({ asciiOnly: true }) : this.box)
193
325
  : null;
194
326
  const border = this.borderStyle.isNull ? undefined : this.borderStyle;
195
- // Calculate column widths
196
- const totalWidth = this.tableWidth ?? (this.expand ? options.maxWidth : undefined);
197
- const colWidths = this._calculateWidths(options, totalWidth);
198
- const tableActualWidth = this._totalTableWidth(colWidths, box);
199
- const [_padTop, padRight, _padBottom, padLeft] = this.padding;
327
+ // The one division of the width every row below is measured against.
328
+ const geometry = this._geometry(this._outerWidth(options));
329
+ const edge = geometry.edge === 1;
200
330
  // Title
201
331
  if (this.title) {
202
- yield* this._renderTitle(this.title, tableActualWidth, this.titleStyle, this.titleJustify);
332
+ yield* this._renderTitle(this.title, geometry.totalWidth, this.titleStyle, this.titleJustify);
203
333
  }
204
334
  // Top border
205
335
  if (box && this.showEdge) {
206
- yield* box.getTop(colWidths.map((w) => w + padLeft + padRight), border, true);
336
+ yield* box.getTop(geometry.cellWidths, border, edge);
207
337
  }
208
338
  // Header row
209
339
  if (this.showHeader && this._columns.some((c) => c.header.hasContent)) {
210
340
  const headerCells = this._columns.map((c) => c.header);
211
- yield* this._renderRow(headerCells, colWidths, box, border, this.headerStyle);
341
+ yield* this._renderRow(headerCells, geometry, box, border, this.headerStyle);
212
342
  // Header separator
213
343
  if (box) {
214
- yield* box.getRow(colWidths.map((w) => w + padLeft + padRight), "head", border, this.showEdge);
344
+ yield* box.getRow(geometry.cellWidths, "head", border, edge);
215
345
  }
216
346
  }
217
347
  // Data rows
218
348
  for (let rowIdx = 0; rowIdx < this._rows.length; rowIdx++) {
219
349
  const row = this._rows[rowIdx];
220
- const rowCells = [];
221
- for (let colIdx = 0; colIdx < this._columns.length; colIdx++) {
222
- const cell = row.cells[colIdx];
223
- rowCells.push(toRenderable(cell));
224
- }
350
+ const rowCells = this._columns.map((_, colIdx) => toRenderable(row.cells[colIdx]));
225
351
  const rowStyle = this.rowStyles.length > 0
226
352
  ? resolveStyle(this.rowStyles[rowIdx % this.rowStyles.length])
227
353
  : NULL_STYLE;
228
- yield* this._renderRow(rowCells, colWidths, box, border, rowStyle);
354
+ yield* this._renderRow(rowCells, geometry, box, border, rowStyle);
229
355
  // Row separator
230
356
  const showSep = this.showLines || row.endSection;
231
357
  if (showSep && box && rowIdx < this._rows.length - 1) {
232
- yield* box.getRow(colWidths.map((w) => w + padLeft + padRight), "row", border, this.showEdge);
358
+ yield* box.getRow(geometry.cellWidths, "row", border, edge);
233
359
  }
234
360
  }
235
361
  // Footer
236
362
  if (this.showFooter && this._columns.some((c) => c.footer)) {
237
363
  if (box) {
238
- yield* box.getRow(colWidths.map((w) => w + padLeft + padRight), "foot", border, this.showEdge);
364
+ yield* box.getRow(geometry.cellWidths, "foot", border, edge);
239
365
  }
240
366
  const footerCells = this._columns.map((c) => (c.footer ?? new RichText("", { end: "" })));
241
- yield* this._renderRow(footerCells, colWidths, box, border, this.footerStyle);
367
+ yield* this._renderRow(footerCells, geometry, box, border, this.footerStyle);
242
368
  }
243
369
  // Bottom border
244
370
  if (box && this.showEdge) {
245
- yield* box.getBottom(colWidths.map((w) => w + padLeft + padRight), border, true);
371
+ yield* box.getBottom(geometry.cellWidths, border, edge);
246
372
  }
247
373
  // Caption
248
374
  if (this.caption) {
249
- yield* this._renderTitle(this.caption, tableActualWidth, this.captionStyle, this.captionJustify);
375
+ yield* this._renderTitle(this.caption, geometry.totalWidth, this.captionStyle, this.captionJustify);
250
376
  }
251
377
  }
252
- measure(options) {
253
- const colWidths = this._calculateWidths(options);
254
- const total = this._totalTableWidth(colWidths, this.box);
378
+ /**
379
+ * [LAW:one-source-of-truth] Both ends of the range are widths the geometry
380
+ * actually produced — the maximum from the demands as they stand, the
381
+ * minimum from the same layout with every column asking for a single cell.
382
+ * Neither can exceed the width offered and the tighter request cannot exceed
383
+ * the looser one, so the range cannot invert. Deriving the minimum from raw
384
+ * column and padding counts instead is what used to return
385
+ * `{minimum: 6, maximum: 1}` at `maxWidth: 1` — a floor above its own ceiling.
386
+ */
387
+ measure(rawOptions) {
388
+ const options = withCellWidth(rawOptions);
389
+ const outerWidth = this._outerWidth(options);
390
+ const frame = this._frame();
391
+ const demands = this._columnDemands();
392
+ const laidOut = layoutTable(outerWidth, demands, this.padding, frame).totalWidth;
393
+ // `UNBOUNDED` is this table's own infinity, so a layout that reached it has
394
+ // no natural width to report — a column asked for every cell there is. Said
395
+ // as the number, it escapes as a width a caller would try to draw:
396
+ // `minWidth: Infinity` measured 18014398509481988 and rendered
397
+ // `RangeError: Invalid string length` out of the top border.
398
+ const maximum = laidOut >= UNBOUNDED ? Infinity : laidOut;
399
+ const tightest = layoutTable(outerWidth, demands.map((demand) => ({
400
+ reserved: 0,
401
+ want: Math.min(1, demand.want),
402
+ weight: 1,
403
+ })), this.padding, frame).totalWidth;
255
404
  return {
256
- minimum: Math.max(this._columns.length * 2, this.minWidth ?? 0),
257
- maximum: Math.min(total, options.maxWidth),
405
+ minimum: Math.min(maximum, Math.max(tightest, this.minWidth ?? 0)),
406
+ maximum,
258
407
  };
259
408
  }
260
409
  // --- Static ---
@@ -268,142 +417,108 @@ export class Table {
268
417
  });
269
418
  }
270
419
  // --- Private ---
271
- _calculateWidths(options, totalWidth) {
272
- const numCols = this._columns.length;
273
- if (numCols === 0)
274
- return [];
275
- const [_padTop, padRight, _padBottom, padLeft] = this.padding;
276
- const cellPad = padLeft + padRight;
277
- const borders = this.box
278
- ? (this.showEdge ? 2 : 0) + (numCols - 1)
279
- : 0;
280
- const availableWidth = (totalWidth ?? options.maxWidth) - borders - cellPad * numCols;
281
- const widths = new Array(numCols);
282
- // Fixed width columns first
283
- let remainingWidth = availableWidth;
284
- let flexCols = 0;
285
- for (let i = 0; i < numCols; i++) {
286
- const col = this._columns[i];
420
+ _frame() {
421
+ return {
422
+ divider: this.box ? 1 : 0,
423
+ edge: this.box && this.showEdge ? 1 : 0,
424
+ };
425
+ }
426
+ /**
427
+ * The width this table lays itself out against: what it was told to be, as a
428
+ * count of cells, never wider than what it was offered.
429
+ *
430
+ * [LAW:one-source-of-truth] `render` and `measure` divide the same two fields
431
+ * and once did it in two places. `measure` bounded the declared width by the
432
+ * offer and `render` preferred it outright, walking past the value
433
+ * `withBoundedWidth` had just resolved — so `new Table({width: Infinity})`
434
+ * with a `{ratio: 1}` column granted that column `UNBOUNDED` cells and threw
435
+ * `RangeError: Invalid string length` out of the top border, at a perfectly
436
+ * ordinary 80-cell offer.
437
+ */
438
+ _outerWidth(options) {
439
+ return Math.min(this.tableWidth === undefined ? options.maxWidth : cells(this.tableWidth), options.maxWidth);
440
+ }
441
+ _geometry(outerWidth) {
442
+ return layoutTable(outerWidth, this._columnDemands(), this.padding, this._frame());
443
+ }
444
+ /**
445
+ * [LAW:dataflow-not-control-flow] The three ways a column can be sized —
446
+ * declared width, ratio, natural content — differ only in the `want` and
447
+ * `weight` they produce. They are resolved once, here, into uniform data, so
448
+ * `layoutTable` runs the same apportionment for every table and no sizing
449
+ * mode gets its own path through the width division.
450
+ */
451
+ _columnDemands() {
452
+ // A ratio on any column makes every non-fixed column elastic: a ratio
453
+ // expresses a split of the whole width, so a column that declares none
454
+ // still holds a share of it (1).
455
+ const elastic = this._columns.some((col) => col.flexible);
456
+ return this._columns.map((col, index) => {
287
457
  if (col.width !== undefined) {
288
- widths[i] = col.width;
289
- remainingWidth -= col.width;
458
+ // [LAW:single-enforcer] floored where it is parsed, the same rule
459
+ // `normalizePadding` applies to a negative padding side.
460
+ const declared = demandCells(col.width);
461
+ return { reserved: declared, want: declared, weight: 0 };
290
462
  }
291
- else {
292
- widths[i] = 0;
293
- flexCols++;
294
- }
295
- }
296
- // Distribute remaining width among flex columns
297
- if (flexCols > 0) {
298
- // Measure content to determine natural widths
299
- const naturalWidths = [];
300
- for (let i = 0; i < numCols; i++) {
301
- const col = this._columns[i];
302
- if (col.width !== undefined) {
303
- naturalWidths.push(col.width);
304
- continue;
305
- }
306
- let maxContent = cellLen(col.header.plain);
307
- for (const row of this._rows) {
308
- const cell = row.cells[i];
309
- const cellText = String(cell ?? "");
310
- maxContent = Math.max(maxContent, cellLen(cellText));
311
- }
312
- if (col.minWidth !== undefined)
313
- maxContent = Math.max(maxContent, col.minWidth);
314
- if (col.maxWidth !== undefined)
315
- maxContent = Math.min(maxContent, col.maxWidth);
316
- naturalWidths.push(maxContent);
317
- }
318
- // Check if ratio-based distribution applies
319
- const hasRatios = this._columns.some((c) => c.flexible);
320
- if (hasRatios) {
321
- const totalRatio = this._columns.reduce((s, c) => s + (c.ratio ?? 1), 0);
322
- for (let i = 0; i < numCols; i++) {
323
- if (this._columns[i].width !== undefined)
324
- continue;
325
- const ratio = this._columns[i].ratio ?? 1;
326
- widths[i] = Math.max(1, Math.floor(remainingWidth * ratio / totalRatio));
327
- }
328
- }
329
- else {
330
- // Distribute equally or by natural width
331
- const totalNatural = naturalWidths.reduce((s, w, i) => this._columns[i].width !== undefined ? s : s + w, 0);
332
- for (let i = 0; i < numCols; i++) {
333
- if (this._columns[i].width !== undefined)
334
- continue;
335
- const natural = naturalWidths[i];
336
- if (totalNatural <= remainingWidth) {
337
- widths[i] = natural;
338
- }
339
- else {
340
- widths[i] = Math.max(1, Math.floor(remainingWidth * natural / totalNatural));
341
- }
342
- }
343
- }
344
- }
345
- return widths;
346
- }
347
- _totalTableWidth(colWidths, box) {
348
- const [_padTop, padRight, _padBottom, padLeft] = this.padding;
349
- const cellPad = padLeft + padRight;
350
- const contentWidth = colWidths.reduce((s, w) => s + w + cellPad, 0);
351
- const borders = box
352
- ? (this.showEdge ? 2 : 0) + (colWidths.length - 1)
353
- : 0;
354
- return contentWidth + borders;
463
+ if (elastic)
464
+ return { reserved: 0, want: UNBOUNDED, weight: demandCells(col.ratio ?? 1) };
465
+ const natural = demandCells(this._naturalWidth(col, index));
466
+ return { reserved: 0, want: natural, weight: natural };
467
+ });
355
468
  }
356
- *_renderRow(cells, colWidths, box, border, rowStyle) {
357
- const [_padTop, padRight, _padBottom, padLeft] = this.padding;
358
- // Render each cell and split into lines
359
- const cellLines = [];
360
- let maxLines = 1;
361
- for (let i = 0; i < this._columns.length; i++) {
362
- const col = this._columns[i];
363
- const cell = cells[i] ?? toRenderable("");
364
- const cellWidth = colWidths[i] ?? 1;
365
- const cellOpts = {
366
- ...({ maxWidth: cellWidth }),
367
- justify: col.justify,
368
- overflow: col.overflow,
369
- noWrap: col.noWrap,
370
- };
371
- const segs = [...cell.render(cellOpts)];
372
- const lines = Segment.splitLines(segs);
373
- const adjusted = lines.map((line) => Segment.adjustLineLength(line, cellWidth));
374
- // Ensure at least one line
375
- const finalLines = adjusted.length > 0 ? adjusted : [[new Segment(" ".repeat(cellWidth))]];
376
- cellLines.push(finalLines);
377
- maxLines = Math.max(maxLines, finalLines.length);
469
+ /**
470
+ * The widest cell in a column, bounded by its own `minWidth`/`maxWidth`.
471
+ * Zero for a column with no header and no content — a gutter asks for its
472
+ * padding and nothing else.
473
+ */
474
+ _naturalWidth(col, index) {
475
+ let natural = cellLen(col.header.plain);
476
+ for (const row of this._rows) {
477
+ natural = Math.max(natural, cellLen(String(row.cells[index] ?? "")));
378
478
  }
379
- // Render line by line
479
+ if (col.minWidth !== undefined)
480
+ natural = Math.max(natural, col.minWidth);
481
+ if (col.maxWidth !== undefined)
482
+ natural = Math.min(natural, col.maxWidth);
483
+ return natural;
484
+ }
485
+ *_renderRow(cells, geometry, box, border, rowStyle) {
486
+ const { padLeft, padRight, columns } = geometry;
487
+ // Render each cell onto the canvas the geometry gave its column. Columns
488
+ // the width could not seat are absent from `columns` and so are never
489
+ // rendered at all.
490
+ const cellLines = columns.map((cellWidth, index) => {
491
+ const col = this._columns[index];
492
+ const cell = cells[index] ?? toRenderable("");
493
+ const segs = [...cell.render({
494
+ maxWidth: cellWidth,
495
+ justify: col.justify,
496
+ overflow: col.overflow,
497
+ noWrap: col.noWrap,
498
+ })];
499
+ const lines = Segment.splitLines(segs).map((line) => Segment.adjustLineLength(line, cellWidth));
500
+ return lines.length > 0 ? lines : [[new Segment(" ".repeat(cellWidth))]];
501
+ });
502
+ const maxLines = cellLines.reduce((most, lines) => Math.max(most, lines.length), 1);
380
503
  for (let lineIdx = 0; lineIdx < maxLines; lineIdx++) {
381
- if (box && this.showEdge) {
504
+ if (box && geometry.edge === 1) {
382
505
  yield new Segment(box.left, border);
383
506
  }
384
- for (let colIdx = 0; colIdx < this._columns.length; colIdx++) {
507
+ for (let colIdx = 0; colIdx < columns.length; colIdx++) {
385
508
  if (colIdx > 0 && box) {
386
509
  yield new Segment(box.vertical, border);
387
510
  }
388
- const colLines = cellLines[colIdx];
389
- const cellWidth = colWidths[colIdx] ?? 1;
390
- // Left padding
511
+ const cellWidth = columns[colIdx];
391
512
  if (padLeft > 0)
392
513
  yield new Segment(" ".repeat(padLeft));
393
- const line = colLines[lineIdx];
394
- if (line) {
395
- const adjusted = Segment.adjustLineLength(line, cellWidth);
396
- const styled = rowStyle.isNull ? adjusted : [...Segment.applyStyle(adjusted, rowStyle)];
397
- yield* styled;
398
- }
399
- else {
400
- yield new Segment(" ".repeat(cellWidth));
401
- }
402
- // Right padding
514
+ // A cell that ran out of lines contributes blanks, so every column
515
+ // spans the same number of rows and the frame stays rectangular.
516
+ const line = cellLines[colIdx][lineIdx] ?? [new Segment(" ".repeat(cellWidth))];
517
+ yield* rowStyle.isNull ? line : Segment.applyStyle(line, rowStyle);
403
518
  if (padRight > 0)
404
519
  yield new Segment(" ".repeat(padRight));
405
520
  }
406
- if (box && this.showEdge) {
521
+ if (box && geometry.edge === 1) {
407
522
  yield new Segment(box.right, border);
408
523
  }
409
524
  yield Segment.line();
@@ -413,8 +528,11 @@ export class Table {
413
528
  const titleStyle = style.isNull ? undefined : style;
414
529
  const plain = text.plain;
415
530
  const textWidth = cellLen(plain);
531
+ // Cropped by cells, not by code units: a title of wide characters sliced
532
+ // at `tableWidth` code units is up to twice `tableWidth` cells on screen,
533
+ // which is the overflow this crop exists to prevent.
416
534
  if (textWidth >= tableWidth) {
417
- yield new Segment(plain.slice(0, tableWidth), titleStyle);
535
+ yield new Segment(setCellSize(plain, asCellCol(tableWidth)), titleStyle);
418
536
  yield Segment.line();
419
537
  return;
420
538
  }