@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
package/dist/core/strip.d.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import type { Anchor } from "./anchor.js";
|
|
29
29
|
import { Segment } from "./segment.js";
|
|
30
30
|
import { Style } from "./style.js";
|
|
31
|
-
import { type
|
|
31
|
+
import { type Measurable, type Renderable, type RenderOptions } from "./protocol.js";
|
|
32
32
|
/**
|
|
33
33
|
* Items in a `Strip` expose their per-edge style so joiners can paint the
|
|
34
34
|
* transition glyph at each boundary.
|
|
@@ -97,12 +97,11 @@ export declare function drawJoin<T extends StyledRenderable>(joiner: Joiner<T>,
|
|
|
97
97
|
* item.
|
|
98
98
|
*/
|
|
99
99
|
export declare function drawItem(item: StyledRenderable): Renderable;
|
|
100
|
-
export declare class Strip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable
|
|
100
|
+
export declare class Strip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable {
|
|
101
101
|
readonly items: readonly T[];
|
|
102
102
|
readonly joiner: Joiner<T>;
|
|
103
103
|
constructor(items: readonly T[], joiner: Joiner<T>);
|
|
104
104
|
render(options: RenderOptions): Iterable<Segment>;
|
|
105
|
-
contents(): Iterable<Renderable>;
|
|
106
105
|
/**
|
|
107
106
|
* [LAW:one-source-of-truth] The width of the one row it draws, and that is
|
|
108
107
|
* both bounds: the row never wraps and no item is offered less than the whole
|
|
@@ -167,15 +166,12 @@ export type SeamCell = JoinCell | {
|
|
|
167
166
|
readonly cap: "lead" | "tail";
|
|
168
167
|
};
|
|
169
168
|
/**
|
|
170
|
-
* What a joiner draws between two neighbours, in one form:
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* indistinct from its own ground (see `SEAM_MIN_DELTA_E`), so the seam
|
|
174
|
-
* would vanish; the same width as `seam`.
|
|
169
|
+
* What a joiner draws between two neighbours, in one form: `seam`, the cells
|
|
170
|
+
* between them. Each is drawn with its ink held off its own ground
|
|
171
|
+
* (`SEAM_MIN_DELTA_E`, `standOff`).
|
|
175
172
|
*/
|
|
176
173
|
export interface SeamForm {
|
|
177
174
|
readonly seam: readonly SeamCell[];
|
|
178
|
-
readonly divider: readonly SeamCell[];
|
|
179
175
|
}
|
|
180
176
|
/**
|
|
181
177
|
* What a joiner draws at the ends of a run, in one form:
|
|
@@ -201,10 +197,10 @@ export interface JoinCaps {
|
|
|
201
197
|
readonly ascii: CapsForm;
|
|
202
198
|
}
|
|
203
199
|
/**
|
|
204
|
-
* The least ΔE_OK a
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* one cell wide.
|
|
200
|
+
* The least ΔE_OK a join cell's ink must stand off its own ground by to be
|
|
201
|
+
* seen. Below it the eye cannot tell the ink from the ground, so the joiner
|
|
202
|
+
* lifts the ink to it (`standOff`). Twice the ~.02 threshold of a visible
|
|
203
|
+
* difference, because a seam is one cell wide.
|
|
208
204
|
*/
|
|
209
205
|
export declare const SEAM_MIN_DELTA_E = 0.04;
|
|
210
206
|
/**
|
|
@@ -215,9 +211,10 @@ export declare const SEAM_MIN_DELTA_E = 0.04;
|
|
|
215
211
|
* Which part a join draws is read off its two neighbours. A neighbour is
|
|
216
212
|
* missing at the strip's ends, and counts as missing — an endpoint — when the
|
|
217
213
|
* seam paints with its ground anywhere and it has none to paint. With both
|
|
218
|
-
* present the join is the seam
|
|
219
|
-
*
|
|
220
|
-
*
|
|
214
|
+
* present the join is the seam; with only the right, the lead; with only the
|
|
215
|
+
* left, the tail; with neither, nothing. Every cell is drawn with its ink held
|
|
216
|
+
* off its ground at `options.colorSystem` (`standOff`), measured per cell, so a
|
|
217
|
+
* seam stepping in thirds shows every step however close its grounds. A cap
|
|
221
218
|
* that paints with its neighbour's ground is drawn only beside a neighbour
|
|
222
219
|
* with one, so caps that need grounds never stop a seam that needs none.
|
|
223
220
|
*
|
package/dist/core/strip.js
CHANGED
|
@@ -131,10 +131,6 @@ export class Strip {
|
|
|
131
131
|
// of which renderables end their own line and which are fragments.
|
|
132
132
|
yield Segment.line();
|
|
133
133
|
}
|
|
134
|
-
// The items; a join is drawn from what the joiner emits for it, not held.
|
|
135
|
-
contents() {
|
|
136
|
-
return this.items;
|
|
137
|
-
}
|
|
138
134
|
/**
|
|
139
135
|
* [LAW:one-source-of-truth] The width of the one row it draws, and that is
|
|
140
136
|
* both bounds: the row never wraps and no item is offered less than the whole
|
|
@@ -149,12 +145,12 @@ export class Strip {
|
|
|
149
145
|
return { minimum: width, maximum: width };
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
|
-
const PARTS = ["seam", "
|
|
148
|
+
const PARTS = ["seam", "lead", "tail"];
|
|
153
149
|
/**
|
|
154
|
-
* The least ΔE_OK a
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* one cell wide.
|
|
150
|
+
* The least ΔE_OK a join cell's ink must stand off its own ground by to be
|
|
151
|
+
* seen. Below it the eye cannot tell the ink from the ground, so the joiner
|
|
152
|
+
* lifts the ink to it (`standOff`). Twice the ~.02 threshold of a visible
|
|
153
|
+
* difference, because a seam is one cell wide.
|
|
158
154
|
*/
|
|
159
155
|
export const SEAM_MIN_DELTA_E = 0.04;
|
|
160
156
|
function* paintsIn(paint) {
|
|
@@ -174,7 +170,6 @@ const pickGlyph = (cell, part, index, at) => {
|
|
|
174
170
|
return glyphs[Math.floor((parseInt(fnv1a(`${part}:${index}:${at}`), 16) / 2 ** 32) * glyphs.length)];
|
|
175
171
|
};
|
|
176
172
|
const cellWidth = (cell) => cellLen(glyphsOf(cell)[0] ?? "");
|
|
177
|
-
const partWidth = (cells) => cells.reduce((width, cell) => width + cellWidth(cell), 0);
|
|
178
173
|
// `raw` as a `Paint`, or refused. `absent` is the neighbour this part may not
|
|
179
174
|
// name, because the part is also drawn where that neighbour does not exist.
|
|
180
175
|
function parsePaint(raw, absent, refuse) {
|
|
@@ -197,9 +192,9 @@ function parsePaint(raw, absent, refuse) {
|
|
|
197
192
|
return refuse(`has a colour that is not a Paint: ${JSON.stringify(raw)}`);
|
|
198
193
|
}
|
|
199
194
|
const parseColor = (raw, absent, refuse) => raw === "ground" ? raw : parsePaint(raw, absent, refuse);
|
|
200
|
-
const ABSENT = { seam: undefined,
|
|
195
|
+
const ABSENT = { seam: undefined, lead: "left", tail: "right" };
|
|
201
196
|
// Which argument of a joiner a part is authored in, so a refusal names it.
|
|
202
|
-
const AUTHORED_IN = { seam: "seam",
|
|
197
|
+
const AUTHORED_IN = { seam: "seam", lead: "caps", tail: "caps" };
|
|
203
198
|
function parseCell(cell, form, part, refuse) {
|
|
204
199
|
const glyph = cell.glyph;
|
|
205
200
|
const oneOf = typeof glyph === "object" && glyph !== null ? glyph.oneOf : undefined;
|
|
@@ -247,14 +242,6 @@ const asSeamCell = (form, part) => (cell, refuse) => {
|
|
|
247
242
|
refuse("names a cap and draws cells of its own; a cell is one or the other");
|
|
248
243
|
return cap === "lead" || cap === "tail" ? Object.freeze({ cap }) : refuse(`names a cap that is neither lead nor tail: ${JSON.stringify(cap)}`);
|
|
249
244
|
};
|
|
250
|
-
// A seam's width as a sum over whatever caps stand in it: its own cells', and
|
|
251
|
-
// how many times it names each cap. Two parts equal in all three are the same
|
|
252
|
-
// width under every caps.
|
|
253
|
-
const seamWidth = (cells) => [
|
|
254
|
-
partWidth(cells.filter((cell) => !("cap" in cell))),
|
|
255
|
-
cells.filter((cell) => "cap" in cell && cell.cap === "lead").length,
|
|
256
|
-
cells.filter((cell) => "cap" in cell && cell.cap === "tail").length,
|
|
257
|
-
];
|
|
258
245
|
/**
|
|
259
246
|
* [LAW:parse-dont-validate] The one place a seam is checked, so every rule
|
|
260
247
|
* the cell model states is refused at construction and the render reads cells
|
|
@@ -263,16 +250,7 @@ const seamWidth = (cells) => [
|
|
|
263
250
|
* back is a frozen copy the caller can no longer change.
|
|
264
251
|
*/
|
|
265
252
|
function parseSeam(raw) {
|
|
266
|
-
const parseForm = (form) => {
|
|
267
|
-
const seam = parseCells(raw, form, "seam", asSeamCell(form, "seam"));
|
|
268
|
-
// A divider authored as the seam itself stays the seam, so a render can see
|
|
269
|
-
// it has no choice to make.
|
|
270
|
-
const divider = raw[form].divider === raw[form].seam ? seam : parseCells(raw, form, "divider", asSeamCell(form, "divider"));
|
|
271
|
-
const [seamAt, dividerAt] = [seamWidth(seam), seamWidth(divider)];
|
|
272
|
-
if (seamAt.some((n, i) => n !== dividerAt[i]))
|
|
273
|
-
throw new Error(`seam: ${form} divider is not the width of the seam under every caps`);
|
|
274
|
-
return Object.freeze({ seam, divider });
|
|
275
|
-
};
|
|
253
|
+
const parseForm = (form) => Object.freeze({ seam: parseCells(raw, form, "seam", asSeamCell(form, "seam")) });
|
|
276
254
|
return Object.freeze({ glyph: parseForm("glyph"), ascii: parseForm("ascii") });
|
|
277
255
|
}
|
|
278
256
|
/** [LAW:parse-dont-validate] `parseSeam`'s twin for the caps. */
|
|
@@ -282,18 +260,15 @@ function parseCaps(raw) {
|
|
|
282
260
|
}
|
|
283
261
|
// One form of the seam joined with that form of the caps.
|
|
284
262
|
function joinForms(seam, caps) {
|
|
285
|
-
|
|
286
|
-
const seamCells = splice(seam.seam);
|
|
287
|
-
return { seam: seamCells, divider: seam.divider === seam.seam ? seamCells : splice(seam.divider), lead: caps.lead, tail: caps.tail };
|
|
263
|
+
return { seam: seam.seam.flatMap((cell) => ("cap" in cell ? caps[cell.cap] : [cell])), lead: caps.lead, tail: caps.tail };
|
|
288
264
|
}
|
|
289
265
|
// Whether any of `cells` paints with `side`'s ground.
|
|
290
266
|
const readsGround = (cells, side) => cells.some((cell) => [cell.fg, cell.bg].some((color) => color !== "ground" && [...paintsIn(color)].some((p) => "edge" in p && p.edge === side && p.part === "bg")));
|
|
291
267
|
function formOf(shape) {
|
|
292
|
-
const between = [...shape.seam, ...shape.divider];
|
|
293
268
|
return {
|
|
294
269
|
shape,
|
|
295
270
|
reads: {
|
|
296
|
-
seam: { left: readsGround(
|
|
271
|
+
seam: { left: readsGround(shape.seam, "left"), right: readsGround(shape.seam, "right") },
|
|
297
272
|
lead: readsGround(shape.lead, "right"),
|
|
298
273
|
tail: readsGround(shape.tail, "left"),
|
|
299
274
|
},
|
|
@@ -312,29 +287,82 @@ function paintableBg(bg) {
|
|
|
312
287
|
function drawnGround(bg) {
|
|
313
288
|
return bg.flattenAlpha(SURFACE_BLACK);
|
|
314
289
|
}
|
|
315
|
-
//
|
|
316
|
-
// is
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
290
|
+
// Whether ink stands off its ground, both as a depth draws them, by the floor a
|
|
291
|
+
// seam is seen at. A colour whose RGB is the terminal theme's own (ANSI 0–15, a
|
|
292
|
+
// blend reaching one) has no value here, so two of them stand apart unless they
|
|
293
|
+
// are one expression: the same palette slot — its `number`, never the name or
|
|
294
|
+
// the depth that spells it ("red", "color(1)", an EIGHT_BIT spec on 0–15 are
|
|
295
|
+
// one slot) — or the same blend, whose name spells each end by its slot for the
|
|
296
|
+
// same reason. At sixteen colours, then, the floor is a different slot. The
|
|
297
|
+
// terminal's default colour is nobody's to measure, and stands apart.
|
|
298
|
+
function standsOff(ink, ground) {
|
|
299
|
+
if (ink.isDefault || ground.isDefault)
|
|
300
|
+
return true;
|
|
301
|
+
const [a, b] = [ink.fixedValue, ground.fixedValue];
|
|
302
|
+
return a !== undefined && b !== undefined
|
|
303
|
+
? Oklch.fromRgba(a).deltaE(Oklch.fromRgba(b)) >= SEAM_MIN_DELTA_E
|
|
304
|
+
: ink.number !== undefined ? ink.number !== ground.number : ink.name !== ground.name;
|
|
305
|
+
}
|
|
306
|
+
// A step along a lightness ray: finer than the steps between the colours a
|
|
307
|
+
// truecolor writer draws, so the walk passes over none of them.
|
|
308
|
+
const LIFT_STEP = 0.001;
|
|
309
|
+
// The steps along `origin`'s lightness ray, its chroma and hue held, in the
|
|
310
|
+
// order the lift tries them: the smaller move first, and at an equal move the
|
|
311
|
+
// lighter, out to black and to white.
|
|
312
|
+
function* lightnessRay(origin) {
|
|
313
|
+
for (let k = 1; k * LIFT_STEP <= 1; k++) {
|
|
314
|
+
for (const l of [origin.l + k * LIFT_STEP, origin.l - k * LIFT_STEP]) {
|
|
315
|
+
if (l >= 0 && l <= 1)
|
|
316
|
+
yield new Oklch(l, origin.c, origin.h);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* `ink`, a glyph's colour on `ground`, held at least `SEAM_MIN_DELTA_E` off it
|
|
322
|
+
* as `depth` draws both (`Style.drawnColors`). Ink that already stands off is
|
|
323
|
+
* returned as it is. Ink that does not becomes the drawn form of the nearest
|
|
324
|
+
* step along its own OKLCH lightness ray, chroma and hue held, that stands off:
|
|
325
|
+
* the smaller step, toward white or toward black. Every step is measured as the
|
|
326
|
+
* depth draws it, so at 256 colours and at sixteen the candidates are the
|
|
327
|
+
* entries the depth draws, and the side the ink moves to can differ between
|
|
328
|
+
* depths. A floor measured on rounded colours is not monotone along a ray, so
|
|
329
|
+
* the walk enumerates and never bisects. A side with no colour to measure is
|
|
330
|
+
* left alone.
|
|
331
|
+
*
|
|
332
|
+
* [LAW:one-source-of-truth] The colour returned is the one measured — the
|
|
333
|
+
* entry the depth draws, not the step that rounds to it — so a reader of the
|
|
334
|
+
* cell at another depth (an export reads a recording in truecolor) sees the
|
|
335
|
+
* colour that cleared the floor, never a step one unit off its ground.
|
|
336
|
+
*
|
|
337
|
+
* [LAW:single-enforcer] The one place a join cell's visibility is decided:
|
|
338
|
+
* nothing outside the joiner predicts whether a seam will show.
|
|
339
|
+
*/
|
|
340
|
+
function standOff(ink, ground, depth) {
|
|
341
|
+
if (ink === undefined || ground === undefined)
|
|
342
|
+
return ink;
|
|
343
|
+
const drawn = (color, at) => new Style({ color, bgcolor: ground }).drawnColors(at);
|
|
344
|
+
const { color: shown, bgcolor: under } = drawn(ink, depth);
|
|
345
|
+
if (standsOff(shown, under))
|
|
346
|
+
return ink;
|
|
347
|
+
// The ray starts from the ink as it is laid on its ground, before a depth
|
|
348
|
+
// rounds it; ink the terminal's theme draws starts from the colour the
|
|
349
|
+
// default terminal theme gives it. Ink that vanishes is drawn in the same
|
|
350
|
+
// kind as its ground — both fixed, or both the theme's — and every step is
|
|
351
|
+
// drawn in that kind too, so it is measured as the ink was. Ink the depth
|
|
352
|
+
// draws as the theme's (a slot, or a mix reaching one at truecolor) steps
|
|
353
|
+
// between slots: an RGB beside a colour only the terminal knows could be
|
|
354
|
+
// that very colour. Ink the depth fixes (that same mix is a cube entry at
|
|
355
|
+
// 256) steps between fixed colours, for the same reason turned round.
|
|
356
|
+
const laid = drawn(ink).color;
|
|
357
|
+
const kind = shown.fixedValue === undefined ? ColorDepth.STANDARD : ColorDepth.TRUECOLOR;
|
|
358
|
+
for (const step of lightnessRay(Oklch.fromRgba(laid.getTruecolor()))) {
|
|
359
|
+
const candidate = drawn(ColorSpec.fromRgba(step.toRgba()).downgrade(kind), depth).color;
|
|
360
|
+
if (standsOff(candidate, under))
|
|
361
|
+
return candidate;
|
|
362
|
+
}
|
|
363
|
+
// [LAW:no-silent-failure] Unreachable: the ray runs out to black and to
|
|
364
|
+
// white, and no ground lies within the floor of both, nor is one slot both.
|
|
365
|
+
throw new Error(`no colour on the lightness ray of ${ink.name} stands off ${ground.name} at depth ${ColorDepth[depth]}`);
|
|
338
366
|
}
|
|
339
367
|
function resolvePaint(paint, edges, options) {
|
|
340
368
|
if ("edge" in paint) {
|
|
@@ -358,8 +386,10 @@ function cellStyle(cell, edges, options) {
|
|
|
358
386
|
// is the terminal's own background.
|
|
359
387
|
if (cell.fg === "ground")
|
|
360
388
|
return new Style({ color: cell.bg === "ground" ? undefined : resolvePaint(cell.bg, edges, options), reverse: true });
|
|
361
|
-
const
|
|
362
|
-
|
|
389
|
+
const bgcolor = cell.bg === "ground" ? undefined : resolvePaint(cell.bg, edges, options);
|
|
390
|
+
// [LAW:dataflow-not-control-flow] Every cell's ink is held off its ground;
|
|
391
|
+
// the gap measured between them decides how far it moves, usually not at all.
|
|
392
|
+
return new Style({ color: standOff(resolvePaint(cell.fg, edges, options), bgcolor, options.colorSystem ?? ColorDepth.TRUECOLOR), bgcolor });
|
|
363
393
|
}
|
|
364
394
|
const shapeGlyphs = (shape) => PARTS.flatMap((part) => shape[part].flatMap(glyphsOf)).join("");
|
|
365
395
|
/**
|
|
@@ -370,9 +400,10 @@ const shapeGlyphs = (shape) => PARTS.flatMap((part) => shape[part].flatMap(glyph
|
|
|
370
400
|
* Which part a join draws is read off its two neighbours. A neighbour is
|
|
371
401
|
* missing at the strip's ends, and counts as missing — an endpoint — when the
|
|
372
402
|
* seam paints with its ground anywhere and it has none to paint. With both
|
|
373
|
-
* present the join is the seam
|
|
374
|
-
*
|
|
375
|
-
*
|
|
403
|
+
* present the join is the seam; with only the right, the lead; with only the
|
|
404
|
+
* left, the tail; with neither, nothing. Every cell is drawn with its ink held
|
|
405
|
+
* off its ground at `options.colorSystem` (`standOff`), measured per cell, so a
|
|
406
|
+
* seam stepping in thirds shows every step however close its grounds. A cap
|
|
376
407
|
* that paints with its neighbour's ground is drawn only beside a neighbour
|
|
377
408
|
* with one, so caps that need grounds never stop a seam that needs none.
|
|
378
409
|
*
|
|
@@ -412,13 +443,7 @@ export class CellJoiner {
|
|
|
412
443
|
yield* styled(leads ? "lead" : "tail");
|
|
413
444
|
return;
|
|
414
445
|
}
|
|
415
|
-
|
|
416
|
-
// its ground at a shallow depth still leaves the others to show the
|
|
417
|
-
// step, and measuring per cell, not across the two grounds, divides a
|
|
418
|
-
// seam stepping in thirds where each third would not be seen.
|
|
419
|
-
const seam = styled("seam");
|
|
420
|
-
const lost = drawn.divider !== drawn.seam && seam.every((segment) => segment.style !== undefined && vanishes(segment.style, options.colorSystem));
|
|
421
|
-
yield* lost ? styled("divider") : seam;
|
|
446
|
+
yield* styled("seam");
|
|
422
447
|
},
|
|
423
448
|
};
|
|
424
449
|
}
|
|
@@ -432,52 +457,35 @@ const GROUND = "ground";
|
|
|
432
457
|
const LEAD = { cap: "lead" };
|
|
433
458
|
const TAIL = { cap: "tail" };
|
|
434
459
|
const both = (glyph, ascii) => ({ glyph, ascii });
|
|
435
|
-
const POWERLINE = { glyph: "\ue0b0",
|
|
436
|
-
const SLANT = { glyph: "\ue0bc",
|
|
437
|
-
const BACKSLANT = { glyph: "\ue0b8",
|
|
438
|
-
const FLAME = { glyph: "\ue0c0",
|
|
439
|
-
const PIXEL = { glyph: "\ue0c4",
|
|
440
|
-
const ICE = { glyph: "\ue0c8",
|
|
441
|
-
const BUBBLE = { glyph: "\ue0b4",
|
|
442
|
-
// `glyph` in the left ground over the right
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
seam: [{ glyph, fg: LEFT_BG, bg: RIGHT_BG }],
|
|
446
|
-
divider: [{ glyph: thin, fg: LEFT_FG, bg: LEFT_BG }],
|
|
447
|
-
});
|
|
448
|
-
const arrowSeam = (f) => both(arrow(f.glyph, f.thin), arrow(f.ascii, f.asciiThin));
|
|
460
|
+
const POWERLINE = { glyph: "\ue0b0", ascii: ">" };
|
|
461
|
+
const SLANT = { glyph: "\ue0bc", ascii: "/" };
|
|
462
|
+
const BACKSLANT = { glyph: "\ue0b8", ascii: "\\" };
|
|
463
|
+
const FLAME = { glyph: "\ue0c0", ascii: "}" };
|
|
464
|
+
const PIXEL = { glyph: "\ue0c4", ascii: "#" };
|
|
465
|
+
const ICE = { glyph: "\ue0c8", ascii: "]" };
|
|
466
|
+
const BUBBLE = { glyph: "\ue0b4", ascii: ")" };
|
|
467
|
+
// `glyph` in the left ground over the right.
|
|
468
|
+
const arrow = (glyph) => ({ seam: [{ glyph, fg: LEFT_BG, bg: RIGHT_BG }] });
|
|
469
|
+
const arrowSeam = (f) => both(arrow(f.glyph), arrow(f.ascii));
|
|
449
470
|
// Three arrows stepping through the two thirds between the grounds.
|
|
450
|
-
function prism(glyph
|
|
471
|
+
function prism(glyph) {
|
|
451
472
|
const third = (t) => ({ mix: [LEFT_BG, RIGHT_BG, t] });
|
|
452
|
-
const pad = { glyph: " ", fg: LEFT_FG, bg: LEFT_BG };
|
|
453
473
|
return {
|
|
454
474
|
seam: [
|
|
455
475
|
{ glyph, fg: LEFT_BG, bg: third(1 / 3) },
|
|
456
476
|
{ glyph, fg: third(1 / 3), bg: third(2 / 3) },
|
|
457
477
|
{ glyph, fg: third(2 / 3), bg: RIGHT_BG },
|
|
458
478
|
],
|
|
459
|
-
divider: [pad, { glyph: thin, fg: LEFT_FG, bg: LEFT_BG }, pad],
|
|
460
479
|
};
|
|
461
480
|
}
|
|
462
481
|
// The runs parted: the caps close the one and open the other, `between` —
|
|
463
482
|
// cells on the terminal's ground — set between them.
|
|
464
|
-
|
|
465
|
-
const seam = [TAIL, ...between, LEAD];
|
|
466
|
-
return { seam, divider: seam };
|
|
467
|
-
}
|
|
483
|
+
const parted = (between) => ({ seam: [TAIL, ...between, LEAD] });
|
|
468
484
|
// One cell drawn over the two grounds' midpoint, so it reads as neither item's.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
// A ragged tear in the left ground over the midpoint; where that ink would
|
|
474
|
-
// vanish into the midpoint — grounds a few steps apart, or one ground — the
|
|
475
|
-
// same tear in the left item's text.
|
|
476
|
-
function torn(glyphs) {
|
|
477
|
-
return { seam: midpoint({ oneOf: glyphs }, LEFT_BG).seam, divider: [{ glyph: { oneOf: glyphs }, fg: LEFT_FG, bg: HALF }] };
|
|
478
|
-
}
|
|
479
|
-
const PLAIN_SEAM = [{ glyph: " | ", fg: { role: "strip.separator" }, bg: GROUND }];
|
|
480
|
-
const PLAIN = { seam: PLAIN_SEAM, divider: PLAIN_SEAM };
|
|
485
|
+
const midpoint = (glyph, fg) => ({ seam: [{ glyph, fg, bg: HALF }] });
|
|
486
|
+
// A ragged tear in the left ground over the midpoint.
|
|
487
|
+
const torn = (glyphs) => midpoint({ oneOf: glyphs }, LEFT_BG);
|
|
488
|
+
const PLAIN = { seam: [{ glyph: " | ", fg: { role: "strip.separator" }, bg: GROUND }] };
|
|
481
489
|
const SPACE = { glyph: " ", fg: LEFT_FG, bg: GROUND };
|
|
482
490
|
const rail = (glyph) => ({ glyph, fg: { role: "strip.rail" }, bg: GROUND });
|
|
483
491
|
/**
|
|
@@ -487,7 +495,7 @@ const rail = (glyph) => ({ glyph, fg: { role: "strip.rail" }, bg: GROUND });
|
|
|
487
495
|
*/
|
|
488
496
|
export const SEAMS = Object.freeze({
|
|
489
497
|
powerline: parseSeam(arrowSeam(POWERLINE)),
|
|
490
|
-
prism: parseSeam(both(prism(POWERLINE.glyph
|
|
498
|
+
prism: parseSeam(both(prism(POWERLINE.glyph), prism(POWERLINE.ascii))),
|
|
491
499
|
slant: parseSeam(arrowSeam(SLANT)),
|
|
492
500
|
backslant: parseSeam(arrowSeam(BACKSLANT)),
|
|
493
501
|
flame: parseSeam(arrowSeam(FLAME)),
|
|
@@ -572,12 +580,13 @@ function gradient(steps, glyph) {
|
|
|
572
580
|
// samples 2i (its ink, the left half) and 2i+1 (its ground, the right half),
|
|
573
581
|
// so no sample equals either neighbour's ground.
|
|
574
582
|
const samples = 2 * steps;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
583
|
+
return {
|
|
584
|
+
seam: Array.from({ length: steps }, (_, i) => ({
|
|
585
|
+
glyph,
|
|
586
|
+
fg: { mix: [LEFT_BG, RIGHT_BG, (2 * i + 0.5) / samples] },
|
|
587
|
+
bg: { mix: [LEFT_BG, RIGHT_BG, (2 * i + 1.5) / samples] },
|
|
588
|
+
})),
|
|
589
|
+
};
|
|
581
590
|
}
|
|
582
591
|
/**
|
|
583
592
|
* A ramp of `steps` cells from the left ground to the right. Each cell is the
|
package/dist/core/style.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ import { type Anchor } from "./anchor.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const ATTRIBUTE_NAMES: readonly ["bold", "dim", "italic", "underline", "blink", "blink2", "reverse", "conceal", "strike", "underline2", "frame", "encircle", "overline"];
|
|
13
13
|
export type AttributeName = (typeof ATTRIBUTE_NAMES)[number];
|
|
14
|
+
/**
|
|
15
|
+
* Each text attribute as a style sets it: on, off, or unset — and an unset
|
|
16
|
+
* one lets a style laid beneath (`Style.add`) show through.
|
|
17
|
+
*/
|
|
18
|
+
export type Attributes = {
|
|
19
|
+
readonly [A in AttributeName]: boolean | undefined;
|
|
20
|
+
};
|
|
14
21
|
export declare const ATTRIBUTE_SGR: Record<AttributeName, number>;
|
|
15
22
|
/**
|
|
16
23
|
* Canonical short-attribute aliases (`b` → `bold`, `i` → `italic`, …).
|
|
@@ -18,7 +25,7 @@ export declare const ATTRIBUTE_SGR: Record<AttributeName, number>;
|
|
|
18
25
|
* bindings — adding an alias here makes it available everywhere.
|
|
19
26
|
* [LAW:one-source-of-truth]
|
|
20
27
|
*/
|
|
21
|
-
export declare const ATTRIBUTE_SHORT_ALIASES: Record<string, AttributeName
|
|
28
|
+
export declare const ATTRIBUTE_SHORT_ALIASES: Readonly<Record<string, AttributeName>>;
|
|
22
29
|
export interface StyleOptions {
|
|
23
30
|
color?: string | ColorSpec;
|
|
24
31
|
bgcolor?: string | ColorSpec;
|
|
@@ -38,7 +45,18 @@ export interface StyleOptions {
|
|
|
38
45
|
link?: string;
|
|
39
46
|
meta?: Record<string, unknown>;
|
|
40
47
|
anchor?: Anchor;
|
|
41
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The hold the style's cells are in, both ends or neither: a cell saying
|
|
50
|
+
* only when it next changes would claim, by default, that it always was,
|
|
51
|
+
* and a timeline run backwards past its change would never wake for it.
|
|
52
|
+
* [LAW:types-are-the-program] Absent, the cells never change.
|
|
53
|
+
*/
|
|
54
|
+
hold?: Hold;
|
|
55
|
+
}
|
|
56
|
+
/** A hold: since when a sample has been what it is, and until when it stays so, on one clock. */
|
|
57
|
+
export interface Hold {
|
|
58
|
+
readonly lastChange: number;
|
|
59
|
+
readonly nextChange: number;
|
|
42
60
|
}
|
|
43
61
|
export declare class StyleSyntaxError extends Error {
|
|
44
62
|
constructor(message: string, options?: ErrorOptions);
|
|
@@ -60,7 +78,7 @@ export declare class Style {
|
|
|
60
78
|
readonly encircle: boolean | undefined;
|
|
61
79
|
readonly overline: boolean | undefined;
|
|
62
80
|
readonly link: string | undefined;
|
|
63
|
-
readonly meta: Record<string, unknown
|
|
81
|
+
readonly meta: Readonly<Record<string, unknown>> | undefined;
|
|
64
82
|
/**
|
|
65
83
|
* Where this style's cells sat in the output of whatever drew them; see
|
|
66
84
|
* `./anchor.ts`. It draws nothing, and it makes two styles that draw alike
|
|
@@ -68,17 +86,19 @@ export declare class Style {
|
|
|
68
86
|
*/
|
|
69
87
|
readonly anchor: Anchor | undefined;
|
|
70
88
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
89
|
+
* The hold this style's cells are in, on the timeline of whoever drew the
|
|
90
|
+
* frame: since when what they show has been what it is (`lastChange`), and
|
|
91
|
+
* when it next changes (`nextChange`) — both `t` for a cell drawn by
|
|
92
|
+
* something that moves every frame, and `-Infinity`/`Infinity` for one
|
|
74
93
|
* that never changes. It draws nothing. Whatever draws for a handed time
|
|
75
|
-
* stamps its cells with it (`
|
|
76
|
-
* through every container as the anchor does (`./anchor.ts`), and
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* asking each container. Two styles
|
|
94
|
+
* stamps its cells with it (`placedRender`, `Effected`), the stamp rides up
|
|
95
|
+
* through every container as the anchor does (`./anchor.ts`), and whoever
|
|
96
|
+
* holds the composed frame reads it off (`Segment.nextChange`, and
|
|
97
|
+
* `Segment.lastChange` for a host running its timeline backwards), so a
|
|
98
|
+
* host knows when to draw again without asking each container. Two styles
|
|
99
|
+
* laid together are in the intersection of their holds.
|
|
80
100
|
*/
|
|
81
|
-
readonly
|
|
101
|
+
readonly hold: Hold;
|
|
82
102
|
constructor(options?: StyleOptions);
|
|
83
103
|
get isNull(): boolean;
|
|
84
104
|
get transparentBackground(): boolean;
|
|
@@ -97,6 +117,19 @@ export declare class Style {
|
|
|
97
117
|
* The same look, drawn from the place `anchor` names, or from nowhere.
|
|
98
118
|
*/
|
|
99
119
|
withAnchor(anchor: Anchor | undefined): Style;
|
|
120
|
+
/** The style's text attributes, each as it sets it. */
|
|
121
|
+
get attributes(): Attributes;
|
|
122
|
+
/**
|
|
123
|
+
* The same style with each attribute `attributes` names set as it says —
|
|
124
|
+
* on, off, or unset — and every other field as it was.
|
|
125
|
+
*/
|
|
126
|
+
withAttributes(attributes: Partial<Attributes>): Style;
|
|
127
|
+
/**
|
|
128
|
+
* This style with its motion taken out, what reduced motion draws: `blink`
|
|
129
|
+
* and `blink2`, the motion a terminal makes on its own, unset, and every
|
|
130
|
+
* other field as it was. A style that does not blink is its own.
|
|
131
|
+
*/
|
|
132
|
+
reduced(): Style;
|
|
100
133
|
/**
|
|
101
134
|
* This style on the cell `cells` to the right: its anchor shifted along its
|
|
102
135
|
* row, or the style itself when it has no anchor.
|
|
@@ -130,7 +163,7 @@ export declare class Style {
|
|
|
130
163
|
/**
|
|
131
164
|
* The look as a canonical string that round-trips through `Style.parse()`:
|
|
132
165
|
* colours, attributes and the link, which is all a style string can spell.
|
|
133
|
-
* What a style carries beyond its look — `meta`, `anchor`, `
|
|
166
|
+
* What a style carries beyond its look — `meta`, `anchor`, `hold` —
|
|
134
167
|
* has no spelling and is not here, so two styles that differ only there
|
|
135
168
|
* read alike; `equals` tells them apart.
|
|
136
169
|
*/
|
|
@@ -198,7 +231,7 @@ export declare class Theme {
|
|
|
198
231
|
resolve(style: TextStyle): Style;
|
|
199
232
|
has(name: string): boolean;
|
|
200
233
|
}
|
|
201
|
-
export declare const DEFAULT_STYLES: Record<string, Style
|
|
234
|
+
export declare const DEFAULT_STYLES: Readonly<Record<string, Style>>;
|
|
202
235
|
/**
|
|
203
236
|
* The theme a render resolves names against when nothing supplied one — what
|
|
204
237
|
* a renderable sees outside a `Console`, as in `renderToString`.
|