@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.
- package/README.md +31 -30
- package/dist/core/box.d.ts +3 -3
- package/dist/core/box.d.ts.map +1 -1
- package/dist/core/box.js.map +1 -1
- package/dist/core/cells.d.ts +46 -1
- package/dist/core/cells.d.ts.map +1 -1
- package/dist/core/cells.js +55 -14
- package/dist/core/cells.js.map +1 -1
- package/dist/core/console.d.ts +28 -10
- package/dist/core/console.d.ts.map +1 -1
- package/dist/core/console.js +141 -80
- package/dist/core/console.js.map +1 -1
- package/dist/core/markup.d.ts +0 -2
- package/dist/core/markup.d.ts.map +1 -1
- package/dist/core/markup.js +31 -17
- package/dist/core/markup.js.map +1 -1
- package/dist/core/measure.d.ts +11 -0
- package/dist/core/measure.d.ts.map +1 -1
- package/dist/core/measure.js +15 -3
- package/dist/core/measure.js.map +1 -1
- package/dist/core/oklch.d.ts +22 -0
- package/dist/core/oklch.d.ts.map +1 -1
- package/dist/core/oklch.js +62 -18
- package/dist/core/oklch.js.map +1 -1
- package/dist/core/pretty.d.ts +87 -0
- package/dist/core/pretty.d.ts.map +1 -0
- package/dist/core/pretty.js +304 -0
- package/dist/core/pretty.js.map +1 -0
- package/dist/core/protocol.d.ts +64 -0
- package/dist/core/protocol.d.ts.map +1 -1
- package/dist/core/protocol.js +72 -0
- package/dist/core/protocol.js.map +1 -1
- package/dist/core/segment.d.ts +21 -0
- package/dist/core/segment.d.ts.map +1 -1
- package/dist/core/segment.js +40 -1
- package/dist/core/segment.js.map +1 -1
- package/dist/core/subscription.d.ts +13 -0
- package/dist/core/subscription.d.ts.map +1 -0
- package/dist/core/subscription.js +13 -0
- package/dist/core/subscription.js.map +1 -0
- package/dist/core/text.d.ts.map +1 -1
- package/dist/core/text.js +45 -13
- package/dist/core/text.js.map +1 -1
- package/dist/host/host-stream.d.ts +31 -0
- package/dist/host/host-stream.d.ts.map +1 -0
- package/dist/host/host-stream.js +36 -0
- package/dist/host/host-stream.js.map +1 -0
- package/dist/host/index.d.ts +4 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +3 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/terminal-host.d.ts +144 -0
- package/dist/host/terminal-host.d.ts.map +1 -0
- package/dist/host/terminal-host.js +133 -0
- package/dist/host/terminal-host.js.map +1 -0
- package/dist/index.d.ts +10 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -21
- package/dist/index.js.map +1 -1
- package/dist/node/terminal-host.d.ts +85 -0
- package/dist/node/terminal-host.d.ts.map +1 -0
- package/dist/node/terminal-host.js +161 -0
- package/dist/node/terminal-host.js.map +1 -0
- package/dist/node/traceback.d.ts +28 -0
- package/dist/node/traceback.d.ts.map +1 -0
- package/dist/node/traceback.js +113 -0
- package/dist/node/traceback.js.map +1 -0
- package/dist/renderables/columns.d.ts +24 -2
- package/dist/renderables/columns.d.ts.map +1 -1
- package/dist/renderables/columns.js +91 -32
- package/dist/renderables/columns.js.map +1 -1
- package/dist/renderables/layout.d.ts +61 -5
- package/dist/renderables/layout.d.ts.map +1 -1
- package/dist/renderables/layout.js +191 -19
- package/dist/renderables/layout.js.map +1 -1
- package/dist/renderables/padding.d.ts +15 -2
- package/dist/renderables/padding.d.ts.map +1 -1
- package/dist/renderables/padding.js +92 -37
- package/dist/renderables/padding.js.map +1 -1
- package/dist/renderables/panel.d.ts +44 -3
- package/dist/renderables/panel.d.ts.map +1 -1
- package/dist/renderables/panel.js +149 -80
- package/dist/renderables/panel.js.map +1 -1
- package/dist/renderables/table.d.ts +40 -4
- package/dist/renderables/table.d.ts.map +1 -1
- package/dist/renderables/table.js +276 -158
- package/dist/renderables/table.js.map +1 -1
- package/dist/renderables/traceback.d.ts +6 -2
- package/dist/renderables/traceback.d.ts.map +1 -1
- package/dist/renderables/traceback.js +6 -10
- package/dist/renderables/traceback.js.map +1 -1
- package/dist/renderables/tree.d.ts +27 -2
- package/dist/renderables/tree.d.ts.map +1 -1
- package/dist/renderables/tree.js +82 -23
- package/dist/renderables/tree.js.map +1 -1
- package/dist/template-bindings/color-funcs.d.ts +3 -3
- package/dist/template-bindings/color-funcs.js +3 -3
- package/dist/template-bindings/index.d.ts +6 -7
- package/dist/template-bindings/index.d.ts.map +1 -1
- package/dist/template-bindings/index.js +6 -7
- package/dist/template-bindings/index.js.map +1 -1
- package/dist/template-bindings/palette-funcs.d.ts +17 -5
- package/dist/template-bindings/palette-funcs.d.ts.map +1 -1
- package/dist/template-bindings/palette-funcs.js +69 -6
- package/dist/template-bindings/palette-funcs.js.map +1 -1
- package/dist/template-bindings/style-funcs.d.ts +1 -1
- package/dist/template-bindings/style-funcs.js +1 -1
- package/dist/themes/ramp.d.ts +84 -0
- package/dist/themes/ramp.d.ts.map +1 -0
- package/dist/themes/ramp.js +123 -0
- package/dist/themes/ramp.js.map +1 -0
- package/dist/widgets/dropdown.d.ts +1 -1
- package/dist/widgets/dropdown.js +1 -1
- package/dist/widgets/event-router.d.ts +3 -2
- package/dist/widgets/event-router.d.ts.map +1 -1
- package/dist/widgets/focus-manager.d.ts +2 -1
- package/dist/widgets/focus-manager.d.ts.map +1 -1
- package/dist/widgets/index.d.ts +3 -6
- package/dist/widgets/index.d.ts.map +1 -1
- package/dist/widgets/index.js +4 -3
- package/dist/widgets/index.js.map +1 -1
- package/dist/widgets/screen.d.ts +1 -1
- package/dist/widgets/screen.d.ts.map +1 -1
- package/dist/widgets/terminal-host.d.ts +13 -63
- package/dist/widgets/terminal-host.d.ts.map +1 -1
- package/dist/widgets/terminal-host.js +11 -142
- package/dist/widgets/terminal-host.js.map +1 -1
- package/dist/widgets/text-input.d.ts +2 -3
- package/dist/widgets/text-input.d.ts.map +1 -1
- package/dist/widgets/text-input.js +4 -12
- package/dist/widgets/text-input.js.map +1 -1
- package/dist/widgets/types.d.ts +1 -1
- package/dist/widgets/types.d.ts.map +1 -1
- package/dist/widgets/types.js.map +1 -1
- package/dist/widgets/widget-base.d.ts +2 -1
- package/dist/widgets/widget-base.d.ts.map +1 -1
- 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(
|
|
99
|
-
|
|
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
|
|
105
|
-
|
|
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":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAc,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,GAAG,EAAc,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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(
|
|
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
|
-
//
|
|
196
|
-
const
|
|
197
|
-
const
|
|
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,
|
|
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(
|
|
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,
|
|
341
|
+
yield* this._renderRow(headerCells, geometry, box, border, this.headerStyle);
|
|
212
342
|
// Header separator
|
|
213
343
|
if (box) {
|
|
214
|
-
yield* box.getRow(
|
|
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,
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
371
|
+
yield* box.getBottom(geometry.cellWidths, border, edge);
|
|
246
372
|
}
|
|
247
373
|
// Caption
|
|
248
374
|
if (this.caption) {
|
|
249
|
-
yield* this._renderTitle(this.caption,
|
|
375
|
+
yield* this._renderTitle(this.caption, geometry.totalWidth, this.captionStyle, this.captionJustify);
|
|
250
376
|
}
|
|
251
377
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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(
|
|
257
|
-
maximum
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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 &&
|
|
504
|
+
if (box && geometry.edge === 1) {
|
|
382
505
|
yield new Segment(box.left, border);
|
|
383
506
|
}
|
|
384
|
-
for (let colIdx = 0; 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
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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 &&
|
|
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
|
|
535
|
+
yield new Segment(setCellSize(plain, asCellCol(tableWidth)), titleStyle);
|
|
418
536
|
yield Segment.line();
|
|
419
537
|
return;
|
|
420
538
|
}
|