@promptctl/rich-js 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/anchor.d.ts +45 -0
- package/dist/core/anchor.js +39 -0
- package/dist/core/ansi.d.ts +65 -0
- package/dist/core/ansi.js +231 -0
- package/dist/core/box.d.ts +143 -0
- package/dist/core/box.js +379 -0
- package/dist/core/cells.d.ts +159 -0
- package/dist/core/cells.js +287 -0
- package/dist/core/color.d.ts +246 -0
- package/dist/core/color.js +1038 -0
- package/dist/core/console.d.ts +165 -0
- package/dist/core/console.js +527 -0
- package/dist/core/emoji.d.ts +24 -0
- package/dist/core/emoji.js +391 -0
- package/dist/core/export-html.d.ts +59 -0
- package/dist/core/export-html.js +200 -0
- package/dist/core/export-lines.d.ts +121 -0
- package/dist/core/export-lines.js +141 -0
- package/dist/core/highlighter.d.ts +33 -0
- package/dist/core/highlighter.js +124 -0
- package/dist/core/json.d.ts +27 -0
- package/dist/core/json.js +53 -0
- package/dist/core/markup.d.ts +119 -0
- package/dist/core/markup.js +731 -0
- package/dist/core/measure.d.ts +27 -0
- package/dist/core/measure.js +62 -0
- package/dist/core/oklch.d.ts +161 -0
- package/dist/core/oklch.js +312 -0
- package/dist/core/osc8.d.ts +68 -0
- package/dist/core/osc8.js +90 -0
- package/dist/core/place.d.ts +30 -0
- package/dist/core/place.js +51 -0
- package/dist/core/pretty.d.ts +144 -0
- package/dist/core/pretty.js +476 -0
- package/dist/core/protocol.d.ts +267 -0
- package/dist/core/protocol.js +163 -0
- package/dist/core/render.d.ts +84 -0
- package/dist/core/render.js +145 -0
- package/dist/core/segment.d.ts +161 -0
- package/dist/core/segment.js +492 -0
- package/dist/core/spinnerData.d.ts +12 -0
- package/dist/core/spinnerData.js +117 -0
- package/dist/core/strip.d.ts +141 -0
- package/dist/core/strip.js +305 -0
- package/dist/core/style.d.ts +169 -0
- package/dist/core/style.js +695 -0
- package/dist/core/subscription.d.ts +12 -0
- package/dist/core/subscription.js +12 -0
- package/dist/core/text.d.ts +256 -0
- package/dist/core/text.js +976 -0
- package/dist/core/wrap.d.ts +41 -0
- package/dist/core/wrap.js +127 -0
- package/dist/host/app.d.ts +121 -0
- package/dist/host/app.js +255 -0
- package/dist/host/host-environment.d.ts +26 -0
- package/dist/host/host-environment.js +44 -0
- package/dist/host/index.d.ts +5 -0
- package/dist/host/index.js +3 -0
- package/dist/host/terminal-host.d.ts +179 -0
- package/dist/host/terminal-host.js +186 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +107 -0
- package/dist/node/prompt.d.ts +21 -0
- package/dist/node/prompt.js +30 -0
- package/dist/node/save.d.ts +17 -0
- package/dist/node/save.js +21 -0
- package/dist/node/terminal-host.d.ts +113 -0
- package/dist/node/terminal-host.js +227 -0
- package/dist/node/traceback.d.ts +37 -0
- package/dist/node/traceback.js +134 -0
- package/dist/renderables/align.d.ts +16 -0
- package/dist/renderables/align.js +33 -0
- package/dist/renderables/columns.d.ts +61 -0
- package/dist/renderables/columns.js +152 -0
- package/dist/renderables/constrain.d.ts +15 -0
- package/dist/renderables/constrain.js +37 -0
- package/dist/renderables/embed.d.ts +50 -0
- package/dist/renderables/embed.js +71 -0
- package/dist/renderables/flexStrip.d.ts +41 -0
- package/dist/renderables/flexStrip.js +200 -0
- package/dist/renderables/group.d.ts +11 -0
- package/dist/renderables/group.js +17 -0
- package/dist/renderables/layout.d.ts +92 -0
- package/dist/renderables/layout.js +312 -0
- package/dist/renderables/live.d.ts +50 -0
- package/dist/renderables/live.js +175 -0
- package/dist/renderables/markdown.d.ts +24 -0
- package/dist/renderables/markdown.js +184 -0
- package/dist/renderables/padding.d.ts +38 -0
- package/dist/renderables/padding.js +141 -0
- package/dist/renderables/panel.d.ts +116 -0
- package/dist/renderables/panel.js +307 -0
- package/dist/renderables/progress.d.ts +90 -0
- package/dist/renderables/progress.js +238 -0
- package/dist/renderables/progressBar.d.ts +31 -0
- package/dist/renderables/progressBar.js +53 -0
- package/dist/renderables/prompt.d.ts +49 -0
- package/dist/renderables/prompt.js +125 -0
- package/dist/renderables/rule.d.ts +25 -0
- package/dist/renderables/rule.js +90 -0
- package/dist/renderables/spinner.d.ts +31 -0
- package/dist/renderables/spinner.js +70 -0
- package/dist/renderables/status.d.ts +23 -0
- package/dist/renderables/status.js +60 -0
- package/dist/renderables/syntax.d.ts +35 -0
- package/dist/renderables/syntax.js +115 -0
- package/dist/renderables/table.d.ts +194 -0
- package/dist/renderables/table.js +795 -0
- package/dist/renderables/traceback.d.ts +31 -0
- package/dist/renderables/traceback.js +108 -0
- package/dist/renderables/tree.d.ts +68 -0
- package/dist/renderables/tree.js +150 -0
- package/dist/renderables/viewport.d.ts +114 -0
- package/dist/renderables/viewport.js +198 -0
- package/dist/template-bindings/color-funcs.d.ts +87 -0
- package/dist/template-bindings/color-funcs.js +217 -0
- package/dist/template-bindings/helpers.d.ts +24 -0
- package/dist/template-bindings/helpers.js +48 -0
- package/dist/template-bindings/index.d.ts +107 -0
- package/dist/template-bindings/index.js +146 -0
- package/dist/template-bindings/palette-funcs.d.ts +94 -0
- package/dist/template-bindings/palette-funcs.js +141 -0
- package/dist/template-bindings/style-funcs.d.ts +28 -0
- package/dist/template-bindings/style-funcs.js +155 -0
- package/dist/themes/buildPalette.d.ts +36 -0
- package/dist/themes/buildPalette.js +56 -0
- package/dist/themes/colorMath.d.ts +82 -0
- package/dist/themes/colorMath.js +254 -0
- package/dist/themes/colorRef.d.ts +51 -0
- package/dist/themes/colorRef.js +96 -0
- package/dist/themes/data/ansi-tables.d.ts +14 -0
- package/dist/themes/data/ansi-tables.js +49 -0
- package/dist/themes/data/atom-one-dark.d.ts +3 -0
- package/dist/themes/data/atom-one-dark.js +177 -0
- package/dist/themes/data/atom-one-light.d.ts +3 -0
- package/dist/themes/data/atom-one-light.js +177 -0
- package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
- package/dist/themes/data/catppuccin-frappe.js +177 -0
- package/dist/themes/data/catppuccin-latte.d.ts +3 -0
- package/dist/themes/data/catppuccin-latte.js +177 -0
- package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
- package/dist/themes/data/catppuccin-macchiato.js +177 -0
- package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
- package/dist/themes/data/catppuccin-mocha.js +177 -0
- package/dist/themes/data/cyberpunk.d.ts +17 -0
- package/dist/themes/data/cyberpunk.js +193 -0
- package/dist/themes/data/default.d.ts +3 -0
- package/dist/themes/data/default.js +160 -0
- package/dist/themes/data/dracula.d.ts +3 -0
- package/dist/themes/data/dracula.js +177 -0
- package/dist/themes/data/flexoki.d.ts +3 -0
- package/dist/themes/data/flexoki.js +177 -0
- package/dist/themes/data/gruvbox.d.ts +3 -0
- package/dist/themes/data/gruvbox.js +177 -0
- package/dist/themes/data/index.d.ts +35 -0
- package/dist/themes/data/index.js +64 -0
- package/dist/themes/data/monokai.d.ts +3 -0
- package/dist/themes/data/monokai.js +177 -0
- package/dist/themes/data/nord.d.ts +3 -0
- package/dist/themes/data/nord.js +177 -0
- package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
- package/dist/themes/data/rose-pine-dawn.js +177 -0
- package/dist/themes/data/rose-pine-moon.d.ts +3 -0
- package/dist/themes/data/rose-pine-moon.js +177 -0
- package/dist/themes/data/rose-pine.d.ts +3 -0
- package/dist/themes/data/rose-pine.js +177 -0
- package/dist/themes/data/solarized-dark.d.ts +3 -0
- package/dist/themes/data/solarized-dark.js +177 -0
- package/dist/themes/data/solarized-light.d.ts +3 -0
- package/dist/themes/data/solarized-light.js +177 -0
- package/dist/themes/data/svg-export.d.ts +3 -0
- package/dist/themes/data/svg-export.js +177 -0
- package/dist/themes/data/textual-ansi.d.ts +3 -0
- package/dist/themes/data/textual-ansi.js +160 -0
- package/dist/themes/data/textual-dark.d.ts +3 -0
- package/dist/themes/data/textual-dark.js +160 -0
- package/dist/themes/data/textual-light.d.ts +3 -0
- package/dist/themes/data/textual-light.js +160 -0
- package/dist/themes/data/tokyo-night.d.ts +3 -0
- package/dist/themes/data/tokyo-night.js +177 -0
- package/dist/themes/data/types.d.ts +31 -0
- package/dist/themes/data/types.js +10 -0
- package/dist/themes/palette.d.ts +21 -0
- package/dist/themes/palette.js +28 -0
- package/dist/themes/ramp.d.ts +83 -0
- package/dist/themes/ramp.js +122 -0
- package/dist/themes/registry.d.ts +44 -0
- package/dist/themes/registry.js +94 -0
- package/dist/themes/terminalThemes.d.ts +49 -0
- package/dist/themes/terminalThemes.js +63 -0
- package/dist/themes/transpose.d.ts +87 -0
- package/dist/themes/transpose.js +138 -0
- package/dist/widgets/button.d.ts +49 -0
- package/dist/widgets/button.js +208 -0
- package/dist/widgets/checkbox.d.ts +41 -0
- package/dist/widgets/checkbox.js +157 -0
- package/dist/widgets/dropdown.d.ts +67 -0
- package/dist/widgets/dropdown.js +450 -0
- package/dist/widgets/event-router.d.ts +71 -0
- package/dist/widgets/event-router.js +629 -0
- package/dist/widgets/focus-manager.d.ts +45 -0
- package/dist/widgets/focus-manager.js +191 -0
- package/dist/widgets/hit.d.ts +64 -0
- package/dist/widgets/hit.js +113 -0
- package/dist/widgets/index.d.ts +20 -0
- package/dist/widgets/index.js +14 -0
- package/dist/widgets/slider.d.ts +57 -0
- package/dist/widgets/slider.js +270 -0
- package/dist/widgets/static-item.d.ts +34 -0
- package/dist/widgets/static-item.js +54 -0
- package/dist/widgets/text-input.d.ts +274 -0
- package/dist/widgets/text-input.js +1165 -0
- package/dist/widgets/toggle.d.ts +44 -0
- package/dist/widgets/toggle.js +180 -0
- package/dist/widgets/types.d.ts +87 -0
- package/dist/widgets/types.js +30 -0
- package/dist/widgets/widget-app.d.ts +63 -0
- package/dist/widgets/widget-app.js +199 -0
- package/dist/widgets/widget-base.d.ts +40 -0
- package/dist/widgets/widget-base.js +165 -0
- package/package.json +6 -2
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Columns — arranges renderables in a multi-column layout.
|
|
3
|
+
*/
|
|
4
|
+
import { Segment } from "../core/segment.js";
|
|
5
|
+
import { Measurement } from "../core/measure.js";
|
|
6
|
+
import { isMeasurable, stackedHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
7
|
+
import { cellCount } from "../core/cells.js";
|
|
8
|
+
import { embed } from "./embed.js";
|
|
9
|
+
export class Columns {
|
|
10
|
+
renderables;
|
|
11
|
+
expand;
|
|
12
|
+
equal;
|
|
13
|
+
colWidth;
|
|
14
|
+
columnFirst;
|
|
15
|
+
gutterWidth;
|
|
16
|
+
constructor(items, options) {
|
|
17
|
+
this.renderables = items ? [...items].map(embed) : [];
|
|
18
|
+
this.expand = options?.expand ?? false;
|
|
19
|
+
this.equal = options?.equal ?? false;
|
|
20
|
+
// A declared column width is a cell count like any other, and it reaches
|
|
21
|
+
// the layout without passing through `withCellWidth`, which parses only
|
|
22
|
+
// what the *caller of render* supplied. Left raw, `width: NaN` made
|
|
23
|
+
// `Math.max(1, NaN)` a NaN column count and `new Array` threw `Invalid
|
|
24
|
+
// array length` before a single column was laid out. Absence is preserved
|
|
25
|
+
// rather than parsed: `undefined` selects auto-fit, and `cellCount` would
|
|
26
|
+
// read it as a declared zero.
|
|
27
|
+
this.colWidth = options?.width === undefined ? undefined : cellCount(options.width);
|
|
28
|
+
this.columnFirst = options?.columnFirst ?? false;
|
|
29
|
+
this.gutterWidth = 2; // default gutter between columns
|
|
30
|
+
}
|
|
31
|
+
*render(rawOptions) {
|
|
32
|
+
const items = this.renderables;
|
|
33
|
+
if (items.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
// The parsed options, not just a parsed local: `_divide` hands them to
|
|
36
|
+
// `Measurement.get`, and a raw NaN reaching it comes back as a NaN item
|
|
37
|
+
// width, then a NaN column count, then `Invalid array length` out of
|
|
38
|
+
// `new Array(numCols)` before a single column is laid out. An unbounded
|
|
39
|
+
// width reaches the same `new Array` by the same route, which is why the
|
|
40
|
+
// parse here is the bounded one.
|
|
41
|
+
const options = withBoundedWidth(rawOptions, this);
|
|
42
|
+
const { numCols, colWidths } = this._divide(options);
|
|
43
|
+
// Layout items into rows
|
|
44
|
+
const numRows = Math.ceil(items.length / numCols);
|
|
45
|
+
// [LAW:types-are-the-program] The element type is Renderable — any number
|
|
46
|
+
// of lines. Each grid cell renders to its full set of lines; an out-of-range
|
|
47
|
+
// slot (the last row is rarely full) is an empty cell. mergeHorizontal then
|
|
48
|
+
// stacks every row of every cell line-by-line, so multi-line children
|
|
49
|
+
// (Panels, Tables) compose instead of being truncated to their first row.
|
|
50
|
+
for (let row = 0; row < numRows; row++) {
|
|
51
|
+
const cells = colWidths.map((width, col) => {
|
|
52
|
+
const idx = this.columnFirst ? col * numRows + row : row * numCols + col;
|
|
53
|
+
const item = items[idx];
|
|
54
|
+
const lines = item === undefined
|
|
55
|
+
? []
|
|
56
|
+
: Segment.splitLines([
|
|
57
|
+
...item.render({ ...options, maxWidth: width, height: stackedHeight(options.height) }),
|
|
58
|
+
]);
|
|
59
|
+
return { lines, width };
|
|
60
|
+
});
|
|
61
|
+
yield* Segment.mergeHorizontal(cells, this.gutterWidth);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The requested width divided into columns, once.
|
|
66
|
+
*
|
|
67
|
+
* [LAW:single-enforcer] The three modes disagree only about how many columns
|
|
68
|
+
* there are and how wide each one is, so they disagree in one place. `render`
|
|
69
|
+
* lays out against this division and `_naturalWidth` is the width at which it
|
|
70
|
+
* comes out as one row of every item.
|
|
71
|
+
*/
|
|
72
|
+
_divide(options) {
|
|
73
|
+
const maxWidth = options.maxWidth;
|
|
74
|
+
const gutter = this.gutterWidth;
|
|
75
|
+
const declared = this._declaredWidth(options);
|
|
76
|
+
if (declared !== undefined) {
|
|
77
|
+
const numCols = Math.max(1, Math.floor((maxWidth + gutter) / (declared + gutter)));
|
|
78
|
+
return { numCols, colWidths: new Array(numCols).fill(declared) };
|
|
79
|
+
}
|
|
80
|
+
if (this.equal) {
|
|
81
|
+
const itemWidth = this._itemWidth(options);
|
|
82
|
+
const numCols = Math.max(1, Math.floor((maxWidth + gutter) / (itemWidth + gutter)));
|
|
83
|
+
const equalWidth = Math.floor((maxWidth - gutter * (numCols - 1)) / numCols);
|
|
84
|
+
return { numCols, colWidths: new Array(numCols).fill(equalWidth) };
|
|
85
|
+
}
|
|
86
|
+
// Auto: as many columns as the widest item fits into, capped at the item
|
|
87
|
+
// count. Sized from the content rather than from a flat four cells per
|
|
88
|
+
// column, because `_naturalWidth` below is the inverse of *this* expression
|
|
89
|
+
// and a constant has no inverse: six two-cell items reported a natural
|
|
90
|
+
// width of 22 and then wrapped into two rows at 22, since `floor(22 / 4)`
|
|
91
|
+
// is five columns no matter how narrow the items are.
|
|
92
|
+
const itemWidth = this._itemWidth(options);
|
|
93
|
+
const numCols = Math.min(this.renderables.length, Math.max(1, Math.floor((maxWidth + gutter) / (itemWidth + gutter))));
|
|
94
|
+
const colW = Math.floor((maxWidth - gutter * (numCols - 1)) / numCols);
|
|
95
|
+
return { numCols, colWidths: new Array(numCols).fill(colW) };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The widest any one item wants to be.
|
|
99
|
+
*
|
|
100
|
+
* An item that cannot measure itself wants the offer, which is what `Panel`,
|
|
101
|
+
* `Padding`, `Layout` and `Tree` all answer for the same case — and under an
|
|
102
|
+
* unbounded offer that is `Infinity`, so `withBoundedWidth` throws and says
|
|
103
|
+
* the request was unanswerable. Counting it as one cell instead reported a
|
|
104
|
+
* natural width of 1, which resolved an unbounded offer to a single column and
|
|
105
|
+
* cropped forty cells of content down to `"x"` with no error at all.
|
|
106
|
+
*/
|
|
107
|
+
_itemWidth(options) {
|
|
108
|
+
let widest = 1;
|
|
109
|
+
for (const item of this.renderables) {
|
|
110
|
+
widest = Math.max(widest, isMeasurable(item)
|
|
111
|
+
? Measurement.get(options, item).maximum
|
|
112
|
+
: options.maxWidth);
|
|
113
|
+
}
|
|
114
|
+
return widest;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The declared column width, bounded by the width offered.
|
|
118
|
+
*
|
|
119
|
+
* [LAW:single-enforcer] A declared width is what a column asks for, not what
|
|
120
|
+
* it takes — the contract `Table._outerWidth` keeps for a declared table
|
|
121
|
+
* width. Read by `_divide` for render and by `_naturalWidth` for measure, so
|
|
122
|
+
* the two answer from one number: laid out at the raw declared width, a
|
|
123
|
+
* six-cell column offered three emitted six-cell lines while `measure`
|
|
124
|
+
* reported three, and the terminal's soft wrap took the frame of everything
|
|
125
|
+
* printed after it.
|
|
126
|
+
*/
|
|
127
|
+
_declaredWidth(options) {
|
|
128
|
+
return this.colWidth === undefined ? undefined : Math.min(this.colWidth, options.maxWidth);
|
|
129
|
+
}
|
|
130
|
+
/** The width at which every item sits on one row: n columns and the gutters between them. */
|
|
131
|
+
_naturalWidth(options) {
|
|
132
|
+
const count = this.renderables.length;
|
|
133
|
+
if (count === 0)
|
|
134
|
+
return 0;
|
|
135
|
+
const width = this._declaredWidth(options) ?? this._itemWidth(options);
|
|
136
|
+
return count * width + this.gutterWidth * (count - 1);
|
|
137
|
+
}
|
|
138
|
+
measure(rawOptions) {
|
|
139
|
+
// A floor of one cell is what Columns asks for; the ceiling is what it was
|
|
140
|
+
// offered, and the ceiling wins. Stated as a bare `minimum: 1`, a Columns
|
|
141
|
+
// measured into no width at all reported the range 1..0 — a floor above
|
|
142
|
+
// its own ceiling, which the parent that asked cannot divide.
|
|
143
|
+
//
|
|
144
|
+
// The ceiling is not the whole answer, though: reported as the offer alone,
|
|
145
|
+
// a Columns claimed every cell it was shown, so `Panel` in fit mode drew a
|
|
146
|
+
// 40-cell frame around five cells of content and an unbounded offer came
|
|
147
|
+
// back unbounded.
|
|
148
|
+
const parsed = withCellWidth(rawOptions);
|
|
149
|
+
const maximum = Math.min(this._naturalWidth(parsed), parsed.maxWidth);
|
|
150
|
+
return { minimum: Math.min(1, maximum), maximum };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrain — wraps a renderable and constrains its maximum width.
|
|
3
|
+
*/
|
|
4
|
+
import { Segment } from "../core/segment.js";
|
|
5
|
+
import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
6
|
+
export declare class Constrain implements Renderable, Measurable {
|
|
7
|
+
readonly renderable: Renderable;
|
|
8
|
+
readonly width: number | undefined;
|
|
9
|
+
constructor(renderable: Renderable, width?: number);
|
|
10
|
+
render(options: RenderOptions): Iterable<Segment>;
|
|
11
|
+
measure(options: RenderOptions): {
|
|
12
|
+
minimum: number;
|
|
13
|
+
maximum: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrain — wraps a renderable and constrains its maximum width.
|
|
3
|
+
*/
|
|
4
|
+
import { Measurement } from "../core/measure.js";
|
|
5
|
+
import { isMeasurable } from "../core/protocol.js";
|
|
6
|
+
export class Constrain {
|
|
7
|
+
renderable;
|
|
8
|
+
width;
|
|
9
|
+
constructor(renderable, width) {
|
|
10
|
+
this.renderable = renderable;
|
|
11
|
+
this.width = width;
|
|
12
|
+
}
|
|
13
|
+
*render(options) {
|
|
14
|
+
// [LAW:dataflow-not-control-flow] Always compute constrainedWidth; undefined means no constraint
|
|
15
|
+
const constrainedWidth = this.width !== undefined
|
|
16
|
+
? Math.min(this.width, options.maxWidth)
|
|
17
|
+
: options.maxWidth;
|
|
18
|
+
const innerOptions = {
|
|
19
|
+
...options,
|
|
20
|
+
maxWidth: constrainedWidth,
|
|
21
|
+
};
|
|
22
|
+
yield* this.renderable.render(innerOptions);
|
|
23
|
+
}
|
|
24
|
+
measure(options) {
|
|
25
|
+
if (isMeasurable(this.renderable)) {
|
|
26
|
+
const measurement = Measurement.get(options, this.renderable);
|
|
27
|
+
const maxWidth = this.width !== undefined
|
|
28
|
+
? Math.min(this.width, options.maxWidth)
|
|
29
|
+
: options.maxWidth;
|
|
30
|
+
return {
|
|
31
|
+
minimum: measurement.minimum,
|
|
32
|
+
maximum: Math.min(measurement.maximum, maxWidth),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { minimum: 1, maximum: options.maxWidth };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one crossing where content a caller hands a renderable — a table cell, a
|
|
3
|
+
* panel's body, title or subtitle, a tree label, a column item, a layout pane,
|
|
4
|
+
* a rule's title — becomes something that renderable can lay out.
|
|
5
|
+
* [LAW:single-enforcer]
|
|
6
|
+
*
|
|
7
|
+
* Each of those sites used to build its own `RichText` straight from the
|
|
8
|
+
* constructor, which does not parse markup, so `[red]Solo[/red]` reached the
|
|
9
|
+
* terminal with its tags intact. Table cells were fixed on their own first; the
|
|
10
|
+
* rest kept the defect until the rule moved here.
|
|
11
|
+
*
|
|
12
|
+
* Markup is parsed because Rich parses it: every one of these positions
|
|
13
|
+
* reaches the wire through Rich's `render_str`, and a console's markup is on by
|
|
14
|
+
* default. Rich's `render_str` also honours `Console(markup=False)` and runs the
|
|
15
|
+
* console's highlighter; this crossing does neither, because the console's
|
|
16
|
+
* settings do not reach it (rich-markup-3sw).
|
|
17
|
+
*
|
|
18
|
+
* `end` is cleared because embedded text is a fragment rather than a printed
|
|
19
|
+
* line; left at the default `"\n"` it draws a trailing blank row. A `RichText`
|
|
20
|
+
* is copied first, since clearing in place would reach back into the caller's
|
|
21
|
+
* object.
|
|
22
|
+
*/
|
|
23
|
+
import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
24
|
+
import { Segment } from "../core/segment.js";
|
|
25
|
+
import type { Style } from "../core/style.js";
|
|
26
|
+
import { RichText } from "../core/text.js";
|
|
27
|
+
/**
|
|
28
|
+
* Caller content as the text an embedding site lays out, `end` cleared. A
|
|
29
|
+
* string is the only kind of content that can contain markup, so it is the only
|
|
30
|
+
* kind parsed; any other value is its `String` form as written, so an object's
|
|
31
|
+
* `[object Object]` is not eaten as a tag.
|
|
32
|
+
*/
|
|
33
|
+
export declare function embeddedText(content: unknown): RichText;
|
|
34
|
+
/**
|
|
35
|
+
* Caller content as something an embedding site can render. A non-text
|
|
36
|
+
* `Renderable` (a nested `Panel` or `Table`) passes through: it carries no
|
|
37
|
+
* `end` to clear and no markup to parse. Everything else is `embeddedText`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function embed(content: unknown): Renderable & Partial<Measurable>;
|
|
40
|
+
/**
|
|
41
|
+
* Caller content set into a line it shares with other drawing — a panel's
|
|
42
|
+
* title or subtitle in its border, a rule's title — as one line of segments:
|
|
43
|
+
* a space either side, its own styles over `base`. The caller cuts it to the
|
|
44
|
+
* room it has with `Segment.adjustLineLength`, which is cell-aware.
|
|
45
|
+
*
|
|
46
|
+
* Content with no text is no label at all, not two spaces: an absent title, an
|
|
47
|
+
* empty string, an empty `RichText` and markup that styles nothing all draw
|
|
48
|
+
* the plain line, as Rich's do (an empty `Text` is falsy there).
|
|
49
|
+
*/
|
|
50
|
+
export declare function inlineLabel(content: unknown, options: RenderOptions, base: Style | undefined): Segment[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one crossing where content a caller hands a renderable — a table cell, a
|
|
3
|
+
* panel's body, title or subtitle, a tree label, a column item, a layout pane,
|
|
4
|
+
* a rule's title — becomes something that renderable can lay out.
|
|
5
|
+
* [LAW:single-enforcer]
|
|
6
|
+
*
|
|
7
|
+
* Each of those sites used to build its own `RichText` straight from the
|
|
8
|
+
* constructor, which does not parse markup, so `[red]Solo[/red]` reached the
|
|
9
|
+
* terminal with its tags intact. Table cells were fixed on their own first; the
|
|
10
|
+
* rest kept the defect until the rule moved here.
|
|
11
|
+
*
|
|
12
|
+
* Markup is parsed because Rich parses it: every one of these positions
|
|
13
|
+
* reaches the wire through Rich's `render_str`, and a console's markup is on by
|
|
14
|
+
* default. Rich's `render_str` also honours `Console(markup=False)` and runs the
|
|
15
|
+
* console's highlighter; this crossing does neither, because the console's
|
|
16
|
+
* settings do not reach it (rich-markup-3sw).
|
|
17
|
+
*
|
|
18
|
+
* `end` is cleared because embedded text is a fragment rather than a printed
|
|
19
|
+
* line; left at the default `"\n"` it draws a trailing blank row. A `RichText`
|
|
20
|
+
* is copied first, since clearing in place would reach back into the caller's
|
|
21
|
+
* object.
|
|
22
|
+
*/
|
|
23
|
+
import { renderMarkup } from "../core/markup.js";
|
|
24
|
+
import { Segment } from "../core/segment.js";
|
|
25
|
+
import { RichText } from "../core/text.js";
|
|
26
|
+
/**
|
|
27
|
+
* Caller content as the text an embedding site lays out, `end` cleared. A
|
|
28
|
+
* string is the only kind of content that can contain markup, so it is the only
|
|
29
|
+
* kind parsed; any other value is its `String` form as written, so an object's
|
|
30
|
+
* `[object Object]` is not eaten as a tag.
|
|
31
|
+
*/
|
|
32
|
+
export function embeddedText(content) {
|
|
33
|
+
const text = content instanceof RichText
|
|
34
|
+
? content.copy()
|
|
35
|
+
: typeof content === "string"
|
|
36
|
+
? renderMarkup(content)
|
|
37
|
+
: new RichText(String(content ?? ""));
|
|
38
|
+
text.end = "";
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Caller content as something an embedding site can render. A non-text
|
|
43
|
+
* `Renderable` (a nested `Panel` or `Table`) passes through: it carries no
|
|
44
|
+
* `end` to clear and no markup to parse. Everything else is `embeddedText`.
|
|
45
|
+
*/
|
|
46
|
+
export function embed(content) {
|
|
47
|
+
if (!(content instanceof RichText) && typeof content === "object" && content !== null && "render" in content) {
|
|
48
|
+
return content;
|
|
49
|
+
}
|
|
50
|
+
return embeddedText(content);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Caller content set into a line it shares with other drawing — a panel's
|
|
54
|
+
* title or subtitle in its border, a rule's title — as one line of segments:
|
|
55
|
+
* a space either side, its own styles over `base`. The caller cuts it to the
|
|
56
|
+
* room it has with `Segment.adjustLineLength`, which is cell-aware.
|
|
57
|
+
*
|
|
58
|
+
* Content with no text is no label at all, not two spaces: an absent title, an
|
|
59
|
+
* empty string, an empty `RichText` and markup that styles nothing all draw
|
|
60
|
+
* the plain line, as Rich's do (an empty `Text` is falsy there).
|
|
61
|
+
*/
|
|
62
|
+
export function inlineLabel(content, options, base) {
|
|
63
|
+
// The label leaves at its natural width, `"ignore"`, because the caller does
|
|
64
|
+
// the cutting; the label's own overflow method would cut it first, and to a
|
|
65
|
+
// width no one measured. `"ignore"` also leaves it unjustified, so its spaces
|
|
66
|
+
// stay where `pad` put them.
|
|
67
|
+
const bare = embeddedText(content);
|
|
68
|
+
const text = bare.plain === "" ? bare : bare.pad(1);
|
|
69
|
+
text.overflow = "ignore";
|
|
70
|
+
return [...Segment.applyStyle(text.render(options), base)];
|
|
71
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlexStrip — wrap-to-width horizontal layout for `StyledRenderable` items.
|
|
3
|
+
*
|
|
4
|
+
* Pack as many items as fit on a line, then break and continue. Each line is
|
|
5
|
+
* an independent sub-strip: optional `Joiner` end-caps fire at every line
|
|
6
|
+
* boundary (not just the strip's first/last position), so a line break looks
|
|
7
|
+
* the same as an endpoint to the joiner. Composes with the Strip + Joiner
|
|
8
|
+
* primitive in `core/strip` — same `Joiner` protocol, no new join semantics.
|
|
9
|
+
*
|
|
10
|
+
* [LAW:dataflow-not-control-flow] The pack walk is the same shape every
|
|
11
|
+
* render: measure each item, render every joiner form (start-cap, end-cap,
|
|
12
|
+
* mid-join) up-front, then sweep items into lines. Line breaks are decided by
|
|
13
|
+
* width data, not by control-flow special cases.
|
|
14
|
+
*
|
|
15
|
+
* [LAW:one-source-of-truth] The Strip primitive owns the join protocol;
|
|
16
|
+
* FlexStrip reuses it verbatim. There is no second concept of "join."
|
|
17
|
+
*/
|
|
18
|
+
import { Segment } from "../core/segment.js";
|
|
19
|
+
import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
20
|
+
import type { Joiner, StyledRenderable } from "../core/strip.js";
|
|
21
|
+
export type FlexAlign = "left" | "center" | "right" | "justify";
|
|
22
|
+
export interface FlexStripOptions<T extends StyledRenderable> {
|
|
23
|
+
/** Joiner used between items within a line, and at line endpoints. */
|
|
24
|
+
joiner?: Joiner<T>;
|
|
25
|
+
/** Cells of horizontal space inserted on each side of an inter-item joiner (default 0). */
|
|
26
|
+
gap?: number;
|
|
27
|
+
/** Line alignment when packed items don't fill the available width (default "left"). */
|
|
28
|
+
align?: FlexAlign;
|
|
29
|
+
}
|
|
30
|
+
export declare class FlexStrip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable {
|
|
31
|
+
readonly items: readonly T[];
|
|
32
|
+
readonly joiner: Joiner<T> | undefined;
|
|
33
|
+
readonly gap: number;
|
|
34
|
+
readonly align: FlexAlign;
|
|
35
|
+
constructor(items: readonly T[], options?: FlexStripOptions<T>);
|
|
36
|
+
render(options: RenderOptions): Iterable<Segment>;
|
|
37
|
+
measure(options: RenderOptions): {
|
|
38
|
+
minimum: number;
|
|
39
|
+
maximum: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlexStrip — wrap-to-width horizontal layout for `StyledRenderable` items.
|
|
3
|
+
*
|
|
4
|
+
* Pack as many items as fit on a line, then break and continue. Each line is
|
|
5
|
+
* an independent sub-strip: optional `Joiner` end-caps fire at every line
|
|
6
|
+
* boundary (not just the strip's first/last position), so a line break looks
|
|
7
|
+
* the same as an endpoint to the joiner. Composes with the Strip + Joiner
|
|
8
|
+
* primitive in `core/strip` — same `Joiner` protocol, no new join semantics.
|
|
9
|
+
*
|
|
10
|
+
* [LAW:dataflow-not-control-flow] The pack walk is the same shape every
|
|
11
|
+
* render: measure each item, render every joiner form (start-cap, end-cap,
|
|
12
|
+
* mid-join) up-front, then sweep items into lines. Line breaks are decided by
|
|
13
|
+
* width data, not by control-flow special cases.
|
|
14
|
+
*
|
|
15
|
+
* [LAW:one-source-of-truth] The Strip primitive owns the join protocol;
|
|
16
|
+
* FlexStrip reuses it verbatim. There is no second concept of "join."
|
|
17
|
+
*/
|
|
18
|
+
import { Segment } from "../core/segment.js";
|
|
19
|
+
function renderToBlock(r, options) {
|
|
20
|
+
const segments = [...r.render(options)];
|
|
21
|
+
let width = 0;
|
|
22
|
+
for (const s of segments)
|
|
23
|
+
width += s.cellLength;
|
|
24
|
+
return { segments, width };
|
|
25
|
+
}
|
|
26
|
+
const EMPTY_BLOCK = { segments: [], width: 0 };
|
|
27
|
+
const PAD_LEFT_BY_ALIGN = {
|
|
28
|
+
left: () => 0,
|
|
29
|
+
justify: () => 0,
|
|
30
|
+
center: (spare) => Math.floor(spare / 2),
|
|
31
|
+
right: (spare) => spare,
|
|
32
|
+
};
|
|
33
|
+
function computePadLeft(align, spare) {
|
|
34
|
+
return PAD_LEFT_BY_ALIGN[align](spare);
|
|
35
|
+
}
|
|
36
|
+
// Return per-slot fill widths (length = itemCount - 1, possibly empty).
|
|
37
|
+
// Non-justify cases produce all zeros — the emit loop runs the same way.
|
|
38
|
+
function computeSlotFills(align, isFinalLine, spare, itemCount) {
|
|
39
|
+
const slots = Math.max(0, itemCount - 1);
|
|
40
|
+
const distribute = align === "justify" && !isFinalLine && slots > 0 && spare > 0;
|
|
41
|
+
const totalFill = distribute ? spare : 0;
|
|
42
|
+
const base = slots > 0 ? Math.floor(totalFill / slots) : 0;
|
|
43
|
+
const extra = totalFill - base * slots;
|
|
44
|
+
return Array.from({ length: slots }, (_, k) => base + (k < extra ? 1 : 0));
|
|
45
|
+
}
|
|
46
|
+
export class FlexStrip {
|
|
47
|
+
items;
|
|
48
|
+
joiner;
|
|
49
|
+
gap;
|
|
50
|
+
align;
|
|
51
|
+
constructor(items, options) {
|
|
52
|
+
this.items = items;
|
|
53
|
+
this.joiner = options?.joiner;
|
|
54
|
+
this.gap = options?.gap ?? 0;
|
|
55
|
+
this.align = options?.align ?? "left";
|
|
56
|
+
}
|
|
57
|
+
*render(options) {
|
|
58
|
+
const items = this.items;
|
|
59
|
+
if (items.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
const maxWidth = Math.max(1, options.maxWidth);
|
|
62
|
+
const gap = this.gap;
|
|
63
|
+
const joiner = this.joiner;
|
|
64
|
+
// Pre-render every item and every joiner form we might need. Doing this
|
|
65
|
+
// up-front turns packing into a pure data computation over widths.
|
|
66
|
+
const itemBlocks = items.map((it) => renderToBlock(it, options));
|
|
67
|
+
const startCap = (i) => joiner ? renderToBlock(joiner.join(null, items[i]), options) : EMPTY_BLOCK;
|
|
68
|
+
const endCap = (i) => joiner ? renderToBlock(joiner.join(items[i], null), options) : EMPTY_BLOCK;
|
|
69
|
+
const midJoin = (i, j) => joiner ? renderToBlock(joiner.join(items[i], items[j]), options) : EMPTY_BLOCK;
|
|
70
|
+
const lines = [];
|
|
71
|
+
let line = null;
|
|
72
|
+
const gapBlock = gap > 0
|
|
73
|
+
? { segments: [new Segment(" ".repeat(gap))], width: gap }
|
|
74
|
+
: EMPTY_BLOCK;
|
|
75
|
+
for (let i = 0; i < items.length; i++) {
|
|
76
|
+
const item = itemBlocks[i];
|
|
77
|
+
if (line === null) {
|
|
78
|
+
const sc = startCap(i);
|
|
79
|
+
const ec = endCap(i);
|
|
80
|
+
const blocks = sc.width > 0 ? [sc, item] : [item];
|
|
81
|
+
const itemBlockIndices = [blocks.length - 1];
|
|
82
|
+
line = { blocks, width: sc.width + item.width, lastIndex: i, itemBlockIndices };
|
|
83
|
+
// Tentatively reserve space for the line's eventual end-cap by
|
|
84
|
+
// tracking it separately below — `line.width` is the open width
|
|
85
|
+
// (without end-cap). We test fit using width + endCap on each step.
|
|
86
|
+
// Always start a new line with at least the first item (overflow ok
|
|
87
|
+
// per ticket: graceful fallback when an item is wider than terminal).
|
|
88
|
+
// Stash end-cap for the close step.
|
|
89
|
+
line.endCap = ec;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const prev = line.lastIndex;
|
|
93
|
+
const mid = midJoin(prev, i);
|
|
94
|
+
const newEndCap = endCap(i);
|
|
95
|
+
const addCost = (gap > 0 ? gap : 0) + mid.width + (gap > 0 ? gap : 0) + item.width;
|
|
96
|
+
const closedWidthIfAdded = line.width + addCost + newEndCap.width;
|
|
97
|
+
if (closedWidthIfAdded <= maxWidth) {
|
|
98
|
+
if (gap > 0)
|
|
99
|
+
line.blocks.push(gapBlock);
|
|
100
|
+
if (mid.width > 0)
|
|
101
|
+
line.blocks.push(mid);
|
|
102
|
+
if (gap > 0)
|
|
103
|
+
line.blocks.push(gapBlock);
|
|
104
|
+
line.blocks.push(item);
|
|
105
|
+
line.itemBlockIndices.push(line.blocks.length - 1);
|
|
106
|
+
line.width += addCost;
|
|
107
|
+
line.lastIndex = i;
|
|
108
|
+
line.endCap = newEndCap;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Close current line and start a new one with this item.
|
|
112
|
+
const closing = line.endCap;
|
|
113
|
+
if (closing.width > 0)
|
|
114
|
+
line.blocks.push(closing);
|
|
115
|
+
line.width += closing.width;
|
|
116
|
+
lines.push(line);
|
|
117
|
+
const sc = startCap(i);
|
|
118
|
+
const ec = endCap(i);
|
|
119
|
+
const blocks = sc.width > 0 ? [sc, item] : [item];
|
|
120
|
+
const itemBlockIndices = [blocks.length - 1];
|
|
121
|
+
line = { blocks, width: sc.width + item.width, lastIndex: i, itemBlockIndices };
|
|
122
|
+
line.endCap = ec;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Close final line. `line` is non-null here: the loop runs at least once
|
|
126
|
+
// (items.length > 0 was handled above).
|
|
127
|
+
const finalLine = line;
|
|
128
|
+
{
|
|
129
|
+
const closing = finalLine.endCap;
|
|
130
|
+
if (closing.width > 0)
|
|
131
|
+
finalLine.blocks.push(closing);
|
|
132
|
+
finalLine.width += closing.width;
|
|
133
|
+
lines.push(finalLine);
|
|
134
|
+
}
|
|
135
|
+
// [LAW:dataflow-not-control-flow] Single emission path for every line:
|
|
136
|
+
// emit pad, walk every block, optionally emit a per-slot fill before each
|
|
137
|
+
// non-first item. Variability lives in two precomputed value sequences
|
|
138
|
+
// (`padLeft` and `slotFills`); the emit loop has no per-mode branch.
|
|
139
|
+
//
|
|
140
|
+
// - `padLeft` pushes the line right by 0 (left/justify), spare/2 (center),
|
|
141
|
+
// or full spare (right). Justify-on-final-line follows the left rule.
|
|
142
|
+
// - `slotFills[k]` is the cells of fill emitted before the (k+1)-th item.
|
|
143
|
+
// Always 0 unless align=justify on a non-final line with ≥2 items.
|
|
144
|
+
//
|
|
145
|
+
// Putting the variability in data means the emit loop is the same shape
|
|
146
|
+
// every render, and "what happens between two items" can never accidentally
|
|
147
|
+
// become "what happens between joiner halves" — the data decides.
|
|
148
|
+
const align = this.align;
|
|
149
|
+
for (let li = 0; li < lines.length; li++) {
|
|
150
|
+
const ln = lines[li];
|
|
151
|
+
const isLast = li === lines.length - 1;
|
|
152
|
+
const spare = Math.max(0, maxWidth - ln.width);
|
|
153
|
+
const padLeft = computePadLeft(align, spare);
|
|
154
|
+
const slotFills = computeSlotFills(align, isLast, spare, ln.itemBlockIndices.length);
|
|
155
|
+
const itemRankOf = new Map();
|
|
156
|
+
ln.itemBlockIndices.forEach((blockIdx, rank) => itemRankOf.set(blockIdx, rank));
|
|
157
|
+
if (padLeft > 0)
|
|
158
|
+
yield new Segment(" ".repeat(padLeft));
|
|
159
|
+
for (let bi = 0; bi < ln.blocks.length; bi++) {
|
|
160
|
+
const rank = itemRankOf.get(bi);
|
|
161
|
+
if (rank !== undefined && rank > 0) {
|
|
162
|
+
const fill = slotFills[rank - 1];
|
|
163
|
+
if (fill > 0)
|
|
164
|
+
yield new Segment(" ".repeat(fill));
|
|
165
|
+
}
|
|
166
|
+
yield* ln.blocks[bi].segments;
|
|
167
|
+
}
|
|
168
|
+
yield Segment.line();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
measure(options) {
|
|
172
|
+
const items = this.items;
|
|
173
|
+
if (items.length === 0)
|
|
174
|
+
return { minimum: 0, maximum: 0 };
|
|
175
|
+
let minimum = 0;
|
|
176
|
+
let maximum = 0;
|
|
177
|
+
for (let i = 0; i < items.length; i++) {
|
|
178
|
+
const itemWidth = renderToBlock(items[i], options).width;
|
|
179
|
+
const sc = this.joiner
|
|
180
|
+
? renderToBlock(this.joiner.join(null, items[i]), options).width
|
|
181
|
+
: 0;
|
|
182
|
+
const ec = this.joiner
|
|
183
|
+
? renderToBlock(this.joiner.join(items[i], null), options).width
|
|
184
|
+
: 0;
|
|
185
|
+
minimum = Math.max(minimum, itemWidth + sc + ec);
|
|
186
|
+
maximum += itemWidth;
|
|
187
|
+
if (i > 0) {
|
|
188
|
+
maximum += this.gap * 2;
|
|
189
|
+
if (this.joiner) {
|
|
190
|
+
maximum += renderToBlock(this.joiner.join(items[i - 1], items[i]), options).width;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (this.joiner) {
|
|
195
|
+
maximum += renderToBlock(this.joiner.join(null, items[0]), options).width;
|
|
196
|
+
maximum += renderToBlock(this.joiner.join(items[items.length - 1], null), options).width;
|
|
197
|
+
}
|
|
198
|
+
return { minimum, maximum };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group — renders multiple renderables in sequence.
|
|
3
|
+
* No visual chrome — purely a container.
|
|
4
|
+
*/
|
|
5
|
+
import { Segment } from "../core/segment.js";
|
|
6
|
+
import type { Renderable, RenderOptions } from "../core/protocol.js";
|
|
7
|
+
export declare class Group implements Renderable {
|
|
8
|
+
readonly renderables: Renderable[];
|
|
9
|
+
constructor(...renderables: Renderable[]);
|
|
10
|
+
render(options: RenderOptions): Iterable<Segment>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group — renders multiple renderables in sequence.
|
|
3
|
+
* No visual chrome — purely a container.
|
|
4
|
+
*/
|
|
5
|
+
import { stackedHeight } from "../core/protocol.js";
|
|
6
|
+
export class Group {
|
|
7
|
+
renderables;
|
|
8
|
+
constructor(...renderables) {
|
|
9
|
+
this.renderables = renderables;
|
|
10
|
+
}
|
|
11
|
+
*render(options) {
|
|
12
|
+
const memberOptions = { ...options, height: stackedHeight(options.height) };
|
|
13
|
+
for (const renderable of this.renderables) {
|
|
14
|
+
yield* renderable.render(memberOptions);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|