@promptctl/rich-js 0.27.0 → 0.28.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 +1 -0
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +36 -17
- package/dist/core/animation.js +61 -37
- package/dist/core/ansi.d.ts +15 -8
- package/dist/core/ansi.js +106 -28
- package/dist/core/bidi-data.d.ts +14 -0
- package/dist/core/bidi-data.js +385 -0
- package/dist/core/bidi.d.ts +89 -0
- package/dist/core/bidi.js +615 -0
- package/dist/core/box.d.ts +9 -1
- package/dist/core/box.js +11 -0
- package/dist/core/cell-widths.d.ts +3 -2
- package/dist/core/cell-widths.js +93 -93
- package/dist/core/cells.d.ts +20 -0
- package/dist/core/cells.js +93 -5
- package/dist/core/color.d.ts +6 -7
- package/dist/core/color.js +11 -13
- package/dist/core/console.d.ts +18 -1
- package/dist/core/console.js +24 -5
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +4 -3
- package/dist/core/export-lines.d.ts +2 -0
- package/dist/core/export-lines.js +7 -3
- package/dist/core/export-svg.js +6 -5
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +4 -3
- package/dist/core/orient.d.ts +68 -0
- package/dist/core/orient.js +224 -0
- package/dist/core/osc8.d.ts +12 -5
- package/dist/core/osc8.js +14 -7
- package/dist/core/pretty.js +22 -18
- package/dist/core/protocol.d.ts +27 -6
- package/dist/core/protocol.js +14 -6
- package/dist/core/render.d.ts +8 -3
- package/dist/core/render.js +18 -5
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +9 -1
- package/dist/core/segment.js +11 -1
- package/dist/core/style.d.ts +29 -1
- package/dist/core/style.js +41 -5
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +10 -0
- package/dist/core/text.js +133 -87
- package/dist/core/timeline.js +6 -6
- package/dist/core/view.d.ts +1 -1
- package/dist/core/view.js +5 -4
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +44 -3
- package/dist/host/app.js +111 -6
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +35 -2
- package/dist/host/input.js +88 -4
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +2 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +4 -6
- package/dist/renderables/effect.d.ts +60 -59
- package/dist/renderables/effect.js +91 -81
- package/dist/renderables/effects.d.ts +37 -8
- package/dist/renderables/effects.js +185 -52
- package/dist/renderables/frames.d.ts +1 -10
- package/dist/renderables/frames.js +1 -16
- package/dist/renderables/glyph-effect.d.ts +12 -13
- package/dist/renderables/glyph-effect.js +34 -32
- package/dist/renderables/glyph-effects.d.ts +27 -8
- package/dist/renderables/glyph-effects.js +75 -7
- package/dist/renderables/lifecycle.d.ts +5 -7
- package/dist/renderables/lifecycle.js +6 -7
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +6 -4
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +16 -13
- package/dist/renderables/syntax.js +7 -2
- package/dist/renderables/table.js +2 -1
- package/dist/template-bindings/animation-funcs.d.ts +49 -18
- package/dist/template-bindings/animation-funcs.js +115 -32
- package/dist/template-bindings/index.d.ts +7 -5
- package/dist/template-bindings/index.js +8 -5
- package/dist/template-bindings/palette-funcs.d.ts +1 -1
- package/dist/template-bindings/palette-funcs.js +1 -1
- package/dist/themes/buildPalette.d.ts +3 -2
- package/dist/themes/buildPalette.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/ramp.d.ts +1 -1
- package/dist/themes/ramp.js +1 -1
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +2 -4
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +1 -1
- package/dist/widgets/button.js +6 -6
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +1 -1
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/themed-widget.js +3 -3
- package/dist/widgets/toggle.d.ts +1 -1
- package/dist/widgets/toggle.js +5 -5
- package/package.json +5 -4
package/dist/host/input.js
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* "are we mid-sequence" mode flag — the buffer head carries that state, so on
|
|
14
14
|
* a host whose chunks can split a key, a UTF-8 character or an escape
|
|
15
15
|
* sequence split across two chunks is completed by the second. What a chunk
|
|
16
|
-
* end means is the host's `InputChunks`,
|
|
17
|
-
*
|
|
16
|
+
* end means is the host's `InputChunks`, and whether an ask awaits a reply is
|
|
17
|
+
* what its owner last told `awaitReplies`: the two inputs to the parser that
|
|
18
|
+
* are not bytes, and both decide only what becomes of an unfinished ESC.
|
|
18
19
|
*
|
|
19
20
|
* An unfinished escape sequence is ambiguous: `ESC` alone is the Escape key
|
|
20
21
|
* or the start of a sequence, and `ESC [` is Alt+[ or the start of a CSI.
|
|
@@ -29,6 +30,15 @@
|
|
|
29
30
|
* lone ESC is also settled early by a next chunk that opens with anything but
|
|
30
31
|
* `[` or `O`, because a busy worker can queue the next key behind it.
|
|
31
32
|
* `flush()` settles the hold synchronously.
|
|
33
|
+
*
|
|
34
|
+
* `ESC ]`, `ESC P` and `ESC _` open the control strings a terminal replies
|
|
35
|
+
* in, and are also Alt+], Alt+P and Alt+_. A reply can arrive split only on
|
|
36
|
+
* a split host while an ask awaits one (`replyMayArrive`), and only then is an
|
|
37
|
+
* unterminated one, or a lone ESC that may open one, held — past the escape
|
|
38
|
+
* delay, until `awaitReplies(false)` arms the delay afresh. Otherwise it is Alt
|
|
39
|
+
* on its key at once. A terminated one is a reply either way, and one cut off
|
|
40
|
+
* by an ESC that is not ST was never a reply, and is Alt on its key, as with
|
|
41
|
+
* none awaited.
|
|
32
42
|
*/
|
|
33
43
|
/**
|
|
34
44
|
* The one name a key goes by — what a keymap is keyed by and what
|
|
@@ -136,6 +146,12 @@ const LANGLE = 0x3c; // <
|
|
|
136
146
|
const O_BYTE = 0x4f; // O
|
|
137
147
|
const M_UPPER = 0x4d; // M
|
|
138
148
|
const M_LOWER = 0x6d; // m
|
|
149
|
+
const BEL = 0x07;
|
|
150
|
+
const BACKSLASH = 0x5c;
|
|
151
|
+
// The control strings a terminal answers in, by the byte after ESC: OSC, DCS, APC.
|
|
152
|
+
const STRING_REPLIES = { 0x5d: "osc", 0x50: "dcs", 0x5f: "apc" };
|
|
153
|
+
// A CSI whose parameters open with one of these is a report, never a key.
|
|
154
|
+
const PRIVATE_PREFIXES = new Set(["?", ">"]);
|
|
139
155
|
// [LAW:single-enforcer] Shared encode/decode singletons. The parser
|
|
140
156
|
// receives `Uint8Array | string` from the host and walks bytes through
|
|
141
157
|
// its state machine; conversion happens at the two boundaries (string
|
|
@@ -198,6 +214,7 @@ export class InputParser {
|
|
|
198
214
|
// [LAW:types-are-the-program] `setTimeout`/`clearTimeout` are universal
|
|
199
215
|
// across node and browser; `setImmediate` is node-only.
|
|
200
216
|
escTimer;
|
|
217
|
+
awaiting = false;
|
|
201
218
|
constructor(sink, chunks) {
|
|
202
219
|
this.sink = sink;
|
|
203
220
|
this.chunks = chunks;
|
|
@@ -213,7 +230,9 @@ export class InputParser {
|
|
|
213
230
|
// Any other next chunk means Escape was its own key, even when the hold
|
|
214
231
|
// timer has not run yet: a busy worker can queue the next key ahead of it,
|
|
215
232
|
// and joining them would read Escape then G as Alt+G (rich-host-input-0c4f).
|
|
216
|
-
|
|
233
|
+
// While a reply is awaited, a next chunk opening one of its control strings
|
|
234
|
+
// continues it too: a reply split right after its ESC.
|
|
235
|
+
const escaped = this.holdingEscape() && next.length > 0 && !this.continuesEscape(next[0]);
|
|
217
236
|
this.cancelEscape();
|
|
218
237
|
// The buffer is settled before the sink hears Escape, so a sink that
|
|
219
238
|
// resets the parser on it (an app stopping) leaves nothing to drain.
|
|
@@ -242,14 +261,41 @@ export class InputParser {
|
|
|
242
261
|
// timer before it reads input queued behind a long frame, so the delay's end
|
|
243
262
|
// settles the hold only after one more turn of the event loop has read what
|
|
244
263
|
// was waiting. The parser holds one timer, so arming it replaces any other.
|
|
264
|
+
// While a reply may arrive the delay settles no hold that may open one: a
|
|
265
|
+
// reply split over a slow link may take longer than it. The wait's end arms
|
|
266
|
+
// it afresh (`awaitReplies`), so what is held then gets the whole delay.
|
|
245
267
|
hold() {
|
|
246
268
|
this.cancelEscape();
|
|
247
269
|
if (this.buffer[0] !== ESC)
|
|
248
270
|
return;
|
|
249
271
|
this.escTimer = setTimeout(() => {
|
|
250
|
-
this.escTimer = setTimeout(() =>
|
|
272
|
+
this.escTimer = setTimeout(() => {
|
|
273
|
+
if (!this.replyMayArrive() || !opensString(this.buffer))
|
|
274
|
+
this.flush();
|
|
275
|
+
}, 0);
|
|
251
276
|
}, ESCAPE_DELAY_MS);
|
|
252
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Whether an ask is awaiting the terminal's replies, told by whoever asks
|
|
280
|
+
* at the ask's start and end. Only while one is does an unfinished
|
|
281
|
+
* `ESC ]`, `ESC P` or `ESC _` open a reply still arriving; otherwise it is
|
|
282
|
+
* Alt on that key. The wait's end restarts the escape delay for what is
|
|
283
|
+
* held, on a split host, the only one that holds.
|
|
284
|
+
*/
|
|
285
|
+
awaitReplies(awaiting) {
|
|
286
|
+
this.awaiting = awaiting;
|
|
287
|
+
if (!awaiting && this.chunks === "split")
|
|
288
|
+
this.hold();
|
|
289
|
+
}
|
|
290
|
+
// [LAW:single-enforcer] The one reading of whether an unfinished control
|
|
291
|
+
// string may be a reply still arriving: only a split host splits one, and
|
|
292
|
+
// only an ask awaits one.
|
|
293
|
+
replyMayArrive() {
|
|
294
|
+
return this.chunks === "split" && this.awaiting;
|
|
295
|
+
}
|
|
296
|
+
continuesEscape(byte) {
|
|
297
|
+
return byte === LBRACKET || byte === O_BYTE || (STRING_REPLIES[byte] !== undefined && this.replyMayArrive());
|
|
298
|
+
}
|
|
253
299
|
// A whole-key chunk has ended, so an ESC still at the buffer head opens no
|
|
254
300
|
// sequence, and is read as `reissue` reads it. Each key is taken off the
|
|
255
301
|
// buffer before the sink hears it, so no byte of one chunk outlives it.
|
|
@@ -311,6 +357,8 @@ export class InputParser {
|
|
|
311
357
|
this.sink.key(result.event);
|
|
312
358
|
else if (result.kind === "mouse")
|
|
313
359
|
this.sink.mouse(result.event);
|
|
360
|
+
else if (result.kind === "reply")
|
|
361
|
+
this.sink.reply(result.event);
|
|
314
362
|
// skip → drop bytes silently
|
|
315
363
|
}
|
|
316
364
|
}
|
|
@@ -377,6 +425,15 @@ export class InputParser {
|
|
|
377
425
|
// ESC [ ... → CSI
|
|
378
426
|
if (b1 === LBRACKET)
|
|
379
427
|
return this.consumeCSI(buf);
|
|
428
|
+
const string = STRING_REPLIES[b1];
|
|
429
|
+
if (string !== undefined) {
|
|
430
|
+
const result = consumeString(buf, string);
|
|
431
|
+
// Unterminated where no reply may arrive, it was never a reply: Alt on
|
|
432
|
+
// its key, and what followed is read on its own.
|
|
433
|
+
if (result.kind === "incomplete" && !this.replyMayArrive())
|
|
434
|
+
return { kind: "key", bytes: 2, event: altKey(b1) };
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
380
437
|
// ESC O X → SS3 (F1–F4 / arrows on some terms)
|
|
381
438
|
if (b1 === O_BYTE)
|
|
382
439
|
return this.consumeSS3(buf);
|
|
@@ -555,7 +612,34 @@ function decodeMouseFromCb(cb, x, y, isPress) {
|
|
|
555
612
|
ctrl,
|
|
556
613
|
};
|
|
557
614
|
}
|
|
615
|
+
// A lone ESC, or one opening a control string: the holds a reply may be.
|
|
616
|
+
function opensString(buf) {
|
|
617
|
+
return buf.length === 1 || STRING_REPLIES[buf[1]] !== undefined;
|
|
618
|
+
}
|
|
619
|
+
// A control string runs to its terminator, ST (`ESC \\`) or BEL, and holds
|
|
620
|
+
// any byte but ESC before it. Terminated, it is a reply whether or not one was
|
|
621
|
+
// awaited. Unterminated, it is unfinished, and `consumeEscape` decides by
|
|
622
|
+
// whether a reply may arrive if that means held or Alt on its key. An ESC that
|
|
623
|
+
// is not ST ends it before it closed: it was never a reply, so it is Alt on
|
|
624
|
+
// its key and what followed is read on its own, as with none awaited.
|
|
625
|
+
function consumeString(buf, kind) {
|
|
626
|
+
for (let i = 2; i < buf.length; i++) {
|
|
627
|
+
const b = buf[i];
|
|
628
|
+
if (b === BEL)
|
|
629
|
+
return { kind: "reply", bytes: i + 1, event: { kind, body: UTF8_DECODER.decode(buf.subarray(2, i)) } };
|
|
630
|
+
if (b !== ESC)
|
|
631
|
+
continue;
|
|
632
|
+
if (i + 1 === buf.length)
|
|
633
|
+
return { kind: "incomplete" };
|
|
634
|
+
if (buf[i + 1] !== BACKSLASH)
|
|
635
|
+
return { kind: "key", bytes: 2, event: altKey(buf[1]) };
|
|
636
|
+
return { kind: "reply", bytes: i + 2, event: { kind, body: UTF8_DECODER.decode(buf.subarray(2, i)) } };
|
|
637
|
+
}
|
|
638
|
+
return { kind: "incomplete" };
|
|
639
|
+
}
|
|
558
640
|
function decodeCSI(params, final, bytes) {
|
|
641
|
+
if (PRIVATE_PREFIXES.has(params[0] ?? ""))
|
|
642
|
+
return { kind: "reply", bytes, event: { kind: "csi", body: params + final } };
|
|
559
643
|
// Handle modifier suffixes like `1;2A` (shift+up). We split params on `;`.
|
|
560
644
|
const parts = params.split(";");
|
|
561
645
|
const first = parts[0] ?? "";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal's own foreground and background, asked of it with OSC 10 and
|
|
3
|
+
* OSC 11 — the colours every contrast decision is measured against, which the
|
|
4
|
+
* library otherwise has to assume.
|
|
5
|
+
*
|
|
6
|
+
* [LAW:effects-at-boundaries] The ask is `App`'s, the one reader of its
|
|
7
|
+
* terminal's input; what is here is the query bytes and the parse of their
|
|
8
|
+
* replies, which stays pure.
|
|
9
|
+
*/
|
|
10
|
+
import { TerminalColors } from "../core/color.js";
|
|
11
|
+
import type { App } from "./app.js";
|
|
12
|
+
/**
|
|
13
|
+
* Ask the terminal `app` runs on for its foreground and background, and return
|
|
14
|
+
* the terminal `app` draws on with them in place — its sixteen ANSI colours
|
|
15
|
+
* kept, since nothing asks for those. `undefined` when the terminal did not
|
|
16
|
+
* answer both, so the caller keeps what it had. Set it as `app.terminal`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function terminalColors(app: App): Promise<TerminalColors | undefined>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal's own foreground and background, asked of it with OSC 10 and
|
|
3
|
+
* OSC 11 — the colours every contrast decision is measured against, which the
|
|
4
|
+
* library otherwise has to assume.
|
|
5
|
+
*
|
|
6
|
+
* [LAW:effects-at-boundaries] The ask is `App`'s, the one reader of its
|
|
7
|
+
* terminal's input; what is here is the query bytes and the parse of their
|
|
8
|
+
* replies, which stays pure.
|
|
9
|
+
*/
|
|
10
|
+
import { ColorRgba, TerminalColors } from "../core/color.js";
|
|
11
|
+
import { pattern } from "../core/pattern.js";
|
|
12
|
+
// OSC 10 and OSC 11 with `?` ask for the dynamic foreground and background.
|
|
13
|
+
const QUERIES = ["\x1b]10;?\x1b\\", "\x1b]11;?\x1b\\"];
|
|
14
|
+
// XParseColor's `rgb:` form, 1–4 hex digits a channel, as xterm and every
|
|
15
|
+
// terminal that copies it answers, or `rgba:`, as urxvt answers with
|
|
16
|
+
// transparency on; the alpha says nothing about the colour a cell is drawn in.
|
|
17
|
+
const RGB = pattern(/^rgba?:([0-9a-f]{1,4})\/([0-9a-f]{1,4})\/([0-9a-f]{1,4})(?:\/[0-9a-f]{1,4})?$/i);
|
|
18
|
+
// A channel of n hex digits is a fraction of 16ⁿ − 1, scaled to 8 bits.
|
|
19
|
+
const channel = (hex) => Math.round((parseInt(hex, 16) / (16 ** hex.length - 1)) * 255);
|
|
20
|
+
/**
|
|
21
|
+
* The colour an OSC 10 or OSC 11 reply reports, or `undefined` when `reply`
|
|
22
|
+
* is not that answer to `slot`.
|
|
23
|
+
*/
|
|
24
|
+
function parseColorReply(reply, slot) {
|
|
25
|
+
if (reply.kind !== "osc")
|
|
26
|
+
return undefined;
|
|
27
|
+
const prefix = `${slot};`;
|
|
28
|
+
if (!reply.body.startsWith(prefix))
|
|
29
|
+
return undefined;
|
|
30
|
+
const match = RGB.exec(reply.body.slice(prefix.length));
|
|
31
|
+
return match === null ? undefined : new ColorRgba(channel(match[1]), channel(match[2]), channel(match[3]));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Ask the terminal `app` runs on for its foreground and background, and return
|
|
35
|
+
* the terminal `app` draws on with them in place — its sixteen ANSI colours
|
|
36
|
+
* kept, since nothing asks for those. `undefined` when the terminal did not
|
|
37
|
+
* answer both, so the caller keeps what it had. Set it as `app.terminal`.
|
|
38
|
+
*/
|
|
39
|
+
export async function terminalColors(app) {
|
|
40
|
+
const { replies } = await app.ask(QUERIES);
|
|
41
|
+
const foreground = replies.map((reply) => parseColorReply(reply, 10)).find((color) => color !== undefined);
|
|
42
|
+
const background = replies.map((reply) => parseColorReply(reply, 11)).find((color) => color !== undefined);
|
|
43
|
+
if (foreground === undefined || background === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
return new TerminalColors(background, foreground, app.terminal.ansiColors);
|
|
46
|
+
}
|
|
@@ -69,7 +69,8 @@ export interface TerminalHost {
|
|
|
69
69
|
onData(handler: DataHandler): Unsubscribe;
|
|
70
70
|
/**
|
|
71
71
|
* Where `onData`'s chunks may end. The parser holds an unfinished escape
|
|
72
|
-
* sequence open for its rest
|
|
72
|
+
* sequence open for its rest — for the escape delay, or a control string
|
|
73
|
+
* for as long as an ask awaits its reply — only on a host whose
|
|
73
74
|
* chunks can split one; a whole-keys host settles each chunk as it arrives,
|
|
74
75
|
* because a busy worker there reads a queued next key before any timer
|
|
75
76
|
* runs (rich-host-input-ab9n).
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { cellLen, setCellSize, splitText, chopCells, cellFit, graphemes, asCellC
|
|
|
2
2
|
export type { CellCol, CodeUnit, CodePoint } from "./core/cells.js";
|
|
3
3
|
export { ColorRgba, ColorTable, ColorDepth, ColorSpec, ColorParseError, TerminalTheme, type TerminalColors, parseRgbHex, parseRgbaHex, parseHexColor, blendRgb, resolveColorSystem, detectColorSystem, resolveDestination, STANDARD_TABLE, EIGHT_BIT_TABLE, WINDOWS_TABLE, ANSI_COLOR_NAMES, } from "./core/color.js";
|
|
4
4
|
export type { ColorSystemName, DetectColorOptions, Destination, DestinationOptions } from "./core/color.js";
|
|
5
|
+
export type { TextOrder } from "./core/text-order.js";
|
|
5
6
|
export { Oklch, IDENTITY, INVERT_LIGHTNESS, isIdentityKey, } from "./core/oklch.js";
|
|
6
7
|
export type { ThemeKey, OklchWeights } from "./core/oklch.js";
|
|
7
8
|
export { Palette } from "./themes/palette.js";
|
|
@@ -19,7 +20,7 @@ export type { ColorStop, RampMotion } from "./themes/ramp.js";
|
|
|
19
20
|
export { EASES, Phase, beat, cubicBezier, parseEase, spring, steps } from "./core/easing.js";
|
|
20
21
|
export type { Beat, Ease, SpringOptions, StepPosition, Timing } from "./core/easing.js";
|
|
21
22
|
export { Animation } from "./core/animation.js";
|
|
22
|
-
export type { Timed } from "./core/animation.js";
|
|
23
|
+
export type { Moves, Timed } from "./core/animation.js";
|
|
23
24
|
export { tween } from "./core/tween.js";
|
|
24
25
|
export { Timeline } from "./core/timeline.js";
|
|
25
26
|
export type { View } from "./core/view.js";
|
|
@@ -63,7 +64,7 @@ export type { Env } from "./core/env.js";
|
|
|
63
64
|
export { Constrain } from "./renderables/constrain.js";
|
|
64
65
|
export { Effected, confined, drawnOf } from "./renderables/effect.js";
|
|
65
66
|
export type { CellAttributes, CellColors, CellLook, Drawn, Effect, EffectCell, EffectedOptions, EffectFrame, ListedCell, Pair, Placed } from "./renderables/effect.js";
|
|
66
|
-
export { ATTRIBUTE_DEFAULTS, EFFECT_CATALOGUE, EFFECT_CURVES, EFFECT_LIGHTS, blink, dissolveOut, fadeIn, gradient, onBeat, onColors, pulse, shimmer, sparkle, steppedInk, wheel, } from "./renderables/effects.js";
|
|
67
|
+
export { ATTRIBUTE_DEFAULTS, EFFECT_CATALOGUE, EFFECT_CURVES, EFFECT_LIGHTS, blink, dissolveOut, fadeIn, gradient, onBeat, layered, onColors, pulse, shimmer, sparkle, steppedInk, wheel, } from "./renderables/effects.js";
|
|
67
68
|
export type { AttributeLoopName, Blink, ColorStep, Curve, EffectTarget, Field, Loop, LoopName, Touch, Transition, TransitionName } from "./renderables/effects.js";
|
|
68
69
|
export { crossfade, wipe } from "./renderables/change.js";
|
|
69
70
|
export type { Between, Change } from "./renderables/change.js";
|
|
@@ -73,8 +74,8 @@ export { scroll } from "./renderables/scroll.js";
|
|
|
73
74
|
export type { ScrollOptions } from "./renderables/scroll.js";
|
|
74
75
|
export { GlyphEffected } from "./renderables/glyph-effect.js";
|
|
75
76
|
export type { DrawnGlyphs, GlyphCell, GlyphEffect, GlyphEffectedOptions, GlyphFrame } from "./renderables/glyph-effect.js";
|
|
76
|
-
export { GLYPH_DEFAULTS, GLYPH_EFFECT_CATALOGUE, backspace, caseWave, particles, scramble, scrambleOut, typewriter } from "./renderables/glyph-effects.js";
|
|
77
|
-
export type { CaseWave, GlyphLoopName, GlyphTransition, GlyphTransitionName, Particles } from "./renderables/glyph-effects.js";
|
|
77
|
+
export { GLYPH_DEFAULTS, GLYPH_EFFECT_CATALOGUE, backspace, caseWave, marchingAnts, particles, scramble, scrambleOut, typewriter } from "./renderables/glyph-effects.js";
|
|
78
|
+
export type { CaseWave, GlyphLoopName, GlyphTransition, GlyphTransitionName, MarchingAnts, Particles } from "./renderables/glyph-effects.js";
|
|
78
79
|
export { Align } from "./renderables/align.js";
|
|
79
80
|
export type { Alignment } from "./core/place.js";
|
|
80
81
|
export { Padding } from "./renderables/padding.js";
|
package/dist/index.js
CHANGED
|
@@ -79,12 +79,12 @@ export { Console } from "./core/console.js";
|
|
|
79
79
|
// Renderables
|
|
80
80
|
export { Constrain } from "./renderables/constrain.js";
|
|
81
81
|
export { Effected, confined, drawnOf } from "./renderables/effect.js";
|
|
82
|
-
export { ATTRIBUTE_DEFAULTS, EFFECT_CATALOGUE, EFFECT_CURVES, EFFECT_LIGHTS, blink, dissolveOut, fadeIn, gradient, onBeat, onColors, pulse, shimmer, sparkle, steppedInk, wheel, } from "./renderables/effects.js";
|
|
82
|
+
export { ATTRIBUTE_DEFAULTS, EFFECT_CATALOGUE, EFFECT_CURVES, EFFECT_LIGHTS, blink, dissolveOut, fadeIn, gradient, onBeat, layered, onColors, pulse, shimmer, sparkle, steppedInk, wheel, } from "./renderables/effects.js";
|
|
83
83
|
export { crossfade, wipe } from "./renderables/change.js";
|
|
84
84
|
export { lifecycle, throughColors, throughGlyphs } from "./renderables/lifecycle.js";
|
|
85
85
|
export { scroll } from "./renderables/scroll.js";
|
|
86
86
|
export { GlyphEffected } from "./renderables/glyph-effect.js";
|
|
87
|
-
export { GLYPH_DEFAULTS, GLYPH_EFFECT_CATALOGUE, backspace, caseWave, particles, scramble, scrambleOut, typewriter } from "./renderables/glyph-effects.js";
|
|
87
|
+
export { GLYPH_DEFAULTS, GLYPH_EFFECT_CATALOGUE, backspace, caseWave, marchingAnts, particles, scramble, scrambleOut, typewriter } from "./renderables/glyph-effects.js";
|
|
88
88
|
export { Align } from "./renderables/align.js";
|
|
89
89
|
export { Padding } from "./renderables/padding.js";
|
|
90
90
|
export { Viewport, ViewportPerKey, SCROLLBAR } from "./renderables/viewport.js";
|
|
@@ -21,14 +21,12 @@
|
|
|
21
21
|
* frame to the next, so the same `t` draws the same bytes at any frame rate.
|
|
22
22
|
*/
|
|
23
23
|
import { Animation } from "../core/animation.js";
|
|
24
|
-
import type { TerminalTheme } from "../core/color.js";
|
|
25
24
|
import type { Renderable } from "../core/protocol.js";
|
|
26
25
|
import { type Curve, type Transition } from "./effects.js";
|
|
27
|
-
/** What a change is between: the renderable drawn until now
|
|
26
|
+
/** What a change is between: the renderable drawn until now and the one drawn from now on. */
|
|
28
27
|
export interface Between {
|
|
29
28
|
readonly from: Renderable;
|
|
30
29
|
readonly to: Renderable;
|
|
31
|
-
readonly theme: TerminalTheme;
|
|
32
30
|
}
|
|
33
31
|
/** A change from one renderable to another: what it draws at each moment, from `t = 0` on its own clock. */
|
|
34
32
|
export type Change = (between: Between) => Animation<Renderable>;
|
|
@@ -32,7 +32,7 @@ const shows = (p) => Math.abs(2 * p - 1);
|
|
|
32
32
|
/** The change over `curve`, each cell as far through it as `progress` says. */
|
|
33
33
|
function change(curve, progress) {
|
|
34
34
|
requireCurve(curve);
|
|
35
|
-
return transition(curve.seconds, ({ from, to
|
|
35
|
+
return transition(curve.seconds, ({ from, to }) => Animation.sequence(Animation.continuous((t) => new ChangeFrame(from, to, progress, t, curve.swing)), curve.seconds, Animation.constant(to)));
|
|
36
36
|
}
|
|
37
37
|
/** A crossfade: the old renderable fades to the terminal's ground and the new one rises from it, every cell together, over `curve`. */
|
|
38
38
|
export function crossfade(curve) {
|
|
@@ -61,14 +61,12 @@ export function wipe(curve, edge = WIPE_EDGE) {
|
|
|
61
61
|
class ChangeFrame {
|
|
62
62
|
from;
|
|
63
63
|
to;
|
|
64
|
-
theme;
|
|
65
64
|
progress;
|
|
66
65
|
t;
|
|
67
66
|
swing;
|
|
68
|
-
constructor(from, to,
|
|
67
|
+
constructor(from, to, progress, t, swing) {
|
|
69
68
|
this.from = from;
|
|
70
69
|
this.to = to;
|
|
71
|
-
this.theme = theme;
|
|
72
70
|
this.progress = progress;
|
|
73
71
|
this.t = t;
|
|
74
72
|
this.swing = swing;
|
|
@@ -86,9 +84,9 @@ class ChangeFrame {
|
|
|
86
84
|
...splice(Segment.adjustLineLength(before[row] ?? [], lengths[row]), line, (col) => at(row, col) >= 0.5),
|
|
87
85
|
...(row < after.length - 1 || ended ? [Segment.line()] : []),
|
|
88
86
|
]);
|
|
89
|
-
// The
|
|
87
|
+
// The change's own animation says it moves every frame, and reads nothing this frame tells.
|
|
90
88
|
const veil = veiled(() => (cell) => shows(at(cell.row, cell.col)), this.swing);
|
|
91
|
-
return render(new Recolored({ render: () => rows }, veil, { t: this.t, key: "change"
|
|
89
|
+
return render(new Recolored({ render: () => rows }, veil, { t: this.t, key: "change" }), options);
|
|
92
90
|
}
|
|
93
91
|
measure(options) {
|
|
94
92
|
return Measurement.get(options, this.to);
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
* (`JoinedCell`, `../core/strip.ts`), the way the joiner drew it. Shimmer,
|
|
18
18
|
* pulse, fade, dissolve and a blinking attribute are all this one shape,
|
|
19
19
|
* differing by what the function does with its inputs
|
|
20
|
-
* [LAW:one-type-per-behavior]; two effects stack
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* [LAW:one-type-per-behavior]; two effects stack as layers of one effect
|
|
21
|
+
* (`layered`, `./effects.ts`), each moving what the last left and recognising
|
|
22
|
+
* a cell by what the element drew (`EffectCell.pair`), or by nesting one
|
|
23
|
+
* wrapper in another, which measures the inner one's output instead.
|
|
23
24
|
*
|
|
24
25
|
* A cell's look is one value, its colours and its attributes together
|
|
25
26
|
* (`CellLook`), rather than an attribute stage beside the colour one, so
|
|
@@ -69,12 +70,11 @@
|
|
|
69
70
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
70
71
|
*/
|
|
71
72
|
import { type Anchor } from "../core/anchor.js";
|
|
72
|
-
import { Animation } from "../core/animation.js";
|
|
73
|
-
import type { Hold } from "../core/style.js";
|
|
73
|
+
import { Animation, type Moves, type Shown } from "../core/animation.js";
|
|
74
74
|
import { ColorDepth, type ColorRgba, type TerminalColors } from "../core/color.js";
|
|
75
75
|
import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
76
76
|
import { Segment } from "../core/segment.js";
|
|
77
|
-
import { type Attributes } from "../core/style.js";
|
|
77
|
+
import { type Attributes, type Hold } from "../core/style.js";
|
|
78
78
|
/**
|
|
79
79
|
* A cell's foreground and background as they appear on screen — the glyph's
|
|
80
80
|
* colour and the colour behind it — which are the colours an effect moves.
|
|
@@ -98,33 +98,32 @@ export interface CellLook extends CellColors {
|
|
|
98
98
|
* Where a cell is in the output of the renderable an effect wraps, its seed:
|
|
99
99
|
* a number in [0, 1) that is the same for that cell on every frame, for an
|
|
100
100
|
* effect that varies cell by cell (a per-cell offset in time, a sparkle) —
|
|
101
|
-
* and
|
|
101
|
+
* and what the element drew it in.
|
|
102
102
|
*/
|
|
103
103
|
export interface EffectCell {
|
|
104
104
|
readonly row: number;
|
|
105
105
|
readonly col: number;
|
|
106
106
|
readonly seed: number;
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
108
|
+
* The ink and ground the element drew the cell in: what the cell is, where
|
|
109
|
+
* the `CellLook` beside it is what is there now. Under one effect they are
|
|
110
|
+
* the same colours; under `layered` (`./effects.ts`) the look is what the
|
|
111
|
+
* layers beneath left, so an effect recognises a cell by its pair and moves
|
|
112
|
+
* its look. [LAW:one-source-of-truth] A pair's `terminal` is what tells a
|
|
113
|
+
* colour the terminal resolved from one the element wrote at the same hex.
|
|
112
114
|
*/
|
|
113
|
-
readonly
|
|
114
|
-
readonly fg: boolean;
|
|
115
|
-
readonly bg: boolean;
|
|
116
|
-
};
|
|
117
|
-
/** Whether the cell's style swaps its two slots, as `Pair.reverse` says: with `colors` and `terminal`, the cell's `Pair`. */
|
|
118
|
-
readonly reverse: boolean;
|
|
115
|
+
readonly pair: Pair;
|
|
119
116
|
/**
|
|
120
117
|
* Who drew the cell, and where it sits in their output, as
|
|
121
118
|
* `Segment.anchorAt` reads it off a frame: what tells one owner's cells
|
|
122
119
|
* from another's when their colours are alike (`confined`).
|
|
123
120
|
*/
|
|
124
121
|
readonly anchor: Anchor | undefined;
|
|
122
|
+
/** The drawing of a box the cell is the frame of (`Style.boxFrame`), or none. */
|
|
123
|
+
readonly boxFrame: object | undefined;
|
|
125
124
|
}
|
|
126
125
|
/** Where a cell sits and who drew it: an `EffectCell` before it has a seed or colours. */
|
|
127
|
-
export type Placed = Pick<EffectCell, "row" | "col" | "anchor">;
|
|
126
|
+
export type Placed = Pick<EffectCell, "row" | "col" | "anchor" | "boxFrame">;
|
|
128
127
|
/**
|
|
129
128
|
* A cell as `Drawn.all` lists it: where it sits and who drew it, the
|
|
130
129
|
* columns its glyph takes, its ink and ground as the screen shows them, and
|
|
@@ -145,7 +144,16 @@ export interface ListedCell extends Placed {
|
|
|
145
144
|
* is handed for that cell.
|
|
146
145
|
*/
|
|
147
146
|
export interface Pair extends CellColors {
|
|
148
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Whether the glyph's colour and the colour behind it, as the screen shows
|
|
149
|
+
* them, are the terminal's own: a slot the element's style leaves unset or
|
|
150
|
+
* sets to `default`. Such a colour is resolved through the terminal, so its
|
|
151
|
+
* hex can equal one the element wrote; this is what tells them apart.
|
|
152
|
+
*/
|
|
153
|
+
readonly terminal: {
|
|
154
|
+
readonly fg: boolean;
|
|
155
|
+
readonly bg: boolean;
|
|
156
|
+
};
|
|
149
157
|
/**
|
|
150
158
|
* Whether the ink is written in the style's ground slot and the ground in
|
|
151
159
|
* its ink slot. Below truecolor the slot decides what a moved colour can
|
|
@@ -194,7 +202,7 @@ export interface Drawn {
|
|
|
194
202
|
/** The depth the frame is written at: below truecolor a moved colour lands on a palette colour. */
|
|
195
203
|
readonly depth: ColorDepth;
|
|
196
204
|
/** The terminal the frame is drawn on. */
|
|
197
|
-
readonly
|
|
205
|
+
readonly terminal: TerminalColors;
|
|
198
206
|
}
|
|
199
207
|
/** What an effect does to the cells of one frame: each cell's look and where it is in, its look out. Return `look` to leave a cell be. */
|
|
200
208
|
export type EffectFrame = (look: CellLook, cell: EffectCell) => CellLook;
|
|
@@ -206,11 +214,12 @@ export type EffectFrame = (look: CellLook, cell: EffectCell) => CellLook;
|
|
|
206
214
|
*
|
|
207
215
|
* Its `nextChange` is when the frame next changes: a stepped effect its next
|
|
208
216
|
* step's start, a transition `t` until it is done and `Infinity` after, a
|
|
209
|
-
* loop that holds every cell still at the frame's depth
|
|
210
|
-
* a loop that moves `t`
|
|
211
|
-
* The answer is a fact
|
|
212
|
-
*
|
|
213
|
-
* that a frame is still because of
|
|
217
|
+
* loop that holds every cell still, at the frame's depth or with nothing in
|
|
218
|
+
* it to move, `Infinity` there, and a loop that moves `t`
|
|
219
|
+
* (`Animation.continuous`). [LAW:types-are-the-program] The answer is a fact
|
|
220
|
+
* about the frame, so the animation is made from everything the frame is drawn
|
|
221
|
+
* from; one made from `t` alone could not say that a frame is still because of
|
|
222
|
+
* its depth.
|
|
214
223
|
*/
|
|
215
224
|
export type Effect = (drawn: Drawn) => Animation<EffectFrame>;
|
|
216
225
|
export interface EffectedOptions {
|
|
@@ -219,37 +228,30 @@ export interface EffectedOptions {
|
|
|
219
228
|
* not twinkle in lockstep. The same key gives the same seeds on every frame.
|
|
220
229
|
*/
|
|
221
230
|
readonly key: string;
|
|
222
|
-
/**
|
|
223
|
-
* The terminal the frame is drawn on: the colours a cell that sets none is
|
|
224
|
-
* drawn in, and the sixteen an ANSI colour names. The terminal's own palette
|
|
225
|
-
* is never queried, so the caller says what it is.
|
|
226
|
-
*/
|
|
227
|
-
readonly theme: TerminalColors;
|
|
228
|
-
}
|
|
229
|
-
/** A frame an effect drew at a moment, and the hold the effect says it is in there. */
|
|
230
|
-
export interface Moved {
|
|
231
|
-
readonly segments: Iterable<Segment>;
|
|
232
|
-
readonly hold: Hold;
|
|
233
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* The hold an effect drawn at `t` tells: its animation's there, or held for
|
|
234
|
+
* good over no cell, since it changes how cells look and never which exist.
|
|
235
|
+
* What drew none says for itself when it will (a lifecycle not yet entered).
|
|
236
|
+
*/
|
|
237
|
+
export declare function holdOver(anyCell: boolean, shown: Shown<unknown>, t: number): Hold;
|
|
234
238
|
/**
|
|
235
239
|
* An `Effected` at `t`, or an element through an effect at a time an
|
|
236
240
|
* animation's `at` was handed — a lifecycle's time since it entered: `renderable`
|
|
237
|
-
* with its cells' looks passed through `effect` at `t
|
|
238
|
-
* when that changes, since
|
|
239
|
-
*
|
|
241
|
+
* with its cells' looks passed through `effect` at `t`. Its cells say nothing
|
|
242
|
+
* of when that changes, since `t` is not the frame's clock: it tells the hold
|
|
243
|
+
* it drew in (`RenderOptions.onMoved`), and whatever placed the animation
|
|
244
|
+
* carries that onto the frame's and stamps it. [LAW:one-type-per-behavior]
|
|
240
245
|
*/
|
|
241
246
|
export declare class Recolored implements Renderable, Measurable {
|
|
242
247
|
readonly renderable: Renderable;
|
|
243
248
|
readonly effect: Effect;
|
|
244
|
-
readonly
|
|
249
|
+
private readonly _t;
|
|
245
250
|
readonly key: string;
|
|
246
|
-
readonly theme: TerminalColors;
|
|
247
251
|
constructor(renderable: Renderable, effect: Effect, options: EffectedOptions & {
|
|
248
252
|
readonly t: number;
|
|
249
253
|
});
|
|
250
254
|
render(options: RenderOptions): Iterable<Segment>;
|
|
251
|
-
/** The frame the effect draws at `t`, and the hold it says that is in. */
|
|
252
|
-
moved(options: RenderOptions): Moved;
|
|
253
255
|
private _recolored;
|
|
254
256
|
/** `segment`'s cells drawn in the looks the effect painted them. */
|
|
255
257
|
private _repainted;
|
|
@@ -275,24 +277,22 @@ export declare class Recolored implements Renderable, Measurable {
|
|
|
275
277
|
/**
|
|
276
278
|
* `renderable` under `effect` over time: an `Animation<Renderable>` whose
|
|
277
279
|
* value at `t` is the element through the effect at `t`, placed wherever a
|
|
278
|
-
* renderable goes.
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* depth (`
|
|
283
|
-
*
|
|
284
|
-
* host sleeps.
|
|
280
|
+
* renderable goes. An `Effect` is made from what the element draws, so with
|
|
281
|
+
* nothing drawn its answer to when it changes is `t`, at every time: early,
|
|
282
|
+
* never false. Placed, on its own or as a part of any combinator, it says
|
|
283
|
+
* exactly: the frame's drawing told the effect's hold at that time and the
|
|
284
|
+
* output's depth (`Moves`), so at a depth that shows no motion a loop says it
|
|
285
|
+
* never changes there and the host sleeps.
|
|
285
286
|
*/
|
|
286
287
|
export declare class Effected implements Animation<Renderable> {
|
|
287
288
|
readonly renderable: Renderable;
|
|
288
289
|
readonly effect: Effect;
|
|
289
290
|
readonly key: string;
|
|
290
|
-
readonly theme: TerminalColors;
|
|
291
291
|
constructor(renderable: Renderable, effect: Effect, options: EffectedOptions);
|
|
292
292
|
/** The element through the effect at `t`, unstamped. */
|
|
293
293
|
at(t: number): Recolored;
|
|
294
|
-
nextChange(t: number): number;
|
|
295
|
-
lastChange(t: number): number;
|
|
294
|
+
nextChange(t: number, moves?: Moves): number;
|
|
295
|
+
lastChange(t: number, moves?: Moves): number;
|
|
296
296
|
/** The element under the effect with its motion taken out. */
|
|
297
297
|
reduced(): Effected;
|
|
298
298
|
/** Drawn at the frame's time, its cells stamped with the effect's exact hold there. */
|
|
@@ -303,12 +303,12 @@ export declare class Effected implements Animation<Renderable> {
|
|
|
303
303
|
};
|
|
304
304
|
/**
|
|
305
305
|
* The colours the screen shows for `pair` once it is drawn moved to `to`,
|
|
306
|
-
* both as the screen shows them, at `depth` on `
|
|
306
|
+
* both as the screen shows them, at `depth` on `terminal`: what an effect
|
|
307
307
|
* moving a pair's colours leaves its words reading in.
|
|
308
308
|
* [LAW:one-source-of-truth] Read through `movedSpecs`, the one writer a
|
|
309
309
|
* moved slot is written with.
|
|
310
310
|
*/
|
|
311
|
-
static shows(pair: Pair, to: CellColors, depth: ColorDepth,
|
|
311
|
+
static shows(pair: Pair, to: CellColors, depth: ColorDepth, terminal: TerminalColors): CellColors;
|
|
312
312
|
}
|
|
313
313
|
/**
|
|
314
314
|
* A cell's seed: FNV-1a of the element's key and the cell's position, scaled
|
|
@@ -351,11 +351,12 @@ export declare function inked(text: string): boolean;
|
|
|
351
351
|
*/
|
|
352
352
|
export declare function confined(effect: Effect, ...owners: readonly object[]): Effect;
|
|
353
353
|
/**
|
|
354
|
-
* What `segments`, drawn at `depth` on `
|
|
355
|
-
* as `Effected` measures its child for every frame
|
|
356
|
-
*
|
|
354
|
+
* What `segments`, drawn at `depth` on `terminal`, draw their words in, measured
|
|
355
|
+
* as `Effected` measures its child for every frame, so `options.terminal` means
|
|
356
|
+
* here what it means on a frame. Measure an element's output under an effect to
|
|
357
|
+
* read the contrast its words are left at.
|
|
357
358
|
*/
|
|
358
|
-
export declare function drawnOf(segments: Iterable<Segment>, depth: ColorDepth,
|
|
359
|
+
export declare function drawnOf(segments: Iterable<Segment>, depth: ColorDepth, terminal?: TerminalColors): Drawn;
|
|
359
360
|
/**
|
|
360
361
|
* The attributes a look carries, read off anything that sets them — a
|
|
361
362
|
* `Style`, or a look a frame returned with more on it than `CellAttributes`
|