@promptctl/rich-js 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +7 -0
  2. package/THIRD-PARTY-NOTICES +29 -1
  3. package/dist/core/anchor.d.ts +5 -0
  4. package/dist/core/anchor.js +11 -4
  5. package/dist/core/animation.d.ts +243 -0
  6. package/dist/core/animation.js +375 -0
  7. package/dist/core/ansi.js +2 -2
  8. package/dist/core/box.d.ts +7 -7
  9. package/dist/core/cell-widths.d.ts +9 -0
  10. package/dist/core/cell-widths.js +140 -0
  11. package/dist/core/cells.d.ts +40 -24
  12. package/dist/core/cells.js +151 -66
  13. package/dist/core/clock.d.ts +2 -2
  14. package/dist/core/clock.js +6 -4
  15. package/dist/core/color.d.ts +27 -10
  16. package/dist/core/color.js +21 -29
  17. package/dist/core/console.d.ts +28 -17
  18. package/dist/core/console.js +48 -43
  19. package/dist/core/easing.d.ts +140 -10
  20. package/dist/core/easing.js +328 -50
  21. package/dist/core/emoji.d.ts +1 -1
  22. package/dist/core/emoji.js +1 -2
  23. package/dist/core/export-html.js +2 -1
  24. package/dist/core/export-lines.js +3 -3
  25. package/dist/core/highlighter.d.ts +9 -8
  26. package/dist/core/highlighter.js +3 -2
  27. package/dist/core/json.js +3 -2
  28. package/dist/core/markup.js +16 -12
  29. package/dist/core/measure.js +3 -3
  30. package/dist/core/memo.d.ts +11 -2
  31. package/dist/core/memo.js +16 -0
  32. package/dist/core/oklch.js +17 -6
  33. package/dist/core/osc8.d.ts +1 -1
  34. package/dist/core/osc8.js +4 -5
  35. package/dist/core/paint.d.ts +2 -1
  36. package/dist/core/paint.js +4 -3
  37. package/dist/core/pattern.d.ts +21 -0
  38. package/dist/core/pattern.js +21 -0
  39. package/dist/core/place.js +2 -1
  40. package/dist/core/pretty.js +11 -12
  41. package/dist/core/protocol.d.ts +136 -21
  42. package/dist/core/protocol.js +203 -16
  43. package/dist/core/render.d.ts +15 -2
  44. package/dist/core/render.js +27 -6
  45. package/dist/core/segment.d.ts +31 -6
  46. package/dist/core/segment.js +68 -53
  47. package/dist/core/spinnerData.d.ts +1 -1
  48. package/dist/core/state.d.ts +53 -0
  49. package/dist/core/state.js +82 -0
  50. package/dist/core/strip.d.ts +11 -13
  51. package/dist/core/strip.js +126 -113
  52. package/dist/core/style.d.ts +47 -14
  53. package/dist/core/style.js +74 -34
  54. package/dist/core/text.d.ts +5 -8
  55. package/dist/core/text.js +133 -133
  56. package/dist/core/timeline.d.ts +109 -0
  57. package/dist/core/timeline.js +180 -0
  58. package/dist/core/tween.d.ts +49 -0
  59. package/dist/core/tween.js +71 -0
  60. package/dist/core/view.d.ts +92 -0
  61. package/dist/core/view.js +83 -0
  62. package/dist/host/app.d.ts +49 -21
  63. package/dist/host/app.js +72 -38
  64. package/dist/host/input.js +3 -4
  65. package/dist/index.d.ts +30 -15
  66. package/dist/index.js +20 -6
  67. package/dist/node/traceback.js +4 -3
  68. package/dist/renderables/change.d.ts +42 -0
  69. package/dist/renderables/change.js +112 -0
  70. package/dist/renderables/columns.d.ts +1 -1
  71. package/dist/renderables/columns.js +3 -3
  72. package/dist/renderables/constrain.js +2 -1
  73. package/dist/renderables/effect.d.ts +155 -92
  74. package/dist/renderables/effect.js +203 -136
  75. package/dist/renderables/effects.d.ts +137 -24
  76. package/dist/renderables/effects.js +276 -113
  77. package/dist/renderables/embed.d.ts +1 -1
  78. package/dist/renderables/embed.js +18 -9
  79. package/dist/renderables/flexStrip.js +2 -1
  80. package/dist/renderables/frames.d.ts +45 -43
  81. package/dist/renderables/frames.js +57 -45
  82. package/dist/renderables/glyph-effect.d.ts +158 -0
  83. package/dist/renderables/glyph-effect.js +237 -0
  84. package/dist/renderables/glyph-effects.d.ts +147 -0
  85. package/dist/renderables/glyph-effects.js +283 -0
  86. package/dist/renderables/group.d.ts +1 -1
  87. package/dist/renderables/group.js +2 -2
  88. package/dist/renderables/html-entities.d.ts +8 -0
  89. package/dist/renderables/html-entities.js +386 -0
  90. package/dist/renderables/layout.d.ts +10 -9
  91. package/dist/renderables/layout.js +42 -35
  92. package/dist/renderables/lifecycle.d.ts +77 -0
  93. package/dist/renderables/lifecycle.js +132 -0
  94. package/dist/renderables/list-view.d.ts +2 -2
  95. package/dist/renderables/list-view.js +11 -8
  96. package/dist/renderables/live.d.ts +36 -21
  97. package/dist/renderables/live.js +89 -42
  98. package/dist/renderables/markdown-parse.d.ts +109 -0
  99. package/dist/renderables/markdown-parse.js +1427 -0
  100. package/dist/renderables/markdown-url.d.ts +19 -0
  101. package/dist/renderables/markdown-url.js +314 -0
  102. package/dist/renderables/markdown.d.ts +5 -7
  103. package/dist/renderables/markdown.js +356 -683
  104. package/dist/renderables/padding.d.ts +7 -20
  105. package/dist/renderables/padding.js +29 -50
  106. package/dist/renderables/panel.d.ts +30 -32
  107. package/dist/renderables/panel.js +65 -109
  108. package/dist/renderables/progress.d.ts +83 -32
  109. package/dist/renderables/progress.js +186 -90
  110. package/dist/renderables/progressBar.d.ts +22 -12
  111. package/dist/renderables/progressBar.js +33 -10
  112. package/dist/renderables/prompt.js +3 -2
  113. package/dist/renderables/python-space.d.ts +13 -0
  114. package/dist/renderables/python-space.js +25 -0
  115. package/dist/renderables/scroll.d.ts +50 -0
  116. package/dist/renderables/scroll.js +106 -0
  117. package/dist/renderables/spinner.d.ts +57 -24
  118. package/dist/renderables/spinner.js +101 -49
  119. package/dist/renderables/status.js +1 -1
  120. package/dist/renderables/syntax.d.ts +10 -9
  121. package/dist/renderables/syntax.js +5 -3
  122. package/dist/renderables/table.d.ts +48 -46
  123. package/dist/renderables/table.js +170 -208
  124. package/dist/renderables/traceback.d.ts +3 -3
  125. package/dist/renderables/traceback.js +12 -10
  126. package/dist/renderables/tree.d.ts +28 -25
  127. package/dist/renderables/tree.js +108 -58
  128. package/dist/renderables/viewport.d.ts +77 -38
  129. package/dist/renderables/viewport.js +156 -72
  130. package/dist/template-bindings/animation-funcs.d.ts +78 -9
  131. package/dist/template-bindings/animation-funcs.js +132 -14
  132. package/dist/template-bindings/index.d.ts +5 -5
  133. package/dist/template-bindings/index.js +7 -6
  134. package/dist/themes/ramp.d.ts +62 -4
  135. package/dist/themes/ramp.js +82 -4
  136. package/dist/themes/registry.js +5 -7
  137. package/dist/widgets/button.js +3 -2
  138. package/dist/widgets/checkbox.js +4 -3
  139. package/dist/widgets/dropdown.d.ts +3 -1
  140. package/dist/widgets/dropdown.js +20 -15
  141. package/dist/widgets/event-router.js +4 -2
  142. package/dist/widgets/focus-manager.d.ts +23 -14
  143. package/dist/widgets/focus-manager.js +34 -33
  144. package/dist/widgets/hit.d.ts +6 -0
  145. package/dist/widgets/hit.js +8 -1
  146. package/dist/widgets/index.d.ts +1 -1
  147. package/dist/widgets/observable.d.ts +23 -0
  148. package/dist/widgets/observable.js +43 -0
  149. package/dist/widgets/slider.d.ts +1 -1
  150. package/dist/widgets/slider.js +12 -3
  151. package/dist/widgets/static-item.d.ts +1 -1
  152. package/dist/widgets/static-item.js +6 -7
  153. package/dist/widgets/text-input.d.ts +53 -47
  154. package/dist/widgets/text-input.js +456 -399
  155. package/dist/widgets/themed-widget.js +2 -1
  156. package/dist/widgets/toggle.js +4 -3
  157. package/dist/widgets/types.d.ts +10 -4
  158. package/dist/widgets/types.js +8 -2
  159. package/dist/widgets/widget-app.d.ts +32 -6
  160. package/dist/widgets/widget-app.js +74 -27
  161. package/dist/widgets/widget-base.d.ts +2 -2
  162. package/dist/widgets/widget-base.js +29 -21
  163. package/package.json +9 -5
