@promptctl/rich-js 0.13.0 → 0.14.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/dist/core/color.d.ts +8 -0
- package/dist/core/color.js +8 -0
- package/dist/core/strip.js +2 -2
- package/dist/core/style.d.ts +1 -2
- package/dist/core/style.js +3 -6
- package/dist/themes/colorMath.d.ts +9 -7
- package/dist/themes/colorMath.js +34 -11
- package/package.json +1 -1
package/dist/core/color.d.ts
CHANGED
|
@@ -31,6 +31,14 @@ export declare class ColorRgba {
|
|
|
31
31
|
*/
|
|
32
32
|
compositeOver(bg: ColorRgba): ColorRgba;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The surface a terminal draws a translucent colour over. A terminal cannot
|
|
36
|
+
* know what lies under its cells, so the SGR writer (`Style.toSgrCodes`), the
|
|
37
|
+
* strip's seam test and the contrast choosers (`themes/colorMath`) all
|
|
38
|
+
* composite over this one colour. [LAW:one-source-of-truth] One constant, so
|
|
39
|
+
* the colour text is chosen against is the colour the writer draws.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SURFACE_BLACK: ColorRgba;
|
|
34
42
|
/**
|
|
35
43
|
* WCAG 2.x relative luminance (0..1) of an opaque color. The single
|
|
36
44
|
* luminance function in the codebase — `contrastFor`, `contrastRatio`, and
|
package/dist/core/color.js
CHANGED
|
@@ -65,6 +65,14 @@ export class ColorRgba {
|
|
|
65
65
|
return new ColorRgba(Math.round(bg.red + (this.red - bg.red) * t), Math.round(bg.green + (this.green - bg.green) * t), Math.round(bg.blue + (this.blue - bg.blue) * t), 1);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The surface a terminal draws a translucent colour over. A terminal cannot
|
|
70
|
+
* know what lies under its cells, so the SGR writer (`Style.toSgrCodes`), the
|
|
71
|
+
* strip's seam test and the contrast choosers (`themes/colorMath`) all
|
|
72
|
+
* composite over this one colour. [LAW:one-source-of-truth] One constant, so
|
|
73
|
+
* the colour text is chosen against is the colour the writer draws.
|
|
74
|
+
*/
|
|
75
|
+
export const SURFACE_BLACK = new ColorRgba(0, 0, 0);
|
|
68
76
|
/**
|
|
69
77
|
* WCAG 2.x relative luminance (0..1) of an opaque color. The single
|
|
70
78
|
* luminance function in the codebase — `contrastFor`, `contrastRatio`, and
|
package/dist/core/strip.js
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* styling, so the cell type does too.
|
|
27
27
|
*/
|
|
28
28
|
import { Segment } from "./segment.js";
|
|
29
|
-
import { Style
|
|
30
|
-
import { ColorSpec, blendRgb } from "./color.js";
|
|
29
|
+
import { Style } from "./style.js";
|
|
30
|
+
import { ColorSpec, SURFACE_BLACK, blendRgb } from "./color.js";
|
|
31
31
|
import { Oklch } from "./oklch.js";
|
|
32
32
|
// --- Strip ---
|
|
33
33
|
export class Strip {
|
package/dist/core/style.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Immutable style descriptors — colors, text attributes, links, metadata.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
export declare const SURFACE_BLACK: ColorRgba;
|
|
4
|
+
import { ColorSpec, ColorDepth } from "./color.js";
|
|
6
5
|
/**
|
|
7
6
|
* Canonical text-attribute inventory. Single source of truth consumed by
|
|
8
7
|
* `Style.parse` / `Style.toString` and the template bindings — adding an
|
package/dist/core/style.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Immutable style descriptors — colors, text attributes, links, metadata.
|
|
3
3
|
*/
|
|
4
|
-
import { COLOR_NAMES,
|
|
4
|
+
import { COLOR_NAMES, ColorSpec, SURFACE_BLACK, } from "./color.js";
|
|
5
5
|
import { OSC8_CLOSE, osc8Open } from "./osc8.js";
|
|
6
6
|
// [LAW:one-way-deps] `core/style` depends only on `core/color` and the leaf
|
|
7
|
-
// `core/osc8` (the link wire grammar). The substrate
|
|
8
|
-
//
|
|
9
|
-
// theme constants. Preset themes live in `src/themes/` and depend on core,
|
|
10
|
-
// never the reverse.
|
|
11
|
-
export const SURFACE_BLACK = new ColorRgba(0, 0, 0);
|
|
7
|
+
// `core/osc8` (the link wire grammar). The substrate a translucent colour is
|
|
8
|
+
// flattened over is `SURFACE_BLACK`, defined beside `compositeOver`.
|
|
12
9
|
// --- Attribute definitions ---
|
|
13
10
|
/**
|
|
14
11
|
* Canonical text-attribute inventory. Single source of truth consumed by
|
|
@@ -17,9 +17,10 @@ export declare function alphaBlend(fg: ColorRgba, bg: ColorRgba, alpha: number):
|
|
|
17
17
|
/**
|
|
18
18
|
* Pick a contrasting foreground (black or white) for a background, using the
|
|
19
19
|
* WCAG relative-luminance threshold of 0.179 (the perceptually correct cutoff
|
|
20
|
-
* where black and white are equally readable).
|
|
20
|
+
* where black and white are equally readable). A translucent `bg` is judged
|
|
21
|
+
* as drawn: composited over `substrate` (see `drawnBackground`).
|
|
21
22
|
*/
|
|
22
|
-
export declare function contrastFor(bg: ColorRgba): ColorRgba;
|
|
23
|
+
export declare function contrastFor(bg: ColorRgba, substrate?: ColorRgba): ColorRgba;
|
|
23
24
|
export { relativeLuminance, contrastRatio };
|
|
24
25
|
/**
|
|
25
26
|
* Return a foreground guaranteed to clear `minRatio` against `bg`, keeping the
|
|
@@ -32,10 +33,11 @@ export { relativeLuminance, contrastRatio };
|
|
|
32
33
|
* background where even pure black-or-white tops out below the target) does it
|
|
33
34
|
* fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
|
|
34
35
|
*
|
|
35
|
-
* A translucent `
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* A translucent `bg` is measured as it is drawn — composited over
|
|
37
|
+
* `substrate`, the SGR writer's black by default — and a translucent `fg` is
|
|
38
|
+
* then flattened over that drawn background, the order the writer composites
|
|
39
|
+
* in, so the ratio is measured on what the eye actually sees and the returned
|
|
40
|
+
* color is opaque.
|
|
39
41
|
*
|
|
40
42
|
* `drawnAt` is the depth the terminal will draw the pair at. At 256 colours
|
|
41
43
|
* the terminal rounds text and background independently, and two roundings
|
|
@@ -51,4 +53,4 @@ export { relativeLuminance, contrastRatio };
|
|
|
51
53
|
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
52
54
|
*/
|
|
53
55
|
export declare function ensureContrast(fg: ColorRgba, bg: ColorRgba, minRatio?: number, // WCAG AA for normal text
|
|
54
|
-
drawnAt?: ColorDepth): ColorRgba;
|
|
56
|
+
drawnAt?: ColorDepth, substrate?: ColorRgba): ColorRgba;
|
package/dist/themes/colorMath.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColorDepth, ColorRgba, EIGHT_BIT_DOWNGRADE_TABLE, blendRgb, contrastRatio, relativeLuminance, } from "../core/color.js";
|
|
1
|
+
import { ColorDepth, ColorRgba, EIGHT_BIT_DOWNGRADE_TABLE, blendRgb, contrastRatio, relativeLuminance, SURFACE_BLACK, } from "../core/color.js";
|
|
2
2
|
import { Oklch } from "../core/oklch.js";
|
|
3
3
|
const LEVEL_STEP = 0.1;
|
|
4
4
|
function rgbToHsl(c) {
|
|
@@ -82,10 +82,11 @@ export function alphaBlend(fg, bg, alpha) {
|
|
|
82
82
|
/**
|
|
83
83
|
* Pick a contrasting foreground (black or white) for a background, using the
|
|
84
84
|
* WCAG relative-luminance threshold of 0.179 (the perceptually correct cutoff
|
|
85
|
-
* where black and white are equally readable).
|
|
85
|
+
* where black and white are equally readable). A translucent `bg` is judged
|
|
86
|
+
* as drawn: composited over `substrate` (see `drawnBackground`).
|
|
86
87
|
*/
|
|
87
|
-
export function contrastFor(bg) {
|
|
88
|
-
const lum = relativeLuminance(bg);
|
|
88
|
+
export function contrastFor(bg, substrate = SURFACE_BLACK) {
|
|
89
|
+
const lum = relativeLuminance(drawnBackground(bg, substrate));
|
|
89
90
|
return lum > 0.179
|
|
90
91
|
? new ColorRgba(0, 0, 0)
|
|
91
92
|
: new ColorRgba(255, 255, 255);
|
|
@@ -109,10 +110,11 @@ const CONTRAST_ITERS = 20;
|
|
|
109
110
|
* background where even pure black-or-white tops out below the target) does it
|
|
110
111
|
* fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
|
|
111
112
|
*
|
|
112
|
-
* A translucent `
|
|
113
|
-
* `
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
* A translucent `bg` is measured as it is drawn — composited over
|
|
114
|
+
* `substrate`, the SGR writer's black by default — and a translucent `fg` is
|
|
115
|
+
* then flattened over that drawn background, the order the writer composites
|
|
116
|
+
* in, so the ratio is measured on what the eye actually sees and the returned
|
|
117
|
+
* color is opaque.
|
|
116
118
|
*
|
|
117
119
|
* `drawnAt` is the depth the terminal will draw the pair at. At 256 colours
|
|
118
120
|
* the terminal rounds text and background independently, and two roundings
|
|
@@ -128,19 +130,40 @@ const CONTRAST_ITERS = 20;
|
|
|
128
130
|
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
129
131
|
*/
|
|
130
132
|
export function ensureContrast(fg, bg, minRatio = 4.5, // WCAG AA for normal text
|
|
131
|
-
drawnAt = ColorDepth.TRUECOLOR) {
|
|
132
|
-
const
|
|
133
|
+
drawnAt = ColorDepth.TRUECOLOR, substrate = SURFACE_BLACK) {
|
|
134
|
+
const ground = drawnBackground(bg, substrate);
|
|
135
|
+
const chosen = ensureTruecolorContrast(fg, ground, minRatio);
|
|
133
136
|
// [LAW:dataflow-not-control-flow] The depth names the table the terminal
|
|
134
137
|
// draws from; only one whose entries have a known RGB can be measured.
|
|
135
138
|
const table = MEASURABLE_DOWNGRADE[drawnAt];
|
|
136
139
|
if (table === undefined)
|
|
137
140
|
return chosen;
|
|
138
|
-
const drawnBg = table.get(table.match(
|
|
141
|
+
const drawnBg = table.get(table.match(ground));
|
|
139
142
|
const drawn = table.get(table.match(chosen));
|
|
140
143
|
if (contrastRatio(drawn, drawnBg) >= minRatio)
|
|
141
144
|
return chosen;
|
|
142
145
|
return table.get(table.matchReadable(chosen, drawnBg, minRatio));
|
|
143
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* A background as it is drawn: composited over the surface beneath it. That
|
|
149
|
+
* surface is a fact about where the pair is drawn, so it arrives as a value:
|
|
150
|
+
* the SGR writer (`Style.toSgrCodes`) composites over `SURFACE_BLACK`, the
|
|
151
|
+
* default here; a caller choosing text for a different surface — an export's
|
|
152
|
+
* canvas, `exportCanvas(theme).background` — names that one.
|
|
153
|
+
* [LAW:no-silent-failure] A surface has nothing under it, so a translucent one
|
|
154
|
+
* has no drawn colour to offer; `compositeOver` would read its raw RGB as if
|
|
155
|
+
* it were opaque, so it is refused here rather than measured wrong.
|
|
156
|
+
* [LAW:one-source-of-truth] Text is chosen against the colour the surface will
|
|
157
|
+
* show — measuring the raw RGBA reads a colour that is drawn nowhere, and text
|
|
158
|
+
* that "clears" it can land below the floor. Opaque colours composite to
|
|
159
|
+
* themselves.
|
|
160
|
+
*/
|
|
161
|
+
function drawnBackground(bg, substrate) {
|
|
162
|
+
if (substrate.alpha !== 1) {
|
|
163
|
+
throw new RangeError(`a contrast substrate is the opaque surface under a translucent background; got ${substrate.hex}`);
|
|
164
|
+
}
|
|
165
|
+
return bg.compositeOver(substrate);
|
|
166
|
+
}
|
|
144
167
|
/**
|
|
145
168
|
* The downgrade tables whose entries the terminal draws at a known RGB, by the
|
|
146
169
|
* depth that draws from them. 256 colours is the one: its cube and grey ramp
|