@promptctl/rich-js 0.24.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.
Files changed (95) hide show
  1. package/THIRD-PARTY-NOTICES +29 -1
  2. package/dist/core/animation.d.ts +78 -0
  3. package/dist/core/animation.js +107 -0
  4. package/dist/core/cell-widths.d.ts +9 -0
  5. package/dist/core/cell-widths.js +140 -0
  6. package/dist/core/cells.d.ts +17 -11
  7. package/dist/core/cells.js +100 -25
  8. package/dist/core/console.js +5 -5
  9. package/dist/core/easing.d.ts +110 -10
  10. package/dist/core/easing.js +269 -36
  11. package/dist/core/json.js +2 -1
  12. package/dist/core/measure.js +2 -2
  13. package/dist/core/place.js +2 -1
  14. package/dist/core/pretty.js +2 -2
  15. package/dist/core/protocol.d.ts +73 -19
  16. package/dist/core/protocol.js +56 -15
  17. package/dist/core/render.js +2 -1
  18. package/dist/core/segment.d.ts +18 -1
  19. package/dist/core/segment.js +45 -44
  20. package/dist/core/strip.d.ts +3 -2
  21. package/dist/core/strip.js +11 -7
  22. package/dist/core/style.js +17 -6
  23. package/dist/core/text.js +22 -14
  24. package/dist/core/timeline.d.ts +84 -0
  25. package/dist/core/timeline.js +144 -0
  26. package/dist/core/tween.d.ts +49 -0
  27. package/dist/core/tween.js +69 -0
  28. package/dist/host/app.js +2 -2
  29. package/dist/index.d.ts +10 -7
  30. package/dist/index.js +13 -4
  31. package/dist/renderables/align.d.ts +3 -2
  32. package/dist/renderables/align.js +3 -0
  33. package/dist/renderables/columns.d.ts +3 -2
  34. package/dist/renderables/columns.js +7 -2
  35. package/dist/renderables/constrain.d.ts +3 -2
  36. package/dist/renderables/constrain.js +5 -1
  37. package/dist/renderables/effect.d.ts +43 -58
  38. package/dist/renderables/effect.js +58 -64
  39. package/dist/renderables/effects.d.ts +16 -14
  40. package/dist/renderables/effects.js +64 -45
  41. package/dist/renderables/embed.js +3 -2
  42. package/dist/renderables/flexStrip.d.ts +3 -2
  43. package/dist/renderables/flexStrip.js +6 -1
  44. package/dist/renderables/frames.d.ts +40 -19
  45. package/dist/renderables/frames.js +48 -21
  46. package/dist/renderables/group.d.ts +3 -2
  47. package/dist/renderables/group.js +5 -2
  48. package/dist/renderables/html-entities.d.ts +8 -0
  49. package/dist/renderables/html-entities.js +386 -0
  50. package/dist/renderables/layout.d.ts +3 -2
  51. package/dist/renderables/layout.js +15 -2
  52. package/dist/renderables/list-view.d.ts +3 -2
  53. package/dist/renderables/list-view.js +12 -5
  54. package/dist/renderables/live.js +2 -2
  55. package/dist/renderables/markdown-parse.d.ts +109 -0
  56. package/dist/renderables/markdown-parse.js +1421 -0
  57. package/dist/renderables/markdown-url.d.ts +19 -0
  58. package/dist/renderables/markdown-url.js +314 -0
  59. package/dist/renderables/markdown.d.ts +5 -7
  60. package/dist/renderables/markdown.js +356 -683
  61. package/dist/renderables/padding.d.ts +8 -20
  62. package/dist/renderables/padding.js +32 -50
  63. package/dist/renderables/panel.d.ts +26 -26
  64. package/dist/renderables/panel.js +67 -103
  65. package/dist/renderables/progress.d.ts +4 -2
  66. package/dist/renderables/progress.js +11 -2
  67. package/dist/renderables/progressBar.js +2 -2
  68. package/dist/renderables/python-space.d.ts +13 -0
  69. package/dist/renderables/python-space.js +24 -0
  70. package/dist/renderables/spinner.d.ts +26 -14
  71. package/dist/renderables/spinner.js +49 -37
  72. package/dist/renderables/syntax.js +2 -1
  73. package/dist/renderables/table.d.ts +19 -11
  74. package/dist/renderables/table.js +140 -156
  75. package/dist/renderables/traceback.d.ts +1 -1
  76. package/dist/renderables/traceback.js +6 -5
  77. package/dist/renderables/tree.d.ts +6 -5
  78. package/dist/renderables/tree.js +11 -7
  79. package/dist/renderables/viewport.d.ts +13 -5
  80. package/dist/renderables/viewport.js +41 -20
  81. package/dist/template-bindings/animation-funcs.js +2 -2
  82. package/dist/template-bindings/index.js +2 -1
  83. package/dist/themes/ramp.d.ts +18 -3
  84. package/dist/themes/ramp.js +23 -2
  85. package/dist/widgets/focus-manager.d.ts +21 -14
  86. package/dist/widgets/focus-manager.js +28 -29
  87. package/dist/widgets/index.d.ts +1 -1
  88. package/dist/widgets/static-item.d.ts +4 -3
  89. package/dist/widgets/static-item.js +9 -7
  90. package/dist/widgets/text-input.d.ts +28 -18
  91. package/dist/widgets/text-input.js +144 -101
  92. package/dist/widgets/widget-app.d.ts +19 -3
  93. package/dist/widgets/widget-app.js +39 -21
  94. package/dist/widgets/widget-base.js +0 -3
  95. package/package.json +2 -5
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Timeline — the map from the host's clock to the time an animation is
3
+ * asked for: playback control, and nothing else.
4
+ *
5
+ * Every animation is a pure function of its time (`./animation.ts`), so
6
+ * pausing, reversing, scrubbing and speeding one up are not things done to
7
+ * it but a change in which time it is asked for. A timeline is that map, as
8
+ * a value: an animation time at a host time, a rate — animation seconds per
9
+ * host second, negative running it backwards — and whether it is held. Each
10
+ * operation is a value in and a value out, so a paused timeline resumed at a
11
+ * new host time is one that maps that host time to the time it froze at, and
12
+ * two hosts holding equal timelines ask every animation for the same time.
13
+ *
14
+ * A paused timeline carries no host anchor: it maps every host time to the
15
+ * one time it holds, so there is no origin for it to be wrong about.
16
+ * [LAW:types-are-the-program]
17
+ *
18
+ * `run` is an animation on host time. Its `nextChange` is the host moment
19
+ * the mapped time reaches the animation's own answer, which is what
20
+ * `Spinner`'s speed scaled its holds for before this existed: an answer a
21
+ * host reads is on the host's timeline, and a beat over scaled time answers
22
+ * on the scaled one. Run backwards, the host next sees a change when the
23
+ * mapped time drops under where the value began — the animation's
24
+ * `lastChange` — so a reversed loop turns exactly at its hold boundaries,
25
+ * and a reversed one-shot changes at its switch and never before its start.
26
+ * Paused, nothing changes: never, and never did. The host moment is the
27
+ * first host float the map carries past the edge, searched out from the sum
28
+ * `origin + (edge - time) / rate` (`firstReaching`): a host float is a step
29
+ * of `rate` on the animation's clock, so the floats between that sum and
30
+ * the first one past the edge are as many as the two clocks' resolutions
31
+ * are apart — unbounded, far from the origin or at a slow rate — which is
32
+ * why it is a search and not the ulp walk a sum on one clock takes. A value
33
+ * moving at the mapped time changes at the host time itself.
34
+ *
35
+ * [LAW:no-ambient-temporal-coupling] Nothing here reads a clock. The host
36
+ * time is an argument everywhere, and a timeline is built from one the host
37
+ * hands in, so the same host time maps to the same animation time at one
38
+ * sample a second or sixty.
39
+ */
40
+ import type { Animation } from "./animation.js";
41
+ /**
42
+ * A map from host time to animation time. Playing, it is `time` at `origin`
43
+ * moving `rate` animation seconds a host second; paused, it is `time` at
44
+ * every host time, with the rate it resumes at.
45
+ */
46
+ export type Timeline = {
47
+ readonly playing: true;
48
+ /** The host time, in seconds, at which the animation time is `time`. */
49
+ readonly origin: number;
50
+ /** The animation time, in seconds, at `origin`. */
51
+ readonly time: number;
52
+ /** Animation seconds per host second; negative runs the animation backwards. Never zero: a held timeline is paused. */
53
+ readonly rate: number;
54
+ } | {
55
+ readonly playing: false;
56
+ /** The animation time, in seconds, held at every host time. */
57
+ readonly time: number;
58
+ /** The rate the timeline resumes at. */
59
+ readonly rate: number;
60
+ };
61
+ export declare const Timeline: {
62
+ /** Playing from host time `origin`, at which the animation time is 0, at `rate` animation seconds a host second. */
63
+ play(origin: number, rate?: number): Timeline;
64
+ /** The animation time at host time `host`. */
65
+ at(timeline: Timeline, host: number): number;
66
+ /** `timeline` held at the animation time it maps `host` to. Pausing a paused timeline holds the same time. */
67
+ pause(timeline: Timeline, host: number): Timeline;
68
+ /** `timeline` playing from `host` at the animation time it maps `host` to, at its rate. Resuming a playing timeline is the same map. */
69
+ resume(timeline: Timeline, host: number): Timeline;
70
+ /** `timeline` with the animation at `time` at host time `host`, playing or held as it was. */
71
+ seek(timeline: Timeline, host: number, time: number): Timeline;
72
+ /** `timeline` at `rate` from host time `host` on, with no jump: the animation time at `host` is what it was. */
73
+ withRate(timeline: Timeline, host: number, rate: number): Timeline;
74
+ /**
75
+ * `animation` on host time: its value at the time `timeline` maps each
76
+ * host time to, changing at the host moment the mapped time reaches the
77
+ * animation's own answer — backwards, when it drops under where the value
78
+ * began. Paused, the value never changes and never did. The value is the
79
+ * animation's own, on no clock: what a drawn value's cells say about when
80
+ * they change is stamped by whoever draws it at a host time
81
+ * (`../renderables/frames.ts`, `frameAt`), never carried in from here.
82
+ */
83
+ run<T>(timeline: Timeline, animation: Animation<T>): Animation<T>;
84
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Timeline — the map from the host's clock to the time an animation is
3
+ * asked for: playback control, and nothing else.
4
+ *
5
+ * Every animation is a pure function of its time (`./animation.ts`), so
6
+ * pausing, reversing, scrubbing and speeding one up are not things done to
7
+ * it but a change in which time it is asked for. A timeline is that map, as
8
+ * a value: an animation time at a host time, a rate — animation seconds per
9
+ * host second, negative running it backwards — and whether it is held. Each
10
+ * operation is a value in and a value out, so a paused timeline resumed at a
11
+ * new host time is one that maps that host time to the time it froze at, and
12
+ * two hosts holding equal timelines ask every animation for the same time.
13
+ *
14
+ * A paused timeline carries no host anchor: it maps every host time to the
15
+ * one time it holds, so there is no origin for it to be wrong about.
16
+ * [LAW:types-are-the-program]
17
+ *
18
+ * `run` is an animation on host time. Its `nextChange` is the host moment
19
+ * the mapped time reaches the animation's own answer, which is what
20
+ * `Spinner`'s speed scaled its holds for before this existed: an answer a
21
+ * host reads is on the host's timeline, and a beat over scaled time answers
22
+ * on the scaled one. Run backwards, the host next sees a change when the
23
+ * mapped time drops under where the value began — the animation's
24
+ * `lastChange` — so a reversed loop turns exactly at its hold boundaries,
25
+ * and a reversed one-shot changes at its switch and never before its start.
26
+ * Paused, nothing changes: never, and never did. The host moment is the
27
+ * first host float the map carries past the edge, searched out from the sum
28
+ * `origin + (edge - time) / rate` (`firstReaching`): a host float is a step
29
+ * of `rate` on the animation's clock, so the floats between that sum and
30
+ * the first one past the edge are as many as the two clocks' resolutions
31
+ * are apart — unbounded, far from the origin or at a slow rate — which is
32
+ * why it is a search and not the ulp walk a sum on one clock takes. A value
33
+ * moving at the mapped time changes at the host time itself.
34
+ *
35
+ * [LAW:no-ambient-temporal-coupling] Nothing here reads a clock. The host
36
+ * time is an argument everywhere, and a timeline is built from one the host
37
+ * hands in, so the same host time maps to the same animation time at one
38
+ * sample a second or sixty.
39
+ */
40
+ import { firstReaching } from "./easing.js";
41
+ /** Throws, naming `what`, unless `seconds` is a finite number of seconds. */
42
+ function finiteSeconds(what, seconds) {
43
+ if (!Number.isFinite(seconds))
44
+ throw new RangeError(`${what} must be a finite number of seconds, got ${seconds}`);
45
+ return seconds;
46
+ }
47
+ /** [LAW:single-enforcer] A host time is a finite number of seconds, refused once, at every operation that takes one. */
48
+ const hostTime = (host) => finiteSeconds("Timeline host time", host);
49
+ /**
50
+ * [LAW:parse-dont-validate] A rate is a finite number other than zero,
51
+ * settled where it is given: a rate of zero would be a second way to hold a
52
+ * timeline still, and `pause` is the one.
53
+ */
54
+ function playbackRate(rate) {
55
+ if (!Number.isFinite(rate) || rate === 0)
56
+ throw new RangeError(`Timeline rate must be finite and not zero, got ${rate}`);
57
+ return rate;
58
+ }
59
+ /** `timeline` with `time` at host time `host`, at `rate`, held as it was. A paused timeline keeps no origin, and refuses a host time it cannot use all the same. */
60
+ function anchored(timeline, host, time, rate) {
61
+ const origin = hostTime(host);
62
+ return timeline.playing ? { playing: true, origin, time, rate } : { playing: false, time, rate };
63
+ }
64
+ /** `animation` asked at the time `playing` maps each host time to, saying on host time when it changes. */
65
+ function played(animation, { origin, time, rate }) {
66
+ const mapped = (host) => time + rate * (host - origin);
67
+ const forward = rate > 0;
68
+ // Forwards, the host next sees the animation's own next change, and the
69
+ // value began when the animation says it did; backwards, it next sees a
70
+ // change where the value began, and the value began where the animation
71
+ // says it ends. A hold is `[lastChange, nextChange)`, so the far side of
72
+ // an edge is at it going up and strictly under it coming down.
73
+ const ahead = forward ? (t) => animation.nextChange(t) : (t) => animation.lastChange(t);
74
+ const behind = forward ? (t) => animation.lastChange(t) : (t) => animation.nextChange(t);
75
+ const past = forward ? (t, edge) => t >= edge : (t, edge) => t < edge;
76
+ // The first host moment the map carries past `edge`, an animation time.
77
+ // An edge at infinity, or one so far off at this rate that no host float
78
+ // reaches it, is never reached, forwards or backwards: the sum's own sign
79
+ // says which, as a host moment past either end of the host's clock.
80
+ const hostOf = (edge) => {
81
+ const sum = origin + (edge - time) / rate;
82
+ return Number.isFinite(sum) ? firstReaching(sum, (host) => past(mapped(host), edge)) : sum;
83
+ };
84
+ return {
85
+ at: (host) => animation.at(mapped(hostTime(host))),
86
+ nextChange(host) {
87
+ const h = hostTime(host);
88
+ const t = mapped(h);
89
+ const edge = ahead(t);
90
+ return edge === t ? h : Math.max(h, hostOf(edge));
91
+ },
92
+ lastChange(host) {
93
+ const h = hostTime(host);
94
+ const t = mapped(h);
95
+ const edge = behind(t);
96
+ return edge === t ? h : Math.min(h, hostOf(edge));
97
+ },
98
+ };
99
+ }
100
+ export const Timeline = {
101
+ /** Playing from host time `origin`, at which the animation time is 0, at `rate` animation seconds a host second. */
102
+ play(origin, rate = 1) {
103
+ return { playing: true, origin: finiteSeconds("Timeline.play origin", origin), time: 0, rate: playbackRate(rate) };
104
+ },
105
+ /** The animation time at host time `host`. */
106
+ at(timeline, host) {
107
+ const h = hostTime(host);
108
+ return timeline.playing ? timeline.time + timeline.rate * (h - timeline.origin) : timeline.time;
109
+ },
110
+ /** `timeline` held at the animation time it maps `host` to. Pausing a paused timeline holds the same time. */
111
+ pause(timeline, host) {
112
+ return { playing: false, time: Timeline.at(timeline, host), rate: timeline.rate };
113
+ },
114
+ /** `timeline` playing from `host` at the animation time it maps `host` to, at its rate. Resuming a playing timeline is the same map. */
115
+ resume(timeline, host) {
116
+ return { playing: true, origin: hostTime(host), time: Timeline.at(timeline, host), rate: timeline.rate };
117
+ },
118
+ /** `timeline` with the animation at `time` at host time `host`, playing or held as it was. */
119
+ seek(timeline, host, time) {
120
+ return anchored(timeline, host, finiteSeconds("Timeline.seek time", time), timeline.rate);
121
+ },
122
+ /** `timeline` at `rate` from host time `host` on, with no jump: the animation time at `host` is what it was. */
123
+ withRate(timeline, host, rate) {
124
+ return anchored(timeline, host, Timeline.at(timeline, host), playbackRate(rate));
125
+ },
126
+ /**
127
+ * `animation` on host time: its value at the time `timeline` maps each
128
+ * host time to, changing at the host moment the mapped time reaches the
129
+ * animation's own answer — backwards, when it drops under where the value
130
+ * began. Paused, the value never changes and never did. The value is the
131
+ * animation's own, on no clock: what a drawn value's cells say about when
132
+ * they change is stamped by whoever draws it at a host time
133
+ * (`../renderables/frames.ts`, `frameAt`), never carried in from here.
134
+ */
135
+ run(timeline, animation) {
136
+ if (timeline.playing)
137
+ return played(animation, timeline);
138
+ return {
139
+ at: (host) => animation.at(Timeline.at(timeline, host)),
140
+ nextChange: (host) => (hostTime(host), Number.POSITIVE_INFINITY),
141
+ lastChange: (host) => (hostTime(host), Number.NEGATIVE_INFINITY),
142
+ };
143
+ },
144
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Tween — a value eased from one number to another over seconds, then held:
3
+ * the animation a progress bar's fill, a counter or a width is handed so it
4
+ * moves to its new value instead of jumping. The first `Animation` of a
5
+ * value that is never drawn as cells, so its `nextChange` is its only way of
6
+ * saying when to look again (`./animation.ts`).
7
+ *
8
+ * The value at `t` is the `Timing`'s ease of the way from `from` to `to`
9
+ * through `Phase.once(0, seconds)`: `from` at `t ≤ 0` whenever the ease
10
+ * starts at 0, as every built-in does but `step-start` and the steps that
11
+ * jump at the start of their interval, which show their first jump before
12
+ * the tween begins, as `ColorRamp.at` does; `to` from `seconds` on. It is
13
+ * the one-shot phase every effect transition reads time
14
+ * through, so there is no second rule about when a one-shot begins: it
15
+ * begins at 0 on its own clock, and starting it later is `Animation.sequence`'s
16
+ * or a `Timeline`'s job. [LAW:single-enforcer] An ease that overshoots is
17
+ * shown as it is — a count eased by `back-out` passes its target and comes
18
+ * back, which is what the curve is for — and lands exactly on `to`, because
19
+ * every built-in ease is exactly 1 at 1 and the blend is `(1 − p)·from +
20
+ * p·to`, exact at both ends where `from + (to − from)·p` is not.
21
+ *
22
+ * A tween keeps nothing between calls, so retargeting is not a mutation: a
23
+ * tween whose target changes at `t₁` is the old tween until `t₁`, then a new
24
+ * one from the old one's value there — `Animation.sequence(old, t₁,
25
+ * tween(old.at(t₁), to, timing))` — and starts from where it is because
26
+ * that is where it was built from. [LAW:no-ambient-temporal-coupling]
27
+ *
28
+ * Over colours it is a `ColorRamp` read over time (`../themes/ramp.ts`,
29
+ * `over`): the ramp is already the one place two colours are blended, in
30
+ * OKLCH, and a colour tween is a two-stop ramp over its seconds, not a
31
+ * second blender. [LAW:one-source-of-truth]
32
+ *
33
+ * Tier 2 of `src/core/`: `./easing.ts` and `./animation.ts`, nothing else.
34
+ * [LAW:one-way-deps]
35
+ */
36
+ import { Animation } from "./animation.js";
37
+ import { type Timing } from "./easing.js";
38
+ /**
39
+ * `from` eased to `to` over `timing`, as an `Animation<number>`: moving from
40
+ * 0 to `timing.seconds` on its own clock, the ease of 0 before (`from`, for
41
+ * every ease that starts at 0), `to` after, and so
42
+ * seen at its end, never mid-way, by a host whose frames are further apart
43
+ * than it is long. `from` and `to` must be finite; the seconds are checked
44
+ * where every one-shot's are, in `Phase.once`. A tween to where it already
45
+ * is is `Animation.constant(from)`, decided here once: the blend would land
46
+ * one ulp off `from` at some `p`, and a value that never moves is one that
47
+ * never changes, so a host is told not to look again. [LAW:parse-dont-validate]
48
+ */
49
+ export declare function tween(from: number, to: number, timing: Timing): Animation<number>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Tween — a value eased from one number to another over seconds, then held:
3
+ * the animation a progress bar's fill, a counter or a width is handed so it
4
+ * moves to its new value instead of jumping. The first `Animation` of a
5
+ * value that is never drawn as cells, so its `nextChange` is its only way of
6
+ * saying when to look again (`./animation.ts`).
7
+ *
8
+ * The value at `t` is the `Timing`'s ease of the way from `from` to `to`
9
+ * through `Phase.once(0, seconds)`: `from` at `t ≤ 0` whenever the ease
10
+ * starts at 0, as every built-in does but `step-start` and the steps that
11
+ * jump at the start of their interval, which show their first jump before
12
+ * the tween begins, as `ColorRamp.at` does; `to` from `seconds` on. It is
13
+ * the one-shot phase every effect transition reads time
14
+ * through, so there is no second rule about when a one-shot begins: it
15
+ * begins at 0 on its own clock, and starting it later is `Animation.sequence`'s
16
+ * or a `Timeline`'s job. [LAW:single-enforcer] An ease that overshoots is
17
+ * shown as it is — a count eased by `back-out` passes its target and comes
18
+ * back, which is what the curve is for — and lands exactly on `to`, because
19
+ * every built-in ease is exactly 1 at 1 and the blend is `(1 − p)·from +
20
+ * p·to`, exact at both ends where `from + (to − from)·p` is not.
21
+ *
22
+ * A tween keeps nothing between calls, so retargeting is not a mutation: a
23
+ * tween whose target changes at `t₁` is the old tween until `t₁`, then a new
24
+ * one from the old one's value there — `Animation.sequence(old, t₁,
25
+ * tween(old.at(t₁), to, timing))` — and starts from where it is because
26
+ * that is where it was built from. [LAW:no-ambient-temporal-coupling]
27
+ *
28
+ * Over colours it is a `ColorRamp` read over time (`../themes/ramp.ts`,
29
+ * `over`): the ramp is already the one place two colours are blended, in
30
+ * OKLCH, and a colour tween is a two-stop ramp over its seconds, not a
31
+ * second blender. [LAW:one-source-of-truth]
32
+ *
33
+ * Tier 2 of `src/core/`: `./easing.ts` and `./animation.ts`, nothing else.
34
+ * [LAW:one-way-deps]
35
+ */
36
+ import { Animation } from "./animation.js";
37
+ import { Phase } from "./easing.js";
38
+ /**
39
+ * `from` eased to `to` over `timing`, as an `Animation<number>`: moving from
40
+ * 0 to `timing.seconds` on its own clock, the ease of 0 before (`from`, for
41
+ * every ease that starts at 0), `to` after, and so
42
+ * seen at its end, never mid-way, by a host whose frames are further apart
43
+ * than it is long. `from` and `to` must be finite; the seconds are checked
44
+ * where every one-shot's are, in `Phase.once`. A tween to where it already
45
+ * is is `Animation.constant(from)`, decided here once: the blend would land
46
+ * one ulp off `from` at some `p`, and a value that never moves is one that
47
+ * never changes, so a host is told not to look again. [LAW:parse-dont-validate]
48
+ */
49
+ export function tween(from, to, timing) {
50
+ for (const [name, v] of [["from", from], ["to", to]]) {
51
+ if (!Number.isFinite(v))
52
+ throw new RangeError(`tween ${name} must be finite, got ${v}`);
53
+ }
54
+ const { ease, seconds } = timing;
55
+ const phase = Phase.once(0, seconds);
56
+ if (from === to)
57
+ return Animation.constant(from);
58
+ return {
59
+ at(t) {
60
+ const p = ease(phase(t));
61
+ return (1 - p) * from + p * to;
62
+ },
63
+ // Before it begins the value is held until 0; while it moves, every
64
+ // frame; from `seconds` on, never again — and it became what it is at
65
+ // `seconds`, or always was before it began.
66
+ nextChange: (t) => (t < 0 ? 0 : t < seconds ? t : Number.POSITIVE_INFINITY),
67
+ lastChange: (t) => (t < 0 ? Number.NEGATIVE_INFINITY : t < seconds ? t : seconds),
68
+ };
69
+ }
package/dist/host/app.js CHANGED
@@ -34,7 +34,7 @@ import { frameRate, systemClock } from "../core/clock.js";
34
34
  import { Console } from "../core/console.js";
35
35
  import { Segment } from "../core/segment.js";
36
36
  import { SurfacePainter } from "../core/paint.js";
37
- import { fitHeight } from "../core/protocol.js";
37
+ import { fitHeight, render } from "../core/protocol.js";
38
38
  import { hostEnvironment } from "./host-environment.js";
39
39
  import { InputParser, keyName, parseKeyName } from "./input.js";
40
40
  // [LAW:one-source-of-truth] How a frame draws — its glyphs, the style names
@@ -381,7 +381,7 @@ export class App {
381
381
  const height = this.painter.height(options.height.rows);
382
382
  // [LAW:no-ambient-temporal-coupling] The frame owner reads the clock,
383
383
  // once a frame; the view is handed the time as data.
384
- const lines = Segment.splitLines(this.view(this.clock.now()).render({ ...options, height }));
384
+ const lines = Segment.splitLines(render(this.view(this.clock.now()), { ...options, height }));
385
385
  // The app set the budget, so the app shapes what comes back: no deeper
386
386
  // than the terminal — a taller frame would scroll the rows the painter
387
387
  // goes back over — and, as a region, exactly that deep.
package/dist/index.d.ts CHANGED
@@ -15,8 +15,11 @@ export { transposePalette, themeKeyForRoot, isAnchored, ANCHORED_ROOTS, } from "
15
15
  export { lighten, darken, relativeLuminance, contrastRatio, contrastFor, ensureContrast, ensureDrawn, drawnColour, } from "./themes/colorMath.js";
16
16
  export { ColorRamp } from "./themes/ramp.js";
17
17
  export type { ColorStop } from "./themes/ramp.js";
18
- export { EASES, Phase, beat, cubicBezier, parseEase, steps } from "./core/easing.js";
19
- export type { Beat, Ease, StepPosition } from "./core/easing.js";
18
+ export { EASES, Phase, beat, cubicBezier, parseEase, spring, steps } from "./core/easing.js";
19
+ export type { Beat, Ease, SpringOptions, StepPosition, Timing } from "./core/easing.js";
20
+ export { Animation } from "./core/animation.js";
21
+ export { tween } from "./core/tween.js";
22
+ export { Timeline } from "./core/timeline.js";
20
23
  export { frameRate, systemClock } from "./core/clock.js";
21
24
  export type { Clock, FrameRate } from "./core/clock.js";
22
25
  export { Style, StyleSyntaxError, StyleStack, Theme, NULL_STYLE, DEFAULT_STYLES, } from "./core/style.js";
@@ -27,8 +30,8 @@ export type { Anchor } from "./core/anchor.js";
27
30
  export { Box } from "./core/box.js";
28
31
  export type { EdgeChars, ContentChars, RowLevel, SubstituteOptions } from "./core/box.js";
29
32
  export { ASCII, ASCII2, ASCII_DOUBLE_HEAD, SQUARE, SQUARE_DOUBLE_HEAD, MINIMAL, MINIMAL_HEAVY_HEAD, MINIMAL_DOUBLE_HEAD, SIMPLE, SIMPLE_HEAD, SIMPLE_HEAVY, HORIZONTALS, ROUNDED, HEAVY, HEAVY_EDGE, HEAVY_HEAD, DOUBLE, DOUBLE_EDGE, MARKDOWN, } from "./core/box.js";
30
- export { isRenderable, isMeasurable, withCellWidth, withBoundedWidth, insetHeight, stackedHeight, regionRows, fitHeight, getStyle, } from "./core/protocol.js";
31
- export type { RenderOptions, OverflowMethod, Height, Renderable, Measurable, StyleErrorHandler, } from "./core/protocol.js";
33
+ export { render, isRenderable, isMeasurable, withCellWidth, withBoundedWidth, insetHeight, stackedHeight, regionRows, fitHeight, getStyle, } from "./core/protocol.js";
34
+ export type { RenderOptions, OverflowMethod, Height, Renderable, Measurable, Composite, Presence, StyleErrorHandler, } from "./core/protocol.js";
32
35
  export type { Unsubscribe } from "./core/subscription.js";
33
36
  export { Measurement, measureRenderables } from "./core/measure.js";
34
37
  export { Span, RichText } from "./core/text.js";
@@ -55,8 +58,8 @@ export { Console } from "./core/console.js";
55
58
  export type { ConsoleOptions, ConsoleSink, ConsoleStream, ConsoleEnvironment, LiveRegion, PrintOptions, } from "./core/console.js";
56
59
  export type { Env } from "./core/env.js";
57
60
  export { Constrain } from "./renderables/constrain.js";
58
- export { Effected, confined, drawnOf, nextChangeOf, onBeat, withNextChange } from "./renderables/effect.js";
59
- export type { CellColors, Drawn, Effect, EffectCell, EffectedOptions, Pair, Placed } from "./renderables/effect.js";
61
+ export { Effected, confined, drawnOf, onBeat } from "./renderables/effect.js";
62
+ export type { CellColors, Drawn, Effect, EffectCell, EffectedOptions, EffectFrame, Pair, Placed } from "./renderables/effect.js";
60
63
  export { EFFECT_CATALOGUE, EFFECT_CURVES, EFFECT_LIGHTS, dissolveOut, fadeIn, onColors, pulse, shimmer, sparkle, steppedInk, wheel, } from "./renderables/effects.js";
61
64
  export type { ColorStep, Curve, EffectTarget, Field, Loop, LoopName, Touch, Transition, TransitionName } from "./renderables/effects.js";
62
65
  export { Align } from "./renderables/align.js";
@@ -77,7 +80,7 @@ export { ProgressBar } from "./renderables/progressBar.js";
77
80
  export type { ProgressBarOptions } from "./renderables/progressBar.js";
78
81
  export { Spinner } from "./renderables/spinner.js";
79
82
  export type { SpinnerOptions } from "./renderables/spinner.js";
80
- export { FrameSequence, Frames } from "./renderables/frames.js";
83
+ export { FrameSequence, Frames, frameAt } from "./renderables/frames.js";
81
84
  export type { Frame, FramesOptions } from "./renderables/frames.js";
82
85
  export { Table, Column } from "./renderables/table.js";
83
86
  export type { TableOptions, ColumnOptions } from "./renderables/table.js";
package/dist/index.js CHANGED
@@ -29,7 +29,16 @@ export { lighten, darken, relativeLuminance, contrastRatio, contrastFor, ensureC
29
29
  export { ColorRamp } from "./themes/ramp.js";
30
30
  // Progress in [0, 1]: phases turn seconds into it, eases reshape it. One
31
31
  // vocabulary for ramps and time-based effects alike, CSS's names throughout.
32
- export { EASES, Phase, beat, cubicBezier, parseEase, steps } from "./core/easing.js";
32
+ export { EASES, Phase, beat, cubicBezier, parseEase, spring, steps } from "./core/easing.js";
33
+ // A value for a time, and when it next changes: the one type every kind of
34
+ // motion is an instance of — beats, frame sequences, spinners, effects.
35
+ export { Animation } from "./core/animation.js";
36
+ // A number eased from one value to another over seconds, then held: the
37
+ // value animation a fill, a counter or a width moves by instead of jumping.
38
+ export { tween } from "./core/tween.js";
39
+ // The map from the host's clock to the time an animation is asked for:
40
+ // pause, reverse, scrub and speed, once, above every kind of motion.
41
+ export { Timeline } from "./core/timeline.js";
33
42
  // The time source and timer whoever draws frames is handed, and the one
34
43
  // parser of the rate it ticks at.
35
44
  export { frameRate, systemClock } from "./core/clock.js";
@@ -38,7 +47,7 @@ export { Segment, ControlType, } from "./core/segment.js";
38
47
  export { Box } from "./core/box.js";
39
48
  export { ASCII, ASCII2, ASCII_DOUBLE_HEAD, SQUARE, SQUARE_DOUBLE_HEAD, MINIMAL, MINIMAL_HEAVY_HEAD, MINIMAL_DOUBLE_HEAD, SIMPLE, SIMPLE_HEAD, SIMPLE_HEAVY, HORIZONTALS, ROUNDED, HEAVY, HEAVY_EDGE, HEAVY_HEAD, DOUBLE, DOUBLE_EDGE, MARKDOWN, } from "./core/box.js";
40
49
  // Protocol
41
- export { isRenderable, isMeasurable, withCellWidth, withBoundedWidth, insetHeight, stackedHeight, regionRows, fitHeight, getStyle, } from "./core/protocol.js";
50
+ export { render, isRenderable, isMeasurable, withCellWidth, withBoundedWidth, insetHeight, stackedHeight, regionRows, fitHeight, getStyle, } from "./core/protocol.js";
42
51
  // Measurement
43
52
  export { Measurement, measureRenderables } from "./core/measure.js";
44
53
  // Text
@@ -69,7 +78,7 @@ export { SPINNERS, DEFAULT_SPINNER } from "./core/spinnerData.js";
69
78
  export { Console } from "./core/console.js";
70
79
  // Renderables
71
80
  export { Constrain } from "./renderables/constrain.js";
72
- export { Effected, confined, drawnOf, nextChangeOf, onBeat, withNextChange } from "./renderables/effect.js";
81
+ export { Effected, confined, drawnOf, onBeat } from "./renderables/effect.js";
73
82
  export { EFFECT_CATALOGUE, EFFECT_CURVES, EFFECT_LIGHTS, dissolveOut, fadeIn, onColors, pulse, shimmer, sparkle, steppedInk, wheel, } from "./renderables/effects.js";
74
83
  export { Align } from "./renderables/align.js";
75
84
  export { Padding } from "./renderables/padding.js";
@@ -81,7 +90,7 @@ export { Panel } from "./renderables/panel.js";
81
90
  export { Group } from "./renderables/group.js";
82
91
  export { ProgressBar } from "./renderables/progressBar.js";
83
92
  export { Spinner } from "./renderables/spinner.js";
84
- export { FrameSequence, Frames } from "./renderables/frames.js";
93
+ export { FrameSequence, Frames, frameAt } from "./renderables/frames.js";
85
94
  export { Table, Column } from "./renderables/table.js";
86
95
  export { Tree } from "./renderables/tree.js";
87
96
  export { Columns } from "./renderables/columns.js";
@@ -2,9 +2,9 @@
2
2
  * Align — wraps a renderable and places its output horizontally, as one block.
3
3
  */
4
4
  import { Segment } from "../core/segment.js";
5
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
5
+ import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
6
6
  import { type Alignment } from "../core/place.js";
7
- export declare class Align implements Renderable, Measurable {
7
+ export declare class Align implements Renderable, Measurable, Composite {
8
8
  readonly renderable: Renderable;
9
9
  readonly align: Alignment;
10
10
  constructor(renderable: Renderable, align?: Alignment);
@@ -13,4 +13,5 @@ export declare class Align implements Renderable, Measurable {
13
13
  minimum: number;
14
14
  maximum: number;
15
15
  };
16
+ contents(): Iterable<Renderable>;
16
17
  }
@@ -23,4 +23,7 @@ export class Align {
23
23
  measure(options) {
24
24
  return Measurement.get(options, this.renderable);
25
25
  }
26
+ *contents() {
27
+ yield this.renderable;
28
+ }
26
29
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { Segment } from "../core/segment.js";
12
12
  import type { PaddingDimensions } from "./padding.js";
13
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
13
+ import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
14
14
  export interface ColumnsOptions {
15
15
  expand?: boolean;
16
16
  equal?: boolean;
@@ -18,7 +18,7 @@ export interface ColumnsOptions {
18
18
  padding?: PaddingDimensions;
19
19
  columnFirst?: boolean;
20
20
  }
21
- export declare class Columns implements Renderable, Measurable {
21
+ export declare class Columns implements Renderable, Measurable, Composite {
22
22
  renderables: (Renderable & Partial<Measurable>)[];
23
23
  readonly expand: boolean;
24
24
  readonly equal: boolean;
@@ -27,6 +27,7 @@ export declare class Columns implements Renderable, Measurable {
27
27
  private readonly padding;
28
28
  constructor(items?: Iterable<unknown>, options?: ColumnsOptions);
29
29
  render(rawOptions: RenderOptions): Iterable<Segment>;
30
+ contents(): Iterable<Renderable>;
30
31
  measure(rawOptions: RenderOptions): {
31
32
  minimum: number;
32
33
  maximum: number;
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { Measurement } from "../core/measure.js";
12
12
  import { normalizePadding } from "./padding.js";
13
- import { withBoundedWidth, withCellWidth } from "../core/protocol.js";
13
+ import { render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
14
14
  import { cellCount } from "../core/cells.js";
15
15
  import { EmbeddedText, embed } from "./embed.js";
16
16
  import { Constrain } from "./constrain.js";
@@ -68,7 +68,12 @@ export class Columns {
68
68
  // single column is laid out. An unbounded width reaches the same place by
69
69
  // the same route, which is why the parse here is the bounded one.
70
70
  const options = withBoundedWidth(rawOptions, this);
71
- yield* this._grid(options).render(options);
71
+ yield* render(this._grid(options), options);
72
+ }
73
+ // The items, in the order they were given. Laid out column-first, the grid
74
+ // draws them in an order its width decides, and handed no column it has none.
75
+ contents() {
76
+ return this.renderables;
72
77
  }
73
78
  measure(rawOptions) {
74
79
  // The maximum is the grid's, which reports its width before `expand`: a
@@ -2,12 +2,13 @@
2
2
  * Constrain — wraps a renderable and constrains its maximum width.
3
3
  */
4
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 {
5
+ import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
6
+ export declare class Constrain implements Renderable, Measurable, Composite {
7
7
  readonly renderable: Renderable;
8
8
  readonly width: number | undefined;
9
9
  constructor(renderable: Renderable, width?: number);
10
10
  render(options: RenderOptions): Iterable<Segment>;
11
+ contents(): Iterable<Renderable>;
11
12
  measure(options: RenderOptions): {
12
13
  minimum: number;
13
14
  maximum: number;
@@ -2,6 +2,7 @@
2
2
  * Constrain — wraps a renderable and constrains its maximum width.
3
3
  */
4
4
  import { Measurement } from "../core/measure.js";
5
+ import { render } from "../core/protocol.js";
5
6
  export class Constrain {
6
7
  renderable;
7
8
  width;
@@ -18,7 +19,10 @@ export class Constrain {
18
19
  ...options,
19
20
  maxWidth: constrainedWidth,
20
21
  };
21
- yield* this.renderable.render(innerOptions);
22
+ yield* render(this.renderable, innerOptions);
23
+ }
24
+ *contents() {
25
+ yield this.renderable;
22
26
  }
23
27
  measure(options) {
24
28
  // Measured at the width it will be drawn at, as Rich's `update_width` does;