@@ -0,0 +1,180 @@
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
+ * A stagger — a Group's children pulsing in sequence, a light walking down
36
+ * a Table's rows — is the same map once per child: `stagger` runs each
37
+ * child's animation on a timeline playing from its index times the delay,
38
+ * and the group changes when the soonest child does, each child's answer
39
+ * carried onto the group's clock by `run`. Before its turn a child is asked
40
+ * for a time before 0, which is its own business: a loop is shifted in
41
+ * phase. Tree, Table, Group and Layout take the children's frames as they
42
+ * take any renderables, through `Animation.map`, so none of them knows a
43
+ * stagger exists. [LAW:composability] A lifecycle is not staggered here: it
44
+ * carries its turn as `entered`, on the host's clock its `leave` and `gone`
45
+ * take, so moving it onto a shifted clock would lose them
46
+ * ([LAW:one-source-of-truth] for when it arrives).
47
+ *
48
+ * [LAW:no-ambient-temporal-coupling] Nothing here reads a clock. The host
49
+ * time is an argument everywhere, and a timeline is built from one the host
50
+ * hands in, so the same host time maps to the same animation time at one
51
+ * sample a second or sixty.
52
+ */
53
+ import { Animation } from "./animation.js";
54
+ import { firstReaching } from "./easing.js";
55
+ /** Throws, naming `what`, unless `seconds` is a finite number of seconds. */
56
+ function finiteSeconds(what, seconds) {
57
+ if (!Number.isFinite(seconds))
58
+ throw new RangeError(`${what} must be a finite number of seconds, got ${seconds}`);
59
+ return seconds;
60
+ }
61
+ /** [LAW:single-enforcer] A host time is a finite number of seconds, refused once, at every operation that takes one. */
62
+ const hostTime = (host) => finiteSeconds("Timeline host time", host);
63
+ /**
64
+ * [LAW:parse-dont-validate] A rate is a finite number other than zero,
65
+ * settled where it is given: a rate of zero would be a second way to hold a
66
+ * timeline still, and `pause` is the one.
67
+ */
68
+ function playbackRate(rate) {
69
+ if (!Number.isFinite(rate) || rate === 0)
70
+ throw new RangeError(`Timeline rate must be finite and not zero, got ${rate}`);
71
+ return rate;
72
+ }
73
+ /** `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. */
74
+ function anchored(timeline, host, time, rate) {
75
+ const origin = hostTime(host);
76
+ return timeline.playing ? { playing: true, origin, time, rate } : { playing: false, time, rate };
77
+ }
78
+ /** `animation` asked at the time `playing` maps each host time to, saying on host time when it changes. */
79
+ function played(animation, { origin, time, rate }) {
80
+ const mapped = (host) => time + rate * (host - origin);
81
+ const forward = rate > 0;
82
+ // Forwards, the host next sees the animation's own next change, and the
83
+ // value began when the animation says it did; backwards, it next sees a
84
+ // change where the value began, and the value began where the animation
85
+ // says it ends. A hold is `[lastChange, nextChange)`, so the far side of
86
+ // an edge is at it going up and strictly under it coming down.
87
+ const ahead = forward ? (t) => animation.nextChange(t) : (t) => animation.lastChange(t);
88
+ const behind = forward ? (t) => animation.lastChange(t) : (t) => animation.nextChange(t);
89
+ const past = forward ? (t, edge) => t >= edge : (t, edge) => t < edge;
90
+ // The first host moment the map carries past `edge`, an animation time.
91
+ // An edge at infinity, or one so far off at this rate that no host float
92
+ // reaches it, is never reached, forwards or backwards: the sum's own sign
93
+ // says which, as a host moment past either end of the host's clock.
94
+ const hostOf = (edge) => {
95
+ const sum = origin + (edge - time) / rate;
96
+ return Number.isFinite(sum) ? firstReaching(sum, (host) => past(mapped(host), edge)) : sum;
97
+ };
98
+ return Animation.of({
99
+ at: (host) => animation.at(mapped(hostTime(host))),
100
+ nextChange(host) {
101
+ const h = hostTime(host);
102
+ const t = mapped(h);
103
+ const edge = ahead(t);
104
+ return edge === t ? h : Math.max(h, hostOf(edge));
105
+ },
106
+ lastChange(host) {
107
+ const h = hostTime(host);
108
+ const t = mapped(h);
109
+ const edge = behind(t);
110
+ return edge === t ? h : Math.min(h, hostOf(edge));
111
+ },
112
+ reduced: () => played(animation.reduced(), { playing: true, origin, time, rate }),
113
+ });
114
+ }
115
+ export const Timeline = {
116
+ /** Playing from host time `origin`, at which the animation time is 0, at `rate` animation seconds a host second. */
117
+ play(origin, rate = 1) {
118
+ return { playing: true, origin: finiteSeconds("Timeline.play origin", origin), time: 0, rate: playbackRate(rate) };
119
+ },
120
+ /** The animation time at host time `host`. */
121
+ at(timeline, host) {
122
+ const h = hostTime(host);
123
+ return timeline.playing ? timeline.time + timeline.rate * (h - timeline.origin) : timeline.time;
124
+ },
125
+ /** `timeline` held at the animation time it maps `host` to. Pausing a paused timeline holds the same time. */
126
+ pause(timeline, host) {
127
+ return { playing: false, time: Timeline.at(timeline, host), rate: timeline.rate };
128
+ },
129
+ /** `timeline` playing from `host` at the animation time it maps `host` to, at its rate. Resuming a playing timeline is the same map. */
130
+ resume(timeline, host) {
131
+ return { playing: true, origin: hostTime(host), time: Timeline.at(timeline, host), rate: timeline.rate };
132
+ },
133
+ /** `timeline` with the animation at `time` at host time `host`, playing or held as it was. */
134
+ seek(timeline, host, time) {
135
+ return anchored(timeline, host, finiteSeconds("Timeline.seek time", time), timeline.rate);
136
+ },
137
+ /** `timeline` at `rate` from host time `host` on, with no jump: the animation time at `host` is what it was. */
138
+ withRate(timeline, host, rate) {
139
+ return anchored(timeline, host, Timeline.at(timeline, host), playbackRate(rate));
140
+ },
141
+ /**
142
+ * `animation` on host time: its value at the time `timeline` maps each
143
+ * host time to, changing at the host moment the mapped time reaches the
144
+ * animation's own answer — backwards, when it drops under where the value
145
+ * began. Paused, the value never changes and never did. The value is the
146
+ * animation's own, on no clock: placed, it is stamped at the frame's time
147
+ * (`placedRender`, `./animation.ts`) with this animation's answers, so a
148
+ * value's contents follow the frame's clock and not this map.
149
+ * Reduced, it is the animation reduced on the same timeline: what shows
150
+ * still follows the timeline, and only the motion is gone.
151
+ */
152
+ run(timeline, animation) {
153
+ if (timeline.playing)
154
+ return played(animation, timeline);
155
+ return Animation.of({
156
+ at: (host) => animation.at(Timeline.at(timeline, host)),
157
+ nextChange: (host) => (hostTime(host), Number.POSITIVE_INFINITY),
158
+ lastChange: (host) => (hostTime(host), Number.NEGATIVE_INFINITY),
159
+ reduced: () => Timeline.run(timeline, animation.reduced()),
160
+ });
161
+ },
162
+ /**
163
+ * Every one of `animations` at once, each on a timeline playing from its
164
+ * index times `delay` seconds after the earliest, their values in order, on
165
+ * a clock of the stagger's own from 0. It changes when the soonest of them
166
+ * does, on that clock (`Animation.all` of `run`s). A delay of 0 starts them
167
+ * together and a negative one starts the last at 0 and the first last.
168
+ * Reduced, the offsets are the cascade's choreography, which is motion: every
169
+ * child reduced at once, so from 0 everyone has arrived.
170
+ */
171
+ stagger(animations, delay) {
172
+ finiteSeconds("Timeline.stagger delay", delay);
173
+ const span = finiteSeconds("Timeline.stagger delay times the last index", Math.max(0, animations.length - 1) * delay);
174
+ // [LAW:dataflow-not-control-flow] Whichever way the cascade runs, every
175
+ // origin is shifted by the lowest of them, so the earliest child plays from 0.
176
+ const earliest = Math.min(0, span);
177
+ const cascade = Animation.all(animations.map((animation, i) => Timeline.run(Timeline.play(i * delay - earliest), animation)));
178
+ return Animation.of({ ...cascade, reduced: () => Animation.all(animations.map((animation) => animation.reduced())) });
179
+ },
180
+ };
@@ -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 6 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,71 @@
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 6 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 Animation.of({
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
+ // A one-shot, reduced, cuts from where it starts to where it arrives.
69
+ reduced: () => Animation.cut(from, to),
70
+ });
71
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * View — what a host draws, and the frame it draws from it at a host time on
3
+ * its timeline: the lines, and what they say about when they change.
4
+ *
5
+ * A host — a `Live`, an `App` — reads its clock once a frame and draws its
6
+ * view for that moment. The view is a renderable, drawn as it is now (a
7
+ * table mutated in place between frames), at the time the host's `Timeline`
8
+ * (`./timeline.ts`) maps its clock to, written on the frame
9
+ * (`RenderOptions.frame`): every animation placed anywhere inside it draws
10
+ * at that time and stamps its cells with when it changes, on that clock
11
+ * (`placedRender`, `./animation.ts`). A program may hand a function for one
12
+ * instead, asked each frame so a view of its state is built from the state
13
+ * as it is then. It is handed no time, and `hostView` makes it the
14
+ * renderable it stands for, so a host holds one shape of view and draws it
15
+ * one way. [LAW:one-type-per-behavior]
16
+ *
17
+ * So a view says when it changes only through what it draws: what moves in
18
+ * it is an animation placed there — `Animation.continuous` for a value
19
+ * computed from the time — and never a time handed to the program.
20
+ * [LAW:one-source-of-truth] Pausing, reversing, scrubbing and speeding up
21
+ * what a host shows are changes to one value the host holds and not to
22
+ * anything it draws. What the drawn frame says about when it changes is on
23
+ * the timeline's clock, and `drawView` carries it back onto the host's by
24
+ * running the frame through the same timeline: the frame is a sample held
25
+ * for the intersection of its cells' holds (`Segment.lastChange`,
26
+ * `Segment.nextChange`) and the holds it was told that no cell carries
27
+ * (`RenderOptions.onHold`), and `Timeline.run` is the one place a hold is
28
+ * mapped onto host time, forwards, backwards or paused.
29
+ *
30
+ * What a host does with that is its own policy, and it reads it off the
31
+ * `DrawnView`. A view may also change without saying so — it reads state
32
+ * the host cannot see — so a host that polls for such a change, a `Live`'s
33
+ * auto-refresh, polls every frame.
34
+ *
35
+ * Tier 7 of `src/core/`: `./animation.ts`'s `Animation`, `./timeline.ts`,
36
+ * `./segment.ts`, `./protocol.ts` and `./style.ts`'s `Hold`, nothing else.
37
+ * [LAW:one-way-deps]
38
+ */
39
+ import { type FrameHost, type Renderable, type RenderOptions } from "./protocol.js";
40
+ import { Segment } from "./segment.js";
41
+ import { Timeline } from "./timeline.js";
42
+ /**
43
+ * What a host draws: a renderable — an animation of renderables among them,
44
+ * or anything holding one — or a function asked for one each frame.
45
+ */
46
+ export type View = Renderable | (() => Renderable);
47
+ /** `view` as the renderable a host holds: a function is asked each time it is drawn. */
48
+ export declare function hostView(view: View): Renderable;
49
+ /**
50
+ * A host's time, its clock read once: `now` on that clock; `epoch`, the
51
+ * moment on it the host began; and `timeline`, the map every view is drawn
52
+ * through. Handed none, a host's timeline starts as `Timeline.play(epoch)`,
53
+ * so a frame is drawn for the seconds since `epoch` until the timeline is
54
+ * paused, sought, re-rated or replaced.
55
+ */
56
+ export interface HostTime {
57
+ readonly now: number;
58
+ readonly epoch: number;
59
+ readonly timeline: Timeline;
60
+ }
61
+ /**
62
+ * The time a frame drawn at `time` is drawn for, on the clock its cells are
63
+ * stamped on: the time the timeline maps `now` to. [LAW:single-enforcer]
64
+ * `drawView` draws at it and hands it to the frame as
65
+ * `RenderOptions.frame`, and a host reads it here for anything heard between
66
+ * frames — a key — so a moment a widget keeps and the frame it is compared
67
+ * against are on one clock.
68
+ */
69
+ export declare function frameTime(time: HostTime): number;
70
+ /** What a frame drawn at host time `t` says about when it changes. */
71
+ export interface FrameSays {
72
+ /** The host time the frame was drawn at. */
73
+ readonly t: number;
74
+ /** The first host moment, never before `t`, at which the frame may stop showing this: `t` while it moves, `Infinity` once it never will. */
75
+ readonly nextChange: number;
76
+ }
77
+ /** A frame drawn from a view: its lines, and what it says. */
78
+ export interface DrawnView extends FrameSays {
79
+ readonly lines: Segment[][];
80
+ }
81
+ /** What a frame not yet drawn at host time `t` says: nothing. */
82
+ export declare const undrawn: (t: number) => FrameSays;
83
+ /**
84
+ * `view` drawn by `drawnBy` at `time` under `options`, its lines shaped by
85
+ * `draw` — the host's own render under those options, with its height and
86
+ * overflow — and when the frame changes on the host's clock: the hold its
87
+ * cells say, carried back through the timeline. [LAW:one-source-of-truth]
88
+ * `options` is the one place the frame reads the reader's preference for
89
+ * motion, handed on to `draw` with the frame: the time it is drawn for
90
+ * (`frameTime`) and `drawnBy`, the host whose time that is.
91
+ */
92
+ export declare function drawView(view: Renderable, time: HostTime, drawnBy: FrameHost, options: RenderOptions, draw: (renderable: Renderable, options: RenderOptions) => Segment[][]): DrawnView;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * View — what a host draws, and the frame it draws from it at a host time on
3
+ * its timeline: the lines, and what they say about when they change.
4
+ *
5
+ * A host — a `Live`, an `App` — reads its clock once a frame and draws its
6
+ * view for that moment. The view is a renderable, drawn as it is now (a
7
+ * table mutated in place between frames), at the time the host's `Timeline`
8
+ * (`./timeline.ts`) maps its clock to, written on the frame
9
+ * (`RenderOptions.frame`): every animation placed anywhere inside it draws
10
+ * at that time and stamps its cells with when it changes, on that clock
11
+ * (`placedRender`, `./animation.ts`). A program may hand a function for one
12
+ * instead, asked each frame so a view of its state is built from the state
13
+ * as it is then. It is handed no time, and `hostView` makes it the
14
+ * renderable it stands for, so a host holds one shape of view and draws it
15
+ * one way. [LAW:one-type-per-behavior]
16
+ *
17
+ * So a view says when it changes only through what it draws: what moves in
18
+ * it is an animation placed there — `Animation.continuous` for a value
19
+ * computed from the time — and never a time handed to the program.
20
+ * [LAW:one-source-of-truth] Pausing, reversing, scrubbing and speeding up
21
+ * what a host shows are changes to one value the host holds and not to
22
+ * anything it draws. What the drawn frame says about when it changes is on
23
+ * the timeline's clock, and `drawView` carries it back onto the host's by
24
+ * running the frame through the same timeline: the frame is a sample held
25
+ * for the intersection of its cells' holds (`Segment.lastChange`,
26
+ * `Segment.nextChange`) and the holds it was told that no cell carries
27
+ * (`RenderOptions.onHold`), and `Timeline.run` is the one place a hold is
28
+ * mapped onto host time, forwards, backwards or paused.
29
+ *
30
+ * What a host does with that is its own policy, and it reads it off the
31
+ * `DrawnView`. A view may also change without saying so — it reads state
32
+ * the host cannot see — so a host that polls for such a change, a `Live`'s
33
+ * auto-refresh, polls every frame.
34
+ *
35
+ * Tier 7 of `src/core/`: `./animation.ts`'s `Animation`, `./timeline.ts`,
36
+ * `./segment.ts`, `./protocol.ts` and `./style.ts`'s `Hold`, nothing else.
37
+ * [LAW:one-way-deps]
38
+ */
39
+ import { render } from "./protocol.js";
40
+ import { Segment } from "./segment.js";
41
+ import { Timeline } from "./timeline.js";
42
+ /** `view` as the renderable a host holds: a function is asked each time it is drawn. */
43
+ export function hostView(view) {
44
+ return typeof view === "function" ? { render: (options) => render(view(), options) } : view;
45
+ }
46
+ /**
47
+ * The time a frame drawn at `time` is drawn for, on the clock its cells are
48
+ * stamped on: the time the timeline maps `now` to. [LAW:single-enforcer]
49
+ * `drawView` draws at it and hands it to the frame as
50
+ * `RenderOptions.frame`, and a host reads it here for anything heard between
51
+ * frames — a key — so a moment a widget keeps and the frame it is compared
52
+ * against are on one clock.
53
+ */
54
+ export function frameTime(time) {
55
+ return Timeline.at(time.timeline, time.now);
56
+ }
57
+ /** What a frame not yet drawn at host time `t` says: nothing. */
58
+ export const undrawn = (t) => ({ t, nextChange: Number.POSITIVE_INFINITY });
59
+ /** The hold of the frame `lines` draw, told `told` besides: the intersection of its cells' holds and it. */
60
+ const frameHold = (lines, told) => lines.reduce((hold, row) => ({ lastChange: Math.max(hold.lastChange, Segment.lastChange(row)), nextChange: Math.min(hold.nextChange, Segment.nextChange(row)) }), told);
61
+ /**
62
+ * `view` drawn by `drawnBy` at `time` under `options`, its lines shaped by
63
+ * `draw` — the host's own render under those options, with its height and
64
+ * overflow — and when the frame changes on the host's clock: the hold its
65
+ * cells say, carried back through the timeline. [LAW:one-source-of-truth]
66
+ * `options` is the one place the frame reads the reader's preference for
67
+ * motion, handed on to `draw` with the frame: the time it is drawn for
68
+ * (`frameTime`) and `drawnBy`, the host whose time that is.
69
+ */
70
+ export function drawView(view, time, drawnBy, options, draw) {
71
+ const { now: host, timeline } = time;
72
+ let told = { lastChange: Number.NEGATIVE_INFINITY, nextChange: Number.POSITIVE_INFINITY };
73
+ const onHold = (hold) => {
74
+ told = { lastChange: Math.max(told.lastChange, hold.lastChange), nextChange: Math.min(told.nextChange, hold.nextChange) };
75
+ };
76
+ const lines = draw(view, { ...options, frame: { time: frameTime(time), host: drawnBy }, onHold });
77
+ const hold = frameHold(lines, told);
78
+ // The frame as the sample of the view it is, on the timeline's clock; the
79
+ // timeline says when the host's clock leaves its hold.
80
+ const sample = { at: () => lines, nextChange: () => hold.nextChange, lastChange: () => hold.lastChange, reduced: () => sample };
81
+ const nextChange = Timeline.run(timeline, sample).nextChange(host);
82
+ return { t: host, lines, nextChange };
83
+ }
@@ -34,8 +34,10 @@ import { type Clock, type FrameRate } from "../core/clock.js";
34
34
  import { type ConsoleOptions } from "../core/console.js";
