@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.
- package/README.md +7 -0
- package/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +243 -0
- package/dist/core/animation.js +375 -0
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +40 -24
- package/dist/core/cells.js +151 -66
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +21 -29
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +48 -43
- package/dist/core/easing.d.ts +140 -10
- package/dist/core/easing.js +328 -50
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +3 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +3 -3
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/place.js +2 -1
- package/dist/core/pretty.js +11 -12
- package/dist/core/protocol.d.ts +136 -21
- package/dist/core/protocol.js +203 -16
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +27 -6
- package/dist/core/segment.d.ts +31 -6
- package/dist/core/segment.js +68 -53
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +11 -13
- package/dist/core/strip.js +126 -113
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +74 -34
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +133 -133
- package/dist/core/timeline.d.ts +109 -0
- package/dist/core/timeline.js +180 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +71 -0
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +72 -38
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +30 -15
- package/dist/index.js +20 -6
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +1 -1
- package/dist/renderables/columns.js +3 -3
- package/dist/renderables/constrain.js +2 -1
- package/dist/renderables/effect.d.ts +155 -92
- package/dist/renderables/effect.js +203 -136
- package/dist/renderables/effects.d.ts +137 -24
- package/dist/renderables/effects.js +276 -113
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.js +2 -1
- package/dist/renderables/frames.d.ts +45 -43
- package/dist/renderables/frames.js +57 -45
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +1 -1
- package/dist/renderables/group.js +2 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +10 -9
- package/dist/renderables/layout.js +42 -35
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +2 -2
- package/dist/renderables/list-view.js +11 -8
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +89 -42
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1427 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +5 -7
- package/dist/renderables/markdown.js +356 -683
- package/dist/renderables/padding.d.ts +7 -20
- package/dist/renderables/padding.js +29 -50
- package/dist/renderables/panel.d.ts +30 -32
- package/dist/renderables/panel.js +65 -109
- package/dist/renderables/progress.d.ts +83 -32
- package/dist/renderables/progress.js +186 -90
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +33 -10
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +25 -0
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +57 -24
- package/dist/renderables/spinner.js +101 -49
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +5 -3
- package/dist/renderables/table.d.ts +48 -46
- package/dist/renderables/table.js +170 -208
- package/dist/renderables/traceback.d.ts +3 -3
- package/dist/renderables/traceback.js +12 -10
- package/dist/renderables/tree.d.ts +28 -25
- package/dist/renderables/tree.js +108 -58
- package/dist/renderables/viewport.d.ts +77 -38
- package/dist/renderables/viewport.js +156 -72
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +7 -6
- package/dist/themes/ramp.d.ts +62 -4
- package/dist/themes/ramp.js +82 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +23 -14
- package/dist/widgets/focus-manager.js +34 -33
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +1 -1
- package/dist/widgets/static-item.js +6 -7
- package/dist/widgets/text-input.d.ts +53 -47
- package/dist/widgets/text-input.js +456 -399
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +32 -6
- package/dist/widgets/widget-app.js +74 -27
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -21
- package/package.json +9 -5
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Viewport — a fixed number of rows onto
|
|
3
|
-
* offset that cannot leave the
|
|
2
|
+
* Viewport — a scroll position: a fixed number of rows onto taller content,
|
|
3
|
+
* scrolled by an offset that cannot leave it. It holds the position, not the
|
|
4
|
+
* content: content is drawn through it with `showing` (or `showingLines`), so
|
|
5
|
+
* a view rebuilt every frame keeps one viewport under content it builds anew.
|
|
6
|
+
* [LAW:one-source-of-truth] That is the one way to draw through it, so no
|
|
7
|
+
* viewport is built around content it never draws.
|
|
4
8
|
*
|
|
5
9
|
* Its rows, under the `Height` contract in `../core/protocol.ts`: the region's
|
|
6
10
|
* when it is given one; otherwise its own configured rows, or the content's
|
|
@@ -19,11 +23,18 @@
|
|
|
19
23
|
* against exists before a render: the rows come from the budget the render is
|
|
20
24
|
* handed, and the content's length from rendering it at the width it is handed.
|
|
21
25
|
* So `scrollTo`, `scrollBy` and `ensureVisible` do not move anything when they
|
|
22
|
-
* are called. Each queues a move, and the next render
|
|
23
|
-
* call order, clamping after every move, against the
|
|
24
|
-
* render found. Calling an operation before the first render, or three of them
|
|
26
|
+
* are called. Each queues a move, and the next render that draws the content
|
|
27
|
+
* resolves the queue in call order, clamping after every move, against the
|
|
28
|
+
* rows and length that render found. Calling an operation before the first render, or three of them
|
|
25
29
|
* between two renders, is the same code path as calling one.
|
|
26
30
|
*
|
|
31
|
+
* A render changes nothing (`State`, `../core/state.ts`), so the render draws
|
|
32
|
+
* the offset it resolved without keeping it, and hands the keeping to its pass
|
|
33
|
+
* (`afterPass`): once the frame has been drawn whole, the offset, the rows and
|
|
34
|
+
* length it was clamped against, and the followed range become the viewport's,
|
|
35
|
+
* and the moves it resolved leave the queue. A measurement, which draws
|
|
36
|
+
* nothing anyone sees, keeps nothing.
|
|
37
|
+
*
|
|
27
38
|
* Every cell of its rows, gutter included, is stamped as its own
|
|
28
39
|
* (`Segment.anchorLines`), around whatever anchors its content drew. So the
|
|
29
40
|
* composed frame names the viewport under any of its cells however deep it is
|
|
@@ -35,7 +46,16 @@ import { Segment } from "../core/segment.js";
|
|
|
35
46
|
import { Measurement } from "../core/measure.js";
|
|
36
47
|
import { cellCount, cellLen } from "../core/cells.js";
|
|
37
48
|
import { NULL_STYLE } from "../core/style.js";
|
|
38
|
-
import { drawable, fitHeight, getStyle, regionRows, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
49
|
+
import { drawable, drawsNothing, fitHeight, getStyle, regionRows, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
50
|
+
import { afterPass, render } from "../core/protocol.js";
|
|
51
|
+
import { ListState, State } from "../core/state.js";
|
|
52
|
+
/** `content` drawn whole at the width `options` offers, as lines a viewport slices. */
|
|
53
|
+
function drawnWhole(content) {
|
|
54
|
+
return (options) => {
|
|
55
|
+
const all = Segment.splitLines(render(content, options));
|
|
56
|
+
return { length: all.length, lines: (start, end) => all.slice(start, end) };
|
|
57
|
+
};
|
|
58
|
+
}
|
|
39
59
|
/** A heavy line for the thumb on a light line for the track. */
|
|
40
60
|
export const SCROLLBAR = {
|
|
41
61
|
thumb: { glyph: "┃", style: "scrollbar.thumb" },
|
|
@@ -47,30 +67,26 @@ const NO_SCROLLBAR = {
|
|
|
47
67
|
track: { glyph: "", style: NULL_STYLE },
|
|
48
68
|
};
|
|
49
69
|
export class Viewport {
|
|
50
|
-
/**
|
|
51
|
-
* What the viewport shows. Replace it to show new content from the same
|
|
52
|
-
* scroll position — a view rebuilt every frame keeps one `Viewport`.
|
|
53
|
-
*/
|
|
54
|
-
content;
|
|
55
70
|
rows;
|
|
56
71
|
scrollbar;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
_scroll = new State({
|
|
73
|
+
offset: 0,
|
|
74
|
+
// Nothing rendered, nothing shown: no move can take until the first render.
|
|
75
|
+
extent: { rows: 0, lines: 0 },
|
|
76
|
+
// No range followed yet: NaN equals nothing, so the first range followed moves.
|
|
77
|
+
followed: { start: NaN, end: NaN, rows: NaN },
|
|
78
|
+
});
|
|
79
|
+
_moves = new ListState();
|
|
80
|
+
constructor(options = {}) {
|
|
65
81
|
this.rows = options.rows;
|
|
66
82
|
this.scrollbar = options.scrollbar ?? NO_SCROLLBAR;
|
|
67
83
|
}
|
|
68
84
|
/**
|
|
69
|
-
* The first content line the last
|
|
70
|
-
* not reflected until the next
|
|
85
|
+
* The first content line the last frame showed. A move requested since is
|
|
86
|
+
* not reflected until the next frame resolves it.
|
|
71
87
|
*/
|
|
72
88
|
get offset() {
|
|
73
|
-
return this.
|
|
89
|
+
return this._scroll.get().offset;
|
|
74
90
|
}
|
|
75
91
|
/** Scroll so `line` is the first line shown. */
|
|
76
92
|
scrollTo(line) {
|
|
@@ -82,12 +98,13 @@ export class Viewport {
|
|
|
82
98
|
}
|
|
83
99
|
/**
|
|
84
100
|
* Whether `scrollBy(lines)` would move the viewport from where the moves
|
|
85
|
-
* queued so far leave it, against the rows and length the last
|
|
101
|
+
* queued so far leave it, against the rows and length the last frame
|
|
86
102
|
* found.
|
|
87
103
|
*/
|
|
88
104
|
canScrollBy(lines) {
|
|
89
|
-
const
|
|
90
|
-
|
|
105
|
+
const { scroll, moves } = this.asked();
|
|
106
|
+
const offset = resolve(scroll.offset, moves, scroll.extent);
|
|
107
|
+
return clampOffset(offset + lines, scroll.extent) !== offset;
|
|
91
108
|
}
|
|
92
109
|
/**
|
|
93
110
|
* Scroll the least distance that shows lines `start` up to but not
|
|
@@ -96,23 +113,42 @@ export class Viewport {
|
|
|
96
113
|
* the viewport shows its first line at the top.
|
|
97
114
|
*/
|
|
98
115
|
ensureVisible(start, end) {
|
|
99
|
-
this._moves.push((
|
|
116
|
+
this._moves.push(bringIntoView({ start, end }));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* `content` shown through this viewport from its scroll position. What it
|
|
120
|
+
* draws is anchored to this viewport, so the wheel finds it.
|
|
121
|
+
*
|
|
122
|
+
* `follow` is the lines of `content` to keep in view — a selection's — and
|
|
123
|
+
* is brought into view with `ensureVisible` when it differs from the range
|
|
124
|
+
* the last frame followed: when the selection moves, not on every frame,
|
|
125
|
+
* which would undo on its very render each scroll the mouse wheel queues. A
|
|
126
|
+
* followed range the last frame showed whole stays shown when the row count
|
|
127
|
+
* changes: the same lines can fall off the bottom of a viewport that got
|
|
128
|
+
* shorter. One the wheel had scrolled away from stays away. Absent, the
|
|
129
|
+
* range last followed stands.
|
|
130
|
+
*
|
|
131
|
+
* [LAW:effects-at-boundaries] What a frame drawing it resolves becomes the
|
|
132
|
+
* viewport's once that frame has been drawn whole, so it may be built and
|
|
133
|
+
* drawn inside a render — `ListView` draws its items to learn their lines
|
|
134
|
+
* and shows them this way.
|
|
135
|
+
*/
|
|
136
|
+
showing(content, follow) {
|
|
137
|
+
return {
|
|
138
|
+
render: (options) => this.draw(content, drawnWhole(content), follow, options),
|
|
139
|
+
measure: (options) => this.measureOf(content, options),
|
|
140
|
+
};
|
|
100
141
|
}
|
|
101
142
|
/**
|
|
102
|
-
* `
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* moves — not on every frame, which would undo on its very render each
|
|
106
|
-
* scroll the mouse wheel queues. A followed range the last render showed
|
|
107
|
-
* whole stays shown when the row count changes: the same lines can fall off
|
|
108
|
-
* the bottom of a viewport that got shorter. One the wheel had scrolled
|
|
109
|
-
* away from stays away.
|
|
143
|
+
* `content` shown through this viewport as `showing` shows a renderable,
|
|
144
|
+
* drawing only the lines the frame shows: a frame costs the rows in view,
|
|
145
|
+
* not the content's whole length.
|
|
110
146
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
showingLines(content, follow) {
|
|
148
|
+
return {
|
|
149
|
+
render: (options) => this.draw(content, (o) => content.layout(o), follow, options),
|
|
150
|
+
measure: (options) => this.measureOf(content, options),
|
|
151
|
+
};
|
|
116
152
|
}
|
|
117
153
|
/**
|
|
118
154
|
* The width its content renders at, out of `width`: what the scrollbar's
|
|
@@ -125,26 +161,48 @@ export class Viewport {
|
|
|
125
161
|
get gutter() {
|
|
126
162
|
return gutterWidth(this.scrollbar);
|
|
127
163
|
}
|
|
128
|
-
|
|
129
|
-
|
|
164
|
+
/** The scroll, and the moves queued against it, as they stand. */
|
|
165
|
+
asked() {
|
|
166
|
+
return { scroll: this._scroll.get(), moves: this._moves.get() };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The offset `settled` resolves, with the scroll it leaves kept once the frame
|
|
170
|
+
* is drawn and the `resolved` moves gone from the queue.
|
|
171
|
+
* [LAW:effects-at-boundaries] Kept once the frame is drawn, never while it is.
|
|
172
|
+
* The resolved moves leave by identity, not by count: another frame drawing
|
|
173
|
+
* this viewport in the same pass may already have taken them, and a move
|
|
174
|
+
* queued since stays.
|
|
175
|
+
*/
|
|
176
|
+
keep(settled, resolved) {
|
|
177
|
+
// Taken now: the queue is appended to in place, so `resolved` grows with it.
|
|
178
|
+
const taken = new Set(resolved);
|
|
179
|
+
afterPass(() => {
|
|
180
|
+
this._scroll.set(settled.scroll);
|
|
181
|
+
this._moves.set(this._moves.get().filter((move) => !taken.has(move)));
|
|
182
|
+
});
|
|
183
|
+
return settled.offset;
|
|
184
|
+
}
|
|
185
|
+
/** `content`, laid out by `layout`, drawn from the scroll and the moves queued against it, with `follow` followed. */
|
|
186
|
+
*draw(content, layout, follow, rawOptions) {
|
|
187
|
+
const { height, ...options } = withBoundedWidth(rawOptions, { measure: (o) => this.measureOf(content, o) });
|
|
130
188
|
const gutter = this.gutter;
|
|
131
189
|
const contentWidth = this.contentWidth(options.maxWidth);
|
|
132
|
-
const lines =
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
this.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this.
|
|
144
|
-
const shown = fitHeight(lines.
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
const thumb = thumbRows(extent,
|
|
190
|
+
const lines = layout({ ...options, maxWidth: contentWidth });
|
|
191
|
+
const asked = this.asked();
|
|
192
|
+
const { scroll, moves } = follow === undefined ? asked : following(asked, follow);
|
|
193
|
+
// [LAW:no-ambient-temporal-coupling] Content handed no column is held, not
|
|
194
|
+
// drawn, so this render learns its budget but not the content's length. It
|
|
195
|
+
// keeps nothing: the offset, the followed range and the queued moves wait
|
|
196
|
+
// for a frame that draws, and this one draws the scrollbar against the
|
|
197
|
+
// length the last one found.
|
|
198
|
+
const held = drawsNothing({ maxWidth: contentWidth });
|
|
199
|
+
const length = held ? scroll.extent.lines : lines.length;
|
|
200
|
+
const extent = { rows: viewRows(height, this.rows, length), lines: length };
|
|
201
|
+
const offset = held ? clampOffset(scroll.offset, extent) : this.keep(settle(scroll, moves, extent), asked.moves);
|
|
202
|
+
const shown = fitHeight(lines.lines(offset, offset + extent.rows), { rows: extent.rows, exact: true });
|
|
203
|
+
// The thumb is drawn from the offset this render resolved, not from the
|
|
204
|
+
// offset the last frame kept.
|
|
205
|
+
const thumb = thumbRows(extent, offset);
|
|
148
206
|
// The content's cells and the gutter's drawn cells sum to the offer: a
|
|
149
207
|
// gutter wider than the whole offer is drawn cropped to it.
|
|
150
208
|
const drawn = Math.min(gutter, options.maxWidth);
|
|
@@ -168,26 +226,54 @@ export class Viewport {
|
|
|
168
226
|
yield Segment.line();
|
|
169
227
|
}
|
|
170
228
|
}
|
|
171
|
-
|
|
172
|
-
* [LAW:one-source-of-truth] Where the queued moves leave the offset against
|
|
173
|
-
* `extent`: what a render commits, and what `canScrollBy` asks from.
|
|
174
|
-
* [LAW:dataflow-not-control-flow] The offset is re-clamped with or without
|
|
175
|
-
* queued moves: content that shrank since the last render pulls it back.
|
|
176
|
-
*/
|
|
177
|
-
resolve(extent) {
|
|
178
|
-
return this._moves.reduce((offset, move) => clampOffset(move(offset, extent), extent), clampOffset(this._offset, extent));
|
|
179
|
-
}
|
|
180
|
-
measure(rawOptions) {
|
|
229
|
+
measureOf(content, rawOptions) {
|
|
181
230
|
// Its width is its content's and its gutter's. `withCellWidth` and not
|
|
182
231
|
// `withBoundedWidth`: the bounded parse asks this very method for the
|
|
183
232
|
// natural width.
|
|
184
233
|
const options = withCellWidth(rawOptions);
|
|
185
234
|
const gutter = this.gutter;
|
|
186
235
|
const inner = { ...options, maxWidth: this.contentWidth(options.maxWidth) };
|
|
187
|
-
const
|
|
188
|
-
return new Measurement(
|
|
236
|
+
const measured = Measurement.get(inner, content);
|
|
237
|
+
return new Measurement(measured.minimum + gutter, measured.maximum + gutter).withMaximum(options.maxWidth);
|
|
189
238
|
}
|
|
190
239
|
}
|
|
240
|
+
/** The move that scrolls the least distance showing `range`. */
|
|
241
|
+
const bringIntoView = ({ start, end }) => (offset, { rows }) => Math.min(start, Math.max(offset, end - rows));
|
|
242
|
+
/**
|
|
243
|
+
* `asked` with `range` followed: one that differs from the range last followed
|
|
244
|
+
* becomes the followed range, and a move brings it into view.
|
|
245
|
+
*/
|
|
246
|
+
function following(asked, range) {
|
|
247
|
+
const { followed } = asked.scroll;
|
|
248
|
+
if (range.start === followed.start && range.end === followed.end)
|
|
249
|
+
return asked;
|
|
250
|
+
return {
|
|
251
|
+
scroll: { ...asked.scroll, followed: { start: range.start, end: range.end, rows: NaN } },
|
|
252
|
+
moves: [...asked.moves, bringIntoView(range)],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Where a frame that drew the content against `extent` leaves the scroll: a
|
|
257
|
+
* followed range that was in view is kept in view if the rows changed, and the
|
|
258
|
+
* moves resolve into the offset.
|
|
259
|
+
*/
|
|
260
|
+
function settle(scroll, moves, extent) {
|
|
261
|
+
const { followed, offset } = scroll;
|
|
262
|
+
const wasShown = followed.start >= offset && followed.end <= offset + scroll.extent.rows;
|
|
263
|
+
const keepShown = !Number.isNaN(followed.rows) && followed.rows !== extent.rows && wasShown ? [bringIntoView(followed)] : [];
|
|
264
|
+
const settled = resolve(offset, [...moves, ...keepShown], extent);
|
|
265
|
+
const rows = Number.isNaN(followed.start) ? followed.rows : extent.rows;
|
|
266
|
+
return { scroll: { offset: settled, extent, followed: { ...followed, rows } }, offset: settled };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* [LAW:one-source-of-truth] Where `moves` leave `offset` against `extent`: what
|
|
270
|
+
* a frame keeps, and what `canScrollBy` asks from.
|
|
271
|
+
* [LAW:dataflow-not-control-flow] The offset is re-clamped with or without
|
|
272
|
+
* queued moves: content that shrank since the last frame pulls it back.
|
|
273
|
+
*/
|
|
274
|
+
function resolve(offset, moves, extent) {
|
|
275
|
+
return moves.reduce((at, move) => clampOffset(move(at, extent), extent), clampOffset(offset, extent));
|
|
276
|
+
}
|
|
191
277
|
/**
|
|
192
278
|
* One viewport per thing a pane shows, named by a key. The same viewport comes
|
|
193
279
|
* back while the key holds, so its scroll position does too; a new key gets a
|
|
@@ -210,11 +296,9 @@ export class ViewportPerKey {
|
|
|
210
296
|
return this.held.viewport;
|
|
211
297
|
}
|
|
212
298
|
fresh() {
|
|
213
|
-
|
|
214
|
-
return new Viewport(EMPTY, this.options);
|
|
299
|
+
return new Viewport(this.options);
|
|
215
300
|
}
|
|
216
301
|
}
|
|
217
|
-
const EMPTY = { *render() { } };
|
|
218
302
|
const scrollbarGlyphs = ({ thumb, track }) => thumb.glyph + track.glyph;
|
|
219
303
|
/** `scrollbar` in its own styles, drawn in ASCII. */
|
|
220
304
|
function asciiScrollbar({ thumb, track }) {
|
|
@@ -4,13 +4,25 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A host that builds its cells in templates — cc-candybar's statusline, say —
|
|
6
6
|
* evaluates each template once per redraw and owns the clock that says when
|
|
7
|
-
* that is. So the
|
|
8
|
-
* `richTextFuncs(drawnAt)` takes the drawn depth: `
|
|
9
|
-
*
|
|
7
|
+
* that is. So the frame is a capability the registration takes, the way
|
|
8
|
+
* `richTextFuncs(drawnAt)` takes the drawn depth: `frame()` is read once per
|
|
9
|
+
* call, when the template is evaluated, and nothing here reads a clock.
|
|
10
10
|
* [LAW:no-ambient-temporal-coupling] A host hands in the same `t` and gets
|
|
11
11
|
* the same fragment back; a host that redraws once a second or sixty times
|
|
12
12
|
* a second registers the same functions.
|
|
13
13
|
*
|
|
14
|
+
* Every function draws its animation on a `Timeline` (`../core/timeline.ts`)
|
|
15
|
+
* the host hands in as a getter beside the time — playing from host time 0
|
|
16
|
+
* at rate 1, the host's own clock, when it hands none — so a daemon pauses,
|
|
17
|
+
* reverses, scrubs or speeds one session's animations by changing one value
|
|
18
|
+
* it holds, with nothing recompiled, as `paletteFuncs` follows a theme
|
|
19
|
+
* through its getter. The timeline is a map the drawing runs through, not a
|
|
20
|
+
* number the time is turned into first: a frame's stamp says when it changes
|
|
21
|
+
* on the host's clock, which only `Timeline.run` can carry it back onto —
|
|
22
|
+
* paused, never; backwards, at the hold it is about to drop under.
|
|
23
|
+
* [LAW:single-enforcer] `drawing` is the one place the time, the timeline
|
|
24
|
+
* and the reader's preference for motion are read, for every function.
|
|
25
|
+
*
|
|
14
26
|
* `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
|
|
15
27
|
* (`renderables/frames.ts`) in a template: the frame in progress at the
|
|
16
28
|
* moment, as a fragment at the width of the widest. Its frames arrive as
|
|
@@ -25,13 +37,70 @@
|
|
|
25
37
|
* end, so the engine's `alternating` gate types every slot and lifts each
|
|
26
38
|
* string frame to a fragment; the pairing — a trailing hold with no frame or
|
|
27
39
|
* colour — is parsed in `pairs`, once. [LAW:types-are-the-program]
|
|
40
|
+
*
|
|
41
|
+
* The rest start at a moment the template names, on the timeline's clock —
|
|
42
|
+
* a field of the host's state, the moment a value changed — and each is the
|
|
43
|
+
* animation `Timeline.play(start)` runs, so a start is a timeline like any
|
|
44
|
+
* other and nothing here offsets a time by hand. `tween <seconds> <ease>
|
|
45
|
+
* <start> <from> <to>` is a number eased from one value to another and held
|
|
46
|
+
* (`core/tween.ts`), its ease spelled by name (`parseEase`); `loop <seconds>
|
|
47
|
+
* <start>` is the way through a period, 0 to 1 and round again — two
|
|
48
|
+
* cells started a step apart are a stagger. Each is a number, so it goes
|
|
49
|
+
* wherever a number goes: a colour tween is `ramp (tween …) "linear" 0 $from
|
|
50
|
+
* 1 $to` and a ramp scrolling is `ramp (loop …) …`, because `ColorRamp` is
|
|
51
|
+
* the one place two colours are blended and a colour tween is a two-stop
|
|
52
|
+
* ramp read over time (`ColorRamp.over`). [LAW:one-source-of-truth] A
|
|
53
|
+
* second blender here would be a second answer to what lies between two
|
|
54
|
+
* colours. `glyphs <name> <start> <fragment>` is the fragment through a
|
|
55
|
+
* glyph effect from the catalogue (`GLYPH_EFFECT_CATALOGUE`), at its
|
|
56
|
+
* defaults, before any render (`reglyphText`). Every argument list is an
|
|
57
|
+
* exact arity of typed slots, so a slot of the wrong kind fails at the gate.
|
|
58
|
+
*
|
|
59
|
+
* Every function says when what it drew changes, so a host paints a
|
|
60
|
+
* template while it moves and not after. `frames` and `glyphs` stamp the
|
|
61
|
+
* fragment they draw (`textFrameAt`), covering its own glyphs. A number or a
|
|
62
|
+
* colour carries no cells to stamp, so `tween`, `loop` and `stepped` tell
|
|
63
|
+
* their hold to the frame instead (`TemplateFrame.onHold`), as a placed
|
|
64
|
+
* animation of no cells does (`RenderOptions.onHold`). Nothing here claims
|
|
65
|
+
* a moment it does not know.
|
|
28
66
|
*/
|
|
29
67
|
import type { FuncMap } from "@promptctl/go-template-js";
|
|
68
|
+
import type { RenderOptions } from "../core/protocol.js";
|
|
69
|
+
import { Timeline } from "../core/timeline.js";
|
|
70
|
+
/** What a registration is drawn on, beside the frame each evaluation is handed. */
|
|
71
|
+
export interface AnimationFuncsOptions {
|
|
72
|
+
/** The timeline to draw on, read once per call; the frame's own time when omitted. */
|
|
73
|
+
readonly timeline?: () => Timeline;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The frame a template is evaluated for, in the fields a render's options
|
|
77
|
+
* carry under the same names: `time` in seconds, `onHold`, told when what a
|
|
78
|
+
* function drew there changes, and the reader's preferences —
|
|
79
|
+
* `reducedMotion`, and `asciiOnly`, which the glyphs an effect picks for
|
|
80
|
+
* itself go through. They arrive with every frame and nowhere else, so a
|
|
81
|
+
* template drawn in an `App` is drawn as the app's preferences say.
|
|
82
|
+
* [LAW:one-source-of-truth]
|
|
83
|
+
*
|
|
84
|
+
* `time` is on the clock `AnimationFuncsOptions.timeline` maps from, and
|
|
85
|
+
* `onHold` hears on that clock. A host outside rich-js hands its own clock's
|
|
86
|
+
* time. Inside a rich-js frame the time is the frame's, which its host's
|
|
87
|
+
* timeline has already mapped, so a template drawn there registers no
|
|
88
|
+
* `timeline` and is handed `templateFrame(options)`.
|
|
89
|
+
*/
|
|
90
|
+
export interface TemplateFrame extends Pick<RenderOptions, "onHold" | "reducedMotion" | "asciiOnly"> {
|
|
91
|
+
readonly time: number;
|
|
92
|
+
}
|
|
93
|
+
/** The frame a renderable is drawn in, as the frame a template it evaluates there is drawn for. */
|
|
94
|
+
export declare function templateFrame(options: RenderOptions): TemplateFrame;
|
|
30
95
|
/**
|
|
31
|
-
* Register `frames` and `
|
|
32
|
-
* reports when the template is evaluated
|
|
33
|
-
*
|
|
34
|
-
* `
|
|
35
|
-
*
|
|
96
|
+
* Register `frames`, `stepped`, `tween`, `loop` and `glyphs`, each drawn for
|
|
97
|
+
* the frame `frame` reports when the template is evaluated — its time in
|
|
98
|
+
* seconds, from whatever zero the host counts from — through
|
|
99
|
+
* `options.timeline`, and each telling that frame when what it drew changes.
|
|
100
|
+
* The functions exist whatever `frame` returns, so a template compiles once
|
|
101
|
+
* and is evaluated at every moment the host draws. In a frame that says
|
|
102
|
+
* `reducedMotion` each draws its animation reduced (`Animation.reduced`): a
|
|
103
|
+
* loop at its start, a one-shot cut from where it starts to where it arrives
|
|
104
|
+
* when it starts.
|
|
36
105
|
*/
|
|
37
|
-
export declare function animationFuncs(
|
|
106
|
+
export declare function animationFuncs(frame: () => TemplateFrame, options?: AnimationFuncsOptions): FuncMap;
|
|
@@ -4,13 +4,25 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A host that builds its cells in templates — cc-candybar's statusline, say —
|
|
6
6
|
* evaluates each template once per redraw and owns the clock that says when
|
|
7
|
-
* that is. So the
|
|
8
|
-
* `richTextFuncs(drawnAt)` takes the drawn depth: `
|
|
9
|
-
*
|
|
7
|
+
* that is. So the frame is a capability the registration takes, the way
|
|
8
|
+
* `richTextFuncs(drawnAt)` takes the drawn depth: `frame()` is read once per
|
|
9
|
+
* call, when the template is evaluated, and nothing here reads a clock.
|
|
10
10
|
* [LAW:no-ambient-temporal-coupling] A host hands in the same `t` and gets
|
|
11
11
|
* the same fragment back; a host that redraws once a second or sixty times
|
|
12
12
|
* a second registers the same functions.
|
|
13
13
|
*
|
|
14
|
+
* Every function draws its animation on a `Timeline` (`../core/timeline.ts`)
|
|
15
|
+
* the host hands in as a getter beside the time — playing from host time 0
|
|
16
|
+
* at rate 1, the host's own clock, when it hands none — so a daemon pauses,
|
|
17
|
+
* reverses, scrubs or speeds one session's animations by changing one value
|
|
18
|
+
* it holds, with nothing recompiled, as `paletteFuncs` follows a theme
|
|
19
|
+
* through its getter. The timeline is a map the drawing runs through, not a
|
|
20
|
+
* number the time is turned into first: a frame's stamp says when it changes
|
|
21
|
+
* on the host's clock, which only `Timeline.run` can carry it back onto —
|
|
22
|
+
* paused, never; backwards, at the hold it is about to drop under.
|
|
23
|
+
* [LAW:single-enforcer] `drawing` is the one place the time, the timeline
|
|
24
|
+
* and the reader's preference for motion are read, for every function.
|
|
25
|
+
*
|
|
14
26
|
* `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
|
|
15
27
|
* (`renderables/frames.ts`) in a template: the frame in progress at the
|
|
16
28
|
* moment, as a fragment at the width of the widest. Its frames arrive as
|
|
@@ -25,11 +37,49 @@
|
|
|
25
37
|
* end, so the engine's `alternating` gate types every slot and lifts each
|
|
26
38
|
* string frame to a fragment; the pairing — a trailing hold with no frame or
|
|
27
39
|
* colour — is parsed in `pairs`, once. [LAW:types-are-the-program]
|
|
40
|
+
*
|
|
41
|
+
* The rest start at a moment the template names, on the timeline's clock —
|
|
42
|
+
* a field of the host's state, the moment a value changed — and each is the
|
|
43
|
+
* animation `Timeline.play(start)` runs, so a start is a timeline like any
|
|
44
|
+
* other and nothing here offsets a time by hand. `tween <seconds> <ease>
|
|
45
|
+
* <start> <from> <to>` is a number eased from one value to another and held
|
|
46
|
+
* (`core/tween.ts`), its ease spelled by name (`parseEase`); `loop <seconds>
|
|
47
|
+
* <start>` is the way through a period, 0 to 1 and round again — two
|
|
48
|
+
* cells started a step apart are a stagger. Each is a number, so it goes
|
|
49
|
+
* wherever a number goes: a colour tween is `ramp (tween …) "linear" 0 $from
|
|
50
|
+
* 1 $to` and a ramp scrolling is `ramp (loop …) …`, because `ColorRamp` is
|
|
51
|
+
* the one place two colours are blended and a colour tween is a two-stop
|
|
52
|
+
* ramp read over time (`ColorRamp.over`). [LAW:one-source-of-truth] A
|
|
53
|
+
* second blender here would be a second answer to what lies between two
|
|
54
|
+
* colours. `glyphs <name> <start> <fragment>` is the fragment through a
|
|
55
|
+
* glyph effect from the catalogue (`GLYPH_EFFECT_CATALOGUE`), at its
|
|
56
|
+
* defaults, before any render (`reglyphText`). Every argument list is an
|
|
57
|
+
* exact arity of typed slots, so a slot of the wrong kind fails at the gate.
|
|
58
|
+
*
|
|
59
|
+
* Every function says when what it drew changes, so a host paints a
|
|
60
|
+
* template while it moves and not after. `frames` and `glyphs` stamp the
|
|
61
|
+
* fragment they draw (`textFrameAt`), covering its own glyphs. A number or a
|
|
62
|
+
* colour carries no cells to stamp, so `tween`, `loop` and `stepped` tell
|
|
63
|
+
* their hold to the frame instead (`TemplateFrame.onHold`), as a placed
|
|
64
|
+
* animation of no cells does (`RenderOptions.onHold`). Nothing here claims
|
|
65
|
+
* a moment it does not know.
|
|
28
66
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
67
|
+
import { Animation, frameTimeOf, holdOf, shownAt } from "../core/animation.js";
|
|
68
|
+
import { Phase, beat, finiteTime, parseEase } from "../core/easing.js";
|
|
69
|
+
import { Timeline } from "../core/timeline.js";
|
|
70
|
+
import { tween } from "../core/tween.js";
|
|
71
|
+
import { cellSeed } from "../renderables/effect.js";
|
|
72
|
+
import { reglyphText } from "../renderables/glyph-effect.js";
|
|
73
|
+
import { GLYPH_EFFECT_CATALOGUE } from "../renderables/glyph-effects.js";
|
|
74
|
+
import { FrameSequence, textFrameAt } from "../renderables/frames.js";
|
|
31
75
|
import { asColor } from "./color-funcs.js";
|
|
32
76
|
import { asFragment } from "./helpers.js";
|
|
77
|
+
/** The frame a renderable is drawn in, as the frame a template it evaluates there is drawn for. */
|
|
78
|
+
export function templateFrame(options) {
|
|
79
|
+
return { time: frameTimeOf(options), onHold: options.onHold, reducedMotion: options.reducedMotion, asciiOnly: options.asciiOnly };
|
|
80
|
+
}
|
|
81
|
+
/** The host's own clock as a timeline: host time 0 at animation time 0, at rate 1. */
|
|
82
|
+
const HOST_CLOCK = Timeline.play(0);
|
|
33
83
|
/**
|
|
34
84
|
* The `(seconds, value)` pairs a call spells, each value read through
|
|
35
85
|
* `value`. The gate has typed every even slot as a float and every odd one
|
|
@@ -54,17 +104,59 @@ function pairs(what, tail, value) {
|
|
|
54
104
|
return paired;
|
|
55
105
|
}
|
|
56
106
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
107
|
+
* Every glyph effect in the catalogue by the name a template spells it
|
|
108
|
+
* with, a transition as the effect it runs. [LAW:types-are-the-program]
|
|
109
|
+
* Keyed by both name types, so a name added to the catalogue does not
|
|
110
|
+
* compile until it is spelled here too.
|
|
111
|
+
*/
|
|
112
|
+
const GLYPH_EFFECTS = {
|
|
113
|
+
...GLYPH_EFFECT_CATALOGUE.loops,
|
|
114
|
+
typewriter: (element) => GLYPH_EFFECT_CATALOGUE.transitions.typewriter(element).effect,
|
|
115
|
+
scramble: (element) => GLYPH_EFFECT_CATALOGUE.transitions.scramble(element).effect,
|
|
116
|
+
backspace: (element) => GLYPH_EFFECT_CATALOGUE.transitions.backspace(element).effect,
|
|
117
|
+
scrambleOut: (element) => GLYPH_EFFECT_CATALOGUE.transitions.scrambleOut(element).effect,
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The gate a spelled glyph effect crosses: the name in, the effect's maker
|
|
121
|
+
* out, or a refusal naming every legal one. [LAW:parse-dont-validate]
|
|
122
|
+
*/
|
|
123
|
+
function glyphEffect(name) {
|
|
124
|
+
if (!Object.hasOwn(GLYPH_EFFECTS, name)) {
|
|
125
|
+
throw new RangeError(`unknown glyph effect ${JSON.stringify(name)}; expected one of ${Object.keys(GLYPH_EFFECTS).map((n) => JSON.stringify(n)).join(", ")}`);
|
|
126
|
+
}
|
|
127
|
+
return GLYPH_EFFECTS[name];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Register `frames`, `stepped`, `tween`, `loop` and `glyphs`, each drawn for
|
|
131
|
+
* the frame `frame` reports when the template is evaluated — its time in
|
|
132
|
+
* seconds, from whatever zero the host counts from — through
|
|
133
|
+
* `options.timeline`, and each telling that frame when what it drew changes.
|
|
134
|
+
* The functions exist whatever `frame` returns, so a template compiles once
|
|
135
|
+
* and is evaluated at every moment the host draws. In a frame that says
|
|
136
|
+
* `reducedMotion` each draws its animation reduced (`Animation.reduced`): a
|
|
137
|
+
* loop at its start, a one-shot cut from where it starts to where it arrives
|
|
138
|
+
* when it starts.
|
|
62
139
|
*/
|
|
63
|
-
export function animationFuncs(
|
|
140
|
+
export function animationFuncs(frame, options = {}) {
|
|
141
|
+
const timeline = options.timeline ?? (() => HOST_CLOCK);
|
|
142
|
+
// `animation` on this call's timeline, and the time of `at`, the frame it
|
|
143
|
+
// is drawn in, read once a call so a value and its hold are one frame's.
|
|
144
|
+
const drawing = (what, animation, at) => [Timeline.run(timeline(), animation), finiteTime(what, at.time)];
|
|
145
|
+
// What a value animation shows now, under the reader's preference, its
|
|
146
|
+
// hold told to the frame: a value has no cells to carry it.
|
|
147
|
+
const now = (what, animation) => {
|
|
148
|
+
const at = frame();
|
|
149
|
+
const [drawn, t] = drawing(what, animation, at);
|
|
150
|
+
const shown = shownAt(drawn, t, at);
|
|
151
|
+
at.onHold?.(holdOf(shown, t));
|
|
152
|
+
return shown.value;
|
|
153
|
+
};
|
|
154
|
+
const startedAt = (start, animation) => Timeline.run(Timeline.play(start), animation);
|
|
64
155
|
const framesFunc = {
|
|
65
156
|
fn: ((...tail) => {
|
|
66
157
|
const frames = pairs("frame", tail, asFragment).map(({ seconds, value }) => ({ seconds, text: value }));
|
|
67
|
-
|
|
158
|
+
const at = frame();
|
|
159
|
+
return textFrameAt(...drawing("frames", new FrameSequence(frames), at), at);
|
|
68
160
|
}),
|
|
69
161
|
argTypes: ["float", "liftable"],
|
|
70
162
|
arity: { kind: "alternating", minimum: 2 },
|
|
@@ -73,11 +165,37 @@ export function animationFuncs(frameTime) {
|
|
|
73
165
|
const steppedFunc = {
|
|
74
166
|
fn: ((...tail) => {
|
|
75
167
|
const steps = pairs("color", tail, (v) => asColor(v, "stepped"));
|
|
76
|
-
return steps[beat(steps.map((step) => step.seconds))
|
|
168
|
+
return steps[now("stepped", beat(steps.map((step) => step.seconds)))].value.hex;
|
|
77
169
|
}),
|
|
78
170
|
argTypes: ["float", "string"],
|
|
79
171
|
arity: { kind: "alternating", minimum: 2 },
|
|
80
172
|
returnType: "string",
|
|
81
173
|
};
|
|
82
|
-
|
|
174
|
+
const tweenFunc = {
|
|
175
|
+
fn: ((seconds, ease, start, from, to) => now("tween", startedAt(start, tween(from, to, { seconds, ease: parseEase(ease) })))),
|
|
176
|
+
argTypes: ["float", "string", "float", "float", "float"],
|
|
177
|
+
arity: { kind: "exact" },
|
|
178
|
+
returnType: "float",
|
|
179
|
+
};
|
|
180
|
+
const loopFunc = {
|
|
181
|
+
fn: ((seconds, start) => now("loop", startedAt(start, Animation.continuous(Phase.loop(seconds))))),
|
|
182
|
+
argTypes: ["float", "float"],
|
|
183
|
+
arity: { kind: "exact" },
|
|
184
|
+
returnType: "float",
|
|
185
|
+
};
|
|
186
|
+
const glyphsFunc = {
|
|
187
|
+
fn: ((name, start, child) => {
|
|
188
|
+
const make = glyphEffect(name);
|
|
189
|
+
const fragment = asFragment(child);
|
|
190
|
+
// One text, one seed: the element is named by what it says.
|
|
191
|
+
const key = fragment.plain;
|
|
192
|
+
const at = frame();
|
|
193
|
+
const reglyphed = reglyphText(fragment, make({ z: cellSeed(key, 0, 0) }), { key, asciiOnly: at.asciiOnly === true });
|
|
194
|
+
return textFrameAt(...drawing("glyphs", startedAt(start, reglyphed), at), at);
|
|
195
|
+
}),
|
|
196
|
+
argTypes: ["string", "float", "liftable"],
|
|
197
|
+
arity: { kind: "exact" },
|
|
198
|
+
returnType: "T",
|
|
199
|
+
};
|
|
200
|
+
return { frames: framesFunc, stepped: steppedFunc, tween: tweenFunc, loop: loopFunc, glyphs: glyphsFunc };
|
|
83
201
|
}
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* - `paletteFuncs(getPalette)` — `color` and `ramp`, the two functions that
|
|
15
15
|
* resolve a palette name. This is the *only* registration that knows a
|
|
16
16
|
* palette exists. [LAW:one-way-deps]
|
|
17
|
-
* - `animationFuncs(
|
|
18
|
-
*
|
|
19
|
-
* template is evaluated at a time, and the time
|
|
20
|
-
* (`./animation-funcs.ts`).
|
|
17
|
+
* - `animationFuncs(frame)` — `frames`, `stepped`, `tween`, `loop` and
|
|
18
|
+
* `glyphs`, drawn for a moment on a timeline. It is the only registration
|
|
19
|
+
* that knows the template is evaluated at a time, and the time and the
|
|
20
|
+
* timeline are the host's to hand in (`./animation-funcs.ts`).
|
|
21
21
|
*
|
|
22
22
|
* `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
|
|
23
23
|
* palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
|
|
@@ -43,7 +43,7 @@ import type { RenderOptions } from "../core/protocol.js";
|
|
|
43
43
|
import { type DrawnAt } from "./color-funcs.js";
|
|
44
44
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
45
45
|
export { colorFuncs, readableOnFunc, type DrawnAt } from "./color-funcs.js";
|
|
46
|
-
export { animationFuncs } from "./animation-funcs.js";
|
|
46
|
+
export { animationFuncs, templateFrame, type AnimationFuncsOptions, type TemplateFrame } from "./animation-funcs.js";
|
|
47
47
|
/**
|
|
48
48
|
* Funcs registered by the rich-js binding — the colour sinks, the palette-free
|
|
49
49
|
* colour math, text attributes, and the `link` cell-splitter. Nothing here
|