@promptctl/rich-js 0.23.0 → 0.24.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/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/animation.d.ts +78 -0
- package/dist/core/animation.js +107 -0
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +17 -11
- package/dist/core/cells.js +100 -25
- package/dist/core/clock.d.ts +9 -1
- package/dist/core/clock.js +22 -2
- package/dist/core/color.d.ts +10 -0
- package/dist/core/color.js +36 -1
- package/dist/core/console.js +5 -5
- package/dist/core/easing.d.ts +138 -7
- package/dist/core/easing.js +355 -17
- package/dist/core/json.js +2 -1
- package/dist/core/measure.js +2 -2
- package/dist/core/place.js +2 -1
- package/dist/core/pretty.js +2 -2
- package/dist/core/protocol.d.ts +73 -19
- package/dist/core/protocol.js +56 -15
- package/dist/core/render.js +2 -1
- package/dist/core/segment.d.ts +28 -1
- package/dist/core/segment.js +60 -44
- package/dist/core/strip.d.ts +107 -29
- package/dist/core/strip.js +242 -131
- package/dist/core/style.d.ts +18 -1
- package/dist/core/style.js +49 -9
- package/dist/core/text.js +26 -15
- package/dist/core/timeline.d.ts +84 -0
- package/dist/core/timeline.js +144 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +69 -0
- package/dist/host/app.js +4 -6
- package/dist/host/index.d.ts +1 -1
- package/dist/host/input.d.ts +37 -13
- package/dist/host/input.js +146 -56
- package/dist/host/terminal-host.d.ts +16 -0
- package/dist/host/terminal-host.js +1 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.js +15 -5
- package/dist/node/terminal-host.d.ts +8 -1
- package/dist/node/terminal-host.js +2 -0
- package/dist/renderables/align.d.ts +3 -2
- package/dist/renderables/align.js +3 -0
- package/dist/renderables/columns.d.ts +3 -2
- package/dist/renderables/columns.js +7 -2
- package/dist/renderables/constrain.d.ts +3 -2
- package/dist/renderables/constrain.js +5 -1
- package/dist/renderables/effect.d.ts +83 -30
- package/dist/renderables/effect.js +155 -57
- package/dist/renderables/effects.d.ts +88 -39
- package/dist/renderables/effects.js +444 -105
- package/dist/renderables/embed.d.ts +10 -0
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.d.ts +3 -2
- package/dist/renderables/flexStrip.js +6 -1
- package/dist/renderables/frames.d.ts +116 -0
- package/dist/renderables/frames.js +133 -0
- package/dist/renderables/group.d.ts +3 -2
- package/dist/renderables/group.js +5 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +3 -2
- package/dist/renderables/layout.js +15 -2
- package/dist/renderables/list-view.d.ts +3 -2
- package/dist/renderables/list-view.js +12 -5
- package/dist/renderables/live.d.ts +23 -7
- package/dist/renderables/live.js +69 -35
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1421 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +7 -6
- package/dist/renderables/markdown.js +363 -499
- package/dist/renderables/padding.d.ts +8 -20
- package/dist/renderables/padding.js +32 -50
- package/dist/renderables/panel.d.ts +26 -26
- package/dist/renderables/panel.js +67 -103
- package/dist/renderables/progress.d.ts +5 -3
- package/dist/renderables/progress.js +14 -3
- package/dist/renderables/progressBar.js +4 -4
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +24 -0
- package/dist/renderables/spinner.d.ts +73 -27
- package/dist/renderables/spinner.js +110 -51
- package/dist/renderables/status.d.ts +8 -3
- package/dist/renderables/status.js +8 -5
- package/dist/renderables/syntax.js +2 -1
- package/dist/renderables/table.d.ts +19 -11
- package/dist/renderables/table.js +140 -156
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +6 -5
- package/dist/renderables/tree.js +11 -7
- package/dist/renderables/viewport.d.ts +13 -5
- package/dist/renderables/viewport.js +41 -20
- package/dist/template-bindings/animation-funcs.d.ts +37 -0
- package/dist/template-bindings/animation-funcs.js +83 -0
- package/dist/template-bindings/color-funcs.d.ts +13 -0
- package/dist/template-bindings/color-funcs.js +1 -1
- package/dist/template-bindings/helpers.d.ts +6 -0
- package/dist/template-bindings/helpers.js +12 -3
- package/dist/template-bindings/index.d.ts +6 -2
- package/dist/template-bindings/index.js +8 -3
- package/dist/themes/ramp.d.ts +18 -3
- package/dist/themes/ramp.js +23 -2
- package/dist/widgets/focus-manager.d.ts +21 -14
- package/dist/widgets/focus-manager.js +28 -29
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/static-item.d.ts +4 -3
- package/dist/widgets/static-item.js +9 -7
- package/dist/widgets/text-input.d.ts +28 -18
- package/dist/widgets/text-input.js +144 -101
- package/dist/widgets/widget-app.d.ts +19 -3
- package/dist/widgets/widget-app.js +39 -21
- package/dist/widgets/widget-base.js +0 -3
- package/package.json +2 -5
package/dist/core/easing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Easing and phase: the one vocabulary for "how far along" —
|
|
3
|
-
*
|
|
2
|
+
* Easing and phase: the one vocabulary for "how far along" — a time in
|
|
3
|
+
* seconds becomes progress in [0, 1], and an ease reshapes that progress,
|
|
4
|
+
* usually but not always within [0, 1].
|
|
4
5
|
*
|
|
5
6
|
* Two kinds of curve, composed as `ease(phase(t))`:
|
|
6
7
|
*
|
|
@@ -13,15 +14,26 @@
|
|
|
13
14
|
* built-ins are CSS's easing functions (CSS Easing Functions Level 1):
|
|
14
15
|
* `linear`, `cubic-bezier()` with its named curves, and `steps()` with its
|
|
15
16
|
* jump terms, plus `sine` for pulses — a half-cosine, so a `pingPong`
|
|
16
|
-
* through it brightens and dims with no corner at either end
|
|
17
|
+
* through it brightens and dims with no corner at either end — and the
|
|
18
|
+
* physics-flavoured curves a cursor snapping, a counter settling or a
|
|
19
|
+
* panel sliding in want: `spring`, a damped harmonic oscillator released
|
|
20
|
+
* toward its rest, which is the one of them with parameters and so the one
|
|
21
|
+
* that also says how long it takes; `bounce`, `back-in`, `back-out` and
|
|
22
|
+
* `elastic`, Robert Penner's curves at the constants every animation
|
|
23
|
+
* library since has shipped. An ease that leaves [0, 1] — a spring or a
|
|
24
|
+
* back curve overshooting its rest — is still an ease: a `ColorRamp` holds
|
|
25
|
+
* its end colours where it does, and an effect's `Curve` saturates at its
|
|
26
|
+
* swing, so the overshoot is read by what can show it (a position, a
|
|
27
|
+
* count) and clipped by what cannot.
|
|
17
28
|
*
|
|
18
29
|
* [LAW:no-ambient-temporal-coupling] Nothing here reads a clock. `t` is an
|
|
19
30
|
* argument; whoever draws frames owns the clock and samples these at
|
|
20
31
|
* whatever rate it likes, so the same `t` always gives the same progress.
|
|
21
32
|
*
|
|
22
33
|
* [LAW:one-type-per-behavior] An ease is a plain function value. CSS's named
|
|
23
|
-
* curves are four instances of `cubicBezier`, not four implementations,
|
|
24
|
-
* `ColorRamp` and every effect take
|
|
34
|
+
* curves are four instances of `cubicBezier`, not four implementations, the
|
|
35
|
+
* spring in `EASES` is one `spring()`, and `ColorRamp` and every effect take
|
|
36
|
+
* the same `Ease`.
|
|
25
37
|
*
|
|
26
38
|
* Tier 0 of `src/core/`: imports nothing, so `themes/` and every later
|
|
27
39
|
* `core/` module reach it downhill. [LAW:one-way-deps]
|
|
@@ -79,7 +91,7 @@ export function cubicBezier(x1, y1, x2, y2) {
|
|
|
79
91
|
}
|
|
80
92
|
return s;
|
|
81
93
|
};
|
|
82
|
-
return (
|
|
94
|
+
return settled((p) => yAt(solve(p)));
|
|
83
95
|
}
|
|
84
96
|
/**
|
|
85
97
|
* CSS `steps(n, position)`: progress held flat in `n` equal intervals, jumping
|
|
@@ -103,22 +115,183 @@ export function steps(n, position = "jump-end") {
|
|
|
103
115
|
"jump-none": n - 1,
|
|
104
116
|
}[position];
|
|
105
117
|
const lift = jumpsAtStart ? 1 : 0;
|
|
106
|
-
return (
|
|
107
|
-
const p = Math.min(1, Math.max(0, x));
|
|
108
|
-
return Math.min(jumps, Math.floor(p * n) + lift) / jumps;
|
|
109
|
-
};
|
|
118
|
+
return settled((p) => Math.min(jumps, Math.floor(p * n) + lift) / jumps);
|
|
110
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* The curve `shape` over [0, 1) as an ease that has arrived: its argument
|
|
122
|
+
* held to [0, 1], and exactly 1 at 1. Every built-in ease is one, so the two
|
|
123
|
+
* halves of the `Ease` contract — an argument in [0, 1], the end landed on —
|
|
124
|
+
* are enforced here and nowhere else. [LAW:single-enforcer] The end matters
|
|
125
|
+
* because a curve is 1 at its end only in the limit or up to a rounding — a
|
|
126
|
+
* spring settles to within `SPRING_REST`, the elastic's ringing never quite
|
|
127
|
+
* stops, a back curve's cubic or a bezier's Newton step lands an ulp off —
|
|
128
|
+
* and a count or a width eased by it must land on its target, not a
|
|
129
|
+
* thousandth or an ulp short.
|
|
130
|
+
*/
|
|
131
|
+
const settled = (shape) => (x) => {
|
|
132
|
+
const p = Math.min(1, Math.max(0, x));
|
|
133
|
+
return p === 1 ? 1 : shape(p);
|
|
134
|
+
};
|
|
111
135
|
/** The identity ease: progress unchanged. */
|
|
112
|
-
const linear = (
|
|
136
|
+
const linear = settled((p) => p);
|
|
113
137
|
/** CSS `step-end`: 0 until the very end. */
|
|
114
138
|
const stepEnd = steps(1, "jump-end");
|
|
115
139
|
/** A half-cosine, flat at both ends: the curve a breathing pulse wants. */
|
|
116
|
-
const sine = (
|
|
140
|
+
const sine = settled((p) => (1 - Math.cos(Math.PI * p)) / 2);
|
|
141
|
+
/**
|
|
142
|
+
* How close to its rest a spring must come to count as settled: one part in a
|
|
143
|
+
* thousand of the way it moves, under a colour channel's step (1/255) and
|
|
144
|
+
* under any cell, so what rounds the spring's value to show it draws the rest
|
|
145
|
+
* from there on.
|
|
146
|
+
*/
|
|
147
|
+
export const SPRING_REST = 0.001;
|
|
148
|
+
const DEFAULT_SPRING = { stiffness: 100, damping: 10, mass: 1 };
|
|
149
|
+
/**
|
|
150
|
+
* A damped harmonic oscillator, `m·x″ = −k·x − c·x′`, released at rest one
|
|
151
|
+
* unit from its equilibrium: the spring every animation library means. Its
|
|
152
|
+
* progress at `s` seconds is `1 − displacement(s)`, the closed-form solution
|
|
153
|
+
* of that equation in the regime its damping ratio `ζ = c / 2√(k·m)` puts it
|
|
154
|
+
* in — ringing past its rest for `ζ < 1`, creeping up to it for `ζ ≥ 1` — and
|
|
155
|
+
* its `seconds` is its settling time: the first moment after which the
|
|
156
|
+
* displacement stays within `SPRING_REST`, found once here by `crossing` on
|
|
157
|
+
* the bracket each regime knows its last crossing lies in, so a sample costs
|
|
158
|
+
* an exponential and a cosine whatever `x` is. [LAW:no-ambient-temporal-coupling]
|
|
159
|
+
*
|
|
160
|
+
* The ease is exactly 1 at `x = 1` (`settled`): settled means at rest. The
|
|
161
|
+
* `seconds` are the spring's to say, not the caller's: how long it rings is
|
|
162
|
+
* a fact about its physics, which is why this is the one ease that comes as
|
|
163
|
+
* a `Timing` — released at rest a unit from where it is going, at 1 when it
|
|
164
|
+
* has settled there.
|
|
165
|
+
*/
|
|
166
|
+
export function spring(options = {}) {
|
|
167
|
+
const { stiffness, damping, mass } = { ...DEFAULT_SPRING, ...options };
|
|
168
|
+
for (const [name, v] of [["stiffness", stiffness], ["damping", damping], ["mass", mass]])
|
|
169
|
+
positive(`spring ${name}`, v);
|
|
170
|
+
const w0 = Math.sqrt(stiffness / mass);
|
|
171
|
+
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
|
|
172
|
+
const decay = zeta * w0;
|
|
173
|
+
const { displacement, seconds } = zeta < 1 ? ringing(w0, zeta, decay) : creeping(w0, zeta, decay);
|
|
174
|
+
return { seconds, ease: settled((p) => 1 - displacement(p * seconds)) };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Where `|displacement|` falls through `SPRING_REST` between `lo` and `hi`,
|
|
178
|
+
* by bisection: the caller brackets the last such crossing, the displacement
|
|
179
|
+
* outside rest at `lo`, inside it at `hi` and falling in size between, and
|
|
180
|
+
* gets the settling time back to the bits 64 halvings leave.
|
|
181
|
+
* [LAW:single-enforcer] The one place the rest threshold is read against a
|
|
182
|
+
* motion.
|
|
183
|
+
*/
|
|
184
|
+
function crossing(displacement, lo, hi) {
|
|
185
|
+
for (let i = 0; i < 64; i++) {
|
|
186
|
+
const mid = (lo + hi) / 2;
|
|
187
|
+
if (Math.abs(displacement(mid)) > SPRING_REST)
|
|
188
|
+
lo = mid;
|
|
189
|
+
else
|
|
190
|
+
hi = mid;
|
|
191
|
+
}
|
|
192
|
+
return hi;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Underdamped: `e^(−ζω₀s)·(cos ω_d s + (ζω₀/ω_d) sin ω_d s)` at the damped
|
|
196
|
+
* frequency `ω_d = ω₀√(1 − ζ²)`. Released at rest, its velocity is
|
|
197
|
+
* `−(ω₀²/ω_d)·e^(−ζω₀s)·sin ω_d s`, so its extrema sit at `s_k = kπ/ω_d`,
|
|
198
|
+
* where `|displacement| = e^(−ζω₀·s_k)`, each the same fraction of the last.
|
|
199
|
+
* The first extremum inside rest is the `K` with `e^(−ζω₀·s_K) ≤ SPRING_REST`;
|
|
200
|
+
* the spring never leaves rest after it, and before it the displacement
|
|
201
|
+
* falls in size from the extremum at `s_(K−1)` to its zero at
|
|
202
|
+
* `(Kπ − atan(ω_d/ζω₀)) / ω_d`, so the settling time is the crossing in that
|
|
203
|
+
* bracket. The decay envelope control theory reads settling time off would
|
|
204
|
+
* put it later — by up to half a swing, and without bound as `ζ → 1` — so
|
|
205
|
+
* the true crossing is found instead, and `seconds` is continuous in `ζ`.
|
|
206
|
+
*/
|
|
207
|
+
function ringing(w0, zeta, decay) {
|
|
208
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
209
|
+
const ratio = decay / wd;
|
|
210
|
+
const displacement = (s) => Math.exp(-decay * s) * (Math.cos(wd * s) + ratio * Math.sin(wd * s));
|
|
211
|
+
const K = Math.ceil(Math.log(1 / SPRING_REST) / ((decay * Math.PI) / wd));
|
|
212
|
+
const extremum = ((K - 1) * Math.PI) / wd;
|
|
213
|
+
const zero = (K * Math.PI - Math.atan(1 / ratio)) / wd;
|
|
214
|
+
return { displacement, seconds: crossing(displacement, extremum, zero) };
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Critically damped (`ζ = 1`): `e^(−ω₀s)·(1 + ω₀s)`. Overdamped (`ζ > 1`):
|
|
218
|
+
* two decaying exponentials at the rates `ζω₀ ∓ ω₀√(ζ² − 1)`, written as such
|
|
219
|
+
* rather than as `cosh + sinh` so neither grows before the other cancels it.
|
|
220
|
+
* Both only fall, so the settling time is the one crossing of `SPRING_REST`,
|
|
221
|
+
* bracketed between zero and a time the slower exponential alone puts it under.
|
|
222
|
+
*/
|
|
223
|
+
function creeping(w0, zeta, decay) {
|
|
224
|
+
const { displacement, far } = zeta === 1 ? critical(w0) : overdamped(w0, zeta, decay);
|
|
225
|
+
return { displacement, seconds: crossing(displacement, 0, far) };
|
|
226
|
+
}
|
|
227
|
+
/** `(1 + u)·e^(−u)` in `u = ω₀s`, which is under 10⁻³ from u ≈ 9.24. */
|
|
228
|
+
function critical(w0) {
|
|
229
|
+
return { displacement: (s) => Math.exp(-w0 * s) * (1 + w0 * s), far: 10 / w0 };
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The slower exponential alone bounds the sum, since the faster one's weight
|
|
233
|
+
* `1 − ratio` is negative. Its rate `ζω₀ − ω₀√(ζ² − 1)` is written as
|
|
234
|
+
* `ω₀ / (ζ + √(ζ² − 1))`, the same number without the subtraction, which for
|
|
235
|
+
* a large `ζ` cancels to zero and would make `far` infinite.
|
|
236
|
+
*/
|
|
237
|
+
function overdamped(w0, zeta, decay) {
|
|
238
|
+
const root = Math.sqrt(zeta * zeta - 1);
|
|
239
|
+
const wh = w0 * root;
|
|
240
|
+
const slow = w0 / (zeta + root);
|
|
241
|
+
const fast = decay + wh;
|
|
242
|
+
const ratio = decay / wh;
|
|
243
|
+
return {
|
|
244
|
+
displacement: (s) => ((1 + ratio) * Math.exp(-slow * s) + (1 - ratio) * Math.exp(-fast * s)) / 2,
|
|
245
|
+
far: Math.log((1 + ratio) / (2 * SPRING_REST)) / slow,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Robert Penner's bounce (his `easeOutBounce`): a ball dropped onto its rest,
|
|
250
|
+
* four parabolic arcs each a quarter the height of the last, the constants
|
|
251
|
+
* his and every library's since. Each arc is the same parabola,
|
|
252
|
+
* `7.5625·(x − centre)² + floor`, set over its own span of `x`; the spans
|
|
253
|
+
* are in elevenths of 2.75, where the first arc meets the ground.
|
|
254
|
+
*/
|
|
255
|
+
const BOUNCE_ARCS = [
|
|
256
|
+
{ until: 1 / 2.75, centre: 0, floor: 0 },
|
|
257
|
+
{ until: 2 / 2.75, centre: 1.5 / 2.75, floor: 0.75 },
|
|
258
|
+
{ until: 2.5 / 2.75, centre: 2.25 / 2.75, floor: 0.9375 },
|
|
259
|
+
{ until: Number.POSITIVE_INFINITY, centre: 2.625 / 2.75, floor: 0.984375 },
|
|
260
|
+
];
|
|
261
|
+
const bounce = settled((p) => {
|
|
262
|
+
// The last arc's `until` is unbounded, so an arc is always found.
|
|
263
|
+
const arc = BOUNCE_ARCS.find(({ until }) => p < until);
|
|
264
|
+
return 7.5625 * (p - arc.centre) ** 2 + arc.floor;
|
|
265
|
+
});
|
|
266
|
+
/**
|
|
267
|
+
* Penner's back curves: a cubic that pulls back past its start before going
|
|
268
|
+
* (`back-in`), or overshoots its end before settling (`back-out`), by his
|
|
269
|
+
* overshoot constant 1.70158 — about a tenth of the way. His `(c + 1)·x³ −
|
|
270
|
+
* c·x²` is written as `x³ + c·x²·(x − 1)`, the same cubic with the overshoot
|
|
271
|
+
* term a product that is exactly zero at both ends, so the curve starts on 0
|
|
272
|
+
* and lands on 1 to the bit.
|
|
273
|
+
*/
|
|
274
|
+
const BACK_OVERSHOOT = 1.70158;
|
|
275
|
+
const backIn = settled((p) => p ** 3 + BACK_OVERSHOOT * p ** 2 * (p - 1));
|
|
276
|
+
const backOut = settled((p) => {
|
|
277
|
+
const q = p - 1;
|
|
278
|
+
return 1 + q ** 3 + BACK_OVERSHOOT * q ** 2 * (q + 1);
|
|
279
|
+
});
|
|
280
|
+
/**
|
|
281
|
+
* Penner's elastic (his `easeOutElastic`): overshoots its end and rings back
|
|
282
|
+
* through it, with a period of 0.3 of the way under an amplitude of
|
|
283
|
+
* `2^(−10x)`, so each swing is `2^(−1.5)`, a little over a third, of the
|
|
284
|
+
* last — `2^(−10x)·sin((10x − 0.75)·2π/3) + 1`.
|
|
285
|
+
*/
|
|
286
|
+
const ELASTIC_PERIOD = (2 * Math.PI) / 3;
|
|
287
|
+
const elastic = settled((p) => 2 ** (-10 * p) * Math.sin((10 * p - 0.75) * ELASTIC_PERIOD) + 1);
|
|
117
288
|
/**
|
|
118
289
|
* Every built-in ease by the name a template spells it with — CSS's keyword
|
|
119
290
|
* names, plus `sine`, plus `step`: the spelling `ColorRamp` used before this
|
|
120
291
|
* vocabulary existed, kept so every template written against it renders the
|
|
121
|
-
* same. It is CSS's `step-end`, the same function value.
|
|
292
|
+
* same. It is CSS's `step-end`, the same function value. Then the
|
|
293
|
+
* physics-flavoured curves: `spring` is the default `spring()`, whose
|
|
294
|
+
* `seconds` are `DEFAULT_SPRING`'s; the rest are Penner's at his constants.
|
|
122
295
|
*/
|
|
123
296
|
export const EASES = {
|
|
124
297
|
linear,
|
|
@@ -130,6 +303,11 @@ export const EASES = {
|
|
|
130
303
|
"step-end": stepEnd,
|
|
131
304
|
step: stepEnd,
|
|
132
305
|
sine,
|
|
306
|
+
spring: spring().ease,
|
|
307
|
+
bounce,
|
|
308
|
+
"back-in": backIn,
|
|
309
|
+
"back-out": backOut,
|
|
310
|
+
elastic,
|
|
133
311
|
};
|
|
134
312
|
/**
|
|
135
313
|
* The gate a spelled ease crosses. [LAW:parse-dont-validate] — a name in, the
|
|
@@ -143,10 +321,170 @@ export function parseEase(name) {
|
|
|
143
321
|
}
|
|
144
322
|
return EASES[name];
|
|
145
323
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
324
|
+
/** `v` is a positive, finite number, or this throws naming `what`; `unit` is the number's noun, as in `" of seconds"`. [LAW:single-enforcer] */
|
|
325
|
+
function positive(what, v, unit = "") {
|
|
326
|
+
if (!(Number.isFinite(v) && v > 0))
|
|
327
|
+
throw new RangeError(`${what} must be a positive number${unit}, got ${v}`);
|
|
328
|
+
}
|
|
329
|
+
/** Throws, naming `what`, unless `seconds` is a positive, finite number of seconds. */
|
|
330
|
+
export function positiveSeconds(what, seconds) {
|
|
331
|
+
positive(what, seconds, " of seconds");
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The seconds into the current pass of a `period` at `t`: `t` taken modulo
|
|
335
|
+
* the period, into [0, period), before zero too. [LAW:single-enforcer] The
|
|
336
|
+
* one wrap `Phase.loop` and `beat` both read time through, so a `t` that is
|
|
337
|
+
* not a finite number of seconds fails here, loudly, and nowhere else. A
|
|
338
|
+
* `t` at or past zero is `t % period` alone, which is exact; only a negative
|
|
339
|
+
* `t` is carried up by a period, so a boundary a host samples exactly lands
|
|
340
|
+
* on the pass that begins there, and the answer is always under the period.
|
|
341
|
+
*/
|
|
342
|
+
function wrapSeconds(what, t, period) {
|
|
343
|
+
if (!Number.isFinite(t))
|
|
344
|
+
throw new RangeError(`${what} needs a finite t, got ${t}`);
|
|
345
|
+
const into = t % period;
|
|
346
|
+
if (into >= 0)
|
|
347
|
+
return into;
|
|
348
|
+
// A negative epsilon carried up rounds to the period itself; that is the
|
|
349
|
+
// start of the next pass, never a point past the end of this one.
|
|
350
|
+
const carried = into + period;
|
|
351
|
+
return carried >= period ? 0 : carried;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* The float after `x`: one ulp up, away from zero for a positive `x` and
|
|
355
|
+
* toward it for a negative one, read through the bits of the double.
|
|
356
|
+
*/
|
|
357
|
+
function nextUp(x) {
|
|
358
|
+
if (x === 0)
|
|
359
|
+
return Number.MIN_VALUE;
|
|
360
|
+
DOUBLE[0] = x;
|
|
361
|
+
BITS[0] = BITS[0] + (x > 0 ? 1n : -1n);
|
|
362
|
+
return DOUBLE[0];
|
|
363
|
+
}
|
|
364
|
+
const DOUBLE = new Float64Array(1);
|
|
365
|
+
const BITS = new BigUint64Array(DOUBLE.buffer);
|
|
366
|
+
/** How many ulps the roundings in a sum naming a boundary can put it under that boundary; a walk past this is a boundary finer than time resolves there. */
|
|
367
|
+
const ULPS_OF_ROUNDING = 8;
|
|
368
|
+
/**
|
|
369
|
+
* The first float from `sum` up at which `reached` holds: a time built as a
|
|
370
|
+
* sum to land on a boundary — where a hold begins, where an animation's next
|
|
371
|
+
* part changes — that rounding put an ulp or two under it, walked up to the
|
|
372
|
+
* first moment on the far side. [LAW:single-enforcer] The one walk, so every
|
|
373
|
+
* boundary a time is carried onto is the moment the change shows, to the
|
|
374
|
+
* float. A walk longer than rounding can cost throws `finer()`'s message: the
|
|
375
|
+
* boundary is narrower than the floats around it are apart.
|
|
376
|
+
*/
|
|
377
|
+
export function walkUp(sum, reached, finer) {
|
|
378
|
+
let x = sum;
|
|
379
|
+
for (let walked = 0; !reached(x); walked++) {
|
|
380
|
+
if (walked === ULPS_OF_ROUNDING)
|
|
381
|
+
throw new RangeError(finer());
|
|
382
|
+
x = nextUp(x);
|
|
383
|
+
}
|
|
384
|
+
return x;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* The least float at which `reached` holds, `reached` being false up to some
|
|
388
|
+
* float and true from it on, found from `near`, a guess at it on either
|
|
389
|
+
* side. Where `walkUp` carries a sum onto a boundary on its own clock, and
|
|
390
|
+
* an ulp or two is all rounding can cost, this carries a moment across
|
|
391
|
+
* clocks (`./timeline.ts`): a host float is a step of `rate` on the
|
|
392
|
+
* animation's clock, so the host floats between the guess and the first one
|
|
393
|
+
* past the boundary are as many as the two clocks' resolutions are apart,
|
|
394
|
+
* which no cap bounds and is no error. The bracket is found by doubling the
|
|
395
|
+
* step out from `near`, and halved to two adjacent floats, so the answer is
|
|
396
|
+
* exact to the float and the cost is logarithmic in that distance.
|
|
397
|
+
*/
|
|
398
|
+
export function firstReaching(near, reached) {
|
|
399
|
+
// [LAW:no-silent-failure] A step out from an infinite guess is NaN, and a
|
|
400
|
+
// bracket of NaN never closes.
|
|
401
|
+
if (!Number.isFinite(near))
|
|
402
|
+
throw new RangeError(`firstReaching needs a finite guess, got ${near}`);
|
|
403
|
+
let lo = near;
|
|
404
|
+
let hi = near;
|
|
405
|
+
// Out from `near` until the bracket is `lo` short of the boundary and
|
|
406
|
+
// `hi` past it, within the finite floats: reached at the least of them,
|
|
407
|
+
// it is reached always; not reached at the greatest, never.
|
|
408
|
+
for (let step = nextUp(Math.abs(near)) - Math.abs(near); reached(lo); step *= 2) {
|
|
409
|
+
if (lo === -Number.MAX_VALUE)
|
|
410
|
+
return Number.NEGATIVE_INFINITY;
|
|
411
|
+
hi = lo;
|
|
412
|
+
lo = Math.max(near - step, -Number.MAX_VALUE);
|
|
149
413
|
}
|
|
414
|
+
for (let step = nextUp(Math.abs(near)) - Math.abs(near); !reached(hi); step *= 2) {
|
|
415
|
+
if (hi === Number.MAX_VALUE)
|
|
416
|
+
return Number.POSITIVE_INFINITY;
|
|
417
|
+
lo = hi;
|
|
418
|
+
hi = Math.min(near + step, Number.MAX_VALUE);
|
|
419
|
+
}
|
|
420
|
+
for (;;) {
|
|
421
|
+
const mid = lo + (hi - lo) / 2;
|
|
422
|
+
if (mid <= lo || mid >= hi)
|
|
423
|
+
return hi;
|
|
424
|
+
if (reached(mid))
|
|
425
|
+
hi = mid;
|
|
426
|
+
else
|
|
427
|
+
lo = mid;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/** `holds` as a `Beat`; a beat with no hold, or a hold that is not a positive number of seconds, throws. */
|
|
431
|
+
export function beat(holds) {
|
|
432
|
+
if (holds.length === 0)
|
|
433
|
+
throw new RangeError("a beat needs at least one hold");
|
|
434
|
+
holds.forEach((hold, i) => positiveSeconds(`beat hold ${i}`, hold));
|
|
435
|
+
const period = holds.reduce((sum, hold) => sum + hold, 0);
|
|
436
|
+
// Where each hold begins, in seconds into the period: the unit
|
|
437
|
+
// `wrapSeconds` answers in, so the sum a boundary sits at is the number
|
|
438
|
+
// the wrap of a `t` on that boundary produces, and the hold that begins
|
|
439
|
+
// there is the one in progress, at any rate the host samples.
|
|
440
|
+
const starts = [];
|
|
441
|
+
let elapsed = 0;
|
|
442
|
+
for (const hold of holds) {
|
|
443
|
+
starts.push(elapsed);
|
|
444
|
+
elapsed += hold;
|
|
445
|
+
}
|
|
446
|
+
// A copy, and the one `at` walks: what `at` and `period` were built from
|
|
447
|
+
// stays what `holds` says whatever the caller does to its array.
|
|
448
|
+
const own = [...holds];
|
|
449
|
+
// The last hold whose start the time has reached. The first starts at
|
|
450
|
+
// 0, so the walk always stops, and its length is the count of holds,
|
|
451
|
+
// never a function of `t`.
|
|
452
|
+
const index = (into) => {
|
|
453
|
+
let i = own.length - 1;
|
|
454
|
+
while (starts[i] > into)
|
|
455
|
+
i--;
|
|
456
|
+
return i;
|
|
457
|
+
};
|
|
458
|
+
// Where hold `i` of pass `pass` begins: the pass's start plus the hold's,
|
|
459
|
+
// two integers and two constants, so every `t` in the hold builds the one
|
|
460
|
+
// float. That sum can land an ulp or two under the boundary when the holds
|
|
461
|
+
// are not exact in binary — a tenth of a second is not — and `at` would
|
|
462
|
+
// then read it as the hold before, so it is walked up to the first float
|
|
463
|
+
// `at` places in the hold. The walk is a function of the pass and the hold
|
|
464
|
+
// alone, so every `t` in the hold still answers one float.
|
|
465
|
+
const boundary = (what, pass, i, t) => walkUp(pass * period + starts[i], (began) => index(wrapSeconds(what, began, period)) === i, () => `${what}: hold ${i} (${own[i]} s) is finer than time resolves near t = ${t}`);
|
|
466
|
+
// Which pass `t` is in, as a whole number: `t - into` is a whole number of
|
|
467
|
+
// periods but for rounding, which `round` takes off; carrying `starts[i]`
|
|
468
|
+
// out of `t` itself would leave each sample its own rounding.
|
|
469
|
+
const pass = (t, into) => Math.round((t - into) / period);
|
|
470
|
+
return {
|
|
471
|
+
holds: own,
|
|
472
|
+
period,
|
|
473
|
+
at(t) {
|
|
474
|
+
return index(wrapSeconds("Beat.at", t, period));
|
|
475
|
+
},
|
|
476
|
+
lastChange(t) {
|
|
477
|
+
const into = wrapSeconds("Beat.lastChange", t, period);
|
|
478
|
+
return boundary("Beat.lastChange", pass(t, into), index(into), t);
|
|
479
|
+
},
|
|
480
|
+
// The next hold's start, in the next pass when this hold is the last.
|
|
481
|
+
nextChange(t) {
|
|
482
|
+
const into = wrapSeconds("Beat.nextChange", t, period);
|
|
483
|
+
const i = index(into);
|
|
484
|
+
const last = i === own.length - 1;
|
|
485
|
+
return boundary("Beat.nextChange", pass(t, into) + (last ? 1 : 0), last ? 0 : i + 1, t);
|
|
486
|
+
},
|
|
487
|
+
};
|
|
150
488
|
}
|
|
151
489
|
/** The three phase shapes. Each takes seconds and returns a `Phase`. */
|
|
152
490
|
export const Phase = {
|
|
@@ -160,7 +498,7 @@ export const Phase = {
|
|
|
160
498
|
/** 0 → 1 every `period` seconds, wrapping back to 0; negative `t` wraps too. */
|
|
161
499
|
loop(period) {
|
|
162
500
|
positiveSeconds("Phase.loop period", period);
|
|
163
|
-
return (t) => (
|
|
501
|
+
return (t) => wrapSeconds("Phase.loop", t, period) / period;
|
|
164
502
|
},
|
|
165
503
|
/** 0 → 1 → 0 every `period` seconds: there in the first half, back in the second. */
|
|
166
504
|
pingPong(period) {
|
package/dist/core/json.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { RichText } from "./text.js";
|
|
10
10
|
import { JSONHighlighter } from "./highlighter.js";
|
|
11
|
+
import { render } from "./protocol.js";
|
|
11
12
|
const highlighter = new JSONHighlighter();
|
|
12
13
|
export class JSONRenderable {
|
|
13
14
|
text;
|
|
@@ -45,7 +46,7 @@ export class JSONRenderable {
|
|
|
45
46
|
return new JSONRenderable(text);
|
|
46
47
|
}
|
|
47
48
|
*render(options) {
|
|
48
|
-
yield* this.text
|
|
49
|
+
yield* render(this.text, options);
|
|
49
50
|
}
|
|
50
51
|
measure(options) {
|
|
51
52
|
return this.text.measure(options);
|
package/dist/core/measure.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Measurement — min/max cell width calculation for renderables.
|
|
3
3
|
*/
|
|
4
|
-
import { isMeasurable, measuring } from "./protocol.js";
|
|
4
|
+
import { drawsNothing, isMeasurable, measuring } from "./protocol.js";
|
|
5
5
|
export class Measurement {
|
|
6
6
|
minimum;
|
|
7
7
|
maximum;
|
|
@@ -45,7 +45,7 @@ export class Measurement {
|
|
|
45
45
|
// `Measurement.get` answers for it — so under an unbounded offer it asks for
|
|
46
46
|
// `Infinity`, and `withBoundedWidth` refuses rather than guessing a width.
|
|
47
47
|
static get(options, renderable) {
|
|
48
|
-
if (options
|
|
48
|
+
if (drawsNothing(options))
|
|
49
49
|
return new Measurement(0, 0);
|
|
50
50
|
if (!isMeasurable(renderable))
|
|
51
51
|
return new Measurement(0, options.maxWidth);
|
package/dist/core/place.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { Segment } from "./segment.js";
|
|
21
21
|
import { Measurement } from "./measure.js";
|
|
22
|
+
import { render } from "./protocol.js";
|
|
22
23
|
// The share of the spare cells that goes on the left; the rest go on the right.
|
|
23
24
|
// Right-alignment pads no right side at all, which is the reference's choice
|
|
24
25
|
// and not a zero: a line that ends at the edge carries no trailing blanks.
|
|
@@ -39,7 +40,7 @@ export function placeBlock(renderable, align, options) {
|
|
|
39
40
|
// (`Measurement.get` answers for it), and the block is then only as wide as
|
|
40
41
|
// what it actually drew.
|
|
41
42
|
const width = Measurement.get(options, renderable).maximum;
|
|
42
|
-
const drawn = Segment.splitLines(
|
|
43
|
+
const drawn = Segment.splitLines(render(renderable, { ...options, maxWidth: width, height: undefined }));
|
|
43
44
|
const [blockWidth, height] = Segment.getShape(drawn);
|
|
44
45
|
const [left, right] = PADS[align](Math.max(0, options.maxWidth - blockWidth));
|
|
45
46
|
return Segment.setShape(drawn, blockWidth, height).map((line) => [...pad(left), ...line, ...pad(right)]);
|
package/dist/core/pretty.js
CHANGED
|
@@ -21,7 +21,7 @@ import { divideLine } from "./wrap.js";
|
|
|
21
21
|
import { Segment } from "./segment.js";
|
|
22
22
|
import { RichText, stripControlChars } from "./text.js";
|
|
23
23
|
import { ReprHighlighter } from "./highlighter.js";
|
|
24
|
-
import { drawable } from "./protocol.js";
|
|
24
|
+
import { drawable, render } from "./protocol.js";
|
|
25
25
|
const reprHighlighter = new ReprHighlighter();
|
|
26
26
|
/**
|
|
27
27
|
* What a value is bounded by when whoever formats it has not seen it:
|
|
@@ -491,7 +491,7 @@ export class Pretty {
|
|
|
491
491
|
*render(options) {
|
|
492
492
|
// `toText` has no `end`, for callers that set it inside a line; rendered,
|
|
493
493
|
// a `Pretty` is a block of its own, as the reference's is.
|
|
494
|
-
yield* this.toText(options)
|
|
494
|
+
yield* render(this.toText(options), options);
|
|
495
495
|
yield Segment.line();
|
|
496
496
|
}
|
|
497
497
|
/** The value laid out for `options.maxWidth` and highlighted: the text this renders. */
|
package/dist/core/protocol.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
|
|
4
4
|
*/
|
|
5
5
|
import type { Segment } from "./segment.js";
|
|
6
|
-
import {
|
|
6
|
+
import { Style, type StyleSyntaxError, type Theme } from "./style.js";
|
|
7
7
|
import { ColorDepth } from "./color.js";
|
|
8
8
|
import type { Highlighter } from "./highlighter.js";
|
|
9
9
|
/**
|
|
@@ -87,15 +87,21 @@ export interface RenderOptions {
|
|
|
87
87
|
*/
|
|
88
88
|
onStyleError?: StyleErrorHandler;
|
|
89
89
|
/**
|
|
90
|
-
* Told of
|
|
91
|
-
*
|
|
92
|
-
* the order it renders them
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* not drawing and is not
|
|
90
|
+
* Told of every renderable `render` is asked for, as it is asked, so the
|
|
91
|
+
* calls arrive in document order: a container before what it holds, its
|
|
92
|
+
* contents in the order it renders them. `"drawn"` is a renderable drawn at
|
|
93
|
+
* the width it was handed; `"held"` is one handed no column, which is still
|
|
94
|
+
* in the view but has nothing drawn for it (`render`). A renderable rendered
|
|
95
|
+
* twice is told twice. A render made to measure is not drawing and is not
|
|
96
|
+
* told (`measuring`).
|
|
96
97
|
*/
|
|
97
|
-
|
|
98
|
+
onPresent?: (renderable: Renderable, presence: Presence) => void;
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether a renderable the view holds was drawn, or held: in the view, handed
|
|
102
|
+
* no column, and so not drawn (`render`).
|
|
103
|
+
*/
|
|
104
|
+
export type Presence = "drawn" | "held";
|
|
99
105
|
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
100
106
|
export declare const isAscii: (text: string) => boolean;
|
|
101
107
|
/**
|
|
@@ -210,7 +216,10 @@ export declare function getStyle(options: RenderOptions, style: string | Style):
|
|
|
210
216
|
* every bundled theme. So where the depth would turn a fixed colour of the
|
|
211
217
|
* pair into a slot, the pair is dropped and the run is drawn on the
|
|
212
218
|
* terminal's own foreground and background, which its theme does make
|
|
213
|
-
* readable; its attributes still set it apart.
|
|
219
|
+
* readable; its attributes still set it apart. Both are named, not left
|
|
220
|
+
* unset, so the colour of whatever the run sits in — a quote, a link — does
|
|
221
|
+
* not show through: Rich's code keeps its own colours inside a quote, and the
|
|
222
|
+
* terminal's are the ones it keeps here. A style whose colours are
|
|
214
223
|
* slots already (`white on blue`), or that paints no ground (`bold magenta`),
|
|
215
224
|
* was written for the terminal's own colours and is drawn as written. Rich
|
|
216
225
|
* draws a highlighted line number the same way below 256 colours
|
|
@@ -238,6 +247,40 @@ export declare function groundedStyle(options: RenderOptions, style: Style): Sty
|
|
|
238
247
|
* ignored the request and emitted its full natural width.
|
|
239
248
|
*/
|
|
240
249
|
export declare function withCellWidth(options: RenderOptions): RenderOptions;
|
|
250
|
+
/** Under one column there is nothing to draw, or to measure. */
|
|
251
|
+
export declare function drawsNothing(options: {
|
|
252
|
+
readonly maxWidth: number;
|
|
253
|
+
}): boolean;
|
|
254
|
+
/**
|
|
255
|
+
* `renderable` drawn under `options`: Rich's `Console.render`, which every
|
|
256
|
+
* renderable there is drawn through, so under one column nothing is.
|
|
257
|
+
*
|
|
258
|
+
* [LAW:single-enforcer] The one place that rule is kept. Every call of a
|
|
259
|
+
* `Renderable`'s `render` in `src/` goes through here — a container drawing
|
|
260
|
+
* its child, a renderable drawing the text it built, a host drawing a frame —
|
|
261
|
+
* and `test/seam/render-calls.test.ts` fails on one that does not. Before
|
|
262
|
+
* this, each container asked `drawsNothing` itself, and the ones that forgot
|
|
263
|
+
* drew a child handed no column anyway.
|
|
264
|
+
*
|
|
265
|
+
* It is also the one reporter of what the view holds (`onPresent`). Under one
|
|
266
|
+
* column a renderable is held, not drawn: what it holds (`Composite`) is
|
|
267
|
+
* walked and reported held in turn, and nothing renders, so no renderable
|
|
268
|
+
* that keeps state while it renders — a `Viewport`'s offset, a `TextInput`'s
|
|
269
|
+
* scroll window — ever commits it at a width that is not shown. The walk runs
|
|
270
|
+
* whether or not anyone listens. Drawing the squeezed subtree at one column
|
|
271
|
+
* instead, and dropping what it drew, did commit that state.
|
|
272
|
+
*/
|
|
273
|
+
export declare function render(renderable: Renderable, options: RenderOptions): Iterable<Segment>;
|
|
274
|
+
/**
|
|
275
|
+
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
276
|
+
* its shape draws nothing on the frame, so it reports nothing to `onPresent` —
|
|
277
|
+
* one that did would put a widget in the view at the moment a container
|
|
278
|
+
* measured it, and keep one the frame then cropped away.
|
|
279
|
+
*
|
|
280
|
+
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
281
|
+
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
282
|
+
*/
|
|
283
|
+
export declare function measuring(options: RenderOptions): RenderOptions;
|
|
241
284
|
/**
|
|
242
285
|
* The options a renderable should *lay out against*: `withCellWidth`, with an
|
|
243
286
|
* unbounded offer resolved to the renderable's own natural width.
|
|
@@ -273,16 +316,6 @@ export declare function withCellWidth(options: RenderOptions): RenderOptions;
|
|
|
273
316
|
* rather than zero, and saying so names the cause — where `String.repeat` and
|
|
274
317
|
* `new Array` only ever name their own argument.
|
|
275
318
|
*/
|
|
276
|
-
/**
|
|
277
|
-
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
278
|
-
* its shape draws nothing on the frame, so it reports no owner to `onDraw` —
|
|
279
|
-
* one that did would put a widget in document order at the moment a
|
|
280
|
-
* container measured it, and keep one the frame then cropped away.
|
|
281
|
-
*
|
|
282
|
-
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
283
|
-
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
284
|
-
*/
|
|
285
|
-
export declare function measuring(options: RenderOptions): RenderOptions;
|
|
286
319
|
export declare function withBoundedWidth(options: RenderOptions, self: Measurable): RenderOptions;
|
|
287
320
|
export interface Renderable {
|
|
288
321
|
render(options: RenderOptions): Iterable<Segment>;
|
|
@@ -293,6 +326,26 @@ export interface Measurable {
|
|
|
293
326
|
maximum: number;
|
|
294
327
|
};
|
|
295
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* A container: a renderable that draws other renderables. `contents` is what
|
|
331
|
+
* its `render` hands to `render`, in that order where the order is not a
|
|
332
|
+
* width's, decided as the render decides it — and so it is how a container
|
|
333
|
+
* handed no column still reports what it holds (`render`). It takes no
|
|
334
|
+
* options: what a container holds may depend on its state (a hidden `Layout`
|
|
335
|
+
* holds nothing) but never on a width, because under one column there is none.
|
|
336
|
+
* It commits no state, and yields only renderables: text a container builds
|
|
337
|
+
* from a string it holds holds nothing.
|
|
338
|
+
*
|
|
339
|
+
* [LAW:one-source-of-truth] A second description of each container's
|
|
340
|
+
* traversal, so `test/renderables/contents.test.ts` holds every exported
|
|
341
|
+
* renderable's `contents` to what its `render` reports.
|
|
342
|
+
*
|
|
343
|
+
* A consumer's container that does not implement it reports nothing it holds
|
|
344
|
+
* while it is handed no column.
|
|
345
|
+
*/
|
|
346
|
+
export interface Composite {
|
|
347
|
+
contents(): Iterable<Renderable>;
|
|
348
|
+
}
|
|
296
349
|
/**
|
|
297
350
|
* What the wheel scrolls: an owner of anchors on the frame (`./anchor.ts`)
|
|
298
351
|
* that moves by lines. `canScrollBy` says whether `scrollBy` with the same
|
|
@@ -305,3 +358,4 @@ export interface Scrollable {
|
|
|
305
358
|
export declare function isRenderable(obj: unknown): obj is Renderable;
|
|
306
359
|
export declare function isMeasurable(obj: unknown): obj is Measurable;
|
|
307
360
|
export declare function isScrollable(obj: object): obj is Scrollable;
|
|
361
|
+
export declare function isComposite(obj: object): obj is Composite;
|