35
35
  import { Segment } from "../core/segment.js";
36
36
  import { type Surface } from "../core/paint.js";
37
- import { type Renderable } from "../core/protocol.js";
37
+ import { type FrameHost } from "../core/protocol.js";
38
38
  import type { Unsubscribe } from "../core/subscription.js";
39
+ import { Timeline } from "../core/timeline.js";
40
+ import { type View } from "../core/view.js";
39
41
  import { type KeyPress, type ScreenMouseEvent } from "./input.js";
40
42
  import type { TerminalHost } from "./terminal-host.js";
41
43
  /**
@@ -44,7 +46,7 @@ import type { TerminalHost } from "./terminal-host.js";
44
46
  * the program; `stopped` has handed it back for good.
45
47
  */
46
48
  export type AppPhase = "idle" | "running" | "suspended" | "stopped";
47
- declare const DRAW_OPTIONS: readonly ["asciiOnly", "theme", "onStyleError", "markup", "highlight", "highlighter"];
49
+ declare const DRAW_OPTIONS: readonly ["asciiOnly", "reducedMotion", "theme", "onStyleError", "markup", "highlight", "highlighter"];
48
50
  type DrawOption = (typeof DRAW_OPTIONS)[number];
49
51
  type DrawOptions = Readonly<Pick<ConsoleOptions, DrawOption>>;
