@promptctl/rich-js 0.27.0 → 0.29.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 -6
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +65 -46
- package/dist/core/animation.js +91 -67
- package/dist/core/ansi.d.ts +19 -12
- package/dist/core/ansi.js +114 -36
- 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 +94 -6
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/color.d.ts +56 -57
- package/dist/core/color.js +79 -81
- package/dist/core/console.d.ts +24 -7
- package/dist/core/console.js +27 -8
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/easing.d.ts +40 -40
- package/dist/core/easing.js +40 -40
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +5 -4
- package/dist/core/export-lines.d.ts +13 -11
- package/dist/core/export-lines.js +19 -15
- package/dist/core/export-svg.d.ts +3 -3
- package/dist/core/export-svg.js +10 -9
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +5 -4
- package/dist/core/memo.d.ts +1 -1
- package/dist/core/memo.js +1 -1
- package/dist/core/oklch.d.ts +14 -14
- package/dist/core/oklch.js +10 -10
- 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/paint.d.ts +9 -9
- package/dist/core/paint.js +3 -3
- package/dist/core/pretty.d.ts +1 -1
- package/dist/core/pretty.js +23 -19
- package/dist/core/protocol.d.ts +47 -26
- package/dist/core/protocol.js +31 -23
- package/dist/core/render.d.ts +19 -14
- package/dist/core/render.js +30 -17
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +12 -4
- package/dist/core/segment.js +16 -6
- package/dist/core/strip.d.ts +83 -83
- package/dist/core/strip.js +70 -70
- package/dist/core/style.d.ts +46 -18
- package/dist/core/style.js +92 -56
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +12 -2
- package/dist/core/text.js +136 -90
- package/dist/core/timeline.d.ts +5 -5
- package/dist/core/timeline.js +11 -11
- package/dist/core/tween.d.ts +6 -6
- package/dist/core/tween.js +10 -10
- package/dist/core/view.d.ts +5 -5
- package/dist/core/view.js +15 -14
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +50 -9
- package/dist/host/app.js +120 -15
- package/dist/host/host-environment.d.ts +2 -2
- package/dist/host/host-environment.js +2 -2
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +42 -9
- package/dist/host/input.js +91 -7
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +4 -3
- package/dist/host/terminal-host.js +1 -1
- package/dist/index.d.ts +17 -16
- package/dist/index.js +11 -11
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.js +1 -1
- package/dist/node/terminal-host.d.ts +1 -1
- package/dist/node/traceback.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +6 -8
- package/dist/renderables/effect.d.ts +88 -87
- package/dist/renderables/effect.js +124 -114
- package/dist/renderables/effects.d.ts +105 -76
- package/dist/renderables/effects.js +309 -176
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +2 -2
- package/dist/renderables/flexStrip.d.ts +7 -7
- package/dist/renderables/flexStrip.js +15 -15
- package/dist/renderables/frames.d.ts +7 -16
- package/dist/renderables/frames.js +12 -27
- package/dist/renderables/glyph-effect.d.ts +22 -23
- package/dist/renderables/glyph-effect.js +43 -41
- package/dist/renderables/glyph-effects.d.ts +33 -14
- package/dist/renderables/glyph-effects.js +90 -22
- package/dist/renderables/html-entities.d.ts +1 -1
- package/dist/renderables/html-entities.js +1 -1
- package/dist/renderables/lifecycle.d.ts +8 -10
- package/dist/renderables/lifecycle.js +17 -18
- package/dist/renderables/live.d.ts +2 -2
- package/dist/renderables/live.js +7 -7
- package/dist/renderables/markdown-parse.js +3 -3
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +7 -5
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +1 -1
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +21 -18
- package/dist/renderables/prompt.d.ts +1 -1
- package/dist/renderables/scroll.d.ts +1 -1
- package/dist/renderables/scroll.js +3 -3
- package/dist/renderables/spinner.d.ts +4 -4
- package/dist/renderables/spinner.js +10 -10
- package/dist/renderables/syntax.d.ts +2 -2
- package/dist/renderables/syntax.js +9 -4
- package/dist/renderables/table.js +5 -4
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +1 -1
- package/dist/renderables/tree.js +3 -3
- package/dist/template-bindings/animation-funcs.d.ts +60 -29
- package/dist/template-bindings/animation-funcs.js +126 -43
- package/dist/template-bindings/color-funcs.d.ts +14 -14
- package/dist/template-bindings/color-funcs.js +25 -25
- package/dist/template-bindings/index.d.ts +16 -14
- package/dist/template-bindings/index.js +17 -14
- package/dist/template-bindings/palette-funcs.d.ts +2 -2
- package/dist/template-bindings/palette-funcs.js +4 -4
- package/dist/template-bindings/style-funcs.d.ts +5 -5
- package/dist/template-bindings/style-funcs.js +16 -16
- package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
- package/dist/themes/{transpose.js → adjust.js} +28 -28
- package/dist/themes/buildPalette.d.ts +6 -5
- package/dist/themes/buildPalette.js +22 -22
- package/dist/themes/colorMath.d.ts +16 -16
- package/dist/themes/colorMath.js +32 -32
- package/dist/themes/data/ansi-tables.d.ts +2 -2
- package/dist/themes/data/ansi-tables.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/data/types.d.ts +4 -4
- package/dist/themes/data/types.js +2 -2
- package/dist/themes/palette.d.ts +12 -12
- package/dist/themes/palette.js +16 -16
- package/dist/themes/ramp.d.ts +17 -17
- package/dist/themes/ramp.js +12 -12
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +6 -8
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +3 -3
- package/dist/widgets/button.js +8 -8
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +3 -3
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/event-router.d.ts +6 -6
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/focus-manager.d.ts +3 -3
- package/dist/widgets/focus-manager.js +3 -3
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/ink.d.ts +3 -3
- package/dist/widgets/ink.js +4 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/static-item.d.ts +2 -2
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +5 -5
- package/dist/widgets/themed-widget.js +4 -4
- package/dist/widgets/toggle.d.ts +4 -4
- package/dist/widgets/toggle.js +6 -6
- package/dist/widgets/types.d.ts +6 -6
- package/dist/widgets/types.js +2 -2
- package/dist/widgets/widget-app.d.ts +10 -10
- package/dist/widgets/widget-app.js +8 -8
- package/dist/widgets/widget-base.d.ts +2 -2
- package/package.json +5 -4
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* number the time is turned into first: a frame's stamp says when it changes
|
|
21
21
|
* on the host's clock, which only `Timeline.run` can carry it back onto —
|
|
22
22
|
* paused, never; backwards, at the hold it is about to drop under.
|
|
23
|
-
* [LAW:single-enforcer] `
|
|
23
|
+
* [LAW:single-enforcer] `drawn` is the one place the time, the timeline
|
|
24
24
|
* and the reader's preference for motion are read, for every function.
|
|
25
25
|
*
|
|
26
26
|
* `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
|
|
@@ -29,54 +29,89 @@
|
|
|
29
29
|
* arguments on every evaluation, so the sequence is prepared on every one:
|
|
30
30
|
* a template has nowhere to keep it, and keeping it here would be the memo
|
|
31
31
|
* keyed on the arguments that `FrameSequence` is instead of. `stepped <seconds>
|
|
32
|
-
* <color> <seconds> <color> …` is the
|
|
33
|
-
*
|
|
34
|
-
* nests where a
|
|
32
|
+
* <color> <seconds> <color> …` is the color in progress at the moment, a
|
|
33
|
+
* color value like every color function's (`./color-funcs.ts`), so it
|
|
34
|
+
* nests where a color goes — `fg (stepped …)`, `ensureContrast (stepped …) $bg
|
|
35
35
|
* 4.5` — and the ground it must read on is the template's to name, as it
|
|
36
|
-
* is for any
|
|
36
|
+
* is for any color. Either argument list is one float/value cycle end to
|
|
37
37
|
* end, so the engine's `alternating` gate types every slot and lifts each
|
|
38
38
|
* string frame to a fragment; the pairing — a trailing hold with no frame or
|
|
39
|
-
*
|
|
39
|
+
* color — is parsed in `pairs`, once. [LAW:types-are-the-program]
|
|
40
40
|
*
|
|
41
41
|
* The rest start at a moment the template names, on the timeline's clock —
|
|
42
42
|
* a field of the host's state, the moment a value changed — and each is the
|
|
43
43
|
* animation `Timeline.play(start)` runs, so a start is a timeline like any
|
|
44
44
|
* other and nothing here offsets a time by hand. `tween <seconds> <ease>
|
|
45
45
|
* <start> <from> <to>` is a number eased from one value to another and held
|
|
46
|
-
* (`core/tween.ts`), its ease spelled by name (`
|
|
46
|
+
* (`core/tween.ts`), its ease spelled by name (`parseEasing`); `loop <seconds>
|
|
47
47
|
* <start>` is the way through a period, 0 to 1 and round again — two
|
|
48
48
|
* cells started a step apart are a stagger. Each is a number, so it goes
|
|
49
|
-
* wherever a number goes: a
|
|
49
|
+
* wherever a number goes: a color tween is `ramp (tween …) "linear" 0 $from
|
|
50
50
|
* 1 $to` and a ramp scrolling is `ramp (loop …) …`, because `ColorRamp` is
|
|
51
|
-
* the one place two
|
|
51
|
+
* the one place two colors are blended and a color tween is a two-stop
|
|
52
52
|
* ramp read over time (`ColorRamp.over`). [LAW:one-source-of-truth] A
|
|
53
53
|
* second blender here would be a second answer to what lies between two
|
|
54
|
-
*
|
|
54
|
+
* colors. `glyphs <name> <start> <fragment>` is the fragment through a
|
|
55
55
|
* glyph effect from the catalogue (`GLYPH_EFFECT_CATALOGUE`), at its
|
|
56
56
|
* defaults, before any render (`reglyphText`). Every argument list is an
|
|
57
57
|
* exact arity of typed slots, so a slot of the wrong kind fails at the gate.
|
|
58
58
|
*
|
|
59
59
|
* Every function says when what it drew changes, so a host paints a
|
|
60
|
-
* template while it moves and not after
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
60
|
+
* template while it moves and not after — and says it only for what the
|
|
61
|
+
* output depends on. A number or a color has no cells to carry a hold, and
|
|
62
|
+
* a fragment's cells lose theirs the moment it is flattened (`printf "%s"`),
|
|
63
|
+
* so no function stamps cells or tells the frame itself. Each marks its
|
|
64
|
+
* return with the hold its value is in (`marked`, the engine's evaluation
|
|
65
|
+
* provenance), and the engine carries the mark beside the value into every
|
|
66
|
+
* variable, dot and function the value passes through, reporting it where
|
|
67
|
+
* the output depends on it. `emitted` is where a template's cells take a
|
|
68
|
+
* hold: every fragment the template emits, stamped with the holds of the
|
|
69
|
+
* marked values its action read. `decided` is the route for a hold no cell
|
|
70
|
+
* can carry, told to the frame (`TemplateFrame.onValidity`): a mark an
|
|
71
|
+
* `if`/`with`/`range` consumed, whichever arm it took, because the frame may
|
|
72
|
+
* show cells for it later and must say when without a cell to say it on, as
|
|
73
|
+
* a placed animation of no cells does (`placed`) — and, by the same rule, a
|
|
74
|
+
* mark a writing function or a throwing action read; `EngineConfig.decided`
|
|
75
|
+
* is the authority on when it is called. A mark bound somewhere nobody reads
|
|
76
|
+
* is reported nowhere, so a value a template computes and never draws or
|
|
77
|
+
* decides on tells the frame nothing (rich-animation-27tq.0gd). Early, never
|
|
78
|
+
* late: an emission may carry a hold it did not strictly need, and a hold
|
|
79
|
+
* the output depends on is never dropped. [LAW:single-enforcer]
|
|
80
|
+
*
|
|
81
|
+
* The functions and the two hooks go into the host's engine config together,
|
|
82
|
+
* by `withAnimation`: the functions wired without the hooks would compile,
|
|
83
|
+
* draw and never say they move. [LAW:no-silent-failure]
|
|
66
84
|
*/
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
85
|
+
import { marked } from "@promptctl/go-template-js";
|
|
86
|
+
import { Animation, frameTimeOf, validityOf, shownAt, stampValidity } from "../core/animation.js";
|
|
87
|
+
import { WrapMode, stepSequence, finiteTime, parseEasing } from "../core/easing.js";
|
|
88
|
+
import { layerStyle } from "../core/style.js";
|
|
69
89
|
import { Timeline } from "../core/timeline.js";
|
|
70
90
|
import { tween } from "../core/tween.js";
|
|
71
91
|
import { cellSeed } from "../renderables/effect.js";
|
|
72
92
|
import { reglyphText } from "../renderables/glyph-effect.js";
|
|
73
93
|
import { GLYPH_EFFECT_CATALOGUE } from "../renderables/glyph-effects.js";
|
|
74
|
-
import { FrameSequence
|
|
94
|
+
import { FrameSequence } from "../renderables/frames.js";
|
|
75
95
|
import { asColor } from "./color-funcs.js";
|
|
76
96
|
import { asFragment } from "./helpers.js";
|
|
77
97
|
/** The frame a renderable is drawn in, as the frame a template it evaluates there is drawn for. */
|
|
78
98
|
export function templateFrame(options) {
|
|
79
|
-
return { time: frameTimeOf(options),
|
|
99
|
+
return { time: frameTimeOf(options), onValidity: options.onValidity, reducedMotion: options.reducedMotion, asciiOnly: options.asciiOnly };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The mark a function puts on its return: the hold its value is in, and the
|
|
103
|
+
* frame it was drawn for, which is the one `decided` tells. Only the two
|
|
104
|
+
* hooks here read one; a consumer's own marks pass through them untouched,
|
|
105
|
+
* and a foreign token shaped like a `ValidityInterval` is not one.
|
|
106
|
+
* [LAW:types-are-the-program]
|
|
107
|
+
*/
|
|
108
|
+
class ValidityMark {
|
|
109
|
+
validity;
|
|
110
|
+
frame;
|
|
111
|
+
constructor(validity, frame) {
|
|
112
|
+
this.validity = validity;
|
|
113
|
+
this.frame = frame;
|
|
114
|
+
}
|
|
80
115
|
}
|
|
81
116
|
/** The host's own clock as a timeline: host time 0 at animation time 0, at rate 1. */
|
|
82
117
|
const HOST_CLOCK = Timeline.play(0);
|
|
@@ -127,36 +162,41 @@ function glyphEffect(name) {
|
|
|
127
162
|
return GLYPH_EFFECTS[name];
|
|
128
163
|
}
|
|
129
164
|
/**
|
|
130
|
-
*
|
|
131
|
-
* the frame `frame` reports when
|
|
132
|
-
* seconds, from whatever zero the
|
|
133
|
-
* `options.timeline`, and each
|
|
165
|
+
* `config` with `frames`, `stepped`, `tween`, `loop` and `glyphs` registered
|
|
166
|
+
* beside its own functions, each drawn for the frame `frame` reports when
|
|
167
|
+
* the template is evaluated — its time in seconds, from whatever zero the
|
|
168
|
+
* host counts from — through `options.timeline`, and each marking its
|
|
169
|
+
* return with the hold it is in there; and with the two hooks that carry
|
|
170
|
+
* those holds to the cells (`emitted`) and the frame (`decided`) installed
|
|
171
|
+
* over any `config` already has, which still hear everything they did. One
|
|
172
|
+
* call, so the functions and the hooks that report for them cannot be wired
|
|
173
|
+
* apart: the functions alone would compile, draw, and never say they move.
|
|
174
|
+
* [LAW:types-are-the-program] [LAW:no-silent-failure] [LAW:composability]
|
|
175
|
+
* A `config` that already has one of the five names is refused, which is
|
|
176
|
+
* also what a second `withAnimation` over its own result meets: its template
|
|
177
|
+
* calls would otherwise reach these with no word said.
|
|
178
|
+
*
|
|
134
179
|
* The functions exist whatever `frame` returns, so a template compiles once
|
|
135
180
|
* and is evaluated at every moment the host draws. In a frame that says
|
|
136
181
|
* `reducedMotion` each draws its animation reduced (`Animation.reduced`): a
|
|
137
182
|
* loop at its start, a one-shot cut from where it starts to where it arrives
|
|
138
183
|
* when it starts.
|
|
139
184
|
*/
|
|
140
|
-
export function
|
|
185
|
+
export function withAnimation(config, frame, options = {}) {
|
|
141
186
|
const timeline = options.timeline ?? (() => HOST_CLOCK);
|
|
142
|
-
// `animation
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const [drawn, t] = drawing(what, animation, at);
|
|
150
|
-
const shown = shownAt(drawn, t, at);
|
|
151
|
-
at.onHold?.(holdOf(shown, t));
|
|
152
|
-
return shown.value;
|
|
187
|
+
// What `animation`, on this call's timeline, shows in the frame `at`,
|
|
188
|
+
// under the reader's preference, marked with how long for: the time is
|
|
189
|
+
// read once a call so a value and its hold are one frame's.
|
|
190
|
+
const drawn = (what, animation, at) => {
|
|
191
|
+
const t = finiteTime(what, at.time);
|
|
192
|
+
const shown = shownAt(Timeline.run(timeline(), animation), t, at);
|
|
193
|
+
return marked(shown.value, new ValidityMark(validityOf(shown, t), at));
|
|
153
194
|
};
|
|
154
195
|
const startedAt = (start, animation) => Timeline.run(Timeline.play(start), animation);
|
|
155
196
|
const framesFunc = {
|
|
156
197
|
fn: ((...tail) => {
|
|
157
198
|
const frames = pairs("frame", tail, asFragment).map(({ seconds, value }) => ({ seconds, text: value }));
|
|
158
|
-
|
|
159
|
-
return textFrameAt(...drawing("frames", new FrameSequence(frames), at), at);
|
|
199
|
+
return drawn("frames", new FrameSequence(frames), frame());
|
|
160
200
|
}),
|
|
161
201
|
argTypes: ["float", "liftable"],
|
|
162
202
|
arity: { kind: "alternating", minimum: 2 },
|
|
@@ -165,20 +205,20 @@ export function animationFuncs(frame, options = {}) {
|
|
|
165
205
|
const steppedFunc = {
|
|
166
206
|
fn: ((...tail) => {
|
|
167
207
|
const steps = pairs("color", tail, (v) => asColor(v, "stepped"));
|
|
168
|
-
return
|
|
208
|
+
return drawn("stepped", Animation.map(stepSequence(steps.map((s) => s.seconds)), (step) => steps[step].value.hex), frame());
|
|
169
209
|
}),
|
|
170
210
|
argTypes: ["float", "string"],
|
|
171
211
|
arity: { kind: "alternating", minimum: 2 },
|
|
172
212
|
returnType: "string",
|
|
173
213
|
};
|
|
174
214
|
const tweenFunc = {
|
|
175
|
-
fn: ((seconds, ease, start, from, to) =>
|
|
215
|
+
fn: ((seconds, ease, start, from, to) => drawn("tween", startedAt(start, tween(from, to, { seconds, ease: parseEasing(ease) })), frame())),
|
|
176
216
|
argTypes: ["float", "string", "float", "float", "float"],
|
|
177
217
|
arity: { kind: "exact" },
|
|
178
218
|
returnType: "float",
|
|
179
219
|
};
|
|
180
220
|
const loopFunc = {
|
|
181
|
-
fn: ((seconds, start) =>
|
|
221
|
+
fn: ((seconds, start) => drawn("loop", startedAt(start, Animation.continuous(WrapMode.loop(seconds))), frame())),
|
|
182
222
|
argTypes: ["float", "float"],
|
|
183
223
|
arity: { kind: "exact" },
|
|
184
224
|
returnType: "float",
|
|
@@ -191,11 +231,54 @@ export function animationFuncs(frame, options = {}) {
|
|
|
191
231
|
const key = fragment.plain;
|
|
192
232
|
const at = frame();
|
|
193
233
|
const reglyphed = reglyphText(fragment, make({ z: cellSeed(key, 0, 0) }), { key, asciiOnly: at.asciiOnly === true });
|
|
194
|
-
return
|
|
234
|
+
return drawn("glyphs", startedAt(start, reglyphed), at);
|
|
195
235
|
}),
|
|
196
236
|
argTypes: ["string", "float", "liftable"],
|
|
197
237
|
arity: { kind: "exact" },
|
|
198
238
|
returnType: "T",
|
|
199
239
|
};
|
|
200
|
-
|
|
240
|
+
const funcs = { frames: framesFunc, stepped: steppedFunc, tween: tweenFunc, loop: loopFunc, glyphs: glyphsFunc };
|
|
241
|
+
const taken = Object.keys(funcs).filter((name) => Object.hasOwn(config.funcs ?? {}, name));
|
|
242
|
+
if (taken.length > 0) {
|
|
243
|
+
throw new RangeError(`withAnimation registers ${taken.join(", ")}, already in config.funcs`);
|
|
244
|
+
}
|
|
245
|
+
const holdMarks = (marks) => [...marks].filter((mark) => mark instanceof ValidityMark);
|
|
246
|
+
// The holds among `marks` layered onto the fragment's base style, which an
|
|
247
|
+
// empty text still draws as its one piece of no width: `Style.add` keeps
|
|
248
|
+
// the later `validFrom` and the sooner `validUntil`, so cells that read
|
|
249
|
+
// two animated values are in the intersection of their holds, as two
|
|
250
|
+
// styles laid together are anywhere. A fragment no hold reaches is handed
|
|
251
|
+
// back as it came — the engine calls this for every emission — and one a
|
|
252
|
+
// hold reaches is a copy, since the engine may hand the one fragment a
|
|
253
|
+
// scope holds to more than one emission.
|
|
254
|
+
const held = (fragment, marks) => {
|
|
255
|
+
const holds = holdMarks(marks);
|
|
256
|
+
if (holds.length === 0)
|
|
257
|
+
return fragment;
|
|
258
|
+
const result = fragment.copy();
|
|
259
|
+
result.style = holds.reduce((style, mark) => layerStyle(style, stampValidity(mark.validity)), fragment.style);
|
|
260
|
+
return result;
|
|
261
|
+
};
|
|
262
|
+
const told = (marks) => {
|
|
263
|
+
for (const mark of holdMarks(marks))
|
|
264
|
+
mark.frame.onValidity?.(mark.validity);
|
|
265
|
+
};
|
|
266
|
+
// The hooks `config` brought run first and hear everything they did; a
|
|
267
|
+
// consumer that stamps or listens on its own loses nothing to this binding.
|
|
268
|
+
const emitted = config.emitted ?? ((fragment) => fragment);
|
|
269
|
+
const decided = config.decided ?? (() => { });
|
|
270
|
+
return {
|
|
271
|
+
...config,
|
|
272
|
+
funcs: { ...config.funcs, ...funcs },
|
|
273
|
+
emitted: (fragment, marks) => held(emitted(fragment, marks), marks),
|
|
274
|
+
// `finally`: a consumer hook that throws still leaves the frame told its holds.
|
|
275
|
+
decided: (marks) => {
|
|
276
|
+
try {
|
|
277
|
+
decided(marks);
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
told(marks);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
};
|
|
201
284
|
}
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
* No precision is given up: `darken`, `blendRgb`, and `contrastFor` already
|
|
45
45
|
* take and return 8-bit `ColorRgba`. The one caveat is OKLCH: each of the
|
|
46
46
|
* axis functions below round-trips sRGB→OKLCH→sRGB, so chaining three or more
|
|
47
|
-
* of them quantizes visibly. Compose two, or reach for `
|
|
47
|
+
* of them quantizes visibly. Compose two, or reach for `adjustPalette` when
|
|
48
48
|
* a whole palette needs adapting — the same caveat that already applies to
|
|
49
|
-
* chained
|
|
49
|
+
* chained adjustments.
|
|
50
50
|
*
|
|
51
51
|
* ### Palette-free on purpose
|
|
52
52
|
*
|
|
@@ -66,23 +66,23 @@ import { ColorDepth, type ColorRgba, type TerminalTheme } from "../core/color.js
|
|
|
66
66
|
* same one — a palette name passed where a color belongs (`darken "primary" 2`)
|
|
67
67
|
* — and an author who reads "wrap it: darken (color "primary")" is corrected in
|
|
68
68
|
* one step, where "expected #RRGGBB" leaves them guessing. What it parses
|
|
69
|
-
* must then be a
|
|
69
|
+
* must then be a color these functions can compute with (`computable`).
|
|
70
70
|
* [LAW:no-silent-failure]
|
|
71
71
|
*/
|
|
72
72
|
export declare function asColor(value: string, func: string): ColorRgba;
|
|
73
73
|
/**
|
|
74
|
-
* A
|
|
75
|
-
* theme's "no background", `#00000000` — has no
|
|
74
|
+
* A color the color functions can compute with. A fully transparent one — a
|
|
75
|
+
* theme's "no background", `#00000000` — has no color of its own: what shows
|
|
76
76
|
* through it is whatever surface it lands on, which a template cannot know.
|
|
77
77
|
* Darkening, mixing or contrasting it would answer for a surface nobody named,
|
|
78
|
-
* so it is refused here, for every
|
|
78
|
+
* so it is refused here, for every color function and every `ramp` stop.
|
|
79
79
|
* [LAW:single-enforcer]
|
|
80
80
|
*/
|
|
81
|
-
export declare function computable(
|
|
81
|
+
export declare function computable(color: ColorRgba, func: string, written: string): ColorRgba;
|
|
82
82
|
/**
|
|
83
|
-
* Where `
|
|
84
|
-
* and the terminal that draws it when that is known. At sixteen
|
|
85
|
-
* terminal paints each slot in its own theme's
|
|
83
|
+
* Where `ensureContrast`'s text will be drawn: the depth the output is encoded at,
|
|
84
|
+
* and the terminal that draws it when that is known. At sixteen colors a
|
|
85
|
+
* terminal paints each slot in its own theme's color, so the floor is measured
|
|
86
86
|
* in `terminal`'s sixteen; without one, in the VGA defaults (the Windows
|
|
87
87
|
* console's at `WINDOWS`). Both are facts about the one place the text lands,
|
|
88
88
|
* so they arrive together.
|
|
@@ -92,11 +92,11 @@ export interface DrawnAt {
|
|
|
92
92
|
readonly terminal?: TerminalTheme;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* The `
|
|
95
|
+
* The `ensureContrast` binding, measuring where `drawnAt` says the text lands when
|
|
96
96
|
* it is evaluated — a consumer that renders to a per-call destination
|
|
97
97
|
* registers its own.
|
|
98
98
|
*/
|
|
99
|
-
export declare const
|
|
99
|
+
export declare const ensureContrastFunc: (drawnAt: () => DrawnAt) => TemplateFunc;
|
|
100
100
|
/**
|
|
101
101
|
* The palette-free color vocabulary: every function takes colors and returns a
|
|
102
102
|
* color, so they compose by nesting.
|
|
@@ -106,7 +106,7 @@ export declare const readableOnFunc: (drawnAt: () => DrawnAt) => TemplateFunc;
|
|
|
106
106
|
* | `darken c n` / `lighten c n` | slide HSL lightness by N 10% levels |
|
|
107
107
|
* | `mix a b pct` | blend `a` toward `b` by `pct`% (0 → `a`, 100 → `b`) |
|
|
108
108
|
* | `contrastOn bg` | black or white, whichever is readable on `bg` |
|
|
109
|
-
* | `
|
|
109
|
+
* | `ensureContrast fg bg ratio` | `fg` nudged in OKLCH lightness until it clears `ratio` on `bg`, hue preserved |
|
|
110
110
|
* | `shiftHue c deg` | rotate hue in OKLCH |
|
|
111
111
|
* | `scaleChroma c f` | multiply chroma (0 → gray, 1 → identity) |
|
|
112
112
|
* | `scaleLightness c f` | multiply lightness (1 → identity, -1 → invert) |
|
|
@@ -116,7 +116,7 @@ export declare const readableOnFunc: (drawnAt: () => DrawnAt) => TemplateFunc;
|
|
|
116
116
|
* `richTextStyleFuncs()`'s `fg`/`bg` to paint them onto text.
|
|
117
117
|
*
|
|
118
118
|
* @param drawnAt where the text will be drawn — the depth, and the terminal
|
|
119
|
-
* when known — read on every `
|
|
119
|
+
* when known — read on every `ensureContrast` evaluation so a host that learns
|
|
120
120
|
* its destination per render passes one getter; defaults to truecolor.
|
|
121
121
|
*/
|
|
122
122
|
export declare function colorFuncs(drawnAt?: () => DrawnAt): FuncMap;
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
* No precision is given up: `darken`, `blendRgb`, and `contrastFor` already
|
|
45
45
|
* take and return 8-bit `ColorRgba`. The one caveat is OKLCH: each of the
|
|
46
46
|
* axis functions below round-trips sRGB→OKLCH→sRGB, so chaining three or more
|
|
47
|
-
* of them quantizes visibly. Compose two, or reach for `
|
|
47
|
+
* of them quantizes visibly. Compose two, or reach for `adjustPalette` when
|
|
48
48
|
* a whole palette needs adapting — the same caveat that already applies to
|
|
49
|
-
* chained
|
|
49
|
+
* chained adjustments.
|
|
50
50
|
*
|
|
51
51
|
* ### Palette-free on purpose
|
|
52
52
|
*
|
|
@@ -67,13 +67,13 @@ import { darken, contrastFor, ensureContrast } from "../themes/colorMath.js";
|
|
|
67
67
|
* same one — a palette name passed where a color belongs (`darken "primary" 2`)
|
|
68
68
|
* — and an author who reads "wrap it: darken (color "primary")" is corrected in
|
|
69
69
|
* one step, where "expected #RRGGBB" leaves them guessing. What it parses
|
|
70
|
-
* must then be a
|
|
70
|
+
* must then be a color these functions can compute with (`computable`).
|
|
71
71
|
* [LAW:no-silent-failure]
|
|
72
72
|
*/
|
|
73
73
|
export function asColor(value, func) {
|
|
74
|
-
let
|
|
74
|
+
let color;
|
|
75
75
|
try {
|
|
76
|
-
|
|
76
|
+
color = parseHexColor(value.trim());
|
|
77
77
|
}
|
|
78
78
|
catch (cause) {
|
|
79
79
|
if (!(cause instanceof ColorParseError))
|
|
@@ -81,24 +81,24 @@ export function asColor(value, func) {
|
|
|
81
81
|
throw new TypeError(`${func} expected a color (#RRGGBB or #RRGGBBAA), got ${JSON.stringify(value)}` +
|
|
82
82
|
` — to use a palette name here, wrap it: ${func} (color ${JSON.stringify(value)}) …`, { cause });
|
|
83
83
|
}
|
|
84
|
-
return computable(
|
|
84
|
+
return computable(color, func, JSON.stringify(value));
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
* A
|
|
88
|
-
* theme's "no background", `#00000000` — has no
|
|
87
|
+
* A color the color functions can compute with. A fully transparent one — a
|
|
88
|
+
* theme's "no background", `#00000000` — has no color of its own: what shows
|
|
89
89
|
* through it is whatever surface it lands on, which a template cannot know.
|
|
90
90
|
* Darkening, mixing or contrasting it would answer for a surface nobody named,
|
|
91
|
-
* so it is refused here, for every
|
|
91
|
+
* so it is refused here, for every color function and every `ramp` stop.
|
|
92
92
|
* [LAW:single-enforcer]
|
|
93
93
|
*/
|
|
94
|
-
export function computable(
|
|
95
|
-
if (
|
|
96
|
-
throw new RangeError(`${func} got ${written}, which is fully transparent: it draws no
|
|
97
|
-
`so there is nothing to compute with — use the
|
|
94
|
+
export function computable(color, func, written) {
|
|
95
|
+
if (color.alpha === 0) {
|
|
96
|
+
throw new RangeError(`${func} got ${written}, which is fully transparent: it draws no color of its own, ` +
|
|
97
|
+
`so there is nothing to compute with — use the color of the surface it sits on, e.g. (color "background")`);
|
|
98
98
|
}
|
|
99
|
-
return
|
|
99
|
+
return color;
|
|
100
100
|
}
|
|
101
|
-
// [LAW:single-enforcer] Every
|
|
101
|
+
// [LAW:single-enforcer] Every color function is fixed-arity, so the engine's
|
|
102
102
|
// arity gate refuses a missing or surplus argument before the body runs.
|
|
103
103
|
function colorFunc(argTypes, fn) {
|
|
104
104
|
return { fn, argTypes, arity: { kind: "exact" }, returnType: "string" };
|
|
@@ -147,7 +147,7 @@ const mixFunc = colorFunc(["string", "string", "float"], ((fromHex, towardHex, p
|
|
|
147
147
|
//
|
|
148
148
|
// Two different questions, so two functions rather than one with a flag:
|
|
149
149
|
// `contrastOn` asks "what is maximally readable here" (black or white, WCAG
|
|
150
|
-
// relative-luminance cutoff); `
|
|
150
|
+
// relative-luminance cutoff); `ensureContrast` asks "keep this color recognizably
|
|
151
151
|
// itself, but guarantee it clears `ratio`" — it slides OKLCH lightness and
|
|
152
152
|
// preserves hue, so a blue on dark blue becomes a lighter blue rather than
|
|
153
153
|
// white.
|
|
@@ -168,19 +168,19 @@ const RATIO_RANGE = {
|
|
|
168
168
|
const contrastOnFunc = colorFunc(["string"], ((bgHex) => contrastFor(asColor(bgHex, "contrastOn")).hex));
|
|
169
169
|
const TRUECOLOR = { depth: ColorDepth.TRUECOLOR };
|
|
170
170
|
/**
|
|
171
|
-
* The `
|
|
171
|
+
* The `ensureContrast` binding, measuring where `drawnAt` says the text lands when
|
|
172
172
|
* it is evaluated — a consumer that renders to a per-call destination
|
|
173
173
|
* registers its own.
|
|
174
174
|
*/
|
|
175
|
-
export const
|
|
175
|
+
export const ensureContrastFunc = (drawnAt) => colorFunc(["string", "string", "float"], ((fgHex, bgHex, ratio) => {
|
|
176
176
|
const { depth, terminal } = drawnAt();
|
|
177
|
-
return ensureContrast(asColor(fgHex, "
|
|
177
|
+
return ensureContrast(asColor(fgHex, "ensureContrast"), asColor(bgHex, "ensureContrast"), asAmount(ratio, "ensureContrast", "ratio", RATIO_RANGE), depth, undefined, terminal).hex;
|
|
178
178
|
}));
|
|
179
179
|
// --- OKLCH axes ---
|
|
180
180
|
//
|
|
181
|
-
// [LAW:one-source-of-truth] One function per `
|
|
182
|
-
// keyed BY the axis so the compiler proves it total: add a field to `
|
|
183
|
-
// and this object stops satisfying `Record<keyof
|
|
181
|
+
// [LAW:one-source-of-truth] One function per `ColorAdjustment` axis, and the table is
|
|
182
|
+
// keyed BY the axis so the compiler proves it total: add a field to `ColorAdjustment`
|
|
183
|
+
// and this object stops satisfying `Record<keyof ColorAdjustment, string>` until a
|
|
184
184
|
// binding name is chosen for it. The same derive-registrations-from-the-real-
|
|
185
185
|
// inventory trick `attributeFuncs()` uses over `ATTRIBUTE_NAMES`.
|
|
186
186
|
//
|
|
@@ -218,7 +218,7 @@ function oklchAxisFuncs() {
|
|
|
218
218
|
* | `darken c n` / `lighten c n` | slide HSL lightness by N 10% levels |
|
|
219
219
|
* | `mix a b pct` | blend `a` toward `b` by `pct`% (0 → `a`, 100 → `b`) |
|
|
220
220
|
* | `contrastOn bg` | black or white, whichever is readable on `bg` |
|
|
221
|
-
* | `
|
|
221
|
+
* | `ensureContrast fg bg ratio` | `fg` nudged in OKLCH lightness until it clears `ratio` on `bg`, hue preserved |
|
|
222
222
|
* | `shiftHue c deg` | rotate hue in OKLCH |
|
|
223
223
|
* | `scaleChroma c f` | multiply chroma (0 → gray, 1 → identity) |
|
|
224
224
|
* | `scaleLightness c f` | multiply lightness (1 → identity, -1 → invert) |
|
|
@@ -228,7 +228,7 @@ function oklchAxisFuncs() {
|
|
|
228
228
|
* `richTextStyleFuncs()`'s `fg`/`bg` to paint them onto text.
|
|
229
229
|
*
|
|
230
230
|
* @param drawnAt where the text will be drawn — the depth, and the terminal
|
|
231
|
-
* when known — read on every `
|
|
231
|
+
* when known — read on every `ensureContrast` evaluation so a host that learns
|
|
232
232
|
* its destination per render passes one getter; defaults to truecolor.
|
|
233
233
|
*/
|
|
234
234
|
export function colorFuncs(drawnAt = () => TRUECOLOR) {
|
|
@@ -237,7 +237,7 @@ export function colorFuncs(drawnAt = () => TRUECOLOR) {
|
|
|
237
237
|
lighten: lightenFunc,
|
|
238
238
|
mix: mixFunc,
|
|
239
239
|
contrastOn: contrastOnFunc,
|
|
240
|
-
|
|
240
|
+
ensureContrast: ensureContrastFunc(drawnAt),
|
|
241
241
|
...oklchAxisFuncs(),
|
|
242
242
|
};
|
|
243
243
|
}
|
|
@@ -6,26 +6,28 @@
|
|
|
6
6
|
* functions. The split between the two exported registrations is exactly the
|
|
7
7
|
* split between what needs a theme and what does not:
|
|
8
8
|
*
|
|
9
|
-
* - `richTextFuncs()` — the
|
|
9
|
+
* - `richTextFuncs()` — the color sinks (`fg`/`bg`), the palette-free color
|
|
10
10
|
* math (`darken`, `mix`, `contrastOn`, the OKLCH axes …), text attributes,
|
|
11
11
|
* and `link`. Needs no configuration; safe to register unconditionally. A
|
|
12
|
-
* consumer with no theme system still gets the complete
|
|
12
|
+
* consumer with no theme system still gets the complete color vocabulary by
|
|
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
16
|
* palette exists. [LAW:one-way-deps]
|
|
17
|
-
* - `
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* - `withAnimation(config, frame)` — the engine config with `frames`,
|
|
18
|
+
* `stepped`, `tween`, `loop` and `glyphs` registered, drawn for a moment on
|
|
19
|
+
* a timeline, and the two engine hooks their holds reach the frame through
|
|
20
|
+
* installed beside them. It is the only registration that knows the
|
|
21
|
+
* template is evaluated at a time, and the time and the timeline are the
|
|
22
|
+
* host's to hand in (`./animation-funcs.ts`).
|
|
21
23
|
*
|
|
22
24
|
* `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
|
|
23
|
-
* palette. Consumers that name theme
|
|
25
|
+
* palette. Consumers that name theme colors call `paletteFuncs(getPalette)`
|
|
24
26
|
* and merge it into their own engine config.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
28
|
+
* Colors compose by nesting, like every other function:
|
|
27
29
|
* `{{ fg (darken (color "primary") 2) (bold "x") }}`. There is no second
|
|
28
|
-
* markup grammar and no
|
|
30
|
+
* markup grammar and no color-spec mini-language — the template *is* the
|
|
29
31
|
* composition mechanism.
|
|
30
32
|
*
|
|
31
33
|
* Fragment type: `RichText`. Chosen because it is the library's primary
|
|
@@ -42,14 +44,14 @@ import { Segment } from "../core/segment.js";
|
|
|
42
44
|
import type { RenderOptions } from "../core/protocol.js";
|
|
43
45
|
import { type DrawnAt } from "./color-funcs.js";
|
|
44
46
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
45
|
-
export { colorFuncs,
|
|
46
|
-
export {
|
|
47
|
+
export { colorFuncs, ensureContrastFunc, type DrawnAt } from "./color-funcs.js";
|
|
48
|
+
export { withAnimation, templateFrame, type AnimationOptions, type TemplateFrame } from "./animation-funcs.js";
|
|
47
49
|
/**
|
|
48
|
-
* Funcs registered by the rich-js binding — the
|
|
49
|
-
*
|
|
50
|
+
* Funcs registered by the rich-js binding — the color sinks, the palette-free
|
|
51
|
+
* color math, text attributes, and the `link` cell-splitter. Nothing here
|
|
50
52
|
* needs a theme; the two that do (`color`, `ramp`) ship separately via
|
|
51
53
|
* `paletteFuncs(getPalette)`, merged consumer-side. `drawnAt` is forwarded to
|
|
52
|
-
* `colorFuncs` — the depth `
|
|
54
|
+
* `colorFuncs` — the depth `ensureContrast` measures at, truecolor when omitted.
|
|
53
55
|
*
|
|
54
56
|
* `FuncMap` is not parameterised over `T` in `@promptctl/go-template-js` — the engine's
|
|
55
57
|
* `T` lives on the `Engine`/`EngineConfig`, and per-function input/output
|
|
@@ -6,26 +6,28 @@
|
|
|
6
6
|
* functions. The split between the two exported registrations is exactly the
|
|
7
7
|
* split between what needs a theme and what does not:
|
|
8
8
|
*
|
|
9
|
-
* - `richTextFuncs()` — the
|
|
9
|
+
* - `richTextFuncs()` — the color sinks (`fg`/`bg`), the palette-free color
|
|
10
10
|
* math (`darken`, `mix`, `contrastOn`, the OKLCH axes …), text attributes,
|
|
11
11
|
* and `link`. Needs no configuration; safe to register unconditionally. A
|
|
12
|
-
* consumer with no theme system still gets the complete
|
|
12
|
+
* consumer with no theme system still gets the complete color vocabulary by
|
|
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
16
|
* palette exists. [LAW:one-way-deps]
|
|
17
|
-
* - `
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* - `withAnimation(config, frame)` — the engine config with `frames`,
|
|
18
|
+
* `stepped`, `tween`, `loop` and `glyphs` registered, drawn for a moment on
|
|
19
|
+
* a timeline, and the two engine hooks their holds reach the frame through
|
|
20
|
+
* installed beside them. It is the only registration that knows the
|
|
21
|
+
* template is evaluated at a time, and the time and the timeline are the
|
|
22
|
+
* host's to hand in (`./animation-funcs.ts`).
|
|
21
23
|
*
|
|
22
24
|
* `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
|
|
23
|
-
* palette. Consumers that name theme
|
|
25
|
+
* palette. Consumers that name theme colors call `paletteFuncs(getPalette)`
|
|
24
26
|
* and merge it into their own engine config.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
28
|
+
* Colors compose by nesting, like every other function:
|
|
27
29
|
* `{{ fg (darken (color "primary") 2) (bold "x") }}`. There is no second
|
|
28
|
-
* markup grammar and no
|
|
30
|
+
* markup grammar and no color-spec mini-language — the template *is* the
|
|
29
31
|
* composition mechanism.
|
|
30
32
|
*
|
|
31
33
|
* Fragment type: `RichText`. Chosen because it is the library's primary
|
|
@@ -44,14 +46,14 @@ import { render } from "../core/protocol.js";
|
|
|
44
46
|
import { richTextStyleFuncs } from "./style-funcs.js";
|
|
45
47
|
import { colorFuncs } from "./color-funcs.js";
|
|
46
48
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
47
|
-
export { colorFuncs,
|
|
48
|
-
export {
|
|
49
|
+
export { colorFuncs, ensureContrastFunc } from "./color-funcs.js";
|
|
50
|
+
export { withAnimation, templateFrame } from "./animation-funcs.js";
|
|
49
51
|
/**
|
|
50
|
-
* Funcs registered by the rich-js binding — the
|
|
51
|
-
*
|
|
52
|
+
* Funcs registered by the rich-js binding — the color sinks, the palette-free
|
|
53
|
+
* color math, text attributes, and the `link` cell-splitter. Nothing here
|
|
52
54
|
* needs a theme; the two that do (`color`, `ramp`) ship separately via
|
|
53
55
|
* `paletteFuncs(getPalette)`, merged consumer-side. `drawnAt` is forwarded to
|
|
54
|
-
* `colorFuncs` — the depth `
|
|
56
|
+
* `colorFuncs` — the depth `ensureContrast` measures at, truecolor when omitted.
|
|
55
57
|
*
|
|
56
58
|
* `FuncMap` is not parameterised over `T` in `@promptctl/go-template-js` — the engine's
|
|
57
59
|
* `T` lives on the `Engine`/`EngineConfig`, and per-function input/output
|
|
@@ -84,6 +86,7 @@ export function richTextFuncs(drawnAt) {
|
|
|
84
86
|
export function createRichTextEngine() {
|
|
85
87
|
return createEngine({
|
|
86
88
|
fromString: (s) => new RichText(s),
|
|
89
|
+
isT: (v) => v instanceof RichText,
|
|
87
90
|
toString: (rt) => rt.plain,
|
|
88
91
|
funcs: richTextFuncs(),
|
|
89
92
|
});
|
|
@@ -69,7 +69,7 @@ import type { Palette } from "../themes/palette.js";
|
|
|
69
69
|
*
|
|
70
70
|
* `color` resolves a palette variable name to its hex string — `#RRGGBB`, or
|
|
71
71
|
* `#00000000` for a theme's "no background", which `bg` paints as no
|
|
72
|
-
* background and the
|
|
72
|
+
* background and the color functions refuse — and passes
|
|
73
73
|
* an already-literal color through unchanged. That second half is not a
|
|
74
74
|
* convenience — it makes `color` **idempotent**, which is what lets consumers
|
|
75
75
|
* apply it unconditionally to any author-written color string without first
|
|
@@ -83,7 +83,7 @@ import type { Palette } from "../themes/palette.js";
|
|
|
83
83
|
* @example
|
|
84
84
|
* ```ts
|
|
85
85
|
* const engine = createEngine({
|
|
86
|
-
* fromString: (s) => new RichText(s),
|
|
86
|
+
* fromString: (s) => new RichText(s), isT: (v) => v instanceof RichText,
|
|
87
87
|
* toString: (rt) => rt.plain,
|
|
88
88
|
* funcs: {
|
|
89
89
|
* ...richTextFuncs(),
|