@promptctl/rich-js 0.23.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/animation.d.ts +78 -0
- package/dist/core/animation.js +107 -0
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +17 -11
- package/dist/core/cells.js +100 -25
- package/dist/core/clock.d.ts +9 -1
- package/dist/core/clock.js +22 -2
- package/dist/core/color.d.ts +10 -0
- package/dist/core/color.js +36 -1
- package/dist/core/console.js +5 -5
- package/dist/core/easing.d.ts +138 -7
- package/dist/core/easing.js +355 -17
- package/dist/core/json.js +2 -1
- package/dist/core/measure.js +2 -2
- package/dist/core/place.js +2 -1
- package/dist/core/pretty.js +2 -2
- package/dist/core/protocol.d.ts +73 -19
- package/dist/core/protocol.js +56 -15
- package/dist/core/render.js +2 -1
- package/dist/core/segment.d.ts +28 -1
- package/dist/core/segment.js +60 -44
- package/dist/core/strip.d.ts +107 -29
- package/dist/core/strip.js +242 -131
- package/dist/core/style.d.ts +18 -1
- package/dist/core/style.js +49 -9
- package/dist/core/text.js +26 -15
- package/dist/core/timeline.d.ts +84 -0
- package/dist/core/timeline.js +144 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +69 -0
- package/dist/host/app.js +4 -6
- package/dist/host/index.d.ts +1 -1
- package/dist/host/input.d.ts +37 -13
- package/dist/host/input.js +146 -56
- package/dist/host/terminal-host.d.ts +16 -0
- package/dist/host/terminal-host.js +1 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.js +15 -5
- package/dist/node/terminal-host.d.ts +8 -1
- package/dist/node/terminal-host.js +2 -0
- package/dist/renderables/align.d.ts +3 -2
- package/dist/renderables/align.js +3 -0
- package/dist/renderables/columns.d.ts +3 -2
- package/dist/renderables/columns.js +7 -2
- package/dist/renderables/constrain.d.ts +3 -2
- package/dist/renderables/constrain.js +5 -1
- package/dist/renderables/effect.d.ts +83 -30
- package/dist/renderables/effect.js +155 -57
- package/dist/renderables/effects.d.ts +88 -39
- package/dist/renderables/effects.js +444 -105
- package/dist/renderables/embed.d.ts +10 -0
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.d.ts +3 -2
- package/dist/renderables/flexStrip.js +6 -1
- package/dist/renderables/frames.d.ts +116 -0
- package/dist/renderables/frames.js +133 -0
- package/dist/renderables/group.d.ts +3 -2
- package/dist/renderables/group.js +5 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +3 -2
- package/dist/renderables/layout.js +15 -2
- package/dist/renderables/list-view.d.ts +3 -2
- package/dist/renderables/list-view.js +12 -5
- package/dist/renderables/live.d.ts +23 -7
- package/dist/renderables/live.js +69 -35
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1421 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +7 -6
- package/dist/renderables/markdown.js +363 -499
- package/dist/renderables/padding.d.ts +8 -20
- package/dist/renderables/padding.js +32 -50
- package/dist/renderables/panel.d.ts +26 -26
- package/dist/renderables/panel.js +67 -103
- package/dist/renderables/progress.d.ts +5 -3
- package/dist/renderables/progress.js +14 -3
- package/dist/renderables/progressBar.js +4 -4
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +24 -0
- package/dist/renderables/spinner.d.ts +73 -27
- package/dist/renderables/spinner.js +110 -51
- package/dist/renderables/status.d.ts +8 -3
- package/dist/renderables/status.js +8 -5
- package/dist/renderables/syntax.js +2 -1
- package/dist/renderables/table.d.ts +19 -11
- package/dist/renderables/table.js +140 -156
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +6 -5
- package/dist/renderables/tree.js +11 -7
- package/dist/renderables/viewport.d.ts +13 -5
- package/dist/renderables/viewport.js +41 -20
- package/dist/template-bindings/animation-funcs.d.ts +37 -0
- package/dist/template-bindings/animation-funcs.js +83 -0
- package/dist/template-bindings/color-funcs.d.ts +13 -0
- package/dist/template-bindings/color-funcs.js +1 -1
- package/dist/template-bindings/helpers.d.ts +6 -0
- package/dist/template-bindings/helpers.js +12 -3
- package/dist/template-bindings/index.d.ts +6 -2
- package/dist/template-bindings/index.js +8 -3
- package/dist/themes/ramp.d.ts +18 -3
- package/dist/themes/ramp.js +23 -2
- package/dist/widgets/focus-manager.d.ts +21 -14
- package/dist/widgets/focus-manager.js +28 -29
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/static-item.d.ts +4 -3
- package/dist/widgets/static-item.js +9 -7
- package/dist/widgets/text-input.d.ts +28 -18
- package/dist/widgets/text-input.js +144 -101
- package/dist/widgets/widget-app.d.ts +19 -3
- package/dist/widgets/widget-app.js +39 -21
- package/dist/widgets/widget-base.js +0 -3
- package/package.json +2 -5
package/dist/renderables/tree.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Segment } from "../core/segment.js";
|
|
|
5
5
|
import { cellFit, cellLen, asCellCol } from "../core/cells.js";
|
|
6
6
|
import { Style, NULL_STYLE } from "../core/style.js";
|
|
7
7
|
import { embed } from "./embed.js";
|
|
8
|
-
import { drawable, getStyle, stackedHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
8
|
+
import { drawable, getStyle, render, stackedHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
9
9
|
import { Measurement } from "../core/measure.js";
|
|
10
10
|
/** Every glyph of every set is this wide, so a row's guides are as wide however they are drawn. */
|
|
11
11
|
const GUIDE_CELLS = 4;
|
|
@@ -62,6 +62,12 @@ export class Tree {
|
|
|
62
62
|
yield* this._renderRow(options, row);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
// Every label `render` draws, from the same walk. Not `children`, which is
|
|
66
|
+
// the subtrees, as Rich names them.
|
|
67
|
+
*contents() {
|
|
68
|
+
for (const row of this._walk())
|
|
69
|
+
yield row.label;
|
|
70
|
+
}
|
|
65
71
|
/**
|
|
66
72
|
* The rows `render` and `measure` both read, entered in exactly one place so
|
|
67
73
|
* the two cannot start the walk differently. A hidden root takes its own row
|
|
@@ -115,9 +121,9 @@ export class Tree {
|
|
|
115
121
|
* [LAW:single-enforcer] Tree also owns its row boundaries here rather than
|
|
116
122
|
* depending on label renderables to invent trailing newlines: the label's
|
|
117
123
|
* output is cut into lines and every line ends where Tree ends it. A label
|
|
118
|
-
* that emits nothing
|
|
119
|
-
* its own gets the blank row that line is, as in
|
|
120
|
-
* width when the label is justified.
|
|
124
|
+
* that emits nothing — one left no column, say — has no row, and one
|
|
125
|
+
* that ends in a newline of its own gets the blank row that line is, as in
|
|
126
|
+
* Rich — padded to the label's width when the label is justified.
|
|
121
127
|
*
|
|
122
128
|
* The node's style sits under the label's own, as Rich's `Styled` puts it, so
|
|
123
129
|
* markup in the label still wins; its background alone reaches under the
|
|
@@ -140,9 +146,7 @@ export class Tree {
|
|
|
140
146
|
// guides were fitted into. No highlighter: a label is drawn plain, as
|
|
141
147
|
// Rich's `Tree` hands its labels `highlight=False`.
|
|
142
148
|
const labelOptions = { ...options, highlight: false, maxWidth: width, height: stackedHeight(options.height) };
|
|
143
|
-
const lines = Segment.splitLines(Segment.applyStyle(Segment.cropLines(row.label
|
|
144
|
-
if (lines.length === 0)
|
|
145
|
-
lines.push([]);
|
|
149
|
+
const lines = Segment.splitLines(Segment.applyStyle(Segment.cropLines(render(row.label, labelOptions), width), style));
|
|
146
150
|
for (let i = 0; i < lines.length; i++) {
|
|
147
151
|
let left = options.maxWidth;
|
|
148
152
|
for (let g = 0; g < row.guides.length; g++) {
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
* against exists before a render: the rows come from the budget the render is
|
|
20
20
|
* handed, and the content's length from rendering it at the width it is handed.
|
|
21
21
|
* 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
|
|
22
|
+
* are called. Each queues a move, and the next render that draws the content
|
|
23
|
+
* resolves the queue in call order, clamping after every move, against the
|
|
24
|
+
* rows and length that render found. Calling an operation before the first render, or three of them
|
|
25
25
|
* between two renders, is the same code path as calling one.
|
|
26
26
|
*
|
|
27
27
|
* Every cell of its rows, gutter included, is stamped as its own
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { Segment } from "../core/segment.js";
|
|
35
35
|
import type { Style } from "../core/style.js";
|
|
36
|
-
import type { Measurable, Renderable, RenderOptions, Scrollable } from "../core/protocol.js";
|
|
36
|
+
import type { Composite, Measurable, Renderable, RenderOptions, Scrollable } from "../core/protocol.js";
|
|
37
37
|
/** How one part of a scrollbar draws each row it covers. */
|
|
38
38
|
export interface ScrollbarPart {
|
|
39
39
|
readonly glyph: string;
|
|
@@ -60,7 +60,7 @@ export interface ViewportOptions {
|
|
|
60
60
|
/** A scrollbar down the right edge. Absent, the content has the full width. */
|
|
61
61
|
scrollbar?: Scrollbar;
|
|
62
62
|
}
|
|
63
|
-
export declare class Viewport implements Renderable, Measurable, Scrollable {
|
|
63
|
+
export declare class Viewport implements Renderable, Measurable, Scrollable, Composite {
|
|
64
64
|
/**
|
|
65
65
|
* What the viewport shows. Replace it to show new content from the same
|
|
66
66
|
* scroll position — a view rebuilt every frame keeps one `Viewport`.
|
|
@@ -113,7 +113,15 @@ export declare class Viewport implements Renderable, Measurable, Scrollable {
|
|
|
113
113
|
contentWidth(width: number): number;
|
|
114
114
|
/** The cells the scrollbar's gutter takes beside the content. */
|
|
115
115
|
get gutter(): number;
|
|
116
|
+
contents(): Iterable<Renderable>;
|
|
116
117
|
render(rawOptions: RenderOptions): Iterable<Segment>;
|
|
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;
|
|
117
125
|
/**
|
|
118
126
|
* [LAW:one-source-of-truth] Where the queued moves leave the offset against
|
|
119
127
|
* `extent`: what a render commits, and what `canScrollBy` asks from.
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
* against exists before a render: the rows come from the budget the render is
|
|
20
20
|
* handed, and the content's length from rendering it at the width it is handed.
|
|
21
21
|
* 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
|
|
22
|
+
* are called. Each queues a move, and the next render that draws the content
|
|
23
|
+
* resolves the queue in call order, clamping after every move, against the
|
|
24
|
+
* rows and length that render found. Calling an operation before the first render, or three of them
|
|
25
25
|
* between two renders, is the same code path as calling one.
|
|
26
26
|
*
|
|
27
27
|
* Every cell of its rows, gutter included, is stamped as its own
|
|
@@ -35,7 +35,8 @@ import { Segment } from "../core/segment.js";
|
|
|
35
35
|
import { Measurement } from "../core/measure.js";
|
|
36
36
|
import { cellCount, cellLen } from "../core/cells.js";
|
|
37
37
|
import { NULL_STYLE } from "../core/style.js";
|
|
38
|
-
import { drawable, fitHeight, getStyle, regionRows, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
38
|
+
import { drawable, drawsNothing, fitHeight, getStyle, regionRows, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
39
|
+
import { render } from "../core/protocol.js";
|
|
39
40
|
/** A heavy line for the thumb on a light line for the track. */
|
|
40
41
|
export const SCROLLBAR = {
|
|
41
42
|
thumb: { glyph: "┃", style: "scrollbar.thumb" },
|
|
@@ -125,26 +126,27 @@ export class Viewport {
|
|
|
125
126
|
get gutter() {
|
|
126
127
|
return gutterWidth(this.scrollbar);
|
|
127
128
|
}
|
|
129
|
+
*contents() {
|
|
130
|
+
yield this.content;
|
|
131
|
+
}
|
|
128
132
|
*render(rawOptions) {
|
|
129
133
|
const { height, ...options } = withBoundedWidth(rawOptions, this);
|
|
130
134
|
const gutter = this.gutter;
|
|
131
135
|
const contentWidth = this.contentWidth(options.maxWidth);
|
|
132
|
-
const lines = Segment.splitLines(this.content
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// this render just resolved, not from `offset` as it stood before it.
|
|
147
|
-
const thumb = thumbRows(extent, this._offset);
|
|
136
|
+
const lines = Segment.splitLines(render(this.content, { ...options, maxWidth: contentWidth }));
|
|
137
|
+
// [LAW:no-ambient-temporal-coupling] Content handed no column is held, not
|
|
138
|
+
// drawn, so this render learns its budget but not the content's length. It
|
|
139
|
+
// commits nothing: the offset, the followed range and the queued moves wait
|
|
140
|
+
// for a render that draws, and this one draws the scrollbar against the
|
|
141
|
+
// length the last one found.
|
|
142
|
+
const held = drawsNothing({ maxWidth: contentWidth });
|
|
143
|
+
const length = held ? this._extent.lines : lines.length;
|
|
144
|
+
const extent = { rows: viewRows(height, this.rows, length), lines: length };
|
|
145
|
+
const offset = held ? clampOffset(this._offset, extent) : this.commit(extent);
|
|
146
|
+
const shown = fitHeight(lines.slice(offset, offset + extent.rows), { rows: extent.rows, exact: true });
|
|
147
|
+
// The thumb is drawn from the offset this render resolved, not from
|
|
148
|
+
// `offset` as it stood before it.
|
|
149
|
+
const thumb = thumbRows(extent, offset);
|
|
148
150
|
// The content's cells and the gutter's drawn cells sum to the offer: a
|
|
149
151
|
// gutter wider than the whole offer is drawn cropped to it.
|
|
150
152
|
const drawn = Math.min(gutter, options.maxWidth);
|
|
@@ -168,6 +170,25 @@ export class Viewport {
|
|
|
168
170
|
yield Segment.line();
|
|
169
171
|
}
|
|
170
172
|
}
|
|
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
|
+
}
|
|
171
192
|
/**
|
|
172
193
|
* [LAW:one-source-of-truth] Where the queued moves leave the offset against
|
|
173
194
|
* `extent`: what a render commits, and what `canScrollBy` asks from.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation registrations for the rich-js template binding: what a template
|
|
3
|
+
* draws for the moment it is evaluated at.
|
|
4
|
+
*
|
|
5
|
+
* A host that builds its cells in templates — cc-candybar's statusline, say —
|
|
6
|
+
* evaluates each template once per redraw and owns the clock that says when
|
|
7
|
+
* that is. So the time is a capability the registration takes, the way
|
|
8
|
+
* `richTextFuncs(drawnAt)` takes the drawn depth: `frameTime()` is read once
|
|
9
|
+
* per call, when the template is evaluated, and nothing here reads a clock.
|
|
10
|
+
* [LAW:no-ambient-temporal-coupling] A host hands in the same `t` and gets
|
|
11
|
+
* the same fragment back; a host that redraws once a second or sixty times
|
|
12
|
+
* a second registers the same functions.
|
|
13
|
+
*
|
|
14
|
+
* `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
|
|
15
|
+
* (`renderables/frames.ts`) in a template: the frame in progress at the
|
|
16
|
+
* moment, as a fragment at the width of the widest. Its frames arrive as
|
|
17
|
+
* arguments on every evaluation, so the sequence is prepared on every one:
|
|
18
|
+
* a template has nowhere to keep it, and keeping it here would be the memo
|
|
19
|
+
* keyed on the arguments that `FrameSequence` is instead of. `stepped <seconds>
|
|
20
|
+
* <color> <seconds> <color> …` is the colour in progress at the moment, a
|
|
21
|
+
* colour value like every colour function's (`./color-funcs.ts`), so it
|
|
22
|
+
* nests where a colour goes — `fg (stepped …)`, `readableOn (stepped …) $bg
|
|
23
|
+
* 4.5` — and the ground it must read on is the template's to name, as it
|
|
24
|
+
* is for any colour. Either argument list is one float/value cycle end to
|
|
25
|
+
* end, so the engine's `alternating` gate types every slot and lifts each
|
|
26
|
+
* string frame to a fragment; the pairing — a trailing hold with no frame or
|
|
27
|
+
* colour — is parsed in `pairs`, once. [LAW:types-are-the-program]
|
|
28
|
+
*/
|
|
29
|
+
import type { FuncMap } from "@promptctl/go-template-js";
|
|
30
|
+
/**
|
|
31
|
+
* Register `frames` and `stepped`, each drawn for the moment `frameTime`
|
|
32
|
+
* reports when the template is evaluated: seconds on the host's own
|
|
33
|
+
* timeline, from whatever zero it counts from. The functions exist whatever
|
|
34
|
+
* `frameTime` returns, so a template compiles once and is evaluated at every
|
|
35
|
+
* moment the host draws.
|
|
36
|
+
*/
|
|
37
|
+
export declare function animationFuncs(frameTime: () => number): FuncMap;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation registrations for the rich-js template binding: what a template
|
|
3
|
+
* draws for the moment it is evaluated at.
|
|
4
|
+
*
|
|
5
|
+
* A host that builds its cells in templates — cc-candybar's statusline, say —
|
|
6
|
+
* evaluates each template once per redraw and owns the clock that says when
|
|
7
|
+
* that is. So the time is a capability the registration takes, the way
|
|
8
|
+
* `richTextFuncs(drawnAt)` takes the drawn depth: `frameTime()` is read once
|
|
9
|
+
* per call, when the template is evaluated, and nothing here reads a clock.
|
|
10
|
+
* [LAW:no-ambient-temporal-coupling] A host hands in the same `t` and gets
|
|
11
|
+
* the same fragment back; a host that redraws once a second or sixty times
|
|
12
|
+
* a second registers the same functions.
|
|
13
|
+
*
|
|
14
|
+
* `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
|
|
15
|
+
* (`renderables/frames.ts`) in a template: the frame in progress at the
|
|
16
|
+
* moment, as a fragment at the width of the widest. Its frames arrive as
|
|
17
|
+
* arguments on every evaluation, so the sequence is prepared on every one:
|
|
18
|
+
* a template has nowhere to keep it, and keeping it here would be the memo
|
|
19
|
+
* keyed on the arguments that `FrameSequence` is instead of. `stepped <seconds>
|
|
20
|
+
* <color> <seconds> <color> …` is the colour in progress at the moment, a
|
|
21
|
+
* colour value like every colour function's (`./color-funcs.ts`), so it
|
|
22
|
+
* nests where a colour goes — `fg (stepped …)`, `readableOn (stepped …) $bg
|
|
23
|
+
* 4.5` — and the ground it must read on is the template's to name, as it
|
|
24
|
+
* is for any colour. Either argument list is one float/value cycle end to
|
|
25
|
+
* end, so the engine's `alternating` gate types every slot and lifts each
|
|
26
|
+
* string frame to a fragment; the pairing — a trailing hold with no frame or
|
|
27
|
+
* colour — is parsed in `pairs`, once. [LAW:types-are-the-program]
|
|
28
|
+
*/
|
|
29
|
+
import { beat } from "../core/easing.js";
|
|
30
|
+
import { FrameSequence, frameAt } from "../renderables/frames.js";
|
|
31
|
+
import { asColor } from "./color-funcs.js";
|
|
32
|
+
import { asFragment } from "./helpers.js";
|
|
33
|
+
/**
|
|
34
|
+
* The `(seconds, value)` pairs a call spells, each value read through
|
|
35
|
+
* `value`. The gate has typed every even slot as a float and every odd one
|
|
36
|
+
* as `value`'s input; what it cannot see is the pairing, or that a hold is
|
|
37
|
+
* a positive number of seconds: both settled here, under the engine's
|
|
38
|
+
* "error calling <function>" and nothing else, since a template author
|
|
39
|
+
* knows neither `FrameSequence` nor `beat`, which refuse the same hold in their
|
|
40
|
+
* own names. [LAW:parse-dont-validate]
|
|
41
|
+
*/
|
|
42
|
+
function pairs(what, tail, value) {
|
|
43
|
+
if (tail.length % 2 !== 0) {
|
|
44
|
+
throw new RangeError(`last hold (${String(tail[tail.length - 1])} s) has no ${what} — the arguments are <seconds> <${what}> pairs`);
|
|
45
|
+
}
|
|
46
|
+
const paired = [];
|
|
47
|
+
for (let i = 0; i < tail.length; i += 2) {
|
|
48
|
+
const seconds = tail[i];
|
|
49
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
50
|
+
throw new RangeError(`hold ${i / 2} must be a positive number of seconds, got ${seconds}`);
|
|
51
|
+
}
|
|
52
|
+
paired.push({ seconds, value: value(tail[i + 1]) });
|
|
53
|
+
}
|
|
54
|
+
return paired;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Register `frames` and `stepped`, each drawn for the moment `frameTime`
|
|
58
|
+
* reports when the template is evaluated: seconds on the host's own
|
|
59
|
+
* timeline, from whatever zero it counts from. The functions exist whatever
|
|
60
|
+
* `frameTime` returns, so a template compiles once and is evaluated at every
|
|
61
|
+
* moment the host draws.
|
|
62
|
+
*/
|
|
63
|
+
export function animationFuncs(frameTime) {
|
|
64
|
+
const framesFunc = {
|
|
65
|
+
fn: ((...tail) => {
|
|
66
|
+
const frames = pairs("frame", tail, asFragment).map(({ seconds, value }) => ({ seconds, text: value }));
|
|
67
|
+
return frameAt(new FrameSequence(frames), frameTime());
|
|
68
|
+
}),
|
|
69
|
+
argTypes: ["float", "liftable"],
|
|
70
|
+
arity: { kind: "alternating", minimum: 2 },
|
|
71
|
+
returnType: "T",
|
|
72
|
+
};
|
|
73
|
+
const steppedFunc = {
|
|
74
|
+
fn: ((...tail) => {
|
|
75
|
+
const steps = pairs("color", tail, (v) => asColor(v, "stepped"));
|
|
76
|
+
return steps[beat(steps.map((step) => step.seconds)).at(frameTime())].value.hex;
|
|
77
|
+
}),
|
|
78
|
+
argTypes: ["float", "string"],
|
|
79
|
+
arity: { kind: "alternating", minimum: 2 },
|
|
80
|
+
returnType: "string",
|
|
81
|
+
};
|
|
82
|
+
return { frames: framesFunc, stepped: steppedFunc };
|
|
83
|
+
}
|
|
@@ -57,6 +57,19 @@
|
|
|
57
57
|
*/
|
|
58
58
|
import type { FuncMap, TemplateFunc } from "@promptctl/go-template-js";
|
|
59
59
|
import { ColorDepth, type ColorRgba, type TerminalTheme } from "../core/color.js";
|
|
60
|
+
/**
|
|
61
|
+
* The gate every color argument crosses. [LAW:parse-dont-validate] — the
|
|
62
|
+
* function bodies below receive `ColorRgba` and never re-check.
|
|
63
|
+
*
|
|
64
|
+
* Parsing itself is `parseHexColor`'s job; this wrapper exists only to replace
|
|
65
|
+
* its error *wording*, because the mistake it catches is almost always the
|
|
66
|
+
* same one — a palette name passed where a color belongs (`darken "primary" 2`)
|
|
67
|
+
* — and an author who reads "wrap it: darken (color "primary")" is corrected in
|
|
68
|
+
* one step, where "expected #RRGGBB" leaves them guessing. What it parses
|
|
69
|
+
* must then be a colour these functions can compute with (`computable`).
|
|
70
|
+
* [LAW:no-silent-failure]
|
|
71
|
+
*/
|
|
72
|
+
export declare function asColor(value: string, func: string): ColorRgba;
|
|
60
73
|
/**
|
|
61
74
|
* A colour the colour functions can compute with. A fully transparent one — a
|
|
62
75
|
* theme's "no background", `#00000000` — has no colour of its own: what shows
|
|
@@ -70,7 +70,7 @@ import { darken, contrastFor, ensureContrast } from "../themes/colorMath.js";
|
|
|
70
70
|
* must then be a colour these functions can compute with (`computable`).
|
|
71
71
|
* [LAW:no-silent-failure]
|
|
72
72
|
*/
|
|
73
|
-
function asColor(value, func) {
|
|
73
|
+
export function asColor(value, func) {
|
|
74
74
|
let colour;
|
|
75
75
|
try {
|
|
76
76
|
colour = parseHexColor(value.trim());
|
|
@@ -24,3 +24,9 @@ import { RichText } from "../core/text.js";
|
|
|
24
24
|
* before the render whose theme says what the name stands for.
|
|
25
25
|
*/
|
|
26
26
|
export declare function applyStyleToFragment(child: unknown, style: Style): RichText;
|
|
27
|
+
/**
|
|
28
|
+
* The gate every `"liftable"` slot's value crosses: the binding's own `T`, or
|
|
29
|
+
* a loud failure. [LAW:parse-dont-validate] What leaves is a `RichText`, so
|
|
30
|
+
* no body re-checks it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function asFragment(child: unknown): RichText;
|
|
@@ -24,10 +24,19 @@ import { RichText } from "../core/text.js";
|
|
|
24
24
|
* before the render whose theme says what the name stands for.
|
|
25
25
|
*/
|
|
26
26
|
export function applyStyleToFragment(child, style) {
|
|
27
|
+
const fragment = asFragment(child);
|
|
28
|
+
const result = fragment.copy();
|
|
29
|
+
result.style = layerStyle(fragment.style, style);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The gate every `"liftable"` slot's value crosses: the binding's own `T`, or
|
|
34
|
+
* a loud failure. [LAW:parse-dont-validate] What leaves is a `RichText`, so
|
|
35
|
+
* no body re-checks it.
|
|
36
|
+
*/
|
|
37
|
+
export function asFragment(child) {
|
|
27
38
|
if (!(child instanceof RichText)) {
|
|
28
39
|
throw new TypeError(`template function expected a RichText fragment, got ${typeof child === "object" ? Object.prototype.toString.call(child) : typeof child}`);
|
|
29
40
|
}
|
|
30
|
-
|
|
31
|
-
result.style = layerStyle(child.style, style);
|
|
32
|
-
return result;
|
|
41
|
+
return child;
|
|
33
42
|
}
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* feeding it hex literals.
|
|
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
|
-
* palette exists
|
|
17
|
-
*
|
|
16
|
+
* palette exists. [LAW:one-way-deps]
|
|
17
|
+
* - `animationFuncs(frameTime)` — `frames` and `stepped`, a fragment and a
|
|
18
|
+
* colour drawn for a moment. It is the only registration that knows the
|
|
19
|
+
* template is evaluated at a time, and the time is the host's to hand in
|
|
20
|
+
* (`./animation-funcs.ts`).
|
|
18
21
|
*
|
|
19
22
|
* `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
|
|
20
23
|
* palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
|
|
@@ -40,6 +43,7 @@ import type { RenderOptions } from "../core/protocol.js";
|
|
|
40
43
|
import { type DrawnAt } from "./color-funcs.js";
|
|
41
44
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
42
45
|
export { colorFuncs, readableOnFunc, type DrawnAt } from "./color-funcs.js";
|
|
46
|
+
export { animationFuncs } from "./animation-funcs.js";
|
|
43
47
|
/**
|
|
44
48
|
* Funcs registered by the rich-js binding — the colour sinks, the palette-free
|
|
45
49
|
* colour math, text attributes, and the `link` cell-splitter. Nothing here
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* feeding it hex literals.
|
|
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
|
-
* palette exists
|
|
17
|
-
*
|
|
16
|
+
* palette exists. [LAW:one-way-deps]
|
|
17
|
+
* - `animationFuncs(frameTime)` — `frames` and `stepped`, a fragment and a
|
|
18
|
+
* colour drawn for a moment. It is the only registration that knows the
|
|
19
|
+
* template is evaluated at a time, and the time is the host's to hand in
|
|
20
|
+
* (`./animation-funcs.ts`).
|
|
18
21
|
*
|
|
19
22
|
* `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
|
|
20
23
|
* palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
|
|
@@ -37,10 +40,12 @@ import { createEngine } from "@promptctl/go-template-js";
|
|
|
37
40
|
import { RichText } from "../core/text.js";
|
|
38
41
|
import { Style } from "../core/style.js";
|
|
39
42
|
import { Segment } from "../core/segment.js";
|
|
43
|
+
import { render } from "../core/protocol.js";
|
|
40
44
|
import { richTextStyleFuncs } from "./style-funcs.js";
|
|
41
45
|
import { colorFuncs } from "./color-funcs.js";
|
|
42
46
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
43
47
|
export { colorFuncs, readableOnFunc } from "./color-funcs.js";
|
|
48
|
+
export { animationFuncs } from "./animation-funcs.js";
|
|
44
49
|
/**
|
|
45
50
|
* Funcs registered by the rich-js binding — the colour sinks, the palette-free
|
|
46
51
|
* colour math, text attributes, and the `link` cell-splitter. Nothing here
|
|
@@ -127,7 +132,7 @@ export function renderTemplate(engine, source, scope = {}, options) {
|
|
|
127
132
|
catch (e) {
|
|
128
133
|
return [new Segment(errorLine(e, maxWidth), safeErrorStyle(errorStyle))];
|
|
129
134
|
}
|
|
130
|
-
return Array.from(
|
|
135
|
+
return Array.from(render(text, { isTerminal: true, ...renderOptions, maxWidth }));
|
|
131
136
|
}
|
|
132
137
|
/**
|
|
133
138
|
* A caught error as the one line `renderTemplate` promises. The summary is the
|
package/dist/themes/ramp.d.ts
CHANGED
|
@@ -20,12 +20,14 @@
|
|
|
20
20
|
* theme colors passes through perceptually even steps rather than the muddy
|
|
21
21
|
* midpoints of an sRGB blend.
|
|
22
22
|
*
|
|
23
|
-
* [LAW:one-way-deps] Imports `core/color`, `core/easing
|
|
24
|
-
*
|
|
23
|
+
* [LAW:one-way-deps] Imports `core/color`, `core/easing`, `core/oklch`,
|
|
24
|
+
* and for `over`, `core/animation` and `core/tween`; nothing here knows
|
|
25
|
+
* what a palette is. Resolving stop *names* is the template binding's
|
|
25
26
|
* job (`paletteFuncs`), which hands this module resolved colors.
|
|
26
27
|
*/
|
|
28
|
+
import { Animation } from "../core/animation.js";
|
|
27
29
|
import type { ColorRgba } from "../core/color.js";
|
|
28
|
-
import type
|
|
30
|
+
import { type Ease } from "../core/easing.js";
|
|
29
31
|
/** One stop: the color the ramp is exactly `color` at position `at`. */
|
|
30
32
|
export interface ColorStop {
|
|
31
33
|
readonly at: number;
|
|
@@ -67,4 +69,17 @@ export declare class ColorRamp {
|
|
|
67
69
|
* where it does: a ramp never paints outside its stops.
|
|
68
70
|
*/
|
|
69
71
|
at(value: number): ColorRgba;
|
|
72
|
+
/**
|
|
73
|
+
* This ramp read over time: its span of positions, from the first stop's
|
|
74
|
+
* to the last's, crossed at a steady rate in `seconds` and held at the last
|
|
75
|
+
* stop from then on, each colour along the way the ramp's own. A colour
|
|
76
|
+
* tween is a two-stop ramp over its seconds — the ramp's ease shaping the
|
|
77
|
+
* move, so `new ColorRamp(ease, [{ at: 0, color: a }, { at: 1, color: b
|
|
78
|
+
* }]).over(seconds)` is `a` eased to `b` — and a ramp of more stops over
|
|
79
|
+
* time is a sweep through all of them. [LAW:one-source-of-truth] The
|
|
80
|
+
* blending is `at`'s; what time adds is `tween`'s (`core/tween.ts`), so a
|
|
81
|
+
* host sampling once a second or sixty times reads the stop colours byte
|
|
82
|
+
* for byte where `at` paints them.
|
|
83
|
+
*/
|
|
84
|
+
over(seconds: number): Animation<ColorRgba>;
|
|
70
85
|
}
|
package/dist/themes/ramp.js
CHANGED
|
@@ -20,11 +20,15 @@
|
|
|
20
20
|
* theme colors passes through perceptually even steps rather than the muddy
|
|
21
21
|
* midpoints of an sRGB blend.
|
|
22
22
|
*
|
|
23
|
-
* [LAW:one-way-deps] Imports `core/color`, `core/easing
|
|
24
|
-
*
|
|
23
|
+
* [LAW:one-way-deps] Imports `core/color`, `core/easing`, `core/oklch`,
|
|
24
|
+
* and for `over`, `core/animation` and `core/tween`; nothing here knows
|
|
25
|
+
* what a palette is. Resolving stop *names* is the template binding's
|
|
25
26
|
* job (`paletteFuncs`), which hands this module resolved colors.
|
|
26
27
|
*/
|
|
28
|
+
import { Animation } from "../core/animation.js";
|
|
29
|
+
import { EASES } from "../core/easing.js";
|
|
27
30
|
import { Oklch } from "../core/oklch.js";
|
|
31
|
+
import { tween } from "../core/tween.js";
|
|
28
32
|
/**
|
|
29
33
|
* An immutable ramp over stops sorted by position.
|
|
30
34
|
*
|
|
@@ -100,4 +104,21 @@ export class ColorRamp {
|
|
|
100
104
|
return to.color;
|
|
101
105
|
return Oklch.fromRgba(from.color).mix(Oklch.fromRgba(to.color), t).toRgba();
|
|
102
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* This ramp read over time: its span of positions, from the first stop's
|
|
109
|
+
* to the last's, crossed at a steady rate in `seconds` and held at the last
|
|
110
|
+
* stop from then on, each colour along the way the ramp's own. A colour
|
|
111
|
+
* tween is a two-stop ramp over its seconds — the ramp's ease shaping the
|
|
112
|
+
* move, so `new ColorRamp(ease, [{ at: 0, color: a }, { at: 1, color: b
|
|
113
|
+
* }]).over(seconds)` is `a` eased to `b` — and a ramp of more stops over
|
|
114
|
+
* time is a sweep through all of them. [LAW:one-source-of-truth] The
|
|
115
|
+
* blending is `at`'s; what time adds is `tween`'s (`core/tween.ts`), so a
|
|
116
|
+
* host sampling once a second or sixty times reads the stop colours byte
|
|
117
|
+
* for byte where `at` paints them.
|
|
118
|
+
*/
|
|
119
|
+
over(seconds) {
|
|
120
|
+
const first = this.stops[0].at;
|
|
121
|
+
const last = this.stops[this.stops.length - 1].at;
|
|
122
|
+
return Animation.map(tween(first, last, { seconds, ease: EASES.linear }), (value) => this.at(value));
|
|
123
|
+
}
|
|
103
124
|
}
|
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { InteractiveWidget, FocusManager, KeyEvent } from "./types.js";
|
|
8
8
|
import type { Unsubscribe } from "../core/subscription.js";
|
|
9
|
+
/** What focus follows: the frame a `WidgetApp` just painted. */
|
|
10
|
+
export interface FocusView {
|
|
11
|
+
/** Every widget the view holds, in document order, whether or not it has a cell on screen. */
|
|
12
|
+
present(): readonly InteractiveWidget[];
|
|
13
|
+
/** The widgets with a cell on the frame on screen, in document order. */
|
|
14
|
+
onScreen(): readonly InteractiveWidget[];
|
|
15
|
+
}
|
|
9
16
|
export declare class DefaultFocusManager implements FocusManager {
|
|
10
|
-
private readonly
|
|
17
|
+
private readonly view;
|
|
11
18
|
accessor currentWidget: InteractiveWidget | null;
|
|
12
19
|
private readonly changeHandlers;
|
|
13
20
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* of the widgets held here would be a second picture of the frame, and
|
|
18
|
-
* wrong from the next paint.
|
|
21
|
+
* [LAW:one-source-of-truth] The view is asked every time rather than kept:
|
|
22
|
+
* a list of the widgets held here would be a second picture of the frame,
|
|
23
|
+
* and wrong from the next paint.
|
|
19
24
|
*/
|
|
20
|
-
constructor(
|
|
25
|
+
constructor(view: FocusView);
|
|
21
26
|
get current(): InteractiveWidget | null;
|
|
22
27
|
get widgets(): readonly InteractiveWidget[];
|
|
23
28
|
next(): void;
|
|
@@ -25,20 +30,22 @@ export declare class DefaultFocusManager implements FocusManager {
|
|
|
25
30
|
focus(widget: InteractiveWidget): void;
|
|
26
31
|
/**
|
|
27
32
|
* Put focus where the frame just painted lets it rest: it stays on a widget
|
|
28
|
-
* the view
|
|
29
|
-
* one on screen that can, or to none.
|
|
33
|
+
* the view holds (`present`) that can take it, and otherwise moves to the
|
|
34
|
+
* first one on screen that can, or to none.
|
|
30
35
|
*
|
|
31
|
-
*
|
|
32
|
-
* shrank, a viewport scrolled, an overlay covers it
|
|
33
|
-
*
|
|
36
|
+
* In the view, not on screen: a widget cropped for the moment — the
|
|
37
|
+
* terminal shrank, a viewport scrolled, an overlay covers it, its container
|
|
38
|
+
* handed it no column — is still in the view, and keeps focus and whatever
|
|
39
|
+
* focus holds, like a half-typed filter.
|
|
34
40
|
*
|
|
35
41
|
* [LAW:single-enforcer] The one place focus follows the frame — onto the
|
|
36
|
-
* first widget when the app starts, off a widget that
|
|
42
|
+
* first widget when the app starts, off a widget that left the view
|
|
37
43
|
* or was disabled.
|
|
38
44
|
*/
|
|
39
|
-
settle(
|
|
45
|
+
settle(): void;
|
|
40
46
|
onChange(handler: (current: InteractiveWidget | null) => void): Unsubscribe;
|
|
41
47
|
handleKey(event: KeyEvent): void;
|
|
48
|
+
private step;
|
|
42
49
|
private clear;
|
|
43
50
|
private setFocus;
|
|
44
51
|
private emitChange;
|