50
52
  export interface AppOptions extends DrawOptions {
@@ -52,15 +54,28 @@ export interface AppOptions extends DrawOptions {
52
54
  readonly host: TerminalHost;
53
55
  readonly surface: Surface;
54
56
  /**
55
- * The app's root renderable, asked for once per frame, so a view of the
56
- * app's state is drawn from the state as it is at that frame. `t` is the
57
- * frame's time in seconds on the app's `clock`, which is what an effect is
58
- * sampled at.
57
+ * What the app draws: a renderable, or a function asked for one once per
58
+ * frame — so a view of the app's state is drawn from the state as it is at
59
+ * that frame. What moves in it is an `Animation` placed there, drawn at the
60
+ * frame's time on the app's `timeline`, which says when it changes, so the
61
+ * app knows when to paint again.
59
62
  */
60
- readonly view: (t: number) => Renderable;
63
+ readonly view: View;
61
64
  /** What the app reads the time from and ticks on. The platform's, unless given. */
62
65
  readonly clock?: Clock;
63
- /** How often the app paints while something is animating. 30 frames a second, unless given. */
66
+ /**
67
+ * The map from the clock to the time the view is drawn for (`Timeline`).
68
+ * Playing from 0 when the host is made, at the clock's pace, unless given
69
+ * — so a tween in the view starts when the host is made, not when the clock
70
+ * began; `app.timeline` reads and replaces it.
71
+ */
72
+ readonly timeline?: Timeline;
73
+ /**
74
+ * How often the app paints while what it shows moves every moment — a
75
+ * frame that says it moves. A frame held until a
76
+ * moment is painted at that moment, whatever the rate. 30 frames a second,
77
+ * unless given.
78
+ */
64
79
  readonly rate?: FrameRate;
65
80
  /**
66
81
  * The keys that stop the app, by `keyName`, heard before any `onKey`
@@ -87,14 +102,17 @@ type WheelTurn = Extract<ScreenMouseEvent, {
87
102
  }>;
88
103
  /** A pointer event `onMouse` hears: every one but the vertical wheel, which is `onWheel`'s. */
89
104
  export type AppMouseEvent = Exclude<ScreenMouseEvent, WheelTurn>;
90
- export declare class App {
105
+ export declare class App implements FrameHost {
91
106
  private readonly host;
92
107
  private readonly surface;
93
108
  private readonly view;
94
- private readonly clock;
109
+ private readonly _clock;
110
+ private readonly _epoch;
111
+ private _timeline;
95
112
  private _rate;
96
- private readonly animations;
97
113
  private ticking;
114
+ private said;
115
+ private waking;
98
116
  private readonly console;
99
117
  private _phase;
100
118
  private _frame;
@@ -110,7 +128,18 @@ export declare class App {
110
128
  private settle;
111
129
  constructor(options: AppOptions);
112
130
  get phase(): AppPhase;
113
- /** How often the app paints while something is animating. */
131
+ /** The clock the app reads the time from: where the host time a `Timeline` operation takes is read. */
132
+ get clock(): Clock;
133
+ /** The map from the clock to the time the view is drawn for. */
134
+ get timeline(): Timeline;
135
+ /**
136
+ * Replace the timeline and paint the frame for the time it now maps the
137
+ * clock's now to, so a seek shows where it sought, once. Paused, the frame
138
+ * then holds: nothing drawn for a held time changes, so the app waits on
139
+ * nothing until the timeline plays again, and paints when asked.
140
+ */
141
+ set timeline(timeline: Timeline);
142
+ /** How often the app paints while what it shows moves every moment. */
114
143
  get rate(): FrameRate;
115
144
  /**
116
145
  * Set how often the app paints: an animation under way goes on at the new
@@ -119,6 +148,14 @@ export declare class App {
119
148
  set rate(rate: FrameRate);
120
149
  /** The rows on screen now, as painted: what a pointer event lands on. */
121
150
  get frame(): readonly (readonly Segment[])[];
151
+ /**
152
+ * The time a frame drawn now is drawn for (`RenderOptions.frame`): the
153
+ * clock's now, through the timeline. What a moment heard
154
+ * between frames — a key — is stamped with, so it is on the clock the
155
+ * frame is.
156
+ */
157
+ get frameTime(): number;
158
+ private _time;
122
159
  /**
123
160
  * Take the terminal, paint the first frame, and resolve when the app stops
124
161
  * — or reject with the error a frame threw or `fail` was given, the terminal
@@ -137,15 +174,6 @@ export declare class App {
137
174
  * for as long as it ran.
138
175
  */
139
176
  refresh(): void;
140
- /**
141
- * Paint at the app's `rate` until the returned function is called: what an
142
- * effect sampled at the frame's time asks for while it moves. The app ticks
143
- * while anything is animating and it holds the terminal, and stops when
144
- * nothing is, so a still app paints only when asked. The animation's first
145
- * frame does not wait out an interval: at one frame every two seconds, that
146
- * would hold its start off screen for two.
147
- */
148
- animate(): Unsubscribe;
149
177
  /**
150
178
  * Hand the terminal to whatever launched the program, and take it back and
151
179
  * repaint when that resumes it. What sends the app here is the app's own —