@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
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Segment } from "../core/segment.js";
|
|
6
6
|
import { type Measurement } from "../core/measure.js";
|
|
7
|
-
import type {
|
|
8
|
-
export declare class Group implements Renderable, Measurable
|
|
9
|
-
readonly renderables: Renderable[];
|
|
7
|
+
import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
8
|
+
export declare class Group implements Renderable, Measurable {
|
|
9
|
+
readonly renderables: readonly Renderable[];
|
|
10
10
|
constructor(...renderables: Renderable[]);
|
|
11
11
|
render(options: RenderOptions): Iterable<Segment>;
|
|
12
12
|
/** As wide as its widest member: the reference's `measure_renderables`. */
|
|
13
13
|
measure(options: RenderOptions): Measurement;
|
|
14
|
-
contents(): Iterable<Renderable>;
|
|
15
14
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Layout — divides the screen into rectangular regions.
|
|
3
3
|
*/
|
|
4
4
|
import { Segment } from "../core/segment.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
6
6
|
import type { CellCol } from "../core/cells.js";
|
|
7
7
|
import type { Weight } from "./ratio.js";
|
|
8
8
|
export interface LayoutOptions {
|
|
@@ -12,16 +12,17 @@ export interface LayoutOptions {
|
|
|
12
12
|
minimumSize?: number;
|
|
13
13
|
visible?: boolean;
|
|
14
14
|
}
|
|
15
|
-
export declare class Layout implements Renderable, Measurable
|
|
16
|
-
name: string | undefined;
|
|
17
|
-
|
|
18
|
-
private _ratio;
|
|
19
|
-
private _size;
|
|
20
|
-
private _minimumSize;
|
|
21
|
-
private _renderable;
|
|
22
|
-
private
|
|
23
|
-
private _splitDirection;
|
|
15
|
+
export declare class Layout implements Renderable, Measurable {
|
|
16
|
+
readonly name: string | undefined;
|
|
17
|
+
private readonly _visible;
|
|
18
|
+
private readonly _ratio;
|
|
19
|
+
private readonly _size;
|
|
20
|
+
private readonly _minimumSize;
|
|
21
|
+
private readonly _renderable;
|
|
22
|
+
private readonly _split;
|
|
24
23
|
constructor(renderable?: Renderable | string, options?: LayoutOptions);
|
|
24
|
+
get visible(): boolean;
|
|
25
|
+
set visible(value: boolean);
|
|
25
26
|
/**
|
|
26
27
|
* The three declared numbers, parsed on assignment rather than at the
|
|
27
28
|
* constructor. All three are public and a caller reaches them long after
|
|
@@ -41,7 +42,7 @@ export declare class Layout implements Renderable, Measurable, Composite {
|
|
|
41
42
|
set size(value: number | undefined);
|
|
42
43
|
get minimumSize(): CellCol;
|
|
43
44
|
set minimumSize(value: number);
|
|
44
|
-
get children(): Layout[];
|
|
45
|
+
get children(): readonly Layout[];
|
|
45
46
|
/**
|
|
46
47
|
* Whether this layout draws `_renderable` or its children.
|
|
47
48
|
*
|
|
@@ -57,7 +58,6 @@ export declare class Layout implements Renderable, Measurable, Composite {
|
|
|
57
58
|
update(renderable: Renderable | string): void;
|
|
58
59
|
getByName(name: string): Layout | undefined;
|
|
59
60
|
render(rawOptions: RenderOptions): Iterable<Segment>;
|
|
60
|
-
contents(): Iterable<Renderable>;
|
|
61
61
|
private _renderColumn;
|
|
62
62
|
private _renderRow;
|
|
63
63
|
/**
|
|
@@ -8,6 +8,7 @@ import { render } from "../core/protocol.js";
|
|
|
8
8
|
import { Measurement } from "../core/measure.js";
|
|
9
9
|
import { cellCount } from "../core/cells.js";
|
|
10
10
|
import { ratioBudget, ratioResolve, shareWeight } from "./ratio.js";
|
|
11
|
+
import { State } from "../core/state.js";
|
|
11
12
|
/**
|
|
12
13
|
* A pane rendered into a region of `rows` and held to exactly that many lines,
|
|
13
14
|
* or — with `rows` undefined — rendered under the layout's own budget, as a
|
|
@@ -21,26 +22,31 @@ function paneLines(pane, options, rows) {
|
|
|
21
22
|
const height = rows === undefined ? stackedHeight(options.height) : { rows, exact: true };
|
|
22
23
|
return fitHeight(Segment.splitLines(render(pane, { ...options, height })), height);
|
|
23
24
|
}
|
|
25
|
+
/** A declared size as a cell count; absence stays absence, a flex pane, which `cellCount` would read as zero. */
|
|
26
|
+
const sizeOf = (value) => (value === undefined ? undefined : cellCount(value));
|
|
24
27
|
export class Layout {
|
|
25
28
|
name;
|
|
26
|
-
|
|
29
|
+
// Rich's `Layout` is split, updated, resized and hidden after it is built,
|
|
30
|
+
// so each of these is state, and a write to one while it is drawn throws.
|
|
31
|
+
_visible;
|
|
27
32
|
_ratio;
|
|
28
33
|
_size;
|
|
29
34
|
_minimumSize;
|
|
30
35
|
_renderable;
|
|
31
|
-
|
|
32
|
-
_splitDirection;
|
|
36
|
+
_split = new State({ direction: undefined, children: [] });
|
|
33
37
|
constructor(renderable, options) {
|
|
34
|
-
|
|
35
|
-
this._renderable = embed(renderable);
|
|
36
|
-
}
|
|
38
|
+
this._renderable = new State(renderable === undefined ? undefined : embed(renderable));
|
|
37
39
|
this.name = options?.name;
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
this._ratio = new State(shareWeight(options?.ratio ?? 1));
|
|
41
|
+
this._size = new State(sizeOf(options?.size));
|
|
42
|
+
this._minimumSize = new State(cellCount(options?.minimumSize ?? 1));
|
|
43
|
+
this._visible = new State(options?.visible !== false);
|
|
44
|
+
}
|
|
45
|
+
get visible() {
|
|
46
|
+
return this._visible.get();
|
|
47
|
+
}
|
|
48
|
+
set visible(value) {
|
|
49
|
+
this._visible.set(value);
|
|
44
50
|
}
|
|
45
51
|
/**
|
|
46
52
|
* The three declared numbers, parsed on assignment rather than at the
|
|
@@ -56,25 +62,25 @@ export class Layout {
|
|
|
56
62
|
* selects a flex pane, and `cellCount` would read it as a declared zero.
|
|
57
63
|
*/
|
|
58
64
|
get ratio() {
|
|
59
|
-
return this._ratio;
|
|
65
|
+
return this._ratio.get();
|
|
60
66
|
}
|
|
61
67
|
set ratio(value) {
|
|
62
|
-
this._ratio
|
|
68
|
+
this._ratio.set(shareWeight(value));
|
|
63
69
|
}
|
|
64
70
|
get size() {
|
|
65
|
-
return this._size;
|
|
71
|
+
return this._size.get();
|
|
66
72
|
}
|
|
67
73
|
set size(value) {
|
|
68
|
-
this._size
|
|
74
|
+
this._size.set(sizeOf(value));
|
|
69
75
|
}
|
|
70
76
|
get minimumSize() {
|
|
71
|
-
return this._minimumSize;
|
|
77
|
+
return this._minimumSize.get();
|
|
72
78
|
}
|
|
73
79
|
set minimumSize(value) {
|
|
74
|
-
this._minimumSize
|
|
80
|
+
this._minimumSize.set(cellCount(value));
|
|
75
81
|
}
|
|
76
82
|
get children() {
|
|
77
|
-
return this.
|
|
83
|
+
return this._split.get().children;
|
|
78
84
|
}
|
|
79
85
|
/**
|
|
80
86
|
* Whether this layout draws `_renderable` or its children.
|
|
@@ -86,23 +92,21 @@ export class Layout {
|
|
|
86
92
|
* fit-mode `Panel` framed twelve cells of air.
|
|
87
93
|
*/
|
|
88
94
|
get _isLeaf() {
|
|
89
|
-
return this.
|
|
95
|
+
return this._split.get().children.length === 0;
|
|
90
96
|
}
|
|
91
97
|
splitColumn(...layouts) {
|
|
92
|
-
this.
|
|
93
|
-
this._splitDirection = "column";
|
|
98
|
+
this._split.set({ direction: "column", children: layouts });
|
|
94
99
|
}
|
|
95
100
|
splitRow(...layouts) {
|
|
96
|
-
this.
|
|
97
|
-
this._splitDirection = "row";
|
|
101
|
+
this._split.set({ direction: "row", children: layouts });
|
|
98
102
|
}
|
|
99
103
|
update(renderable) {
|
|
100
|
-
this._renderable
|
|
104
|
+
this._renderable.set(embed(renderable));
|
|
101
105
|
}
|
|
102
106
|
getByName(name) {
|
|
103
107
|
if (this.name === name)
|
|
104
108
|
return this;
|
|
105
|
-
for (const child of this.
|
|
109
|
+
for (const child of this._split.get().children) {
|
|
106
110
|
const found = child.getByName(name);
|
|
107
111
|
if (found)
|
|
108
112
|
return found;
|
|
@@ -128,8 +132,9 @@ export class Layout {
|
|
|
128
132
|
// ok\n")` is two lines, and a one-row region cuts the second. Every line
|
|
129
133
|
// ended, as the split paths below end theirs: a leaf stacked in a
|
|
130
134
|
// `Group` is still a region of lines.
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
const renderable = this._renderable.get();
|
|
136
|
+
if (renderable) {
|
|
137
|
+
const lines = fitHeight(Segment.splitLines(render(renderable, options)), options.height);
|
|
133
138
|
for (const line of lines) {
|
|
134
139
|
yield* Segment.adjustLineLength(line, options.maxWidth);
|
|
135
140
|
yield Segment.line();
|
|
@@ -137,28 +142,16 @@ export class Layout {
|
|
|
137
142
|
}
|
|
138
143
|
return;
|
|
139
144
|
}
|
|
140
|
-
const visibleChildren = this.
|
|
145
|
+
const visibleChildren = this._split.get().children.filter((c) => c.visible);
|
|
141
146
|
if (visibleChildren.length === 0)
|
|
142
147
|
return;
|
|
143
|
-
if (this.
|
|
148
|
+
if (this._split.get().direction === "row") {
|
|
144
149
|
yield* this._renderRow(visibleChildren, options);
|
|
145
150
|
}
|
|
146
151
|
else {
|
|
147
152
|
yield* this._renderColumn(visibleChildren, options);
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
|
-
// What `render` draws, decided as it decides it: nothing when hidden, a
|
|
151
|
-
// leaf's content, or the visible panes of a split.
|
|
152
|
-
*contents() {
|
|
153
|
-
if (!this.visible)
|
|
154
|
-
return;
|
|
155
|
-
if (this._isLeaf) {
|
|
156
|
-
if (this._renderable)
|
|
157
|
-
yield this._renderable;
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
yield* this._children.filter((c) => c.visible);
|
|
161
|
-
}
|
|
162
155
|
*_renderColumn(children, options) {
|
|
163
156
|
// Each pane gets full width and its share of the region. With no region —
|
|
164
157
|
// a ceiling, or no budget at all — a pane's share is its declared `size`,
|
|
@@ -200,15 +193,16 @@ export class Layout {
|
|
|
200
193
|
if (!this.visible)
|
|
201
194
|
return 0;
|
|
202
195
|
if (this._isLeaf) {
|
|
203
|
-
|
|
196
|
+
const renderable = this._renderable.get();
|
|
197
|
+
if (renderable === undefined)
|
|
204
198
|
return 0;
|
|
205
|
-
return Measurement.get(options,
|
|
199
|
+
return Measurement.get(options, renderable).maximum;
|
|
206
200
|
}
|
|
207
|
-
const visible = this.
|
|
201
|
+
const visible = this._split.get().children.filter((c) => c.visible);
|
|
208
202
|
if (visible.length === 0)
|
|
209
203
|
return 0;
|
|
210
204
|
const widths = visible.map((c) => c._naturalWidth(options));
|
|
211
|
-
if (this.
|
|
205
|
+
if (this._split.get().direction === "row")
|
|
212
206
|
return ratioBudget(visible, widths);
|
|
213
207
|
// Accumulated, not spread: a column split holds as many children as a caller
|
|
214
208
|
// made, and `Math.max(...widths)` passes one argument per child, so a
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An element's lifecycle: it enters, stays, is told to leave, leaves, and is
|
|
3
|
+
* gone — a row added to a table, a line appended to a log, a panel closing.
|
|
4
|
+
* Drawn on the host's clock as an `Animation<Renderable>`, so it is a view a
|
|
5
|
+
* `Live` or an `App` draws as it stands, and a list of them is
|
|
6
|
+
* `Animation.all` of them mapped to whatever lays them out.
|
|
7
|
+
*
|
|
8
|
+
* The two times are handed in, never read: when the element entered, and when
|
|
9
|
+
* it was told to leave — `Infinity` until it is, a time like any other, so no
|
|
10
|
+
* part of the drawing asks whether it is leaving. Telling it is a new value
|
|
11
|
+
* (`leave`), as every `Timeline` operation is. [LAW:no-ambient-temporal-coupling]
|
|
12
|
+
*
|
|
13
|
+
* Each half is a `Transition` of a `Veil` on its own clock from 0, handed the time since
|
|
14
|
+
* its moment, and its schedule carried onto the host's clock by
|
|
15
|
+
* `Timeline.run` is that half's hold — every frame for a fade, once a hold
|
|
16
|
+
* for one on a beat — so the transition's schedule, and `done` read off it,
|
|
17
|
+
* stays the one rule for where a one-shot ends and when its frame changes,
|
|
18
|
+
* and nothing here measures progress itself. The frames carry no word of
|
|
19
|
+
* their own on when they change: placed, the lifecycle stamps them with its
|
|
20
|
+
* hold at the frame's time (`placedRender`). The exit veils whatever the
|
|
21
|
+
* arrival draws, so an element told to leave while it is still arriving leaves
|
|
22
|
+
* from where it is, never shown whole first — though a colour exit is spread
|
|
23
|
+
* over the cells each frame shows, so a cell the arrival adds can move one
|
|
24
|
+
* already leaving back toward whole (`ownChange`, `./effects.ts`).
|
|
25
|
+
* [LAW:dataflow-not-control-flow]
|
|
26
|
+
*
|
|
27
|
+
* A half draws the element through a colour transition (`throughColors`, a
|
|
28
|
+
* `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each lift binds
|
|
29
|
+
* its transition to the one wrapper that draws it, and to what that wrapper
|
|
30
|
+
* needs — a theme for colours, nothing for glyphs — so nothing here asks which
|
|
31
|
+
* kind a half is, and a line can type itself in and dissolve out. An exit is
|
|
32
|
+
* a transition that ends with nothing drawn — `dissolveOut`, `backspace`,
|
|
33
|
+
* `scrambleOut` — so the element has already gone from sight when it is gone.
|
|
34
|
+
*
|
|
35
|
+
* Gone is a fact the host reads (`gone(t)`) to drop the element, and from then
|
|
36
|
+
* the lifecycle draws nothing and never changes, so a host still holding it
|
|
37
|
+
* wakes for nothing. Before it entered — asked by a timeline running
|
|
38
|
+
* backwards — it draws nothing too, but is not gone.
|
|
39
|
+
*/
|
|
40
|
+
import { Animation } from "../core/animation.js";
|
|
41
|
+
import type { TerminalTheme } from "../core/color.js";
|
|
42
|
+
import type { Renderable } from "../core/protocol.js";
|
|
43
|
+
import { Transition } from "./effects.js";
|
|
44
|
+
import type { GlyphTransition } from "./glyph-effects.js";
|
|
45
|
+
/**
|
|
46
|
+
* An element drawn through one half of its lifecycle at `t`, the seconds since
|
|
47
|
+
* that half began, its effect's seeds named by `key`, saying nothing of when
|
|
48
|
+
* that changes — the half's schedule does, as a `Recolored`'s and a
|
|
49
|
+
* `Reglyphed`'s frame say nothing.
|
|
50
|
+
*/
|
|
51
|
+
export type Veil = (element: Renderable, t: number, key: string) => Renderable;
|
|
52
|
+
/** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
|
|
53
|
+
export declare function throughColors(transition: Transition, theme: TerminalTheme): Transition<Veil>;
|
|
54
|
+
/** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
|
|
55
|
+
export declare function throughGlyphs(transition: GlyphTransition): Transition<Veil>;
|
|
56
|
+
/** How an element arrives and leaves, and what it is named for their seeds. */
|
|
57
|
+
export interface LifecycleOptions {
|
|
58
|
+
/** The half it arrives through — `throughColors(fadeIn(curve, z), theme)`. */
|
|
59
|
+
readonly enter: Transition<Veil>;
|
|
60
|
+
/** The half it leaves through — `throughGlyphs(scrambleOut(timing, z))`. */
|
|
61
|
+
readonly exit: Transition<Veil>;
|
|
62
|
+
/** Names the element for both halves' seeds, as `Effected`'s and `GlyphEffected`'s key does. */
|
|
63
|
+
readonly key: string;
|
|
64
|
+
}
|
|
65
|
+
/** An element on the host's clock, from before it entered to after it is gone. */
|
|
66
|
+
export interface Lifecycle extends Animation<Renderable> {
|
|
67
|
+
/** The host time it entered. */
|
|
68
|
+
readonly entered: number;
|
|
69
|
+
/** The host time it was told to leave: `Infinity` until it is. */
|
|
70
|
+
readonly left: number;
|
|
71
|
+
/** Whether it has finished leaving by `t`, so a host drops it. */
|
|
72
|
+
gone(t: number): boolean;
|
|
73
|
+
/** This element told to leave at host time `at`. Throws if it was already told, or `at` is before it entered. */
|
|
74
|
+
leave(at: number): Lifecycle;
|
|
75
|
+
}
|
|
76
|
+
/** `element` entering at host time `entered`, through `options.enter`, and staying until it is told to `leave`. */
|
|
77
|
+
export declare function lifecycle(element: Renderable, options: LifecycleOptions, entered: number): Lifecycle;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An element's lifecycle: it enters, stays, is told to leave, leaves, and is
|
|
3
|
+
* gone — a row added to a table, a line appended to a log, a panel closing.
|
|
4
|
+
* Drawn on the host's clock as an `Animation<Renderable>`, so it is a view a
|
|
5
|
+
* `Live` or an `App` draws as it stands, and a list of them is
|
|
6
|
+
* `Animation.all` of them mapped to whatever lays them out.
|
|
7
|
+
*
|
|
8
|
+
* The two times are handed in, never read: when the element entered, and when
|
|
9
|
+
* it was told to leave — `Infinity` until it is, a time like any other, so no
|
|
10
|
+
* part of the drawing asks whether it is leaving. Telling it is a new value
|
|
11
|
+
* (`leave`), as every `Timeline` operation is. [LAW:no-ambient-temporal-coupling]
|
|
12
|
+
*
|
|
13
|
+
* Each half is a `Transition` of a `Veil` on its own clock from 0, handed the time since
|
|
14
|
+
* its moment, and its schedule carried onto the host's clock by
|
|
15
|
+
* `Timeline.run` is that half's hold — every frame for a fade, once a hold
|
|
16
|
+
* for one on a beat — so the transition's schedule, and `done` read off it,
|
|
17
|
+
* stays the one rule for where a one-shot ends and when its frame changes,
|
|
18
|
+
* and nothing here measures progress itself. The frames carry no word of
|
|
19
|
+
* their own on when they change: placed, the lifecycle stamps them with its
|
|
20
|
+
* hold at the frame's time (`placedRender`). The exit veils whatever the
|
|
21
|
+
* arrival draws, so an element told to leave while it is still arriving leaves
|
|
22
|
+
* from where it is, never shown whole first — though a colour exit is spread
|
|
23
|
+
* over the cells each frame shows, so a cell the arrival adds can move one
|
|
24
|
+
* already leaving back toward whole (`ownChange`, `./effects.ts`).
|
|
25
|
+
* [LAW:dataflow-not-control-flow]
|
|
26
|
+
*
|
|
27
|
+
* A half draws the element through a colour transition (`throughColors`, a
|
|
28
|
+
* `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each lift binds
|
|
29
|
+
* its transition to the one wrapper that draws it, and to what that wrapper
|
|
30
|
+
* needs — a theme for colours, nothing for glyphs — so nothing here asks which
|
|
31
|
+
* kind a half is, and a line can type itself in and dissolve out. An exit is
|
|
32
|
+
* a transition that ends with nothing drawn — `dissolveOut`, `backspace`,
|
|
33
|
+
* `scrambleOut` — so the element has already gone from sight when it is gone.
|
|
34
|
+
*
|
|
35
|
+
* Gone is a fact the host reads (`gone(t)`) to drop the element, and from then
|
|
36
|
+
* the lifecycle draws nothing and never changes, so a host still holding it
|
|
37
|
+
* wakes for nothing. Before it entered — asked by a timeline running
|
|
38
|
+
* backwards — it draws nothing too, but is not gone.
|
|
39
|
+
*/
|
|
40
|
+
import { Animation } from "../core/animation.js";
|
|
41
|
+
import { firstReaching } from "../core/easing.js";
|
|
42
|
+
import { Timeline } from "../core/timeline.js";
|
|
43
|
+
import { Recolored } from "./effect.js";
|
|
44
|
+
import { Transition } from "./effects.js";
|
|
45
|
+
import { Reglyphed } from "./glyph-effect.js";
|
|
46
|
+
/** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
|
|
47
|
+
export function throughColors(transition, theme) {
|
|
48
|
+
return lift(transition, (element, t, key) => new Recolored(element, transition.effect, { t, key, theme }));
|
|
49
|
+
}
|
|
50
|
+
/** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
|
|
51
|
+
export function throughGlyphs(transition) {
|
|
52
|
+
return lift(transition, (element, t, key) => new Reglyphed(element, transition.effect, { t, key }));
|
|
53
|
+
}
|
|
54
|
+
/** [LAW:one-source-of-truth] A lifted half keeps its transition's schedule, so `done` and every hold are the transition's own. */
|
|
55
|
+
function lift(transition, veil) {
|
|
56
|
+
return new Transition(veil, transition.schedule);
|
|
57
|
+
}
|
|
58
|
+
/** What an element draws before it entered and once it is gone: no cells, and no width. */
|
|
59
|
+
const NOTHING = { render: () => [], measure: () => ({ minimum: 0, maximum: 0 }) };
|
|
60
|
+
/** `element` entering at host time `entered`, through `options.enter`, and staying until it is told to `leave`. */
|
|
61
|
+
export function lifecycle(element, options, entered) {
|
|
62
|
+
if (!Number.isFinite(entered))
|
|
63
|
+
throw new RangeError(`lifecycle: entered must be a finite time, got ${entered}`);
|
|
64
|
+
return life(element, options, entered, Number.POSITIVE_INFINITY);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The first host moment `transition`, begun at host time `start`, is done:
|
|
68
|
+
* its own `done` read on host time, never a sum `start + seconds`, which can
|
|
69
|
+
* round to either side of it, as `Animation.sequence` carries its switch.
|
|
70
|
+
* [LAW:single-enforcer] Since `x - start` never falls as `x` rises, a host time
|
|
71
|
+
* is at or past this moment exactly when `done` holds there.
|
|
72
|
+
*/
|
|
73
|
+
function doneAt(transition, start) {
|
|
74
|
+
return Number.isFinite(start) ? firstReaching(start, (x) => transition.done(x - start)) : start;
|
|
75
|
+
}
|
|
76
|
+
function life(element, options, entered, left) {
|
|
77
|
+
const { enter, exit, key } = options;
|
|
78
|
+
const arrived = doneAt(enter, entered);
|
|
79
|
+
const goneAt = doneAt(exit, left);
|
|
80
|
+
// The arrival's schedule on the host's clock: when its frame next changes, and since when it has been what it is.
|
|
81
|
+
const arriving = Timeline.run(Timeline.play(entered), enter.schedule);
|
|
82
|
+
// What the element draws by `t` if it is not leaving: arriving through `enter`, then itself.
|
|
83
|
+
const shown = (t) => (t < arrived ? enter.effect(element, t - entered, key) : element);
|
|
84
|
+
const held = (schedule, t) => ({ last: schedule.lastChange(t), next: schedule.nextChange(t) });
|
|
85
|
+
// [LAW:one-source-of-truth] Each phase's frame and its hold, side by side, so the two cannot disagree.
|
|
86
|
+
// The frames say nothing of when they change — the hold does, stamped where the lifecycle is placed.
|
|
87
|
+
const phase = (t) => {
|
|
88
|
+
if (t < entered)
|
|
89
|
+
return { frame: NOTHING, last: Number.NEGATIVE_INFINITY, next: entered };
|
|
90
|
+
if (t >= goneAt)
|
|
91
|
+
return { frame: NOTHING, last: goneAt, next: Number.POSITIVE_INFINITY };
|
|
92
|
+
// Leaving veils whatever arriving draws, so both schedules count; once arrived, the arrival's never changes again.
|
|
93
|
+
if (t >= left) {
|
|
94
|
+
const leaving = Animation.all([arriving, Timeline.run(Timeline.play(left), exit.schedule)]);
|
|
95
|
+
return { frame: exit.effect(shown(t), t - left, key), ...held(leaving, t) };
|
|
96
|
+
}
|
|
97
|
+
if (t < arrived)
|
|
98
|
+
return { frame: shown(t), ...held(arriving, t) };
|
|
99
|
+
return { frame: element, last: arrived, next: left };
|
|
100
|
+
};
|
|
101
|
+
// [LAW:dataflow-not-control-flow] The spans `phase` draws, the transitions gone from them: told to
|
|
102
|
+
// leave, nothing shows, and the moment it is `gone` is still one a host is woken for, so it drops it on time.
|
|
103
|
+
const there = (t) => t < entered
|
|
104
|
+
? { frame: NOTHING, last: Number.NEGATIVE_INFINITY, next: entered }
|
|
105
|
+
: t < left
|
|
106
|
+
? { frame: element, last: entered, next: left }
|
|
107
|
+
: { frame: NOTHING, last: left, next: t < goneAt ? goneAt : Number.POSITIVE_INFINITY };
|
|
108
|
+
const present = Animation.of({
|
|
109
|
+
at: (t) => there(t).frame,
|
|
110
|
+
nextChange: (t) => there(t).next,
|
|
111
|
+
lastChange: (t) => there(t).last,
|
|
112
|
+
reduced: () => present,
|
|
113
|
+
});
|
|
114
|
+
return Animation.of({
|
|
115
|
+
entered,
|
|
116
|
+
left,
|
|
117
|
+
at: (t) => phase(t).frame,
|
|
118
|
+
nextChange: (t) => phase(t).next,
|
|
119
|
+
lastChange: (t) => phase(t).last,
|
|
120
|
+
// Reduced, the element is there from when it entered until it is told to leave: no arrival or exit to show, and the times kept.
|
|
121
|
+
reduced: () => present,
|
|
122
|
+
// [LAW:one-source-of-truth] The moment `phase` draws nothing from, so an element not yet told to leave is never asked its exit at -Infinity.
|
|
123
|
+
gone: (t) => t >= goneAt,
|
|
124
|
+
leave(at) {
|
|
125
|
+
if (Number.isFinite(left))
|
|
126
|
+
throw new RangeError(`lifecycle: already told to leave at ${left}, told again at ${at}`);
|
|
127
|
+
if (!(Number.isFinite(at) && at >= entered))
|
|
128
|
+
throw new RangeError(`lifecycle: leave must be a finite time from entered (${entered}) on, got ${at}`);
|
|
129
|
+
return life(element, options, entered, at);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* An item can wrap, so the lines the selected one covers exist only at the
|
|
6
6
|
* width it is drawn at, which a render is handed and whoever builds the frame
|
|
7
7
|
* is not. So the items are drawn here, at the viewport's content width and
|
|
8
|
-
* with no height — the lines the viewport scrolls — and
|
|
9
|
-
*
|
|
8
|
+
* with no height — the lines the viewport scrolls — and shown through the
|
|
9
|
+
* viewport with the selected item's range followed.
|
|
10
10
|
* [LAW:single-enforcer] Where the window sits is the viewport's call; this
|
|
11
11
|
* only says which lines the selection is.
|
|
12
12
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* it — so a consumer can take one without the others.
|
|
17
17
|
*/
|
|
18
18
|
import { Segment } from "../core/segment.js";
|
|
19
|
-
import type {
|
|
19
|
+
import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
20
20
|
import type { Viewport } from "./viewport.js";
|
|
21
21
|
export interface ListViewOptions {
|
|
22
22
|
/** Blank lines between one item and the next. Absent, none. */
|
|
@@ -27,7 +27,7 @@ export interface ListViewOptions {
|
|
|
27
27
|
*/
|
|
28
28
|
context?: number;
|
|
29
29
|
}
|
|
30
|
-
export declare class ListView implements Renderable, Measurable
|
|
30
|
+
export declare class ListView implements Renderable, Measurable {
|
|
31
31
|
readonly viewport: Viewport;
|
|
32
32
|
readonly items: readonly Renderable[];
|
|
33
33
|
readonly selected: number;
|
|
@@ -35,7 +35,6 @@ export declare class ListView implements Renderable, Measurable, Composite {
|
|
|
35
35
|
readonly context: number;
|
|
36
36
|
constructor(viewport: Viewport, items: readonly Renderable[], selected: number, options?: ListViewOptions);
|
|
37
37
|
render(rawOptions: RenderOptions): Iterable<Segment>;
|
|
38
|
-
contents(): Iterable<Renderable>;
|
|
39
38
|
measure(rawOptions: RenderOptions): {
|
|
40
39
|
minimum: number;
|
|
41
40
|
maximum: number;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* An item can wrap, so the lines the selected one covers exist only at the
|
|
6
6
|
* width it is drawn at, which a render is handed and whoever builds the frame
|
|
7
7
|
* is not. So the items are drawn here, at the viewport's content width and
|
|
8
|
-
* with no height — the lines the viewport scrolls — and
|
|
9
|
-
*
|
|
8
|
+
* with no height — the lines the viewport scrolls — and shown through the
|
|
9
|
+
* viewport with the selected item's range followed.
|
|
10
10
|
* [LAW:single-enforcer] Where the window sits is the viewport's call; this
|
|
11
11
|
* only says which lines the selection is.
|
|
12
12
|
*
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { Segment } from "../core/segment.js";
|
|
19
19
|
import { Measurement, measureRenderables } from "../core/measure.js";
|
|
20
|
-
import {
|
|
20
|
+
import { render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
21
21
|
function lineCount(name, value) {
|
|
22
22
|
if (!Number.isInteger(value) || value < 0)
|
|
23
23
|
throw new RangeError(`ListView: ${name} ${value} is not a whole number of lines`);
|
|
@@ -51,7 +51,7 @@ export class ListView {
|
|
|
51
51
|
const start = drawn.slice(0, this.selected).reduce((total, item) => total + item.length + this.gap, 0);
|
|
52
52
|
const end = start + (drawn[this.selected]?.length ?? 0);
|
|
53
53
|
const lines = drawn.flatMap((item, i) => (i === 0 ? item : [...gap, ...item]));
|
|
54
|
-
|
|
54
|
+
const content = {
|
|
55
55
|
*render() {
|
|
56
56
|
for (const line of lines) {
|
|
57
57
|
yield* line;
|
|
@@ -62,15 +62,11 @@ export class ListView {
|
|
|
62
62
|
// A range reaching past either end is clamped by the viewport, which holds
|
|
63
63
|
// its offset to the lines that exist. Followed, not ensured: the wheel can
|
|
64
64
|
// scroll away from the selection until the selection moves. Items handed
|
|
65
|
-
// no column are held, not drawn, and
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
yield* render(this.viewport, bounded);
|
|
70
|
-
}
|
|
71
|
-
*contents() {
|
|
72
|
-
yield* this.items;
|
|
73
|
-
yield this.viewport;
|
|
65
|
+
// no column are held, not drawn, and so is the viewport's content, which
|
|
66
|
+
// keeps nothing it is handed — the range last followed stands.
|
|
67
|
+
// [LAW:effects-at-boundaries] Shown through the viewport rather than
|
|
68
|
+
// handed to it: the viewport keeps the lines' range once the frame is drawn.
|
|
69
|
+
yield* render(this.viewport.showing(content, { start: start - this.context, end: end + this.context }), bounded);
|
|
74
70
|
}
|
|
75
71
|
measure(rawOptions) {
|
|
76
72
|
// Stacked, the items are as wide as the widest of them, plus whatever
|
|
@@ -26,17 +26,9 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { type Clock } from "../core/clock.js";
|
|
28
28
|
import { Console } from "../core/console.js";
|
|
29
|
-
import { type
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* the frame's time in seconds on the Live's clock, as an `App`'s view is
|
|
33
|
-
* (`host/app.ts`). A renderable is the view that is the same at every `t` —
|
|
34
|
-
* a table mutated in place between frames — and a function is what a frame
|
|
35
|
-
* drawn for a time needs: `(t) => spinner.at(t)`.
|
|
36
|
-
* [LAW:no-ambient-temporal-coupling] The Live reads its clock once a frame
|
|
37
|
-
* and hands the time down as data, so nothing it draws reads one.
|
|
38
|
-
*/
|
|
39
|
-
export type LiveView = Renderable | ((t: number) => Renderable);
|
|
29
|
+
import { type FrameHost } from "../core/protocol.js";
|
|
30
|
+
import { Timeline } from "../core/timeline.js";
|
|
31
|
+
import { type View } from "../core/view.js";
|
|
40
32
|
export interface LiveOptions {
|
|
41
33
|
/**
|
|
42
34
|
* How often the auto-refresh draws on its own, in frames a second:
|
|
@@ -48,6 +40,13 @@ export interface LiveOptions {
|
|
|
48
40
|
refreshPerSecond?: number;
|
|
49
41
|
/** What the auto-refresh waits on. The platform's, unless given. */
|
|
50
42
|
clock?: Clock;
|
|
43
|
+
/**
|
|
44
|
+
* The map from the clock to the time the view is drawn for (`Timeline`).
|
|
45
|
+
* Playing from 0 when the host is made, at the clock's pace, unless given
|
|
46
|
+
* — so a tween in the view starts when the host is made, not when the clock
|
|
47
|
+
* began; `live.timeline` reads and replaces it.
|
|
48
|
+
*/
|
|
49
|
+
timeline?: Timeline;
|
|
51
50
|
autoRefresh?: boolean;
|
|
52
51
|
transient?: boolean;
|
|
53
52
|
console?: Console;
|
|
@@ -57,26 +56,42 @@ export interface LiveOptions {
|
|
|
57
56
|
* a full-screen layout fills it. */
|
|
58
57
|
altScreen?: boolean;
|
|
59
58
|
}
|
|
60
|
-
export declare class Live {
|
|
61
|
-
private _view;
|
|
62
|
-
private _console;
|
|
59
|
+
export declare class Live implements FrameHost {
|
|
60
|
+
private readonly _view;
|
|
61
|
+
private readonly _console;
|
|
63
62
|
private readonly _rate;
|
|
64
63
|
private readonly _clock;
|
|
65
|
-
private
|
|
66
|
-
private
|
|
67
|
-
private
|
|
64
|
+
private readonly _epoch;
|
|
65
|
+
private readonly _timeline;
|
|
66
|
+
private readonly _autoRefresh;
|
|
67
|
+
private readonly _transient;
|
|
68
|
+
private readonly _verticalOverflow;
|
|
68
69
|
private readonly _painter;
|
|
69
|
-
private _waking;
|
|
70
|
-
private _held;
|
|
71
|
-
constructor(view?:
|
|
70
|
+
private readonly _waking;
|
|
71
|
+
private readonly _held;
|
|
72
|
+
constructor(view?: View, options?: LiveOptions);
|
|
72
73
|
/**
|
|
73
74
|
* The console to print through while this Live runs: what it prints lands
|
|
74
75
|
* above an inline frame. The alternate screen is all frame, and paints over it.
|
|
75
76
|
*/
|
|
76
77
|
get console(): Console;
|
|
78
|
+
/** The clock the auto-refresh waits on: where the host time a `Timeline` operation takes is read. */
|
|
79
|
+
get clock(): Clock;
|
|
80
|
+
/** The map from the clock to the time the view is drawn for. */
|
|
81
|
+
get timeline(): Timeline;
|
|
82
|
+
/** The time a frame drawn now is drawn for: the clock's now, through the timeline. */
|
|
83
|
+
get frameTime(): number;
|
|
84
|
+
private _time;
|
|
85
|
+
/**
|
|
86
|
+
* Replace the timeline and draw the frame for the time it now maps the
|
|
87
|
+
* clock's now to, so a seek shows where it sought, once. Paused, the frame
|
|
88
|
+
* then holds — the auto-refresh waits on nothing until the timeline plays
|
|
89
|
+
* again — while `refresh()` and `update(…, { refresh: true })` still draw.
|
|
90
|
+
*/
|
|
91
|
+
set timeline(timeline: Timeline);
|
|
77
92
|
start(): void;
|
|
78
93
|
stop(): void;
|
|
79
|
-
update(view?:
|
|
94
|
+
update(view?: View, options?: {
|
|
80
95
|
refresh?: boolean;
|
|
81
96
|
}): void;
|
|
82
97
|
refresh(): void;
|