@promptctl/rich-js 0.18.1 → 0.19.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 +9 -7
- package/dist/core/box.d.ts +3 -1
- package/dist/core/box.js +6 -13
- package/dist/core/cells.d.ts +61 -28
- package/dist/core/cells.js +171 -124
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +98 -18
- package/dist/core/color.js +331 -144
- package/dist/core/console.d.ts +38 -7
- package/dist/core/console.js +159 -90
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.js +1 -4
- package/dist/core/export-lines.d.ts +22 -13
- package/dist/core/export-lines.js +37 -19
- package/dist/core/export-svg.d.ts +49 -0
- package/dist/core/export-svg.js +213 -0
- package/dist/core/fnv1a.d.ts +10 -0
- package/dist/core/fnv1a.js +17 -0
- package/dist/core/highlighter.d.ts +2 -2
- package/dist/core/highlighter.js +113 -47
- package/dist/core/json.js +1 -1
- package/dist/core/markup.d.ts +28 -7
- package/dist/core/markup.js +235 -312
- package/dist/core/measure.d.ts +3 -3
- package/dist/core/measure.js +12 -7
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/osc8.d.ts +6 -6
- package/dist/core/osc8.js +9 -21
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +150 -0
- package/dist/core/place.d.ts +1 -1
- package/dist/core/place.js +4 -6
- package/dist/core/pretty.d.ts +105 -2
- package/dist/core/pretty.js +527 -97
- package/dist/core/protocol.d.ts +41 -3
- package/dist/core/protocol.js +20 -0
- package/dist/core/render.d.ts +33 -30
- package/dist/core/render.js +43 -83
- package/dist/core/segment.d.ts +6 -5
- package/dist/core/segment.js +11 -15
- package/dist/core/strip.d.ts +13 -2
- package/dist/core/strip.js +44 -23
- package/dist/core/style.d.ts +27 -7
- package/dist/core/style.js +97 -34
- package/dist/core/text.d.ts +43 -16
- package/dist/core/text.js +180 -59
- package/dist/core/wrap.d.ts +5 -0
- package/dist/core/wrap.js +16 -3
- package/dist/host/app.d.ts +31 -19
- package/dist/host/app.js +90 -60
- package/dist/host/index.d.ts +2 -1
- package/dist/host/terminal-host.d.ts +3 -2
- package/dist/index.d.ts +17 -10
- package/dist/index.js +11 -4
- package/dist/node/prompt.d.ts +13 -0
- package/dist/node/prompt.js +30 -9
- package/dist/node/save.d.ts +4 -3
- package/dist/node/save.js +6 -3
- package/dist/node/terminal-host.d.ts +3 -2
- package/dist/node/traceback.d.ts +2 -1
- package/dist/node/traceback.js +3 -39
- package/dist/renderables/align.js +2 -9
- package/dist/renderables/columns.d.ts +45 -29
- package/dist/renderables/columns.js +142 -103
- package/dist/renderables/constrain.js +6 -12
- package/dist/renderables/effect.d.ts +98 -0
- package/dist/renderables/effect.js +160 -0
- package/dist/renderables/embed.d.ts +87 -30
- package/dist/renderables/embed.js +134 -45
- package/dist/renderables/group.d.ts +6 -3
- package/dist/renderables/group.js +5 -0
- package/dist/renderables/layout.d.ts +6 -23
- package/dist/renderables/layout.js +24 -113
- package/dist/renderables/live.d.ts +40 -16
- package/dist/renderables/live.js +96 -83
- package/dist/renderables/markdown.d.ts +16 -2
- package/dist/renderables/markdown.js +521 -118
- package/dist/renderables/padding.d.ts +22 -1
- package/dist/renderables/padding.js +30 -24
- package/dist/renderables/panel.d.ts +31 -33
- package/dist/renderables/panel.js +137 -141
- package/dist/renderables/progress.d.ts +104 -16
- package/dist/renderables/progress.js +205 -81
- package/dist/renderables/progressBar.d.ts +2 -2
- package/dist/renderables/progressBar.js +30 -19
- package/dist/renderables/prompt.d.ts +37 -15
- package/dist/renderables/prompt.js +125 -85
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +1 -0
- package/dist/renderables/rule.js +44 -48
- package/dist/renderables/spinner.d.ts +28 -2
- package/dist/renderables/spinner.js +52 -19
- package/dist/renderables/status.d.ts +13 -7
- package/dist/renderables/status.js +15 -29
- package/dist/renderables/syntax.d.ts +37 -7
- package/dist/renderables/syntax.js +134 -83
- package/dist/renderables/table.d.ts +58 -20
- package/dist/renderables/table.js +370 -192
- package/dist/renderables/traceback.d.ts +5 -4
- package/dist/renderables/traceback.js +239 -68
- package/dist/renderables/tree.d.ts +10 -2
- package/dist/renderables/tree.js +61 -34
- package/dist/renderables/viewport.js +2 -4
- package/dist/template-bindings/color-funcs.d.ts +31 -9
- package/dist/template-bindings/color-funcs.js +40 -14
- package/dist/template-bindings/helpers.d.ts +4 -2
- package/dist/template-bindings/helpers.js +5 -20
- package/dist/template-bindings/index.d.ts +29 -17
- package/dist/template-bindings/index.js +36 -20
- package/dist/template-bindings/palette-funcs.d.ts +3 -1
- package/dist/template-bindings/palette-funcs.js +11 -7
- package/dist/template-bindings/style-funcs.js +4 -2
- package/dist/themes/buildPalette.d.ts +24 -3
- package/dist/themes/buildPalette.js +52 -8
- package/dist/themes/colorMath.d.ts +29 -17
- package/dist/themes/colorMath.js +74 -48
- package/dist/themes/colorRef.d.ts +2 -17
- package/dist/themes/colorRef.js +13 -31
- package/dist/themes/palette.d.ts +24 -0
- package/dist/themes/palette.js +46 -3
- package/dist/themes/ramp.d.ts +21 -34
- package/dist/themes/ramp.js +23 -42
- package/dist/themes/registry.js +39 -28
- package/dist/widgets/button.d.ts +2 -6
- package/dist/widgets/button.js +9 -40
- package/dist/widgets/checkbox.d.ts +2 -5
- package/dist/widgets/checkbox.js +9 -31
- package/dist/widgets/dropdown.d.ts +2 -5
- package/dist/widgets/dropdown.js +14 -48
- package/dist/widgets/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +2 -5
- package/dist/widgets/slider.js +11 -34
- package/dist/widgets/text-input.d.ts +19 -10
- package/dist/widgets/text-input.js +156 -147
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +2 -5
- package/dist/widgets/toggle.js +8 -31
- package/dist/widgets/widget-app.d.ts +2 -0
- package/dist/widgets/widget-app.js +13 -6
- package/dist/widgets/widget-base.d.ts +2 -0
- package/dist/widgets/widget-base.js +9 -2
- package/package.json +11 -1
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* ### The carrier is a hex string, deliberately
|
|
28
28
|
*
|
|
29
|
-
* A color crosses the template seam as `#RRGGBB
|
|
29
|
+
* A color crosses the template seam as `#RRGGBB` (or `#RRGGBBAA`), not as an opaque object,
|
|
30
30
|
* for three reasons:
|
|
31
31
|
*
|
|
32
32
|
* 1. **The type separates.** `"string"` is the strictest arg slot the template
|
|
@@ -55,9 +55,8 @@
|
|
|
55
55
|
* from that point on the math is pure. A consumer with no theme system at all still gets
|
|
56
56
|
* the full color vocabulary by feeding it hex literals. [LAW:one-way-deps]
|
|
57
57
|
*/
|
|
58
|
-
import { blendRgb, ColorDepth } from "../core/color.js";
|
|
58
|
+
import { blendRgb, ColorDepth, ColorParseError, parseHexColor } from "../core/color.js";
|
|
59
59
|
import { Oklch, IDENTITY } from "../core/oklch.js";
|
|
60
|
-
import { HEX_COLOR_RE, parseHexColor } from "../themes/colorRef.js";
|
|
61
60
|
import { darken, contrastFor, ensureContrast } from "../themes/colorMath.js";
|
|
62
61
|
/**
|
|
63
62
|
* The gate every color argument crosses. [LAW:parse-dont-validate] — the
|
|
@@ -67,15 +66,37 @@ import { darken, contrastFor, ensureContrast } from "../themes/colorMath.js";
|
|
|
67
66
|
* its error *wording*, because the mistake it catches is almost always the
|
|
68
67
|
* same one — a palette name passed where a color belongs (`darken "primary" 2`)
|
|
69
68
|
* — and an author who reads "wrap it: darken (color "primary")" is corrected in
|
|
70
|
-
* one step, where "expected #RRGGBB" leaves them guessing.
|
|
69
|
+
* one step, where "expected #RRGGBB" leaves them guessing. What it parses
|
|
70
|
+
* must then be a colour these functions can compute with (`computable`).
|
|
71
71
|
* [LAW:no-silent-failure]
|
|
72
72
|
*/
|
|
73
73
|
function asColor(value, func) {
|
|
74
|
-
|
|
74
|
+
let colour;
|
|
75
|
+
try {
|
|
76
|
+
colour = parseHexColor(value.trim());
|
|
77
|
+
}
|
|
78
|
+
catch (cause) {
|
|
79
|
+
if (!(cause instanceof ColorParseError))
|
|
80
|
+
throw cause;
|
|
75
81
|
throw new TypeError(`${func} expected a color (#RRGGBB or #RRGGBBAA), got ${JSON.stringify(value)}` +
|
|
76
|
-
` — to use a palette name here, wrap it: ${func} (color ${JSON.stringify(value)})
|
|
82
|
+
` — to use a palette name here, wrap it: ${func} (color ${JSON.stringify(value)}) …`, { cause });
|
|
83
|
+
}
|
|
84
|
+
return computable(colour, func, JSON.stringify(value));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A colour the colour functions can compute with. A fully transparent one — a
|
|
88
|
+
* theme's "no background", `#00000000` — has no colour of its own: what shows
|
|
89
|
+
* through it is whatever surface it lands on, which a template cannot know.
|
|
90
|
+
* Darkening, mixing or contrasting it would answer for a surface nobody named,
|
|
91
|
+
* so it is refused here, for every colour function and every `ramp` stop.
|
|
92
|
+
* [LAW:single-enforcer]
|
|
93
|
+
*/
|
|
94
|
+
export function computable(colour, func, written) {
|
|
95
|
+
if (colour.alpha === 0) {
|
|
96
|
+
throw new RangeError(`${func} got ${written}, which is fully transparent: it draws no colour of its own, ` +
|
|
97
|
+
`so there is nothing to compute with — use the colour of the surface it sits on, e.g. (color "background")`);
|
|
77
98
|
}
|
|
78
|
-
return
|
|
99
|
+
return colour;
|
|
79
100
|
}
|
|
80
101
|
// [LAW:single-enforcer] Every colour function is fixed-arity, so the engine's
|
|
81
102
|
// arity gate refuses a missing or surplus argument before the body runs.
|
|
@@ -145,11 +166,16 @@ const RATIO_RANGE = {
|
|
|
145
166
|
note: "4.5 = AA body text, 3 = AA large text",
|
|
146
167
|
};
|
|
147
168
|
const contrastOnFunc = colorFunc(["string"], ((bgHex) => contrastFor(asColor(bgHex, "contrastOn")).hex));
|
|
169
|
+
const TRUECOLOR = { depth: ColorDepth.TRUECOLOR };
|
|
148
170
|
/**
|
|
149
|
-
* The `readableOn` binding, measuring
|
|
150
|
-
* evaluated — a consumer that renders
|
|
171
|
+
* The `readableOn` binding, measuring where `drawnAt` says the text lands when
|
|
172
|
+
* it is evaluated — a consumer that renders to a per-call destination
|
|
173
|
+
* registers its own.
|
|
151
174
|
*/
|
|
152
|
-
export const readableOnFunc = (drawnAt) => colorFunc(["string", "string", "float"], ((fgHex, bgHex, ratio) =>
|
|
175
|
+
export const readableOnFunc = (drawnAt) => colorFunc(["string", "string", "float"], ((fgHex, bgHex, ratio) => {
|
|
176
|
+
const { depth, terminal } = drawnAt();
|
|
177
|
+
return ensureContrast(asColor(fgHex, "readableOn"), asColor(bgHex, "readableOn"), asAmount(ratio, "readableOn", "ratio", RATIO_RANGE), depth, undefined, terminal).hex;
|
|
178
|
+
}));
|
|
153
179
|
// --- OKLCH axes ---
|
|
154
180
|
//
|
|
155
181
|
// [LAW:one-source-of-truth] One function per `ThemeKey` axis, and the table is
|
|
@@ -201,11 +227,11 @@ function oklchAxisFuncs() {
|
|
|
201
227
|
* Pair with `paletteFuncs()` to name colors from a theme, and with
|
|
202
228
|
* `richTextStyleFuncs()`'s `fg`/`bg` to paint them onto text.
|
|
203
229
|
*
|
|
204
|
-
* @param drawnAt
|
|
205
|
-
* `readableOn` evaluation so a host that learns
|
|
206
|
-
* one getter; defaults to truecolor.
|
|
230
|
+
* @param drawnAt where the text will be drawn — the depth, and the terminal
|
|
231
|
+
* when known — read on every `readableOn` evaluation so a host that learns
|
|
232
|
+
* its destination per render passes one getter; defaults to truecolor.
|
|
207
233
|
*/
|
|
208
|
-
export function colorFuncs(drawnAt = () =>
|
|
234
|
+
export function colorFuncs(drawnAt = () => TRUECOLOR) {
|
|
209
235
|
return {
|
|
210
236
|
darken: darkenFunc,
|
|
211
237
|
lighten: lightenFunc,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* registrations and the palette-function registrations use this same operation;
|
|
7
7
|
* keeping one copy prevents the two from drifting.
|
|
8
8
|
*/
|
|
9
|
-
import { Style } from "../core/style.js";
|
|
9
|
+
import { type Style } from "../core/style.js";
|
|
10
10
|
import { RichText } from "../core/text.js";
|
|
11
11
|
/**
|
|
12
12
|
* Apply a style on top of an already-styled RichText fragment.
|
|
@@ -19,6 +19,8 @@ import { RichText } from "../core/text.js";
|
|
|
19
19
|
* is the binding's own `T`. Misuse (`{{ red someMap }}`) fails loudly here
|
|
20
20
|
* rather than producing a malformed fragment.
|
|
21
21
|
*
|
|
22
|
-
* Conflict resolution: `Style.add` — the outer (newly applied) style wins.
|
|
22
|
+
* Conflict resolution: `Style.add` — the outer (newly applied) style wins. A
|
|
23
|
+
* base style that is a name is layered under, not resolved: a template runs
|
|
24
|
+
* before the render whose theme says what the name stands for.
|
|
23
25
|
*/
|
|
24
26
|
export declare function applyStyleToFragment(child: unknown, style: Style): RichText;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* registrations and the palette-function registrations use this same operation;
|
|
7
7
|
* keeping one copy prevents the two from drifting.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { layerStyle } from "../core/style.js";
|
|
10
10
|
import { RichText } from "../core/text.js";
|
|
11
11
|
/**
|
|
12
12
|
* Apply a style on top of an already-styled RichText fragment.
|
|
@@ -19,30 +19,15 @@ import { RichText } from "../core/text.js";
|
|
|
19
19
|
* is the binding's own `T`. Misuse (`{{ red someMap }}`) fails loudly here
|
|
20
20
|
* rather than producing a malformed fragment.
|
|
21
21
|
*
|
|
22
|
-
* Conflict resolution: `Style.add` — the outer (newly applied) style wins.
|
|
22
|
+
* Conflict resolution: `Style.add` — the outer (newly applied) style wins. A
|
|
23
|
+
* base style that is a name is layered under, not resolved: a template runs
|
|
24
|
+
* before the render whose theme says what the name stands for.
|
|
23
25
|
*/
|
|
24
26
|
export function applyStyleToFragment(child, style) {
|
|
25
27
|
if (!(child instanceof RichText)) {
|
|
26
28
|
throw new TypeError(`template function expected a RichText fragment, got ${typeof child === "object" ? Object.prototype.toString.call(child) : typeof child}`);
|
|
27
29
|
}
|
|
28
30
|
const result = child.copy();
|
|
29
|
-
result.style =
|
|
31
|
+
result.style = layerStyle(child.style, style);
|
|
30
32
|
return result;
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
33
|
-
* A fragment's base style as a `Style`. A string must parse as a definition: a
|
|
34
|
-
* name resolves only against the theme of the render that draws it, and a
|
|
35
|
-
* template runs before any render exists.
|
|
36
|
-
*/
|
|
37
|
-
function baseStyleOf(style) {
|
|
38
|
-
if (style instanceof Style)
|
|
39
|
-
return style;
|
|
40
|
-
try {
|
|
41
|
-
return Style.parse(style);
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
if (!(err instanceof StyleSyntaxError))
|
|
45
|
-
throw err;
|
|
46
|
-
throw new TypeError(`template function cannot style a fragment whose base style "${style}" is not a style definition: a style name resolves only against the theme of a render`, { cause: err });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
import { type Engine, type FuncMap } from "@promptctl/go-template-js";
|
|
37
37
|
import { RichText } from "../core/text.js";
|
|
38
38
|
import { Segment } from "../core/segment.js";
|
|
39
|
-
import {
|
|
39
|
+
import type { RenderOptions } from "../core/protocol.js";
|
|
40
|
+
import { type DrawnAt } from "./color-funcs.js";
|
|
40
41
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
41
|
-
export { colorFuncs, readableOnFunc } from "./color-funcs.js";
|
|
42
|
+
export { colorFuncs, readableOnFunc, type DrawnAt } from "./color-funcs.js";
|
|
42
43
|
/**
|
|
43
44
|
* Funcs registered by the rich-js binding — the colour sinks, the palette-free
|
|
44
45
|
* colour math, text attributes, and the `link` cell-splitter. Nothing here
|
|
@@ -57,7 +58,7 @@ export { colorFuncs, readableOnFunc } from "./color-funcs.js";
|
|
|
57
58
|
* into a wider engine must keep `T = RichText`; merging into an engine
|
|
58
59
|
* whose `T` is something else will compile but fail at evaluation time.
|
|
59
60
|
*/
|
|
60
|
-
export declare function richTextFuncs(drawnAt?: () =>
|
|
61
|
+
export declare function richTextFuncs(drawnAt?: () => DrawnAt): FuncMap;
|
|
61
62
|
/**
|
|
62
63
|
* Construct an `Engine<RichText>` with the rich-js style-function set
|
|
63
64
|
* registered. Consumers that already manage their own engine should call
|
|
@@ -71,6 +72,18 @@ export declare function richTextFuncs(drawnAt?: () => ColorDepth): FuncMap;
|
|
|
71
72
|
* not desired anyway.
|
|
72
73
|
*/
|
|
73
74
|
export declare function createRichTextEngine(): Engine<RichText>;
|
|
75
|
+
export interface RenderTemplateOptions extends Partial<RenderOptions> {
|
|
76
|
+
/**
|
|
77
|
+
* Defaults to 400 — large enough that downstream `splitLines` /
|
|
78
|
+
* `adjustLineLength` clipping decides actual width, matching the typical
|
|
79
|
+
* "render wide, fit on output" pipeline. The error line is fitted to it too,
|
|
80
|
+
* so only a caller that passes the width it draws at sees a cut error end in
|
|
81
|
+
* `…` rather than be cropped by whatever draws it.
|
|
82
|
+
*/
|
|
83
|
+
maxWidth?: number;
|
|
84
|
+
/** The error line's style, a `Style.parse` spec (default `"red dim"`). */
|
|
85
|
+
errorStyle?: string;
|
|
86
|
+
}
|
|
74
87
|
/**
|
|
75
88
|
* Compile a template source against `engine` and render the result to a
|
|
76
89
|
* flat `Segment[]`. The 90%-case convenience over chaining `engine.compile`,
|
|
@@ -79,14 +92,20 @@ export declare function createRichTextEngine(): Engine<RichText>;
|
|
|
79
92
|
* - Runs `engine.compile(source)(scope)` to get the engine's `RichText[]`.
|
|
80
93
|
* - Flattens that fragment list into a single styled `RichText` via
|
|
81
94
|
* `RichText.fromFragments` so every fragment's wrapping style survives.
|
|
82
|
-
* - Renders to a `Segment[]`
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
95
|
+
* - Renders to a `Segment[]` with the given render options — `maxWidth`,
|
|
96
|
+
* `theme`, `onStyleError` and the rest — as a terminal unless they say not.
|
|
97
|
+
* - On a parse/evaluate failure, emits a single dim styled
|
|
98
|
+
* `[error: <message>]` segment, one line fitted to `maxWidth`, the caller
|
|
99
|
+
* can drop into their layout. No bespoke fallback wiring required at every
|
|
86
100
|
* call site.
|
|
87
101
|
*
|
|
102
|
+
* Only compile, evaluate and flatten are inside the `try`: the error line reports a
|
|
103
|
+
* template that failed, nothing else. Whatever the render throws — a style
|
|
104
|
+
* error an `onStyleError` handler rethrew, or a fault in the render itself —
|
|
105
|
+
* leaves this function rather than being folded into an `[error: …]` line.
|
|
106
|
+
*
|
|
88
107
|
* [LAW:single-enforcer] One place owns "render a template to segments,
|
|
89
|
-
* degrade gracefully
|
|
108
|
+
* degrade gracefully when the template fails" — every consumer that wants this exact
|
|
90
109
|
* shape reads from here rather than re-implementing the same try/catch +
|
|
91
110
|
* error-formatting glue.
|
|
92
111
|
*
|
|
@@ -95,13 +114,6 @@ export declare function createRichTextEngine(): Engine<RichText>;
|
|
|
95
114
|
* and use `RichText.fromFragments` to flatten. This helper is sugar for
|
|
96
115
|
* the live-render case (e.g. a preview pane), not a replacement for the
|
|
97
116
|
* compile-once-evaluate-many pattern.
|
|
98
|
-
|
|
99
|
-
* @param maxWidth defaults to 400 — large enough that downstream `splitLines`
|
|
100
|
-
* / `adjustLineLength` clipping decides actual width, matching the typical
|
|
101
|
-
* "render wide, fit on output" pipeline.
|
|
102
|
-
* @param errorStyle is a `Style.parse` spec (default `"red dim"`).
|
|
117
|
+
|
|
103
118
|
*/
|
|
104
|
-
export declare function renderTemplate(engine: Engine<RichText>, source: string, scope?: unknown, options?:
|
|
105
|
-
maxWidth?: number;
|
|
106
|
-
errorStyle?: string;
|
|
107
|
-
}): Segment[];
|
|
119
|
+
export declare function renderTemplate(engine: Engine<RichText>, source: string, scope?: unknown, options?: RenderTemplateOptions): Segment[];
|
|
@@ -91,14 +91,20 @@ export function createRichTextEngine() {
|
|
|
91
91
|
* - Runs `engine.compile(source)(scope)` to get the engine's `RichText[]`.
|
|
92
92
|
* - Flattens that fragment list into a single styled `RichText` via
|
|
93
93
|
* `RichText.fromFragments` so every fragment's wrapping style survives.
|
|
94
|
-
* - Renders to a `Segment[]`
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
94
|
+
* - Renders to a `Segment[]` with the given render options — `maxWidth`,
|
|
95
|
+
* `theme`, `onStyleError` and the rest — as a terminal unless they say not.
|
|
96
|
+
* - On a parse/evaluate failure, emits a single dim styled
|
|
97
|
+
* `[error: <message>]` segment, one line fitted to `maxWidth`, the caller
|
|
98
|
+
* can drop into their layout. No bespoke fallback wiring required at every
|
|
98
99
|
* call site.
|
|
99
100
|
*
|
|
101
|
+
* Only compile, evaluate and flatten are inside the `try`: the error line reports a
|
|
102
|
+
* template that failed, nothing else. Whatever the render throws — a style
|
|
103
|
+
* error an `onStyleError` handler rethrew, or a fault in the render itself —
|
|
104
|
+
* leaves this function rather than being folded into an `[error: …]` line.
|
|
105
|
+
*
|
|
100
106
|
* [LAW:single-enforcer] One place owns "render a template to segments,
|
|
101
|
-
* degrade gracefully
|
|
107
|
+
* degrade gracefully when the template fails" — every consumer that wants this exact
|
|
102
108
|
* shape reads from here rather than re-implementing the same try/catch +
|
|
103
109
|
* error-formatting glue.
|
|
104
110
|
*
|
|
@@ -107,27 +113,37 @@ export function createRichTextEngine() {
|
|
|
107
113
|
* and use `RichText.fromFragments` to flatten. This helper is sugar for
|
|
108
114
|
* the live-render case (e.g. a preview pane), not a replacement for the
|
|
109
115
|
* compile-once-evaluate-many pattern.
|
|
110
|
-
|
|
111
|
-
* @param maxWidth defaults to 400 — large enough that downstream `splitLines`
|
|
112
|
-
* / `adjustLineLength` clipping decides actual width, matching the typical
|
|
113
|
-
* "render wide, fit on output" pipeline.
|
|
114
|
-
* @param errorStyle is a `Style.parse` spec (default `"red dim"`).
|
|
116
|
+
|
|
115
117
|
*/
|
|
116
118
|
export function renderTemplate(engine, source, scope = {}, options) {
|
|
119
|
+
const { errorStyle, ...renderOptions } = options ?? {};
|
|
120
|
+
const maxWidth = renderOptions.maxWidth ?? 400;
|
|
121
|
+
// Flattening belongs to the template: a scope value the engine passes
|
|
122
|
+
// through unlifted fails here, and that is a template that failed.
|
|
123
|
+
let text;
|
|
117
124
|
try {
|
|
118
|
-
|
|
119
|
-
const rt = RichText.fromFragments(frags);
|
|
120
|
-
return Array.from(rt.render({
|
|
121
|
-
maxWidth: options?.maxWidth ?? 400,
|
|
122
|
-
isTerminal: true,
|
|
123
|
-
}));
|
|
125
|
+
text = RichText.fromFragments(engine.compile(source)(scope));
|
|
124
126
|
}
|
|
125
127
|
catch (e) {
|
|
126
|
-
|
|
127
|
-
// multi-line code frame, which would break this single segment across lines.
|
|
128
|
-
const summary = e instanceof Error ? e.message : String(e);
|
|
129
|
-
return [new Segment(`[error: ${summary.slice(0, 80)}]`, safeErrorStyle(options?.errorStyle))];
|
|
128
|
+
return [new Segment(errorLine(e, maxWidth), safeErrorStyle(errorStyle))];
|
|
130
129
|
}
|
|
130
|
+
return Array.from(text.render({ isTerminal: true, ...renderOptions, maxWidth }));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A caught error as the one line `renderTemplate` promises. The summary is the
|
|
134
|
+
* error's `message` — `String(e)` on an engine error appends a multi-line code
|
|
135
|
+
* frame — and even a `message` can carry the character it is complaining about
|
|
136
|
+
* (`bad escape sequence \⏎`). Any whitespace that is not a plain space — a line
|
|
137
|
+
* break, or a tab, whose stop depends on the column the line is set at and so
|
|
138
|
+
* cannot be measured here — folds, with the spaces around it, to one space.
|
|
139
|
+
*
|
|
140
|
+
* [LAW:one-source-of-truth] The line fits the same `maxWidth` the success path
|
|
141
|
+
* draws at, not a width of its own, and a cut ends in an ellipsis so it never
|
|
142
|
+
* reads as the whole message.
|
|
143
|
+
*/
|
|
144
|
+
function errorLine(e, maxWidth) {
|
|
145
|
+
const summary = (e instanceof Error ? e.message : String(e)).replace(/\s*[^\S ]\s*/g, " ");
|
|
146
|
+
return new RichText(`[error: ${summary}]`).truncate(maxWidth).plain;
|
|
131
147
|
}
|
|
132
148
|
// [LAW:single-enforcer] The error-path Style must not itself throw, or
|
|
133
149
|
// the whole "degrade gracefully" promise of renderTemplate is broken. A
|
|
@@ -67,7 +67,9 @@ import type { Palette } from "../themes/palette.js";
|
|
|
67
67
|
/**
|
|
68
68
|
* Register `color "name-or-hex"` against a live palette.
|
|
69
69
|
*
|
|
70
|
-
* `color` resolves a palette variable name to
|
|
70
|
+
* `color` resolves a palette variable name to its hex string — `#RRGGBB`, or
|
|
71
|
+
* `#00000000` for a theme's "no background", which `bg` paints as no
|
|
72
|
+
* background and the colour functions refuse — and passes
|
|
71
73
|
* an already-literal color through unchanged. That second half is not a
|
|
72
74
|
* convenience — it makes `color` **idempotent**, which is what lets consumers
|
|
73
75
|
* apply it unconditionally to any author-written color string without first
|
|
@@ -63,7 +63,9 @@
|
|
|
63
63
|
* they are gone.
|
|
64
64
|
*/
|
|
65
65
|
import { resolveColorRef } from "../themes/colorRef.js";
|
|
66
|
-
import {
|
|
66
|
+
import { parseEase } from "../core/easing.js";
|
|
67
|
+
import { ColorRamp } from "../themes/ramp.js";
|
|
68
|
+
import { computable } from "./color-funcs.js";
|
|
67
69
|
/**
|
|
68
70
|
* The `(position, color-ref)` pairs a `ramp` call's tail spells, as stops.
|
|
69
71
|
*
|
|
@@ -85,7 +87,7 @@ function colorStops(tail, palette) {
|
|
|
85
87
|
for (let i = 0; i < tail.length; i += 2) {
|
|
86
88
|
stops.push({
|
|
87
89
|
at: tail[i],
|
|
88
|
-
color: resolveColorRef(palette, tail[i + 1]),
|
|
90
|
+
color: computable(resolveColorRef(palette, tail[i + 1]), "ramp", JSON.stringify(tail[i + 1])),
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
return stops;
|
|
@@ -93,7 +95,9 @@ function colorStops(tail, palette) {
|
|
|
93
95
|
/**
|
|
94
96
|
* Register `color "name-or-hex"` against a live palette.
|
|
95
97
|
*
|
|
96
|
-
* `color` resolves a palette variable name to
|
|
98
|
+
* `color` resolves a palette variable name to its hex string — `#RRGGBB`, or
|
|
99
|
+
* `#00000000` for a theme's "no background", which `bg` paints as no
|
|
100
|
+
* background and the colour functions refuse — and passes
|
|
97
101
|
* an already-literal color through unchanged. That second half is not a
|
|
98
102
|
* convenience — it makes `color` **idempotent**, which is what lets consumers
|
|
99
103
|
* apply it unconditionally to any author-written color string without first
|
|
@@ -124,14 +128,14 @@ export function paletteFuncs(getPalette) {
|
|
|
124
128
|
arity: { kind: "exact" },
|
|
125
129
|
returnType: "string",
|
|
126
130
|
};
|
|
127
|
-
// `ramp <value> <
|
|
128
|
-
// float/string cycle end to end (value,
|
|
131
|
+
// `ramp <value> <ease> <position> <color> …` — the argument list is one
|
|
132
|
+
// float/string cycle end to end (value, ease, then each stop's position
|
|
129
133
|
// and color), so the engine's `alternating` gate types every slot; the
|
|
130
134
|
// pairing and the references are parsed in `colorStops`. Its minimum is the
|
|
131
|
-
// value, the
|
|
135
|
+
// value, the ease, and one stop, so the gate refuses a stopless call.
|
|
132
136
|
// [LAW:types-are-the-program]
|
|
133
137
|
const rampFunc = {
|
|
134
|
-
fn: ((value,
|
|
138
|
+
fn: ((value, ease, ...tail) => new ColorRamp(parseEase(ease), colorStops(tail, getPalette())).at(value)
|
|
135
139
|
.hex),
|
|
136
140
|
argTypes: ["float", "string"],
|
|
137
141
|
arity: { kind: "alternating", minimum: 4 },
|
|
@@ -113,11 +113,13 @@ const styleSpecFunc = {
|
|
|
113
113
|
// `link` is the cell-splitter for the multi-cell consumer contract.
|
|
114
114
|
// Implementation-wise it is the same shape as any other style function:
|
|
115
115
|
// it sets the `link` slot of `Style` exactly as the existing string-form
|
|
116
|
-
// `link URL` does, so
|
|
116
|
+
// `link URL` does, so `{{ link "u" "x" }}` is byte-equivalent to
|
|
117
117
|
// `RichText("x", { style: Style.parse("link u") })`.
|
|
118
118
|
//
|
|
119
119
|
// The cell-boundary signal that consumers (cc-candybar et al.) walk is
|
|
120
|
-
// `fragment.style.link` being truthy
|
|
120
|
+
// `theme.resolve(fragment.style).link` being truthy: a fragment whose base
|
|
121
|
+
// style is a string keeps the link in a `LayeredStyle` until a theme says
|
|
122
|
+
// what the string stands for. `Style.add` propagates `link`
|
|
121
123
|
// through any outer wrapping call, so `{{ fg "red" (link "u" "x") }}` and
|
|
122
124
|
// `{{ link "u" "x" }}` produce shapes that both qualify as cells from
|
|
123
125
|
// the consumer's perspective. Outer-wins on nested links comes for free
|
|
@@ -14,6 +14,8 @@ export interface BaseColors {
|
|
|
14
14
|
background: ColorRgba;
|
|
15
15
|
foreground: ColorRgba;
|
|
16
16
|
}
|
|
17
|
+
type AccentKey = "primary" | "secondary" | "accent" | "success" | "warning" | "error";
|
|
18
|
+
export declare const ACCENT_KEYS: readonly AccentKey[];
|
|
17
19
|
/**
|
|
18
20
|
* Build a full semantic palette from base colors.
|
|
19
21
|
*
|
|
@@ -25,12 +27,31 @@ export interface BaseColors {
|
|
|
25
27
|
* de-emphasizing body/structural text reaches for
|
|
26
28
|
* `foreground-muted` the same way it reaches for
|
|
27
29
|
* `primary-muted` to de-emphasize an accent.
|
|
28
|
-
* - `text-*` = contrast text tinted 66% with the accent color
|
|
29
|
-
*
|
|
30
|
+
* - `text-*` = contrast text tinted 66% with the accent color, then held
|
|
31
|
+
* to WCAG AA (4.5:1) on `*-muted` — the foreground for a
|
|
32
|
+
* `*-muted` ground
|
|
30
33
|
* - `on-*` = WCAG-correct contrast colour (black or white) for use as
|
|
31
34
|
* foreground when the FULL accent is the background. Picked
|
|
32
35
|
* by relative luminance — single source of truth so widgets
|
|
33
36
|
* never need to invert / reverse fg/bg to get readable text.
|
|
34
37
|
* - `surface` = background blended 5% toward foreground
|
|
38
|
+
*
|
|
39
|
+
* A translucent base colour is drawn onto `background` before anything is
|
|
40
|
+
* derived from it, so `on-*` is chosen against the colour the accent is drawn
|
|
41
|
+
* as and every derived entry is opaque.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildPalette(name: string, dark: boolean, given: BaseColors): Palette;
|
|
44
|
+
/**
|
|
45
|
+
* A palette's `text-*`: its tint held to WCAG AA on `*-muted`, moved toward
|
|
46
|
+
* the palette's own text side so it also reads on `background`, where widgets
|
|
47
|
+
* draw it too. The muted floor is the promise: where no lightness on the text
|
|
48
|
+
* side clears it, the colour that does is on the other side, and the
|
|
49
|
+
* background pair is the one given up. Every bundled palette clears both
|
|
50
|
+
* (test/themes/text-on-muted.test.ts).
|
|
51
|
+
*
|
|
52
|
+
* [LAW:single-enforcer] The one rule for the pair, for the palettes derived
|
|
53
|
+
* here and the authored ones the registry hydrates: the tint is the theme's,
|
|
54
|
+
* whether it reads is decided here.
|
|
35
55
|
*/
|
|
36
|
-
export declare function
|
|
56
|
+
export declare function accentText(tint: ColorRgba, muted: ColorRgba, background: ColorRgba): ColorRgba;
|
|
57
|
+
export {};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { blendRgb } from "../core/color.js";
|
|
2
|
-
import { alphaBlend, contrastFor } from "./colorMath.js";
|
|
3
|
-
import { Palette } from "./palette.js";
|
|
2
|
+
import { alphaBlend, contrastFor, ensureContrastToward } from "./colorMath.js";
|
|
3
|
+
import { Palette, drawnOn } from "./palette.js";
|
|
4
4
|
const MUTED_BLEND = 0.7;
|
|
5
5
|
const TEXT_ALPHA = 0.66;
|
|
6
6
|
const SURFACE_LIFT = 0.05;
|
|
7
|
-
|
|
7
|
+
// WCAG AA for body text: `text-*` is the label drawn on `*-muted`.
|
|
8
|
+
const TEXT_ON_MUTED = 4.5;
|
|
9
|
+
export const ACCENT_KEYS = ["primary", "secondary", "accent", "success", "warning", "error"];
|
|
8
10
|
/**
|
|
9
11
|
* Build a full semantic palette from base colors.
|
|
10
12
|
*
|
|
@@ -16,15 +18,41 @@ const ACCENT_KEYS = ["primary", "secondary", "accent", "success", "warning", "er
|
|
|
16
18
|
* de-emphasizing body/structural text reaches for
|
|
17
19
|
* `foreground-muted` the same way it reaches for
|
|
18
20
|
* `primary-muted` to de-emphasize an accent.
|
|
19
|
-
* - `text-*` = contrast text tinted 66% with the accent color
|
|
20
|
-
*
|
|
21
|
+
* - `text-*` = contrast text tinted 66% with the accent color, then held
|
|
22
|
+
* to WCAG AA (4.5:1) on `*-muted` — the foreground for a
|
|
23
|
+
* `*-muted` ground
|
|
21
24
|
* - `on-*` = WCAG-correct contrast colour (black or white) for use as
|
|
22
25
|
* foreground when the FULL accent is the background. Picked
|
|
23
26
|
* by relative luminance — single source of truth so widgets
|
|
24
27
|
* never need to invert / reverse fg/bg to get readable text.
|
|
25
28
|
* - `surface` = background blended 5% toward foreground
|
|
29
|
+
*
|
|
30
|
+
* A translucent base colour is drawn onto `background` before anything is
|
|
31
|
+
* derived from it, so `on-*` is chosen against the colour the accent is drawn
|
|
32
|
+
* as and every derived entry is opaque.
|
|
26
33
|
*/
|
|
27
|
-
export function buildPalette(name, dark,
|
|
34
|
+
export function buildPalette(name, dark, given) {
|
|
35
|
+
const drawn = drawnOn(name, given.background);
|
|
36
|
+
// Every base colour is derived from, and a fully transparent one draws no
|
|
37
|
+
// colour of its own to derive a muted shade or a contrast from.
|
|
38
|
+
// [LAW:no-silent-failure]
|
|
39
|
+
const draw = (key) => {
|
|
40
|
+
const colour = given[key];
|
|
41
|
+
if (colour.alpha === 0) {
|
|
42
|
+
throw new RangeError(`palette ${JSON.stringify(name)}: ${key} is fully transparent (${colour.hex}), so there is no colour to derive the palette from`);
|
|
43
|
+
}
|
|
44
|
+
return drawn(colour);
|
|
45
|
+
};
|
|
46
|
+
const base = {
|
|
47
|
+
primary: draw("primary"),
|
|
48
|
+
secondary: draw("secondary"),
|
|
49
|
+
accent: draw("accent"),
|
|
50
|
+
success: draw("success"),
|
|
51
|
+
warning: draw("warning"),
|
|
52
|
+
error: draw("error"),
|
|
53
|
+
background: given.background,
|
|
54
|
+
foreground: draw("foreground"),
|
|
55
|
+
};
|
|
28
56
|
const vars = new Map();
|
|
29
57
|
// Base entries
|
|
30
58
|
vars.set("background", base.background);
|
|
@@ -48,9 +76,25 @@ export function buildPalette(name, dark, base) {
|
|
|
48
76
|
const contrastText = contrastFor(base.background);
|
|
49
77
|
for (const key of ACCENT_KEYS) {
|
|
50
78
|
const color = base[key];
|
|
51
|
-
|
|
52
|
-
vars.set(
|
|
79
|
+
const muted = blendRgb(color, base.background, MUTED_BLEND);
|
|
80
|
+
vars.set(`${key}-muted`, muted);
|
|
81
|
+
vars.set(`text-${key}`, accentText(alphaBlend(color, contrastText, TEXT_ALPHA), muted, base.background));
|
|
53
82
|
vars.set(`on-${key}`, contrastFor(color));
|
|
54
83
|
}
|
|
55
84
|
return new Palette(name, dark, vars);
|
|
56
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A palette's `text-*`: its tint held to WCAG AA on `*-muted`, moved toward
|
|
88
|
+
* the palette's own text side so it also reads on `background`, where widgets
|
|
89
|
+
* draw it too. The muted floor is the promise: where no lightness on the text
|
|
90
|
+
* side clears it, the colour that does is on the other side, and the
|
|
91
|
+
* background pair is the one given up. Every bundled palette clears both
|
|
92
|
+
* (test/themes/text-on-muted.test.ts).
|
|
93
|
+
*
|
|
94
|
+
* [LAW:single-enforcer] The one rule for the pair, for the palettes derived
|
|
95
|
+
* here and the authored ones the registry hydrates: the tint is the theme's,
|
|
96
|
+
* whether it reads is decided here.
|
|
97
|
+
*/
|
|
98
|
+
export function accentText(tint, muted, background) {
|
|
99
|
+
return ensureContrastToward(tint, muted, TEXT_ON_MUTED, contrastFor(background));
|
|
100
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColorDepth, ColorRgba, contrastRatio, relativeLuminance } from "../core/color.js";
|
|
1
|
+
import { ColorDepth, ColorRgba, contrastRatio, relativeLuminance, type TerminalTheme } from "../core/color.js";
|
|
2
2
|
/**
|
|
3
3
|
* Darken a color by N levels, where each level reduces HSL lightness by 10%.
|
|
4
4
|
* Negative levels lighten. Level 0 returns an equivalent triplet (after the
|
|
@@ -44,10 +44,11 @@ export { relativeLuminance, contrastRatio };
|
|
|
44
44
|
* can meet in the middle, so the ratio is measured on the drawn pair: a
|
|
45
45
|
* colour that loses the floor there is replaced by the nearest cube/grey entry
|
|
46
46
|
* that clears it (whose own rounding is itself). At ANSI the terminal draws
|
|
47
|
-
* its own theme's
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* each index in its own theme's colour, so the ratio is measured on
|
|
48
|
+
* `terminal`'s sixteen: a theme whose black is light and white dark (Rosé Pine
|
|
49
|
+
* Dawn) turns the nominal table's sides over. A replacement is the colour the
|
|
50
|
+
* writer emits as the index nearest `fg` that clears the floor as `terminal`
|
|
51
|
+
* draws it. Truecolor draws the colour chosen.
|
|
51
52
|
*
|
|
52
53
|
* [LAW:single-enforcer] The one place "is this text readable, and if not fix
|
|
53
54
|
* it" is decided. Callers route every fg/bg pair through here and the
|
|
@@ -56,14 +57,15 @@ export { relativeLuminance, contrastRatio };
|
|
|
56
57
|
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
57
58
|
*/
|
|
58
59
|
export declare function ensureContrast(fg: ColorRgba, bg: ColorRgba, minRatio?: number, // WCAG AA for normal text
|
|
59
|
-
drawnAt?: ColorDepth, substrate?: ColorRgba): ColorRgba;
|
|
60
|
+
drawnAt?: ColorDepth, substrate?: ColorRgba, terminal?: TerminalTheme): ColorRgba;
|
|
60
61
|
/**
|
|
61
62
|
* `chosen` (composited opaque over `substrate`), or — when the depth the
|
|
62
|
-
* terminal draws at rounds it to a colour `accept` refuses — the
|
|
63
|
-
* `accept` takes. `accept` sees the
|
|
64
|
-
*
|
|
65
|
-
* floor once and it holds on the
|
|
66
|
-
* the
|
|
63
|
+
* terminal draws at rounds it to a colour `accept` refuses — the colour the
|
|
64
|
+
* writer emits as the nearest entry `accept` takes. `accept` sees the
|
|
65
|
+
* candidate as drawn, and `drawn`, the same rounding for any other colour it
|
|
66
|
+
* measures against, so the caller states a floor once and it holds on the
|
|
67
|
+
* colours the terminal shows — at ANSI, `terminal`'s own sixteen, as
|
|
68
|
+
* `ensureContrast` measures there. Truecolor
|
|
67
69
|
* draws from no table, so a colour refused there has no replacement;
|
|
68
70
|
* `undefined` means nothing the depth draws is accepted.
|
|
69
71
|
*
|
|
@@ -71,12 +73,22 @@ drawnAt?: ColorDepth, substrate?: ColorRgba): ColorRgba;
|
|
|
71
73
|
* floor that is not text on its background (an open state standing off every
|
|
72
74
|
* closed cell, two planes standing off each other).
|
|
73
75
|
*/
|
|
74
|
-
export declare function ensureDrawn(chosen: ColorRgba, drawnAt: ColorDepth, accept: (candidate: ColorRgba, drawn: (c: ColorRgba) => ColorRgba) => boolean, substrate?: ColorRgba): ColorRgba | undefined;
|
|
76
|
+
export declare function ensureDrawn(chosen: ColorRgba, drawnAt: ColorDepth, accept: (candidate: ColorRgba, drawn: (c: ColorRgba) => ColorRgba) => boolean, substrate?: ColorRgba, terminal?: TerminalTheme): ColorRgba | undefined;
|
|
75
77
|
/**
|
|
76
78
|
* The colour a ground is shown as at `drawnAt`: composited over `substrate`
|
|
77
|
-
* (the SGR writer's black by default),
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* a caller that measures a floor of its own.
|
|
79
|
+
* (the SGR writer's black by default), rounded to the index the writer emits
|
|
80
|
+
* at that depth, and shown as the terminal shows that index — at ANSI, in
|
|
81
|
+
* `terminal`'s own colour. The one account `ensureDrawn` and `ensureContrast`
|
|
82
|
+
* measure by, for a caller that measures a floor of its own.
|
|
81
83
|
*/
|
|
82
|
-
export declare function drawnColour(colour: ColorRgba, drawnAt: ColorDepth, substrate?: ColorRgba): ColorRgba;
|
|
84
|
+
export declare function drawnColour(colour: ColorRgba, drawnAt: ColorDepth, substrate?: ColorRgba, terminal?: TerminalTheme): ColorRgba;
|
|
85
|
+
/**
|
|
86
|
+
* `fg` held to `minRatio` on `bg` in truecolor, moving in lightness toward
|
|
87
|
+
* `toward` (black or white). `ensureContrast` moves toward the pole `bg`
|
|
88
|
+
* itself contrasts with; a colour that already has a side (a palette's
|
|
89
|
+
* `text-*` is its text, tinted) passes that side, since on a ground near the
|
|
90
|
+
* 0.179 cutoff both poles clear and only one also reads on everything else
|
|
91
|
+
* the colour is drawn on. Where `toward` cannot clear `minRatio` on `bg`,
|
|
92
|
+
* the floor wins over the side: the answer is `contrastFor(bg)`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function ensureContrastToward(fg: ColorRgba, bg: ColorRgba, minRatio: number, toward: ColorRgba): ColorRgba;
|