@promptctl/rich-js 0.24.1 → 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/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +175 -10
- package/dist/core/animation.js +283 -15
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cells.d.ts +23 -13
- package/dist/core/cells.js +58 -48
- 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 +44 -39
- package/dist/core/easing.d.ts +30 -0
- package/dist/core/easing.js +65 -20
- 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 +2 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +2 -2
- 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/pretty.js +9 -10
- package/dist/core/protocol.d.ts +100 -39
- package/dist/core/protocol.js +167 -21
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +25 -5
- package/dist/core/segment.d.ts +13 -5
- package/dist/core/segment.js +23 -9
- 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 +13 -16
- package/dist/core/strip.js +119 -110
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +57 -28
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +116 -124
- package/dist/core/timeline.d.ts +30 -5
- package/dist/core/timeline.js +43 -7
- package/dist/core/tween.d.ts +1 -1
- package/dist/core/tween.js +5 -3
- 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 +71 -37
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +24 -12
- package/dist/index.js +9 -4
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/align.d.ts +2 -3
- package/dist/renderables/align.js +0 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +3 -4
- package/dist/renderables/columns.js +2 -7
- package/dist/renderables/constrain.d.ts +2 -3
- package/dist/renderables/constrain.js +0 -3
- package/dist/renderables/effect.d.ts +132 -54
- package/dist/renderables/effect.js +188 -115
- package/dist/renderables/effects.d.ts +122 -11
- package/dist/renderables/effects.js +238 -94
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +16 -8
- package/dist/renderables/flexStrip.d.ts +2 -3
- package/dist/renderables/flexStrip.js +0 -4
- package/dist/renderables/frames.d.ts +35 -54
- package/dist/renderables/frames.js +43 -58
- 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 +3 -4
- package/dist/renderables/group.js +0 -3
- package/dist/renderables/layout.d.ts +12 -12
- package/dist/renderables/layout.js +40 -46
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +4 -5
- package/dist/renderables/list-view.js +9 -13
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +88 -41
- package/dist/renderables/markdown-parse.d.ts +4 -4
- package/dist/renderables/markdown-parse.js +52 -46
- package/dist/renderables/markdown-url.js +11 -11
- package/dist/renderables/padding.d.ts +4 -5
- package/dist/renderables/padding.js +0 -3
- package/dist/renderables/panel.d.ts +14 -16
- package/dist/renderables/panel.js +6 -14
- package/dist/renderables/progress.d.ts +83 -34
- package/dist/renderables/progress.js +182 -95
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +32 -9
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.js +2 -1
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +35 -14
- package/dist/renderables/spinner.js +65 -25
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +3 -2
- package/dist/renderables/table.d.ts +34 -40
- package/dist/renderables/table.js +41 -63
- package/dist/renderables/traceback.d.ts +2 -2
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +27 -25
- package/dist/renderables/tree.js +104 -58
- package/dist/renderables/viewport.d.ts +75 -44
- package/dist/renderables/viewport.js +145 -82
- 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 +5 -5
- package/dist/themes/ramp.d.ts +46 -3
- package/dist/themes/ramp.js +61 -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 +2 -0
- package/dist/widgets/focus-manager.js +6 -4
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -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 +2 -3
- package/dist/widgets/static-item.js +0 -3
- package/dist/widgets/text-input.d.ts +28 -32
- package/dist/widgets/text-input.js +360 -346
- 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 +14 -4
- package/dist/widgets/widget-app.js +36 -7
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -18
- package/package.json +8 -1
|
@@ -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
|
|
@@ -24,6 +28,13 @@
|
|
|
24
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
|
|
@@ -33,7 +44,25 @@
|
|
|
33
44
|
*/
|
|
34
45
|
import { Segment } from "../core/segment.js";
|
|
35
46
|
import type { Style } from "../core/style.js";
|
|
36
|
-
import type {
|
|
47
|
+
import type { Measurable, Renderable, RenderOptions, Scrollable } from "../core/protocol.js";
|
|
48
|
+
/** Lines `start` up to but not including `end` of the content. */
|
|
49
|
+
interface Range {
|
|
50
|
+
readonly start: number;
|
|
51
|
+
readonly end: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Content a viewport shows by the line: laid out at a width, it says how many
|
|
55
|
+
* lines it is and draws only the ones asked for. What a view too long to draw
|
|
56
|
+
* whole every frame — a textarea's rows — is shown as (`showingLines`).
|
|
57
|
+
*/
|
|
58
|
+
export interface Lines extends Measurable {
|
|
59
|
+
layout(options: RenderOptions): LaidLines;
|
|
60
|
+
}
|
|
61
|
+
/** Content laid out at one width: its length, and its lines `start` up to but not including `end`. */
|
|
62
|
+
export interface LaidLines {
|
|
63
|
+
readonly length: number;
|
|
64
|
+
lines(start: number, end: number): Segment[][];
|
|
65
|
+
}
|
|
37
66
|
/** How one part of a scrollbar draws each row it covers. */
|
|
38
67
|
export interface ScrollbarPart {
|
|
39
68
|
readonly glyph: string;
|
|
@@ -60,22 +89,15 @@ export interface ViewportOptions {
|
|
|
60
89
|
/** A scrollbar down the right edge. Absent, the content has the full width. */
|
|
61
90
|
scrollbar?: Scrollbar;
|
|
62
91
|
}
|
|
63
|
-
export declare class Viewport implements
|
|
64
|
-
/**
|
|
65
|
-
* What the viewport shows. Replace it to show new content from the same
|
|
66
|
-
* scroll position — a view rebuilt every frame keeps one `Viewport`.
|
|
67
|
-
*/
|
|
68
|
-
content: Renderable;
|
|
92
|
+
export declare class Viewport implements Scrollable {
|
|
69
93
|
readonly rows: number | undefined;
|
|
70
94
|
readonly scrollbar: Scrollbar;
|
|
71
|
-
private
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
private _followed;
|
|
75
|
-
constructor(content: Renderable, options?: ViewportOptions);
|
|
95
|
+
private readonly _scroll;
|
|
96
|
+
private readonly _moves;
|
|
97
|
+
constructor(options?: ViewportOptions);
|
|
76
98
|
/**
|
|
77
|
-
* The first content line the last
|
|
78
|
-
* not reflected until the next
|
|
99
|
+
* The first content line the last frame showed. A move requested since is
|
|
100
|
+
* not reflected until the next frame resolves it.
|
|
79
101
|
*/
|
|
80
102
|
get offset(): number;
|
|
81
103
|
/** Scroll so `line` is the first line shown. */
|
|
@@ -84,7 +106,7 @@ export declare class Viewport implements Renderable, Measurable, Scrollable, Com
|
|
|
84
106
|
scrollBy(lines: number): void;
|
|
85
107
|
/**
|
|
86
108
|
* Whether `scrollBy(lines)` would move the viewport from where the moves
|
|
87
|
-
* queued so far leave it, against the rows and length the last
|
|
109
|
+
* queued so far leave it, against the rows and length the last frame
|
|
88
110
|
* found.
|
|
89
111
|
*/
|
|
90
112
|
canScrollBy(lines: number): boolean;
|
|
@@ -96,16 +118,30 @@ export declare class Viewport implements Renderable, Measurable, Scrollable, Com
|
|
|
96
118
|
*/
|
|
97
119
|
ensureVisible(start: number, end: number): void;
|
|
98
120
|
/**
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
121
|
+
* `content` shown through this viewport from its scroll position. What it
|
|
122
|
+
* draws is anchored to this viewport, so the wheel finds it.
|
|
123
|
+
*
|
|
124
|
+
* `follow` is the lines of `content` to keep in view — a selection's — and
|
|
125
|
+
* is brought into view with `ensureVisible` when it differs from the range
|
|
126
|
+
* the last frame followed: when the selection moves, not on every frame,
|
|
127
|
+
* which would undo on its very render each scroll the mouse wheel queues. A
|
|
128
|
+
* followed range the last frame showed whole stays shown when the row count
|
|
129
|
+
* changes: the same lines can fall off the bottom of a viewport that got
|
|
130
|
+
* shorter. One the wheel had scrolled away from stays away. Absent, the
|
|
131
|
+
* range last followed stands.
|
|
132
|
+
*
|
|
133
|
+
* [LAW:effects-at-boundaries] What a frame drawing it resolves becomes the
|
|
134
|
+
* viewport's once that frame has been drawn whole, so it may be built and
|
|
135
|
+
* drawn inside a render — `ListView` draws its items to learn their lines
|
|
136
|
+
* and shows them this way.
|
|
137
|
+
*/
|
|
138
|
+
showing(content: Renderable, follow?: Range): Renderable & Measurable;
|
|
139
|
+
/**
|
|
140
|
+
* `content` shown through this viewport as `showing` shows a renderable,
|
|
141
|
+
* drawing only the lines the frame shows: a frame costs the rows in view,
|
|
142
|
+
* not the content's whole length.
|
|
107
143
|
*/
|
|
108
|
-
|
|
144
|
+
showingLines(content: Lines, follow?: Range): Renderable & Measurable;
|
|
109
145
|
/**
|
|
110
146
|
* The width its content renders at, out of `width`: what the scrollbar's
|
|
111
147
|
* gutter leaves. Lines handed to `ensureVisible` are lines at this width.
|
|
@@ -113,26 +149,20 @@ export declare class Viewport implements Renderable, Measurable, Scrollable, Com
|
|
|
113
149
|
contentWidth(width: number): number;
|
|
114
150
|
/** The cells the scrollbar's gutter takes beside the content. */
|
|
115
151
|
get gutter(): number;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* What a render that drew the content found, made the viewport's: `extent`
|
|
120
|
-
* is the rows and length the next move is clamped against, a followed range
|
|
121
|
-
* that was in view is kept in view if the rows changed, and the queued moves
|
|
122
|
-
* resolve into the offset this returns.
|
|
123
|
-
*/
|
|
124
|
-
private commit;
|
|
152
|
+
/** The scroll, and the moves queued against it, as they stand. */
|
|
153
|
+
private asked;
|
|
125
154
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* [LAW:
|
|
129
|
-
*
|
|
155
|
+
* The offset `settled` resolves, with the scroll it leaves kept once the frame
|
|
156
|
+
* is drawn and the `resolved` moves gone from the queue.
|
|
157
|
+
* [LAW:effects-at-boundaries] Kept once the frame is drawn, never while it is.
|
|
158
|
+
* The resolved moves leave by identity, not by count: another frame drawing
|
|
159
|
+
* this viewport in the same pass may already have taken them, and a move
|
|
160
|
+
* queued since stays.
|
|
130
161
|
*/
|
|
131
|
-
private
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
162
|
+
private keep;
|
|
163
|
+
/** `content`, laid out by `layout`, drawn from the scroll and the moves queued against it, with `follow` followed. */
|
|
164
|
+
private draw;
|
|
165
|
+
private measureOf;
|
|
136
166
|
}
|
|
137
167
|
/**
|
|
138
168
|
* One viewport per thing a pane shows, named by a key. The same viewport comes
|
|
@@ -150,3 +180,4 @@ export declare class ViewportPerKey {
|
|
|
150
180
|
of(key: string): Viewport;
|
|
151
181
|
private fresh;
|
|
152
182
|
}
|
|
183
|
+
export {};
|
|
@@ -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
|
|
@@ -24,6 +28,13 @@
|
|
|
24
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
|
|
@@ -36,7 +47,15 @@ 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
49
|
import { drawable, drawsNothing, fitHeight, getStyle, regionRows, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
39
|
-
import { render } 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
|
+
}
|
|
40
59
|
/** A heavy line for the thumb on a light line for the track. */
|
|
41
60
|
export const SCROLLBAR = {
|
|
42
61
|
thumb: { glyph: "┃", style: "scrollbar.thumb" },
|
|
@@ -48,30 +67,26 @@ const NO_SCROLLBAR = {
|
|
|
48
67
|
track: { glyph: "", style: NULL_STYLE },
|
|
49
68
|
};
|
|
50
69
|
export class Viewport {
|
|
51
|
-
/**
|
|
52
|
-
* What the viewport shows. Replace it to show new content from the same
|
|
53
|
-
* scroll position — a view rebuilt every frame keeps one `Viewport`.
|
|
54
|
-
*/
|
|
55
|
-
content;
|
|
56
70
|
rows;
|
|
57
71
|
scrollbar;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 = {}) {
|
|
66
81
|
this.rows = options.rows;
|
|
67
82
|
this.scrollbar = options.scrollbar ?? NO_SCROLLBAR;
|
|
68
83
|
}
|
|
69
84
|
/**
|
|
70
|
-
* The first content line the last
|
|
71
|
-
* 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.
|
|
72
87
|
*/
|
|
73
88
|
get offset() {
|
|
74
|
-
return this.
|
|
89
|
+
return this._scroll.get().offset;
|
|
75
90
|
}
|
|
76
91
|
/** Scroll so `line` is the first line shown. */
|
|
77
92
|
scrollTo(line) {
|
|
@@ -83,12 +98,13 @@ export class Viewport {
|
|
|
83
98
|
}
|
|
84
99
|
/**
|
|
85
100
|
* Whether `scrollBy(lines)` would move the viewport from where the moves
|
|
86
|
-
* 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
|
|
87
102
|
* found.
|
|
88
103
|
*/
|
|
89
104
|
canScrollBy(lines) {
|
|
90
|
-
const
|
|
91
|
-
|
|
105
|
+
const { scroll, moves } = this.asked();
|
|
106
|
+
const offset = resolve(scroll.offset, moves, scroll.extent);
|
|
107
|
+
return clampOffset(offset + lines, scroll.extent) !== offset;
|
|
92
108
|
}
|
|
93
109
|
/**
|
|
94
110
|
* Scroll the least distance that shows lines `start` up to but not
|
|
@@ -97,23 +113,42 @@ export class Viewport {
|
|
|
97
113
|
* the viewport shows its first line at the top.
|
|
98
114
|
*/
|
|
99
115
|
ensureVisible(start, end) {
|
|
100
|
-
this._moves.push((
|
|
116
|
+
this._moves.push(bringIntoView({ start, end }));
|
|
101
117
|
}
|
|
102
118
|
/**
|
|
103
|
-
* `
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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.
|
|
111
135
|
*/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
showing(content, follow) {
|
|
137
|
+
return {
|
|
138
|
+
render: (options) => this.draw(content, drawnWhole(content), follow, options),
|
|
139
|
+
measure: (options) => this.measureOf(content, options),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
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.
|
|
146
|
+
*/
|
|
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
|
+
};
|
|
117
152
|
}
|
|
118
153
|
/**
|
|
119
154
|
* The width its content renders at, out of `width`: what the scrollbar's
|
|
@@ -126,26 +161,47 @@ export class Viewport {
|
|
|
126
161
|
get gutter() {
|
|
127
162
|
return gutterWidth(this.scrollbar);
|
|
128
163
|
}
|
|
129
|
-
|
|
130
|
-
|
|
164
|
+
/** The scroll, and the moves queued against it, as they stand. */
|
|
165
|
+
asked() {
|
|
166
|
+
return { scroll: this._scroll.get(), moves: this._moves.get() };
|
|
131
167
|
}
|
|
132
|
-
|
|
133
|
-
|
|
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) });
|
|
134
188
|
const gutter = this.gutter;
|
|
135
189
|
const contentWidth = this.contentWidth(options.maxWidth);
|
|
136
|
-
const lines =
|
|
190
|
+
const lines = layout({ ...options, maxWidth: contentWidth });
|
|
191
|
+
const asked = this.asked();
|
|
192
|
+
const { scroll, moves } = follow === undefined ? asked : following(asked, follow);
|
|
137
193
|
// [LAW:no-ambient-temporal-coupling] Content handed no column is held, not
|
|
138
194
|
// drawn, so this render learns its budget but not the content's length. It
|
|
139
|
-
//
|
|
140
|
-
// for a
|
|
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
|
|
141
197
|
// length the last one found.
|
|
142
198
|
const held = drawsNothing({ maxWidth: contentWidth });
|
|
143
|
-
const length = held ?
|
|
199
|
+
const length = held ? scroll.extent.lines : lines.length;
|
|
144
200
|
const extent = { rows: viewRows(height, this.rows, length), lines: length };
|
|
145
|
-
const offset = held ? clampOffset(
|
|
146
|
-
const shown = fitHeight(lines.
|
|
147
|
-
// The thumb is drawn from the offset this render resolved, not from
|
|
148
|
-
//
|
|
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.
|
|
149
205
|
const thumb = thumbRows(extent, offset);
|
|
150
206
|
// The content's cells and the gutter's drawn cells sum to the offer: a
|
|
151
207
|
// gutter wider than the whole offer is drawn cropped to it.
|
|
@@ -170,45 +226,54 @@ export class Viewport {
|
|
|
170
226
|
yield Segment.line();
|
|
171
227
|
}
|
|
172
228
|
}
|
|
173
|
-
|
|
174
|
-
* What a render that drew the content found, made the viewport's: `extent`
|
|
175
|
-
* is the rows and length the next move is clamped against, a followed range
|
|
176
|
-
* that was in view is kept in view if the rows changed, and the queued moves
|
|
177
|
-
* resolve into the offset this returns.
|
|
178
|
-
*/
|
|
179
|
-
commit(extent) {
|
|
180
|
-
const followed = this._followed;
|
|
181
|
-
const wasShown = followed.start >= this._offset && followed.end <= this._offset + this._extent.rows;
|
|
182
|
-
this._extent = extent;
|
|
183
|
-
if (!Number.isNaN(followed.rows) && followed.rows !== extent.rows && wasShown) {
|
|
184
|
-
this.ensureVisible(followed.start, followed.end);
|
|
185
|
-
}
|
|
186
|
-
if (!Number.isNaN(followed.start))
|
|
187
|
-
this._followed = { ...followed, rows: extent.rows };
|
|
188
|
-
this._offset = this.resolve(extent);
|
|
189
|
-
this._moves = [];
|
|
190
|
-
return this._offset;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* [LAW:one-source-of-truth] Where the queued moves leave the offset against
|
|
194
|
-
* `extent`: what a render commits, and what `canScrollBy` asks from.
|
|
195
|
-
* [LAW:dataflow-not-control-flow] The offset is re-clamped with or without
|
|
196
|
-
* queued moves: content that shrank since the last render pulls it back.
|
|
197
|
-
*/
|
|
198
|
-
resolve(extent) {
|
|
199
|
-
return this._moves.reduce((offset, move) => clampOffset(move(offset, extent), extent), clampOffset(this._offset, extent));
|
|
200
|
-
}
|
|
201
|
-
measure(rawOptions) {
|
|
229
|
+
measureOf(content, rawOptions) {
|
|
202
230
|
// Its width is its content's and its gutter's. `withCellWidth` and not
|
|
203
231
|
// `withBoundedWidth`: the bounded parse asks this very method for the
|
|
204
232
|
// natural width.
|
|
205
233
|
const options = withCellWidth(rawOptions);
|
|
206
234
|
const gutter = this.gutter;
|
|
207
235
|
const inner = { ...options, maxWidth: this.contentWidth(options.maxWidth) };
|
|
208
|
-
const
|
|
209
|
-
return new Measurement(
|
|
236
|
+
const measured = Measurement.get(inner, content);
|
|
237
|
+
return new Measurement(measured.minimum + gutter, measured.maximum + gutter).withMaximum(options.maxWidth);
|
|
210
238
|
}
|
|
211
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
|
+
}
|
|
212
277
|
/**
|
|
213
278
|
* One viewport per thing a pane shows, named by a key. The same viewport comes
|
|
214
279
|
* back while the key holds, so its scroll position does too; a new key gets a
|
|
@@ -231,11 +296,9 @@ export class ViewportPerKey {
|
|
|
231
296
|
return this.held.viewport;
|
|
232
297
|
}
|
|
233
298
|
fresh() {
|
|
234
|
-
|
|
235
|
-
return new Viewport(EMPTY, this.options);
|
|
299
|
+
return new Viewport(this.options);
|
|
236
300
|
}
|
|
237
301
|
}
|
|
238
|
-
const EMPTY = { *render() { } };
|
|
239
302
|
const scrollbarGlyphs = ({ thumb, track }) => thumb.glyph + track.glyph;
|
|
240
303
|
/** `scrollbar` in its own styles, drawn in ASCII. */
|
|
241
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;
|