@promptctl/rich-js 0.16.0 → 0.18.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 +14 -27
- package/package.json +7 -3
- package/dist/core/box.d.ts +0 -144
- package/dist/core/box.js +0 -391
- package/dist/core/cells.d.ts +0 -148
- package/dist/core/cells.js +0 -266
- package/dist/core/color.d.ts +0 -229
- package/dist/core/color.js +0 -992
- package/dist/core/console.d.ts +0 -142
- package/dist/core/console.js +0 -477
- package/dist/core/emoji.d.ts +0 -24
- package/dist/core/emoji.js +0 -391
- package/dist/core/export-html.d.ts +0 -24
- package/dist/core/export-html.js +0 -106
- package/dist/core/export-lines.d.ts +0 -121
- package/dist/core/export-lines.js +0 -141
- package/dist/core/highlighter.d.ts +0 -33
- package/dist/core/highlighter.js +0 -124
- package/dist/core/json.d.ts +0 -27
- package/dist/core/json.js +0 -53
- package/dist/core/markup.d.ts +0 -97
- package/dist/core/markup.js +0 -619
- package/dist/core/measure.d.ts +0 -27
- package/dist/core/measure.js +0 -61
- package/dist/core/oklch.d.ts +0 -161
- package/dist/core/oklch.js +0 -312
- package/dist/core/osc8.d.ts +0 -68
- package/dist/core/osc8.js +0 -90
- package/dist/core/pretty.d.ts +0 -141
- package/dist/core/pretty.js +0 -472
- package/dist/core/protocol.d.ts +0 -137
- package/dist/core/protocol.js +0 -101
- package/dist/core/render.d.ts +0 -69
- package/dist/core/render.js +0 -140
- package/dist/core/segment.d.ts +0 -142
- package/dist/core/segment.js +0 -433
- package/dist/core/spinnerData.d.ts +0 -10
- package/dist/core/spinnerData.js +0 -115
- package/dist/core/strip.d.ts +0 -139
- package/dist/core/strip.js +0 -302
- package/dist/core/style.d.ts +0 -149
- package/dist/core/style.js +0 -741
- package/dist/core/subscription.d.ts +0 -12
- package/dist/core/subscription.js +0 -12
- package/dist/core/text.d.ts +0 -255
- package/dist/core/text.js +0 -990
- package/dist/core/wrap.d.ts +0 -41
- package/dist/core/wrap.js +0 -127
- package/dist/host/host-stream.d.ts +0 -30
- package/dist/host/host-stream.js +0 -35
- package/dist/host/index.d.ts +0 -3
- package/dist/host/index.js +0 -2
- package/dist/host/terminal-host.d.ts +0 -143
- package/dist/host/terminal-host.js +0 -132
- package/dist/index.d.ts +0 -87
- package/dist/index.js +0 -104
- package/dist/node/prompt.d.ts +0 -21
- package/dist/node/prompt.js +0 -30
- package/dist/node/save.d.ts +0 -17
- package/dist/node/save.js +0 -21
- package/dist/node/terminal-host.d.ts +0 -84
- package/dist/node/terminal-host.js +0 -160
- package/dist/node/traceback.d.ts +0 -27
- package/dist/node/traceback.js +0 -112
- package/dist/renderables/align.d.ts +0 -16
- package/dist/renderables/align.js +0 -46
- package/dist/renderables/columns.d.ts +0 -61
- package/dist/renderables/columns.js +0 -168
- package/dist/renderables/constrain.d.ts +0 -15
- package/dist/renderables/constrain.js +0 -37
- package/dist/renderables/flexStrip.d.ts +0 -41
- package/dist/renderables/flexStrip.js +0 -200
- package/dist/renderables/group.d.ts +0 -11
- package/dist/renderables/group.js +0 -15
- package/dist/renderables/layout.d.ts +0 -92
- package/dist/renderables/layout.js +0 -315
- package/dist/renderables/live.d.ts +0 -47
- package/dist/renderables/live.js +0 -131
- package/dist/renderables/markdown.d.ts +0 -24
- package/dist/renderables/markdown.js +0 -182
- package/dist/renderables/padding.d.ts +0 -38
- package/dist/renderables/padding.js +0 -140
- package/dist/renderables/panel.d.ts +0 -116
- package/dist/renderables/panel.js +0 -322
- package/dist/renderables/progress.d.ts +0 -90
- package/dist/renderables/progress.js +0 -241
- package/dist/renderables/progressBar.d.ts +0 -31
- package/dist/renderables/progressBar.js +0 -52
- package/dist/renderables/prompt.d.ts +0 -49
- package/dist/renderables/prompt.js +0 -125
- package/dist/renderables/rule.d.ts +0 -24
- package/dist/renderables/rule.js +0 -90
- package/dist/renderables/spinner.d.ts +0 -29
- package/dist/renderables/spinner.js +0 -64
- package/dist/renderables/status.d.ts +0 -23
- package/dist/renderables/status.js +0 -60
- package/dist/renderables/syntax.d.ts +0 -35
- package/dist/renderables/syntax.js +0 -114
- package/dist/renderables/table.d.ts +0 -182
- package/dist/renderables/table.js +0 -697
- package/dist/renderables/traceback.d.ts +0 -31
- package/dist/renderables/traceback.js +0 -109
- package/dist/renderables/tree.d.ts +0 -54
- package/dist/renderables/tree.js +0 -159
- package/dist/template-bindings/color-funcs.d.ts +0 -87
- package/dist/template-bindings/color-funcs.js +0 -219
- package/dist/template-bindings/helpers.d.ts +0 -24
- package/dist/template-bindings/helpers.js +0 -48
- package/dist/template-bindings/index.d.ts +0 -107
- package/dist/template-bindings/index.js +0 -144
- package/dist/template-bindings/palette-funcs.d.ts +0 -94
- package/dist/template-bindings/palette-funcs.js +0 -151
- package/dist/template-bindings/style-funcs.d.ts +0 -28
- package/dist/template-bindings/style-funcs.js +0 -151
- package/dist/themes/buildPalette.d.ts +0 -36
- package/dist/themes/buildPalette.js +0 -56
- package/dist/themes/colorMath.d.ts +0 -82
- package/dist/themes/colorMath.js +0 -254
- package/dist/themes/colorRef.d.ts +0 -51
- package/dist/themes/colorRef.js +0 -96
- package/dist/themes/data/atom-one-dark.d.ts +0 -3
- package/dist/themes/data/atom-one-dark.js +0 -158
- package/dist/themes/data/atom-one-light.d.ts +0 -3
- package/dist/themes/data/atom-one-light.js +0 -158
- package/dist/themes/data/catppuccin-frappe.d.ts +0 -3
- package/dist/themes/data/catppuccin-frappe.js +0 -158
- package/dist/themes/data/catppuccin-latte.d.ts +0 -3
- package/dist/themes/data/catppuccin-latte.js +0 -158
- package/dist/themes/data/catppuccin-macchiato.d.ts +0 -3
- package/dist/themes/data/catppuccin-macchiato.js +0 -158
- package/dist/themes/data/catppuccin-mocha.d.ts +0 -3
- package/dist/themes/data/catppuccin-mocha.js +0 -158
- package/dist/themes/data/cyberpunk.d.ts +0 -17
- package/dist/themes/data/cyberpunk.js +0 -191
- package/dist/themes/data/default.d.ts +0 -3
- package/dist/themes/data/default.js +0 -158
- package/dist/themes/data/dracula.d.ts +0 -3
- package/dist/themes/data/dracula.js +0 -158
- package/dist/themes/data/flexoki.d.ts +0 -3
- package/dist/themes/data/flexoki.js +0 -158
- package/dist/themes/data/gruvbox.d.ts +0 -3
- package/dist/themes/data/gruvbox.js +0 -158
- package/dist/themes/data/index.d.ts +0 -35
- package/dist/themes/data/index.js +0 -64
- package/dist/themes/data/monokai.d.ts +0 -3
- package/dist/themes/data/monokai.js +0 -158
- package/dist/themes/data/nord.d.ts +0 -3
- package/dist/themes/data/nord.js +0 -158
- package/dist/themes/data/rose-pine-dawn.d.ts +0 -3
- package/dist/themes/data/rose-pine-dawn.js +0 -158
- package/dist/themes/data/rose-pine-moon.d.ts +0 -3
- package/dist/themes/data/rose-pine-moon.js +0 -158
- package/dist/themes/data/rose-pine.d.ts +0 -3
- package/dist/themes/data/rose-pine.js +0 -158
- package/dist/themes/data/solarized-dark.d.ts +0 -3
- package/dist/themes/data/solarized-dark.js +0 -158
- package/dist/themes/data/solarized-light.d.ts +0 -3
- package/dist/themes/data/solarized-light.js +0 -158
- package/dist/themes/data/svg-export.d.ts +0 -3
- package/dist/themes/data/svg-export.js +0 -158
- package/dist/themes/data/textual-ansi.d.ts +0 -3
- package/dist/themes/data/textual-ansi.js +0 -158
- package/dist/themes/data/textual-dark.d.ts +0 -3
- package/dist/themes/data/textual-dark.js +0 -158
- package/dist/themes/data/textual-light.d.ts +0 -3
- package/dist/themes/data/textual-light.js +0 -158
- package/dist/themes/data/tokyo-night.d.ts +0 -3
- package/dist/themes/data/tokyo-night.js +0 -158
- package/dist/themes/data/types.d.ts +0 -15
- package/dist/themes/data/types.js +0 -1
- package/dist/themes/palette.d.ts +0 -21
- package/dist/themes/palette.js +0 -28
- package/dist/themes/ramp.d.ts +0 -83
- package/dist/themes/ramp.js +0 -122
- package/dist/themes/registry.d.ts +0 -43
- package/dist/themes/registry.js +0 -82
- package/dist/themes/terminalThemes.d.ts +0 -54
- package/dist/themes/terminalThemes.js +0 -68
- package/dist/themes/transpose.d.ts +0 -87
- package/dist/themes/transpose.js +0 -138
- package/dist/widgets/button.d.ts +0 -49
- package/dist/widgets/button.js +0 -206
- package/dist/widgets/checkbox.d.ts +0 -41
- package/dist/widgets/checkbox.js +0 -155
- package/dist/widgets/dropdown.d.ts +0 -67
- package/dist/widgets/dropdown.js +0 -453
- package/dist/widgets/event-router.d.ts +0 -78
- package/dist/widgets/event-router.js +0 -622
- package/dist/widgets/focus-manager.d.ts +0 -26
- package/dist/widgets/focus-manager.js +0 -193
- package/dist/widgets/index.d.ts +0 -22
- package/dist/widgets/index.js +0 -15
- package/dist/widgets/screen.d.ts +0 -98
- package/dist/widgets/screen.js +0 -542
- package/dist/widgets/slider.d.ts +0 -57
- package/dist/widgets/slider.js +0 -270
- package/dist/widgets/static-item.d.ts +0 -40
- package/dist/widgets/static-item.js +0 -60
- package/dist/widgets/text-input.d.ts +0 -274
- package/dist/widgets/text-input.js +0 -1162
- package/dist/widgets/toggle.d.ts +0 -44
- package/dist/widgets/toggle.js +0 -179
- package/dist/widgets/types.d.ts +0 -108
- package/dist/widgets/types.js +0 -31
- package/dist/widgets/widget-base.d.ts +0 -42
- package/dist/widgets/widget-base.js +0 -173
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Style-function registrations for the rich-js template binding.
|
|
3
|
-
*
|
|
4
|
-
* [LAW:one-source-of-truth] The function inventory below mirrors the
|
|
5
|
-
* string-syntax style vocabulary documented in `docs/style.md` — the two
|
|
6
|
-
* colour slots (`fg`/`bg`), text attributes (positive + negated), short
|
|
7
|
-
* aliases, and the hyperlink. Each registration is a templating-time analogue
|
|
8
|
-
* of a piece of `Style.parse`, so a template fragment composed by these
|
|
9
|
-
* functions round-trips through `Style` without semantic drift.
|
|
10
|
-
*
|
|
11
|
-
* [LAW:dataflow-not-control-flow] Every function follows the same
|
|
12
|
-
* shape: child `RichText` in, `RichText` out. The styling difference
|
|
13
|
-
* lives in a captured `Style` value (or constructor closure), not in
|
|
14
|
-
* branches inside a single dispatch. That is why this file is mostly
|
|
15
|
-
* factories — the data (the colour, the attribute name) is the
|
|
16
|
-
* variability; the operation is fixed.
|
|
17
|
-
*/
|
|
18
|
-
import { Style, ATTRIBUTE_NAMES, ATTRIBUTE_SHORT_ALIASES, } from "../core/style.js";
|
|
19
|
-
import { ColorSpec } from "../core/color.js";
|
|
20
|
-
import { applyStyleToFragment } from "./helpers.js";
|
|
21
|
-
function attrFunc(style) {
|
|
22
|
-
return {
|
|
23
|
-
fn: ((child) => applyStyleToFragment(child, style)),
|
|
24
|
-
argTypes: ["liftable"],
|
|
25
|
-
returnType: "T",
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
// --- Colour sinks ---
|
|
29
|
-
//
|
|
30
|
-
// `fg` and `bg` are the only two colour-applying functions, and they are the
|
|
31
|
-
// terminal step of the colour pipeline: `color` names a colour, the functions
|
|
32
|
-
// in `color-funcs.ts` transform it, these paint it onto text.
|
|
33
|
-
//
|
|
34
|
-
// [LAW:composability] They replace five separate families — one function per
|
|
35
|
-
// ANSI colour name (`red`, `bright_blue`, …), `hex`, `rgb`, `color` (256-index),
|
|
36
|
-
// and `on` (background). Every one of those encoded *which colour* in the
|
|
37
|
-
// function's identity, so the vocabulary could only grow by adding names, and
|
|
38
|
-
// a colour computed at render time could not be applied at all. Here the colour
|
|
39
|
-
// is an argument, so the sink admits every colour that exists and every colour
|
|
40
|
-
// that will ever exist. Two functions, unbounded reach.
|
|
41
|
-
//
|
|
42
|
-
// [LAW:types-are-the-program] The slot accepts the full `ColorSpec.parse`
|
|
43
|
-
// vocabulary, which is wider than the hex the colour math produces — and that
|
|
44
|
-
// width is deliberate, not laxity. `#7aa2f7` is a *concrete* colour; `"red"`
|
|
45
|
-
// and `"color(42)"` are *symbolic* ones the terminal resolves against its own
|
|
46
|
-
// theme. Only concrete colours can be darkened or blended, which is why the
|
|
47
|
-
// colour-math functions take hex alone; but both kinds can be painted, so the
|
|
48
|
-
// sink takes the union. The type of each slot is exactly the set of values that
|
|
49
|
-
// slot can mean something for.
|
|
50
|
-
function colorSinkFunc(slot) {
|
|
51
|
-
return {
|
|
52
|
-
fn: ((spec, child) => applyStyleToFragment(child, new Style({ [slot]: ColorSpec.parse(spec) }))),
|
|
53
|
-
argTypes: ["string", "liftable"],
|
|
54
|
-
returnType: "T",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
// --- Text attributes ---
|
|
58
|
-
//
|
|
59
|
-
// [LAW:one-source-of-truth] The attribute and short-alias inventories
|
|
60
|
-
// come from `core/style.ts`. This file derives the template registration
|
|
61
|
-
// set from the same data `Style.parse` consults — adding an attribute or
|
|
62
|
-
// alias in one place propagates here automatically.
|
|
63
|
-
function attrStyle(name, value) {
|
|
64
|
-
return new Style({ [name]: value });
|
|
65
|
-
}
|
|
66
|
-
function attributeFuncs() {
|
|
67
|
-
const out = {};
|
|
68
|
-
for (const name of ATTRIBUTE_NAMES) {
|
|
69
|
-
out[name] = attrFunc(attrStyle(name, true));
|
|
70
|
-
out[`not_${name}`] = attrFunc(attrStyle(name, false));
|
|
71
|
-
}
|
|
72
|
-
for (const [alias, canonical] of Object.entries(ATTRIBUTE_SHORT_ALIASES)) {
|
|
73
|
-
out[alias] = attrFunc(attrStyle(canonical, true));
|
|
74
|
-
}
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
// --- Style spec (multi-attribute one-shot) ---
|
|
78
|
-
//
|
|
79
|
-
// [LAW:one-source-of-truth] `style` accepts the same space-separated grammar
|
|
80
|
-
// `Style.parse` consults — i.e. the inside of `[...]` markup. There is no
|
|
81
|
-
// second parser: a spec that `Style.parse` accepts produces a fragment
|
|
82
|
-
// byte-equivalent to the same spec inside markup, and one that `Style.parse`
|
|
83
|
-
// rejects raises the same `StyleSyntaxError` surface.
|
|
84
|
-
//
|
|
85
|
-
// Motivation: the per-attribute functions (`bold`, `underline`, `fg`, …)
|
|
86
|
-
// compose by nesting. For "apply a fixed set of styles to this child" or
|
|
87
|
-
// "apply this named style set everywhere", nesting is awkward and the
|
|
88
|
-
// style description is fragmented across multiple call sites. `style`
|
|
89
|
-
// collapses that to a single call, and because the spec is a string it
|
|
90
|
-
// flows through Go-template `$vars` and through scope without further
|
|
91
|
-
// machinery:
|
|
92
|
-
//
|
|
93
|
-
// {{ $alert := "bold underline #ff6b6b" }}
|
|
94
|
-
// {{ style $alert "alarm!" }}
|
|
95
|
-
// {{ style $alert .otherField }}
|
|
96
|
-
//
|
|
97
|
-
// [LAW:dataflow-not-control-flow] Same shape as every other style function:
|
|
98
|
-
// a `Style` value (here built by `Style.parse(spec)`) plus a child, in,
|
|
99
|
-
// styled child out. The variability is the spec string; the operation is
|
|
100
|
-
// fixed.
|
|
101
|
-
const styleSpecFunc = {
|
|
102
|
-
fn: ((spec, child) => {
|
|
103
|
-
return applyStyleToFragment(child, Style.parse(spec));
|
|
104
|
-
}),
|
|
105
|
-
argTypes: ["string", "liftable"],
|
|
106
|
-
returnType: "T",
|
|
107
|
-
};
|
|
108
|
-
// --- Hyperlink ---
|
|
109
|
-
//
|
|
110
|
-
// `link` is the cell-splitter for the multi-cell consumer contract.
|
|
111
|
-
// Implementation-wise it is the same shape as any other style function:
|
|
112
|
-
// it sets the `link` slot of `Style` exactly as the existing string-form
|
|
113
|
-
// `link URL` does, so a template-built fragment is byte-equivalent to
|
|
114
|
-
// `RichText("x", { style: Style.parse("link u") })`.
|
|
115
|
-
//
|
|
116
|
-
// The cell-boundary signal that consumers (cc-candybar et al.) walk is
|
|
117
|
-
// `fragment.style.link` being truthy. `Style.add` propagates `link`
|
|
118
|
-
// through any outer wrapping call, so `{{ fg "red" (link "u" "x") }}` and
|
|
119
|
-
// `{{ link "u" "x" }}` produce shapes that both qualify as cells from
|
|
120
|
-
// the consumer's perspective. Outer-wins on nested links comes for free
|
|
121
|
-
// from `Style.add`'s right-wins-on-conflict rule.
|
|
122
|
-
//
|
|
123
|
-
// [LAW:dataflow-not-control-flow] No special-case AST node, no
|
|
124
|
-
// `LinkFragment` subclass, no second emitter path — the variability is
|
|
125
|
-
// the value of `style.link`, not whether some control-flow branch ran.
|
|
126
|
-
const linkFunc = {
|
|
127
|
-
fn: ((url, child) => {
|
|
128
|
-
return applyStyleToFragment(child, new Style({ link: url }));
|
|
129
|
-
}),
|
|
130
|
-
argTypes: ["string", "liftable"],
|
|
131
|
-
returnType: "T",
|
|
132
|
-
};
|
|
133
|
-
// --- Public assembly ---
|
|
134
|
-
/**
|
|
135
|
-
* Text-styling registrations: the two colour sinks (`fg`, `bg`), text
|
|
136
|
-
* attributes (canonical names, short aliases, and `not_*` negations), the
|
|
137
|
-
* hyperlink cell-splitter (`link`), and the multi-attribute `style` spec.
|
|
138
|
-
*
|
|
139
|
-
* Colours themselves come from elsewhere: `colorFuncs()` for the palette-free
|
|
140
|
-
* math, `paletteFuncs()` for naming a theme colour. This module only paints.
|
|
141
|
-
* [LAW:one-way-deps] — nothing here imports a palette.
|
|
142
|
-
*/
|
|
143
|
-
export function richTextStyleFuncs() {
|
|
144
|
-
return {
|
|
145
|
-
fg: colorSinkFunc("color"),
|
|
146
|
-
bg: colorSinkFunc("bgcolor"),
|
|
147
|
-
...attributeFuncs(),
|
|
148
|
-
link: linkFunc,
|
|
149
|
-
style: styleSpecFunc,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { ColorRgba } from "../core/color.js";
|
|
2
|
-
import { Palette } from "./palette.js";
|
|
3
|
-
/**
|
|
4
|
-
* Base colors required to build a full semantic palette.
|
|
5
|
-
* All are ColorRgba — callers construct from whatever source (hex, HSL, etc.).
|
|
6
|
-
*/
|
|
7
|
-
export interface BaseColors {
|
|
8
|
-
primary: ColorRgba;
|
|
9
|
-
secondary: ColorRgba;
|
|
10
|
-
accent: ColorRgba;
|
|
11
|
-
success: ColorRgba;
|
|
12
|
-
warning: ColorRgba;
|
|
13
|
-
error: ColorRgba;
|
|
14
|
-
background: ColorRgba;
|
|
15
|
-
foreground: ColorRgba;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Build a full semantic palette from base colors.
|
|
19
|
-
*
|
|
20
|
-
* Derived entries follow Textual's formulas:
|
|
21
|
-
* - `*-muted` = color blended 70% toward its opposite base role. Defined for
|
|
22
|
-
* every accent AND for the two base roles themselves
|
|
23
|
-
* (`foreground-muted` blends toward `background`,
|
|
24
|
-
* `background-muted` blends toward `foreground`) — a caller
|
|
25
|
-
* de-emphasizing body/structural text reaches for
|
|
26
|
-
* `foreground-muted` the same way it reaches for
|
|
27
|
-
* `primary-muted` to de-emphasize an accent.
|
|
28
|
-
* - `text-*` = contrast text tinted 66% with the accent color (use as
|
|
29
|
-
* foreground in muted/background-tinted contexts)
|
|
30
|
-
* - `on-*` = WCAG-correct contrast colour (black or white) for use as
|
|
31
|
-
* foreground when the FULL accent is the background. Picked
|
|
32
|
-
* by relative luminance — single source of truth so widgets
|
|
33
|
-
* never need to invert / reverse fg/bg to get readable text.
|
|
34
|
-
* - `surface` = background blended 5% toward foreground
|
|
35
|
-
*/
|
|
36
|
-
export declare function buildPalette(name: string, dark: boolean, base: BaseColors): Palette;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { blendRgb } from "../core/color.js";
|
|
2
|
-
import { alphaBlend, contrastFor } from "./colorMath.js";
|
|
3
|
-
import { Palette } from "./palette.js";
|
|
4
|
-
const MUTED_BLEND = 0.7;
|
|
5
|
-
const TEXT_ALPHA = 0.66;
|
|
6
|
-
const SURFACE_LIFT = 0.05;
|
|
7
|
-
const ACCENT_KEYS = ["primary", "secondary", "accent", "success", "warning", "error"];
|
|
8
|
-
/**
|
|
9
|
-
* Build a full semantic palette from base colors.
|
|
10
|
-
*
|
|
11
|
-
* Derived entries follow Textual's formulas:
|
|
12
|
-
* - `*-muted` = color blended 70% toward its opposite base role. Defined for
|
|
13
|
-
* every accent AND for the two base roles themselves
|
|
14
|
-
* (`foreground-muted` blends toward `background`,
|
|
15
|
-
* `background-muted` blends toward `foreground`) — a caller
|
|
16
|
-
* de-emphasizing body/structural text reaches for
|
|
17
|
-
* `foreground-muted` the same way it reaches for
|
|
18
|
-
* `primary-muted` to de-emphasize an accent.
|
|
19
|
-
* - `text-*` = contrast text tinted 66% with the accent color (use as
|
|
20
|
-
* foreground in muted/background-tinted contexts)
|
|
21
|
-
* - `on-*` = WCAG-correct contrast colour (black or white) for use as
|
|
22
|
-
* foreground when the FULL accent is the background. Picked
|
|
23
|
-
* by relative luminance — single source of truth so widgets
|
|
24
|
-
* never need to invert / reverse fg/bg to get readable text.
|
|
25
|
-
* - `surface` = background blended 5% toward foreground
|
|
26
|
-
*/
|
|
27
|
-
export function buildPalette(name, dark, base) {
|
|
28
|
-
const vars = new Map();
|
|
29
|
-
// Base entries
|
|
30
|
-
vars.set("background", base.background);
|
|
31
|
-
vars.set("foreground", base.foreground);
|
|
32
|
-
for (const key of ACCENT_KEYS) {
|
|
33
|
-
vars.set(key, base[key]);
|
|
34
|
-
}
|
|
35
|
-
// Muted variants of the two base roles, same "blend 70% toward
|
|
36
|
-
// background" formula as the accent `*-muted` family below — every
|
|
37
|
-
// base color the palette defines gets a de-emphasized variant, not just
|
|
38
|
-
// the accents. `foreground-muted` is what a caller reaches for to draw
|
|
39
|
-
// structural/contextual text (labels, punctuation, ids) at reduced visual
|
|
40
|
-
// weight without a terminal-support-dependent SGR attribute.
|
|
41
|
-
vars.set("foreground-muted", blendRgb(base.foreground, base.background, MUTED_BLEND));
|
|
42
|
-
vars.set("background-muted", blendRgb(base.background, base.foreground, MUTED_BLEND));
|
|
43
|
-
// Surface — subtle lift from background
|
|
44
|
-
vars.set("surface", blendRgb(base.background, base.foreground, SURFACE_LIFT));
|
|
45
|
-
// Derived: muted, text-, and on- for each accent.
|
|
46
|
-
// [LAW:single-enforcer] contrastFor is the only place that decides
|
|
47
|
-
// black-vs-white for "text on this accent" — widgets read on-${accent}.
|
|
48
|
-
const contrastText = contrastFor(base.background);
|
|
49
|
-
for (const key of ACCENT_KEYS) {
|
|
50
|
-
const color = base[key];
|
|
51
|
-
vars.set(`${key}-muted`, blendRgb(color, base.background, MUTED_BLEND));
|
|
52
|
-
vars.set(`text-${key}`, alphaBlend(color, contrastText, TEXT_ALPHA));
|
|
53
|
-
vars.set(`on-${key}`, contrastFor(color));
|
|
54
|
-
}
|
|
55
|
-
return new Palette(name, dark, vars);
|
|
56
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { ColorDepth, ColorRgba, contrastRatio, relativeLuminance } from "../core/color.js";
|
|
2
|
-
/**
|
|
3
|
-
* Darken a color by N levels, where each level reduces HSL lightness by 10%.
|
|
4
|
-
* Negative levels lighten. Level 0 returns an equivalent triplet (after the
|
|
5
|
-
* RGB↔HSL roundtrip; values may differ by ±1 due to rounding).
|
|
6
|
-
*/
|
|
7
|
-
export declare function darken(color: ColorRgba, levels: number): ColorRgba;
|
|
8
|
-
/**
|
|
9
|
-
* Lighten a color by N levels. Equivalent to `darken(color, -levels)`.
|
|
10
|
-
*/
|
|
11
|
-
export declare function lighten(color: ColorRgba, levels: number): ColorRgba;
|
|
12
|
-
/**
|
|
13
|
-
* Composite `fg` over `bg` with the given alpha (0..1). At alpha=0 returns bg;
|
|
14
|
-
* at alpha=1 returns fg.
|
|
15
|
-
*/
|
|
16
|
-
export declare function alphaBlend(fg: ColorRgba, bg: ColorRgba, alpha: number): ColorRgba;
|
|
17
|
-
/**
|
|
18
|
-
* Pick a contrasting foreground (black or white) for a background, using the
|
|
19
|
-
* WCAG relative-luminance threshold of 0.179 (the perceptually correct cutoff
|
|
20
|
-
* where black and white are equally readable). A translucent `bg` is judged
|
|
21
|
-
* as drawn: composited over `substrate` (see `drawnBackground`).
|
|
22
|
-
*/
|
|
23
|
-
export declare function contrastFor(bg: ColorRgba, substrate?: ColorRgba): ColorRgba;
|
|
24
|
-
export { relativeLuminance, contrastRatio };
|
|
25
|
-
/**
|
|
26
|
-
* Return a foreground guaranteed to clear `minRatio` against `bg`, keeping the
|
|
27
|
-
* color *recognizably itself*. If the themed `fg` already passes it is returned
|
|
28
|
-
* untouched. Otherwise its OKLCH lightness is slid toward the pole that raises
|
|
29
|
-
* contrast — holding hue, and chroma where it stays in gamut (near the poles
|
|
30
|
-
* gamut clamping may reduce chroma, but hue is preserved) — until the ratio is
|
|
31
|
-
* met, so a blue on a dark-blue background becomes a lighter blue, not white.
|
|
32
|
-
* Only when no lightness of that hue can meet the ratio (a mid-toned
|
|
33
|
-
* background where even pure black-or-white tops out below the target) does it
|
|
34
|
-
* fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
|
|
35
|
-
*
|
|
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.
|
|
41
|
-
*
|
|
42
|
-
* `drawnAt` is the depth the terminal will draw the pair at. At 256 colours
|
|
43
|
-
* the terminal rounds text and background independently, and two roundings
|
|
44
|
-
* can meet in the middle, so the ratio is measured on the drawn pair: a
|
|
45
|
-
* colour that loses the floor there is replaced by the nearest cube/grey entry
|
|
46
|
-
* that clears it (whose own rounding is itself). At ANSI the terminal draws
|
|
47
|
-
* its own theme's colours, so the ratio is measured on the table's nominal
|
|
48
|
-
* ones (`DRAWN_FROM`): they say which side of the ground text belongs on, and
|
|
49
|
-
* text on its background's index measures 1:1 and is always replaced.
|
|
50
|
-
* Truecolor draws the colour chosen.
|
|
51
|
-
*
|
|
52
|
-
* [LAW:single-enforcer] The one place "is this text readable, and if not fix
|
|
53
|
-
* it" is decided. Callers route every fg/bg pair through here and the
|
|
54
|
-
* unreadable state never reaches output. [LAW:dataflow-not-control-flow] the
|
|
55
|
-
* function always runs; the measured ratio (data) decides how far the
|
|
56
|
-
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
57
|
-
*/
|
|
58
|
-
export declare function ensureContrast(fg: ColorRgba, bg: ColorRgba, minRatio?: number, // WCAG AA for normal text
|
|
59
|
-
drawnAt?: ColorDepth, substrate?: ColorRgba): ColorRgba;
|
|
60
|
-
/**
|
|
61
|
-
* `chosen` (composited opaque over `substrate`), or — when the depth the
|
|
62
|
-
* terminal draws at rounds it to a colour `accept` refuses — the nearest colour that depth draws as itself which
|
|
63
|
-
* `accept` takes. `accept` sees the candidate as drawn, and `drawn`, the same
|
|
64
|
-
* rounding for any other colour it measures against, so the caller states a
|
|
65
|
-
* floor once and it holds on the colours the terminal shows — at ANSI, on
|
|
66
|
-
* the table's nominal colours, as `ensureContrast` measures there. Truecolor
|
|
67
|
-
* draws from no table, so a colour refused there has no replacement;
|
|
68
|
-
* `undefined` means nothing the depth draws is accepted.
|
|
69
|
-
*
|
|
70
|
-
* `ensureContrast` is this with a contrast ratio as the floor; this is for a
|
|
71
|
-
* floor that is not text on its background (an open state standing off every
|
|
72
|
-
* closed cell, two planes standing off each other).
|
|
73
|
-
*/
|
|
74
|
-
export declare function ensureDrawn(chosen: ColorRgba, drawnAt: ColorDepth, accept: (candidate: ColorRgba, drawn: (c: ColorRgba) => ColorRgba) => boolean, substrate?: ColorRgba): ColorRgba | undefined;
|
|
75
|
-
/**
|
|
76
|
-
* The colour a ground is shown as at `drawnAt`: composited over `substrate`
|
|
77
|
-
* (the SGR writer's black by default), then rounded to the table that depth
|
|
78
|
-
* draws from — at ANSI, that table's nominal colour stands in for the
|
|
79
|
-
* theme's. The one account `ensureDrawn` and `ensureContrast` measure by, for
|
|
80
|
-
* a caller that measures a floor of its own.
|
|
81
|
-
*/
|
|
82
|
-
export declare function drawnColour(colour: ColorRgba, drawnAt: ColorDepth, substrate?: ColorRgba): ColorRgba;
|
package/dist/themes/colorMath.js
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { ColorDepth, ColorRgba, EIGHT_BIT_DOWNGRADE_TABLE, STANDARD_TABLE, blendRgb, contrastRatio, relativeLuminance, SURFACE_BLACK, } from "../core/color.js";
|
|
2
|
-
import { Oklch } from "../core/oklch.js";
|
|
3
|
-
const LEVEL_STEP = 0.1;
|
|
4
|
-
function rgbToHsl(c) {
|
|
5
|
-
const r = c.red / 255;
|
|
6
|
-
const g = c.green / 255;
|
|
7
|
-
const b = c.blue / 255;
|
|
8
|
-
const max = Math.max(r, g, b);
|
|
9
|
-
const min = Math.min(r, g, b);
|
|
10
|
-
const l = (max + min) / 2;
|
|
11
|
-
const d = max - min;
|
|
12
|
-
if (d === 0)
|
|
13
|
-
return { h: 0, s: 0, l };
|
|
14
|
-
const s = d / (l > 0.5 ? 2 - max - min : max + min);
|
|
15
|
-
let h;
|
|
16
|
-
if (max === r)
|
|
17
|
-
h = ((g - b) / d) % 6;
|
|
18
|
-
else if (max === g)
|
|
19
|
-
h = (b - r) / d + 2;
|
|
20
|
-
else
|
|
21
|
-
h = (r - g) / d + 4;
|
|
22
|
-
h = (h * 60 + 360) % 360;
|
|
23
|
-
return { h, s, l };
|
|
24
|
-
}
|
|
25
|
-
function clampChannel(v) {
|
|
26
|
-
// Float HSL math + Math.round can land at -1 or 256 at the boundaries; clamp
|
|
27
|
-
// so an invalid ColorRgba never escapes this function.
|
|
28
|
-
const r = Math.round(v);
|
|
29
|
-
return r < 0 ? 0 : r > 255 ? 255 : r;
|
|
30
|
-
}
|
|
31
|
-
function hslToRgb(hsl) {
|
|
32
|
-
const { h, s, l } = hsl;
|
|
33
|
-
if (s === 0) {
|
|
34
|
-
const v = clampChannel(l * 255);
|
|
35
|
-
return new ColorRgba(v, v, v);
|
|
36
|
-
}
|
|
37
|
-
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
38
|
-
const hp = h / 60;
|
|
39
|
-
const x = c * (1 - Math.abs((hp % 2) - 1));
|
|
40
|
-
let r = 0, g = 0, b = 0;
|
|
41
|
-
if (hp < 1)
|
|
42
|
-
[r, g, b] = [c, x, 0];
|
|
43
|
-
else if (hp < 2)
|
|
44
|
-
[r, g, b] = [x, c, 0];
|
|
45
|
-
else if (hp < 3)
|
|
46
|
-
[r, g, b] = [0, c, x];
|
|
47
|
-
else if (hp < 4)
|
|
48
|
-
[r, g, b] = [0, x, c];
|
|
49
|
-
else if (hp < 5)
|
|
50
|
-
[r, g, b] = [x, 0, c];
|
|
51
|
-
else
|
|
52
|
-
[r, g, b] = [c, 0, x];
|
|
53
|
-
const m = l - c / 2;
|
|
54
|
-
return new ColorRgba(clampChannel((r + m) * 255), clampChannel((g + m) * 255), clampChannel((b + m) * 255));
|
|
55
|
-
}
|
|
56
|
-
function clamp01(v) {
|
|
57
|
-
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Darken a color by N levels, where each level reduces HSL lightness by 10%.
|
|
61
|
-
* Negative levels lighten. Level 0 returns an equivalent triplet (after the
|
|
62
|
-
* RGB↔HSL roundtrip; values may differ by ±1 due to rounding).
|
|
63
|
-
*/
|
|
64
|
-
export function darken(color, levels) {
|
|
65
|
-
const hsl = rgbToHsl(color);
|
|
66
|
-
hsl.l = clamp01(hsl.l - LEVEL_STEP * levels);
|
|
67
|
-
return hslToRgb(hsl);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Lighten a color by N levels. Equivalent to `darken(color, -levels)`.
|
|
71
|
-
*/
|
|
72
|
-
export function lighten(color, levels) {
|
|
73
|
-
return darken(color, -levels);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Composite `fg` over `bg` with the given alpha (0..1). At alpha=0 returns bg;
|
|
77
|
-
* at alpha=1 returns fg.
|
|
78
|
-
*/
|
|
79
|
-
export function alphaBlend(fg, bg, alpha) {
|
|
80
|
-
return blendRgb(bg, fg, clamp01(alpha));
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Pick a contrasting foreground (black or white) for a background, using the
|
|
84
|
-
* WCAG relative-luminance threshold of 0.179 (the perceptually correct cutoff
|
|
85
|
-
* where black and white are equally readable). A translucent `bg` is judged
|
|
86
|
-
* as drawn: composited over `substrate` (see `drawnBackground`).
|
|
87
|
-
*/
|
|
88
|
-
export function contrastFor(bg, substrate = SURFACE_BLACK) {
|
|
89
|
-
const lum = relativeLuminance(drawnBackground(bg, substrate));
|
|
90
|
-
return lum > 0.179
|
|
91
|
-
? new ColorRgba(0, 0, 0)
|
|
92
|
-
: new ColorRgba(255, 255, 255);
|
|
93
|
-
}
|
|
94
|
-
// [LAW:one-way-deps] The WCAG measures live in core/color.ts, beside the
|
|
95
|
-
// ColorTable whose `matchReadable` needs them to pick a drawn text colour, and
|
|
96
|
-
// below the theme math here. Re-exported so this module stays the colour-math
|
|
97
|
-
// surface.
|
|
98
|
-
export { relativeLuminance, contrastRatio };
|
|
99
|
-
// Iterations for the lightness bisection below. 20 resolves L to ~1e-6 — far
|
|
100
|
-
// finer than 8-bit quantization or the eye.
|
|
101
|
-
const CONTRAST_ITERS = 20;
|
|
102
|
-
/**
|
|
103
|
-
* Return a foreground guaranteed to clear `minRatio` against `bg`, keeping the
|
|
104
|
-
* color *recognizably itself*. If the themed `fg` already passes it is returned
|
|
105
|
-
* untouched. Otherwise its OKLCH lightness is slid toward the pole that raises
|
|
106
|
-
* contrast — holding hue, and chroma where it stays in gamut (near the poles
|
|
107
|
-
* gamut clamping may reduce chroma, but hue is preserved) — until the ratio is
|
|
108
|
-
* met, so a blue on a dark-blue background becomes a lighter blue, not white.
|
|
109
|
-
* Only when no lightness of that hue can meet the ratio (a mid-toned
|
|
110
|
-
* background where even pure black-or-white tops out below the target) does it
|
|
111
|
-
* fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
|
|
112
|
-
*
|
|
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.
|
|
118
|
-
*
|
|
119
|
-
* `drawnAt` is the depth the terminal will draw the pair at. At 256 colours
|
|
120
|
-
* the terminal rounds text and background independently, and two roundings
|
|
121
|
-
* can meet in the middle, so the ratio is measured on the drawn pair: a
|
|
122
|
-
* colour that loses the floor there is replaced by the nearest cube/grey entry
|
|
123
|
-
* that clears it (whose own rounding is itself). At ANSI the terminal draws
|
|
124
|
-
* its own theme's colours, so the ratio is measured on the table's nominal
|
|
125
|
-
* ones (`DRAWN_FROM`): they say which side of the ground text belongs on, and
|
|
126
|
-
* text on its background's index measures 1:1 and is always replaced.
|
|
127
|
-
* Truecolor draws the colour chosen.
|
|
128
|
-
*
|
|
129
|
-
* [LAW:single-enforcer] The one place "is this text readable, and if not fix
|
|
130
|
-
* it" is decided. Callers route every fg/bg pair through here and the
|
|
131
|
-
* unreadable state never reaches output. [LAW:dataflow-not-control-flow] the
|
|
132
|
-
* function always runs; the measured ratio (data) decides how far the
|
|
133
|
-
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
134
|
-
*/
|
|
135
|
-
export function ensureContrast(fg, bg, minRatio = 4.5, // WCAG AA for normal text
|
|
136
|
-
drawnAt = ColorDepth.TRUECOLOR, substrate = SURFACE_BLACK) {
|
|
137
|
-
const ground = drawnBackground(bg, substrate);
|
|
138
|
-
const chosen = ensureTruecolorContrast(fg, ground, minRatio);
|
|
139
|
-
// [LAW:dataflow-not-control-flow] The depth names the table the terminal
|
|
140
|
-
// draws from; every table is measured the same way.
|
|
141
|
-
const table = DRAWN_FROM[drawnAt];
|
|
142
|
-
if (table === undefined)
|
|
143
|
-
return chosen;
|
|
144
|
-
const drawnBg = drawnColour(ground, drawnAt, substrate);
|
|
145
|
-
if (contrastRatio(drawnColour(chosen, drawnAt, substrate), drawnBg) >= minRatio)
|
|
146
|
-
return chosen;
|
|
147
|
-
return table.get(table.matchReadable(chosen, drawnBg, minRatio));
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* `chosen` (composited opaque over `substrate`), or — when the depth the
|
|
151
|
-
* terminal draws at rounds it to a colour `accept` refuses — the nearest colour that depth draws as itself which
|
|
152
|
-
* `accept` takes. `accept` sees the candidate as drawn, and `drawn`, the same
|
|
153
|
-
* rounding for any other colour it measures against, so the caller states a
|
|
154
|
-
* floor once and it holds on the colours the terminal shows — at ANSI, on
|
|
155
|
-
* the table's nominal colours, as `ensureContrast` measures there. Truecolor
|
|
156
|
-
* draws from no table, so a colour refused there has no replacement;
|
|
157
|
-
* `undefined` means nothing the depth draws is accepted.
|
|
158
|
-
*
|
|
159
|
-
* `ensureContrast` is this with a contrast ratio as the floor; this is for a
|
|
160
|
-
* floor that is not text on its background (an open state standing off every
|
|
161
|
-
* closed cell, two planes standing off each other).
|
|
162
|
-
*/
|
|
163
|
-
export function ensureDrawn(chosen, drawnAt, accept, substrate = SURFACE_BLACK) {
|
|
164
|
-
// The floor was measured on `chosen` composited over `substrate`, so that
|
|
165
|
-
// opaque colour is what is returned — never a translucent one a different
|
|
166
|
-
// ground would composite into a colour `accept` never saw.
|
|
167
|
-
const opaque = drawnBackground(chosen, substrate);
|
|
168
|
-
const drawn = (c) => drawnColour(c, drawnAt, substrate);
|
|
169
|
-
if (accept(drawn(opaque), drawn))
|
|
170
|
-
return opaque;
|
|
171
|
-
// [LAW:dataflow-not-control-flow] Truecolor draws from no table, so it has
|
|
172
|
-
// no candidates: a refusal there is `undefined` like any exhausted search.
|
|
173
|
-
const table = DRAWN_FROM[drawnAt];
|
|
174
|
-
const index = table?.matchWhere(opaque, (entry) => accept(entry, drawn));
|
|
175
|
-
return index === undefined ? undefined : table.get(index);
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* The colour a ground is shown as at `drawnAt`: composited over `substrate`
|
|
179
|
-
* (the SGR writer's black by default), then rounded to the table that depth
|
|
180
|
-
* draws from — at ANSI, that table's nominal colour stands in for the
|
|
181
|
-
* theme's. The one account `ensureDrawn` and `ensureContrast` measure by, for
|
|
182
|
-
* a caller that measures a floor of its own.
|
|
183
|
-
*/
|
|
184
|
-
export function drawnColour(colour, drawnAt, substrate = SURFACE_BLACK) {
|
|
185
|
-
const opaque = drawnBackground(colour, substrate);
|
|
186
|
-
const table = DRAWN_FROM[drawnAt];
|
|
187
|
-
return table === undefined ? opaque : table.get(table.match(opaque));
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* A background as it is drawn: composited over the surface beneath it. That
|
|
191
|
-
* surface is a fact about where the pair is drawn, so it arrives as a value:
|
|
192
|
-
* the SGR writer (`Style.toSgrCodes`) composites over `SURFACE_BLACK`, the
|
|
193
|
-
* default here; a caller choosing text for a different surface — an export's
|
|
194
|
-
* canvas, `exportCanvas(theme).background` — names that one.
|
|
195
|
-
* [LAW:no-silent-failure] A surface has nothing under it, so a translucent one
|
|
196
|
-
* has no drawn colour to offer; `compositeOver` would read its raw RGB as if
|
|
197
|
-
* it were opaque, so it is refused here rather than measured wrong.
|
|
198
|
-
* [LAW:one-source-of-truth] Text is chosen against the colour the surface will
|
|
199
|
-
* show — measuring the raw RGBA reads a colour that is drawn nowhere, and text
|
|
200
|
-
* that "clears" it can land below the floor. Opaque colours composite to
|
|
201
|
-
* themselves.
|
|
202
|
-
*/
|
|
203
|
-
function drawnBackground(bg, substrate) {
|
|
204
|
-
if (substrate.alpha !== 1) {
|
|
205
|
-
throw new RangeError(`a contrast substrate is the opaque surface under a translucent background; got ${substrate.hex}`);
|
|
206
|
-
}
|
|
207
|
-
return bg.compositeOver(substrate);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* The downgrade table the terminal draws from, by depth, measured at each
|
|
211
|
-
* entry's colour. The 256-colour cube and grey ramp are fixed by xterm, so
|
|
212
|
-
* those measurements are exact. ANSI 0–15 are the terminal theme's own, so
|
|
213
|
-
* the table's nominal colours stand in for them: they are wrong in hue from
|
|
214
|
-
* theme to theme, but not about which side of a ground an entry sits on —
|
|
215
|
-
* and two colours on one index are one colour in every theme, which the
|
|
216
|
-
* nominal ratio of 1 refuses. Truecolor draws the chosen colour itself.
|
|
217
|
-
*/
|
|
218
|
-
const DRAWN_FROM = {
|
|
219
|
-
[ColorDepth.EIGHT_BIT]: EIGHT_BIT_DOWNGRADE_TABLE,
|
|
220
|
-
[ColorDepth.STANDARD]: STANDARD_TABLE,
|
|
221
|
-
};
|
|
222
|
-
function ensureTruecolorContrast(fg, bg, minRatio) {
|
|
223
|
-
// Flatten translucency so the guarantee holds for the displayed color, not
|
|
224
|
-
// the raw bytes (e.g. a "#FFFFFF60" text-disabled over a light surface).
|
|
225
|
-
const opaqueFg = fg.compositeOver(bg);
|
|
226
|
-
if (contrastRatio(opaqueFg, bg) >= minRatio)
|
|
227
|
-
return opaqueFg;
|
|
228
|
-
const lab = Oklch.fromRgba(opaqueFg);
|
|
229
|
-
// The pole that increases contrast: lighten toward white on a dark bg, darken
|
|
230
|
-
// toward black on a light one. `contrastFor`'s 0.179 cutoff names it.
|
|
231
|
-
const poleL = relativeLuminance(bg) > 0.179 ? 0 : 1;
|
|
232
|
-
// If even the pole of this hue can't reach the ratio, the hue physically
|
|
233
|
-
// can't — return the true maximum-contrast pick (pure black/white from
|
|
234
|
-
// contrastFor). The gamut-clamped OKLCH pole is only *near* b/w, so
|
|
235
|
-
// contrastFor is at least as strong and is the honest maximum.
|
|
236
|
-
const pole = new Oklch(poleL, lab.c, lab.h, lab.alpha).toRgba();
|
|
237
|
-
if (contrastRatio(pole, bg) < minRatio)
|
|
238
|
-
return contrastFor(bg);
|
|
239
|
-
// Bisect for the lightness nearest the original that still clears the ratio:
|
|
240
|
-
// the smallest perceptual change that achieves accessibility. Contrast is
|
|
241
|
-
// monotone in L over [lab.l, poleL] (everything below the crossing fails),
|
|
242
|
-
// so the search is well-posed.
|
|
243
|
-
let fail = lab.l;
|
|
244
|
-
let pass = poleL;
|
|
245
|
-
for (let i = 0; i < CONTRAST_ITERS; i++) {
|
|
246
|
-
const mid = (fail + pass) / 2;
|
|
247
|
-
const candidate = new Oklch(mid, lab.c, lab.h, lab.alpha).toRgba();
|
|
248
|
-
if (contrastRatio(candidate, bg) >= minRatio)
|
|
249
|
-
pass = mid;
|
|
250
|
-
else
|
|
251
|
-
fail = mid;
|
|
252
|
-
}
|
|
253
|
-
return new Oklch(pass, lab.c, lab.h, lab.alpha).toRgba();
|
|
254
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { ColorRgba } from "../core/color.js";
|
|
2
|
-
import type { Palette } from "./palette.js";
|
|
3
|
-
/**
|
|
4
|
-
* A **color reference** is the one string form an author uses to name a
|
|
5
|
-
* concrete color: a palette variable name (`"primary"`, `"surface-active"`,
|
|
6
|
-
* `"text-primary"`) or a literal `#RRGGBB` / `#RRGGBBAA`.
|
|
7
|
-
*
|
|
8
|
-
* [LAW:parse-dont-validate] `resolveColorRef` is the single checkpoint that
|
|
9
|
-
* turns an untrusted reference string into a `ColorRgba`. Everything
|
|
10
|
-
* downstream — color math, `Style` construction, serialization — takes
|
|
11
|
-
* `ColorRgba` and therefore cannot ask again whether the name existed. There
|
|
12
|
-
* is no `isValidColorRef` companion, deliberately: a validator would hand back
|
|
13
|
-
* the same `string` it was given and every consumer would re-check.
|
|
14
|
-
*
|
|
15
|
-
* [LAW:no-silent-failure] A miss throws. An unknown palette name is a broken
|
|
16
|
-
* config, and the author needs to see which name and what was available — not
|
|
17
|
-
* a silently substituted default that makes the bar merely look wrong.
|
|
18
|
-
*/
|
|
19
|
-
export declare class ColorRefError extends Error {
|
|
20
|
-
readonly ref: string;
|
|
21
|
-
constructor(ref: string, detail: string);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* The literal-color shape, exported so the template bindings gate on the same
|
|
25
|
-
* pattern this module parses. [LAW:one-source-of-truth] — one regex, one
|
|
26
|
-
* parser body; the bindings add only their own error wording.
|
|
27
|
-
*/
|
|
28
|
-
export declare const HEX_COLOR_RE: RegExp;
|
|
29
|
-
/**
|
|
30
|
-
* Parse a `#RRGGBB` / `#RRGGBBAA` literal. The hex arm of
|
|
31
|
-
* {@link resolveColorRef}, separated so callers that accept *only* literal
|
|
32
|
-
* colors (color math, which cannot meaningfully operate on a palette name)
|
|
33
|
-
* share the one implementation instead of re-deriving it.
|
|
34
|
-
*
|
|
35
|
-
* @throws {ColorRefError} when `hex` is not a well-formed literal.
|
|
36
|
-
*/
|
|
37
|
-
export declare function parseHexColor(hex: string): ColorRgba;
|
|
38
|
-
/**
|
|
39
|
-
* Resolve a color reference against `palette`.
|
|
40
|
-
*
|
|
41
|
-
* Accepting hex alongside names is what makes this **idempotent**:
|
|
42
|
-
* `resolveColorRef(p, resolveColorRef(p, r).hex)` equals
|
|
43
|
-
* `resolveColorRef(p, r)` for every `r` this function accepts. Callers
|
|
44
|
-
* therefore never branch on "is this a palette name or already a color" —
|
|
45
|
-
* they call it unconditionally on whatever the author wrote, whether that is
|
|
46
|
-
* `"surface-active"` or the output of a chain of color math.
|
|
47
|
-
* [LAW:dataflow-not-control-flow]
|
|
48
|
-
*
|
|
49
|
-
* @throws {ColorRefError} on malformed hex or an unknown palette name.
|
|
50
|
-
*/
|
|
51
|
-
export declare function resolveColorRef(palette: Palette, ref: string): ColorRgba;
|