@promptctl/rich-js 0.1.0 → 0.3.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 +29 -15
- package/dist/core/cells.d.ts +83 -3
- package/dist/core/cells.d.ts.map +1 -1
- package/dist/core/cells.js +113 -1
- package/dist/core/cells.js.map +1 -1
- package/dist/core/console.d.ts +0 -1
- package/dist/core/console.d.ts.map +1 -1
- package/dist/core/console.js +20 -16
- package/dist/core/console.js.map +1 -1
- package/dist/core/highlighter.d.ts.map +1 -1
- package/dist/core/highlighter.js.map +1 -1
- package/dist/core/oklch.d.ts +106 -0
- package/dist/core/oklch.d.ts.map +1 -0
- package/dist/core/oklch.js +221 -0
- package/dist/core/oklch.js.map +1 -0
- package/dist/core/render.d.ts +26 -7
- package/dist/core/render.d.ts.map +1 -1
- package/dist/core/render.js +83 -15
- package/dist/core/render.js.map +1 -1
- package/dist/core/segment.d.ts +14 -1
- package/dist/core/segment.d.ts.map +1 -1
- package/dist/core/segment.js +28 -3
- package/dist/core/segment.js.map +1 -1
- package/dist/core/style.d.ts +11 -2
- package/dist/core/style.d.ts.map +1 -1
- package/dist/core/style.js +31 -18
- package/dist/core/style.js.map +1 -1
- package/dist/core/text.d.ts +20 -0
- package/dist/core/text.d.ts.map +1 -1
- package/dist/core/text.js +31 -0
- package/dist/core/text.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/renderables/columns.d.ts.map +1 -1
- package/dist/renderables/columns.js +15 -22
- package/dist/renderables/columns.js.map +1 -1
- package/dist/renderables/layout.d.ts.map +1 -1
- package/dist/renderables/layout.js +8 -29
- package/dist/renderables/layout.js.map +1 -1
- package/dist/renderables/live.d.ts.map +1 -1
- package/dist/renderables/live.js +8 -7
- package/dist/renderables/live.js.map +1 -1
- package/dist/renderables/panel.d.ts +34 -0
- package/dist/renderables/panel.d.ts.map +1 -1
- package/dist/renderables/panel.js +66 -23
- package/dist/renderables/panel.js.map +1 -1
- package/dist/template-bindings/helpers.d.ts +25 -0
- package/dist/template-bindings/helpers.d.ts.map +1 -0
- package/dist/template-bindings/helpers.js +31 -0
- package/dist/template-bindings/helpers.js.map +1 -0
- package/dist/template-bindings/index.d.ts +57 -18
- package/dist/template-bindings/index.d.ts.map +1 -1
- package/dist/template-bindings/index.js +83 -18
- package/dist/template-bindings/index.js.map +1 -1
- package/dist/template-bindings/palette-funcs.d.ts +76 -0
- package/dist/template-bindings/palette-funcs.d.ts.map +1 -0
- package/dist/template-bindings/palette-funcs.js +157 -0
- package/dist/template-bindings/palette-funcs.js.map +1 -0
- package/dist/template-bindings/style-funcs.d.ts.map +1 -1
- package/dist/template-bindings/style-funcs.js +33 -27
- package/dist/template-bindings/style-funcs.js.map +1 -1
- package/dist/themes/colorMath.d.ts +40 -0
- package/dist/themes/colorMath.d.ts.map +1 -1
- package/dist/themes/colorMath.js +82 -1
- package/dist/themes/colorMath.js.map +1 -1
- package/dist/themes/data/catppuccin-frappe.d.ts +4 -0
- package/dist/themes/data/catppuccin-frappe.d.ts.map +1 -0
- package/dist/themes/data/catppuccin-frappe.js +159 -0
- package/dist/themes/data/catppuccin-frappe.js.map +1 -0
- package/dist/themes/data/catppuccin-macchiato.d.ts +4 -0
- package/dist/themes/data/catppuccin-macchiato.d.ts.map +1 -0
- package/dist/themes/data/catppuccin-macchiato.js +159 -0
- package/dist/themes/data/catppuccin-macchiato.js.map +1 -0
- package/dist/themes/data/cyberpunk.d.ts +18 -0
- package/dist/themes/data/cyberpunk.d.ts.map +1 -0
- package/dist/themes/data/cyberpunk.js +192 -0
- package/dist/themes/data/cyberpunk.js.map +1 -0
- package/dist/themes/data/default.d.ts +4 -0
- package/dist/themes/data/default.d.ts.map +1 -0
- package/dist/themes/data/default.js +159 -0
- package/dist/themes/data/default.js.map +1 -0
- package/dist/themes/data/index.d.ts +10 -3
- package/dist/themes/data/index.d.ts.map +1 -1
- package/dist/themes/data/index.js +24 -7
- package/dist/themes/data/index.js.map +1 -1
- package/dist/themes/data/svg-export.d.ts +4 -0
- package/dist/themes/data/svg-export.d.ts.map +1 -0
- package/dist/themes/data/svg-export.js +159 -0
- package/dist/themes/data/svg-export.js.map +1 -0
- package/dist/themes/data/types.d.ts +4 -3
- package/dist/themes/data/types.d.ts.map +1 -1
- package/dist/themes/registry.d.ts +29 -2
- package/dist/themes/registry.d.ts.map +1 -1
- package/dist/themes/registry.js +62 -12
- package/dist/themes/registry.js.map +1 -1
- package/dist/themes/terminalThemes.d.ts +29 -20
- package/dist/themes/terminalThemes.d.ts.map +1 -1
- package/dist/themes/terminalThemes.js +50 -249
- package/dist/themes/terminalThemes.js.map +1 -1
- package/dist/themes/transpose.d.ts +88 -0
- package/dist/themes/transpose.d.ts.map +1 -0
- package/dist/themes/transpose.js +139 -0
- package/dist/themes/transpose.js.map +1 -0
- package/dist/widgets/button.d.ts +8 -5
- package/dist/widgets/button.d.ts.map +1 -1
- package/dist/widgets/button.js +36 -11
- package/dist/widgets/button.js.map +1 -1
- package/dist/widgets/checkbox.d.ts +1 -2
- package/dist/widgets/checkbox.d.ts.map +1 -1
- package/dist/widgets/checkbox.js +20 -7
- package/dist/widgets/checkbox.js.map +1 -1
- package/dist/widgets/dropdown.d.ts +4 -3
- package/dist/widgets/dropdown.d.ts.map +1 -1
- package/dist/widgets/dropdown.js +109 -27
- package/dist/widgets/dropdown.js.map +1 -1
- package/dist/widgets/event-router.d.ts +6 -3
- package/dist/widgets/event-router.d.ts.map +1 -1
- package/dist/widgets/event-router.js +179 -68
- package/dist/widgets/event-router.js.map +1 -1
- package/dist/widgets/focus-manager.d.ts +2 -1
- package/dist/widgets/focus-manager.d.ts.map +1 -1
- package/dist/widgets/focus-manager.js +28 -9
- package/dist/widgets/focus-manager.js.map +1 -1
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.d.ts.map +1 -1
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/index.js.map +1 -1
- package/dist/widgets/screen.d.ts +2 -0
- package/dist/widgets/screen.d.ts.map +1 -1
- package/dist/widgets/screen.js +148 -45
- package/dist/widgets/screen.js.map +1 -1
- package/dist/widgets/slider.d.ts +6 -3
- package/dist/widgets/slider.d.ts.map +1 -1
- package/dist/widgets/slider.js +34 -10
- package/dist/widgets/slider.js.map +1 -1
- package/dist/widgets/static-item.d.ts +1 -1
- package/dist/widgets/static-item.d.ts.map +1 -1
- package/dist/widgets/static-item.js +12 -13
- package/dist/widgets/static-item.js.map +1 -1
- package/dist/widgets/text-input.d.ts +239 -13
- package/dist/widgets/text-input.d.ts.map +1 -1
- package/dist/widgets/text-input.js +1004 -74
- package/dist/widgets/text-input.js.map +1 -1
- package/dist/widgets/toggle.d.ts +1 -2
- package/dist/widgets/toggle.d.ts.map +1 -1
- package/dist/widgets/toggle.js +20 -8
- package/dist/widgets/toggle.js.map +1 -1
- package/dist/widgets/types.d.ts +21 -4
- package/dist/widgets/types.d.ts.map +1 -1
- package/dist/widgets/types.js +19 -0
- package/dist/widgets/types.js.map +1 -1
- package/dist/widgets/widget-base.d.ts +1 -0
- package/dist/widgets/widget-base.d.ts.map +1 -1
- package/dist/widgets/widget-base.js +24 -7
- package/dist/widgets/widget-base.js.map +1 -1
- package/package.json +5 -3
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* rich-js template bindings — public entry point.
|
|
3
3
|
*
|
|
4
|
-
* [LAW:one-source-of-truth] This module is the
|
|
5
|
-
* styling vocabulary
|
|
6
|
-
* functions.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
4
|
+
* [LAW:one-source-of-truth] This module is the public entry point for
|
|
5
|
+
* rich-js's styling vocabulary as `@promptctl/go-template-js` template
|
|
6
|
+
* functions. Two complementary registrations are exported:
|
|
7
|
+
*
|
|
8
|
+
* - `richTextFuncs()` — foreground colours, background, text attributes, and
|
|
9
|
+
* `link`. Does not require configuration; safe to register unconditionally.
|
|
10
|
+
* - `paletteFuncs(resolver)` — semantic palette, auto-contrast, and extended
|
|
11
|
+
* spec forms. Requires a `PaletteResolver` argument bound to the active
|
|
12
|
+
* theme; consumers merge it alongside `richTextFuncs()`.
|
|
13
|
+
*
|
|
14
|
+
* `createRichTextEngine()` is convenience sugar that wires up `richTextFuncs()`
|
|
15
|
+
* only — it does not include `paletteFuncs`, because palette functions require
|
|
16
|
+
* a resolver the factory cannot supply. Consumers that need palette access
|
|
17
|
+
* call `paletteFuncs(resolver)` and merge it into their own engine config.
|
|
18
|
+
* Nesting is plain function composition (`{{ red (bold "x") }}`), not a
|
|
14
19
|
* second markup grammar.
|
|
15
20
|
*
|
|
16
21
|
* Fragment type: `RichText`. Chosen because it is the library's primary
|
|
@@ -23,16 +28,17 @@
|
|
|
23
28
|
*/
|
|
24
29
|
import { createEngine } from "@promptctl/go-template-js";
|
|
25
30
|
import { RichText } from "../core/text.js";
|
|
31
|
+
import { Style } from "../core/style.js";
|
|
32
|
+
import { Segment } from "../core/segment.js";
|
|
26
33
|
import { richTextStyleFuncs } from "./style-funcs.js";
|
|
34
|
+
export { paletteFuncs } from "./palette-funcs.js";
|
|
27
35
|
/**
|
|
28
|
-
* Funcs registered by the rich-js binding
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* so
|
|
34
|
-
* resolver bound at construction time) have a place to receive
|
|
35
|
-
* arguments without breaking the public shape.
|
|
36
|
+
* Funcs registered by the rich-js binding — style functions (foreground,
|
|
37
|
+
* background, attributes) and the `link` cell-splitter. Exposed as a factory
|
|
38
|
+
* so future registrations that need configuration have a place to receive
|
|
39
|
+
* arguments. Palette/theme/auto-contrast functions ship separately via
|
|
40
|
+
* `paletteFuncs(resolver)` and are merged at consumer side — they require a
|
|
41
|
+
* `PaletteResolver` argument and so cannot be included in this generic call.
|
|
36
42
|
*
|
|
37
43
|
* `FuncMap` is not parameterised over `T` in `@promptctl/go-template-js` — the engine's
|
|
38
44
|
* `T` lives on the `Engine`/`EngineConfig`, and per-function input/output
|
|
@@ -67,4 +73,63 @@ export function createRichTextEngine() {
|
|
|
67
73
|
funcs: richTextFuncs(),
|
|
68
74
|
});
|
|
69
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Compile a template source against `engine` and render the result to a
|
|
78
|
+
* flat `Segment[]`. The 90%-case convenience over chaining `engine.compile`,
|
|
79
|
+
* `RichText.fromFragments`, and `.render` by hand:
|
|
80
|
+
*
|
|
81
|
+
* - Runs `engine.compile(source)(scope)` to get the engine's `RichText[]`.
|
|
82
|
+
* - Flattens that fragment list into a single styled `RichText` via
|
|
83
|
+
* `RichText.fromFragments` so every fragment's wrapping style survives.
|
|
84
|
+
* - Renders to a `Segment[]` at the requested `maxWidth`.
|
|
85
|
+
* - Wraps the whole flow in a try/catch — on parse/evaluate failure,
|
|
86
|
+
* emits a single dim styled `[error: <message>]` segment the caller can
|
|
87
|
+
* drop into their layout. No bespoke fallback wiring required at every
|
|
88
|
+
* call site.
|
|
89
|
+
*
|
|
90
|
+
* [LAW:single-enforcer] One place owns "render a template to segments,
|
|
91
|
+
* degrade gracefully on errors" — every consumer that wants this exact
|
|
92
|
+
* shape reads from here rather than re-implementing the same try/catch +
|
|
93
|
+
* error-formatting glue.
|
|
94
|
+
*
|
|
95
|
+
* For the 10% — custom error UX, intermediate access to the `RichText`,
|
|
96
|
+
* pre-compiled templates re-used many times — call the engine directly
|
|
97
|
+
* and use `RichText.fromFragments` to flatten. This helper is sugar for
|
|
98
|
+
* the live-render case (e.g. a preview pane), not a replacement for the
|
|
99
|
+
* compile-once-evaluate-many pattern.
|
|
100
|
+
*
|
|
101
|
+
* @param maxWidth defaults to 400 — large enough that downstream `splitLines`
|
|
102
|
+
* / `adjustLineLength` clipping decides actual width, matching the typical
|
|
103
|
+
* "render wide, fit on output" pipeline.
|
|
104
|
+
* @param errorStyle is a `Style.parse` spec (default `"red dim"`).
|
|
105
|
+
*/
|
|
106
|
+
export function renderTemplate(engine, source, scope = {}, options) {
|
|
107
|
+
try {
|
|
108
|
+
const frags = engine.compile(source)(scope);
|
|
109
|
+
const rt = RichText.fromFragments(frags);
|
|
110
|
+
return Array.from(rt.render({
|
|
111
|
+
maxWidth: options?.maxWidth ?? 400,
|
|
112
|
+
isTerminal: true,
|
|
113
|
+
encoding: "utf-8",
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return [new Segment(`[error: ${String(e).slice(0, 80)}]`, safeErrorStyle(options?.errorStyle))];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// [LAW:single-enforcer] The error-path Style must not itself throw, or
|
|
121
|
+
// the whole "degrade gracefully" promise of renderTemplate is broken. A
|
|
122
|
+
// bogus user-supplied `errorStyle` spec falls back to a hard-coded safe
|
|
123
|
+
// Style — never propagates the parse failure to the caller.
|
|
124
|
+
const FALLBACK_ERROR_STYLE = new Style({ color: "red", dim: true });
|
|
125
|
+
function safeErrorStyle(spec) {
|
|
126
|
+
if (spec === undefined)
|
|
127
|
+
return FALLBACK_ERROR_STYLE;
|
|
128
|
+
try {
|
|
129
|
+
return Style.parse(spec);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return FALLBACK_ERROR_STYLE;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
70
135
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/template-bindings/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/template-bindings/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAA6B,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,kBAAkB,EAAE,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,YAAY,CAAW;QAC5B,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC;QAClC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK;QAC1B,KAAK,EAAE,aAAa,EAAE;KACvB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAwB,EACxB,MAAc,EACd,QAAiB,EAAE,EACnB,OAAoD;IAEpD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;YAC1B,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,GAAG;YAClC,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,OAAO,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAClG,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,wEAAwE;AACxE,wEAAwE;AACxE,4DAA4D;AAC5D,MAAM,oBAAoB,GAAG,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AACpE,SAAS,cAAc,CAAC,IAAwB;IAC9C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,oBAAoB,CAAC;IACpD,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,oBAAoB,CAAC;IAC9B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette / theme / auto-contrast function registrations for the rich-js
|
|
3
|
+
* template binding.
|
|
4
|
+
*
|
|
5
|
+
* [LAW:one-source-of-truth] All palette resolution flows through the existing
|
|
6
|
+
* `PaletteResolver.resolve(spec, ctx)` API — this module adds no resolution
|
|
7
|
+
* logic of its own. The template functions here are thin adaptors that translate
|
|
8
|
+
* function call arguments into a `(spec, ctx)` call and wrap the resulting
|
|
9
|
+
* `ColorRgba` as a `Style` applied to the child fragment.
|
|
10
|
+
*
|
|
11
|
+
* [LAW:dataflow-not-control-flow] Every function follows the same shape:
|
|
12
|
+
* resolve spec → apply as foreground color → return styled fragment. The
|
|
13
|
+
* variability is in the spec string and the optional against context; the
|
|
14
|
+
* operation is fixed.
|
|
15
|
+
*
|
|
16
|
+
* ### Surface design rationale
|
|
17
|
+
*
|
|
18
|
+
* Four layers of ergonomics:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Semantic-name functions** — one function per palette variable whose
|
|
21
|
+
* name is a valid Go template identifier (no hyphens). `{{ primary child }}`,
|
|
22
|
+
* `{{ accent child }}`, etc. The happy path: common semantic names read
|
|
23
|
+
* cleanly in templates. Hyphenated names (`primary-muted`, `text-primary`)
|
|
24
|
+
* cannot be Go template identifiers and are accessed via `palette`.
|
|
25
|
+
*
|
|
26
|
+
* 2. **`palette "spec" child`** — the general-purpose function for any spec
|
|
27
|
+
* that does not need a background context (bare names and darken/lighten
|
|
28
|
+
* modifiers). Covers hyphenated names and modifier chains.
|
|
29
|
+
*
|
|
30
|
+
* 3. **`paletteOver "spec" "#bgHex" child`** — for specs that require a
|
|
31
|
+
* background context: alpha compositing (`"primary 50%"`) and auto-contrast
|
|
32
|
+
* (`"auto"`, `"auto 33%"`). The bg color is threaded as an explicit hex
|
|
33
|
+
* argument rather than via a scope side-channel — no mutable state, no
|
|
34
|
+
* closure magic; the data flows through the function call.
|
|
35
|
+
*
|
|
36
|
+
* 4. **`auto "#bgHex" child`** — syntactic sugar for
|
|
37
|
+
* `{{ paletteOver "auto" "#bgHex" child }}`, the common auto-contrast case.
|
|
38
|
+
*
|
|
39
|
+
* ### Theme switching
|
|
40
|
+
*
|
|
41
|
+
* `paletteFuncs(resolver)` captures `resolver` at construction time. Consumers
|
|
42
|
+
* that need runtime theme switching create a new `paletteFuncs()` from the new
|
|
43
|
+
* theme's resolver and rebuild their engine (or merge into a fresh `FuncMap`).
|
|
44
|
+
* The same template *source* produces different colors because the functions in
|
|
45
|
+
* the engine changed — the template text is the same, the resolver differs.
|
|
46
|
+
*/
|
|
47
|
+
import type { FuncMap } from "@promptctl/go-template-js";
|
|
48
|
+
import { PaletteResolver } from "../themes/paletteResolver.js";
|
|
49
|
+
/**
|
|
50
|
+
* Build a `FuncMap` exposing the semantic palette of `resolver` as template
|
|
51
|
+
* functions. Merge into `richTextFuncs()` (or pass to `createEngine`) to make
|
|
52
|
+
* palette colors available in templates.
|
|
53
|
+
*
|
|
54
|
+
* Registered functions:
|
|
55
|
+
* - One function per palette variable whose name is a valid Go template
|
|
56
|
+
* identifier: `{{ primary child }}`, `{{ accent child }}`, etc.
|
|
57
|
+
* - `palette "spec" child` — any spec without background context.
|
|
58
|
+
* - `paletteOver "spec" "#bgHex" child` — any spec needing a background (alpha,
|
|
59
|
+
* auto-contrast).
|
|
60
|
+
* - `auto "#bgHex" child` — sugar for `paletteOver "auto" bgHex child`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createEngine } from "@promptctl/go-template-js";
|
|
65
|
+
* import { GRUVBOX, PaletteResolver, RichText } from "rich-js";
|
|
66
|
+
* import { richTextFuncs, paletteFuncs } from "rich-js/template-bindings";
|
|
67
|
+
*
|
|
68
|
+
* const engine = createEngine({
|
|
69
|
+
* fromString: (s) => new RichText(s),
|
|
70
|
+
* toString: (rt) => rt.plain,
|
|
71
|
+
* funcs: { ...richTextFuncs(), ...paletteFuncs(new PaletteResolver(GRUVBOX.palette)) },
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function paletteFuncs(resolver: PaletteResolver): FuncMap;
|
|
76
|
+
//# sourceMappingURL=palette-funcs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"palette-funcs.d.ts","sourceRoot":"","sources":["../../src/template-bindings/palette-funcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,2BAA2B,CAAC;AAIvE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAiG/D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAO/D"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette / theme / auto-contrast function registrations for the rich-js
|
|
3
|
+
* template binding.
|
|
4
|
+
*
|
|
5
|
+
* [LAW:one-source-of-truth] All palette resolution flows through the existing
|
|
6
|
+
* `PaletteResolver.resolve(spec, ctx)` API — this module adds no resolution
|
|
7
|
+
* logic of its own. The template functions here are thin adaptors that translate
|
|
8
|
+
* function call arguments into a `(spec, ctx)` call and wrap the resulting
|
|
9
|
+
* `ColorRgba` as a `Style` applied to the child fragment.
|
|
10
|
+
*
|
|
11
|
+
* [LAW:dataflow-not-control-flow] Every function follows the same shape:
|
|
12
|
+
* resolve spec → apply as foreground color → return styled fragment. The
|
|
13
|
+
* variability is in the spec string and the optional against context; the
|
|
14
|
+
* operation is fixed.
|
|
15
|
+
*
|
|
16
|
+
* ### Surface design rationale
|
|
17
|
+
*
|
|
18
|
+
* Four layers of ergonomics:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Semantic-name functions** — one function per palette variable whose
|
|
21
|
+
* name is a valid Go template identifier (no hyphens). `{{ primary child }}`,
|
|
22
|
+
* `{{ accent child }}`, etc. The happy path: common semantic names read
|
|
23
|
+
* cleanly in templates. Hyphenated names (`primary-muted`, `text-primary`)
|
|
24
|
+
* cannot be Go template identifiers and are accessed via `palette`.
|
|
25
|
+
*
|
|
26
|
+
* 2. **`palette "spec" child`** — the general-purpose function for any spec
|
|
27
|
+
* that does not need a background context (bare names and darken/lighten
|
|
28
|
+
* modifiers). Covers hyphenated names and modifier chains.
|
|
29
|
+
*
|
|
30
|
+
* 3. **`paletteOver "spec" "#bgHex" child`** — for specs that require a
|
|
31
|
+
* background context: alpha compositing (`"primary 50%"`) and auto-contrast
|
|
32
|
+
* (`"auto"`, `"auto 33%"`). The bg color is threaded as an explicit hex
|
|
33
|
+
* argument rather than via a scope side-channel — no mutable state, no
|
|
34
|
+
* closure magic; the data flows through the function call.
|
|
35
|
+
*
|
|
36
|
+
* 4. **`auto "#bgHex" child`** — syntactic sugar for
|
|
37
|
+
* `{{ paletteOver "auto" "#bgHex" child }}`, the common auto-contrast case.
|
|
38
|
+
*
|
|
39
|
+
* ### Theme switching
|
|
40
|
+
*
|
|
41
|
+
* `paletteFuncs(resolver)` captures `resolver` at construction time. Consumers
|
|
42
|
+
* that need runtime theme switching create a new `paletteFuncs()` from the new
|
|
43
|
+
* theme's resolver and rebuild their engine (or merge into a fresh `FuncMap`).
|
|
44
|
+
* The same template *source* produces different colors because the functions in
|
|
45
|
+
* the engine changed — the template text is the same, the resolver differs.
|
|
46
|
+
*/
|
|
47
|
+
import { ColorSpec } from "../core/color.js";
|
|
48
|
+
import { Style } from "../core/style.js";
|
|
49
|
+
import { applyStyleToFragment } from "./helpers.js";
|
|
50
|
+
// Regex for validating hex bg strings accepted by `paletteOver` and `auto`.
|
|
51
|
+
// Accepts #RRGGBB and #RRGGBBAA — same gate as the `hex` style function.
|
|
52
|
+
const HEX_BG_RE = /^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;
|
|
53
|
+
/**
|
|
54
|
+
* Convert a hex string to `ColorRgba`. Only #RRGGBB / #RRGGBBAA accepted.
|
|
55
|
+
* Throws `RangeError` on invalid input — surfaces as an `EvalError` in the
|
|
56
|
+
* template engine, giving the author a precise failure message.
|
|
57
|
+
*/
|
|
58
|
+
function hexToColorRgba(hex) {
|
|
59
|
+
if (!HEX_BG_RE.test(hex)) {
|
|
60
|
+
throw new RangeError(`palette background expected #RRGGBB or #RRGGBBAA, got ${JSON.stringify(hex)}`);
|
|
61
|
+
}
|
|
62
|
+
// ColorSpec.parse("#RRGGBB") → TRUECOLOR spec; getTruecolor() returns value directly.
|
|
63
|
+
return ColorSpec.parse(hex).getTruecolor();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve `spec` against the resolver (with optional background context) and
|
|
67
|
+
* apply the resulting color as a foreground `Style` on `child`.
|
|
68
|
+
*
|
|
69
|
+
* Throws on resolution failure with a contextual message — including a hint to
|
|
70
|
+
* use `paletteOver` when the spec needs a background but none was provided.
|
|
71
|
+
*/
|
|
72
|
+
function resolveAndApply(resolver, spec, against, child) {
|
|
73
|
+
const color = resolver.resolve(spec, against !== undefined ? { against } : undefined);
|
|
74
|
+
if (color === null) {
|
|
75
|
+
const hint = against === undefined
|
|
76
|
+
? "; for specs with alpha or auto-contrast, use paletteOver"
|
|
77
|
+
: "";
|
|
78
|
+
throw new Error(`palette spec ${JSON.stringify(spec)} did not resolve — check the spec string is valid and the variable exists${hint}`);
|
|
79
|
+
}
|
|
80
|
+
return applyStyleToFragment(child, new Style({ color: ColorSpec.fromRgba(color) }));
|
|
81
|
+
}
|
|
82
|
+
// [LAW:types-are-the-program] Only palette var names that are valid Go template
|
|
83
|
+
// identifiers (letter/underscore start, alphanumeric/underscore body) get
|
|
84
|
+
// individual functions. Hyphenated names like "primary-muted" parse as subtraction
|
|
85
|
+
// in Go template syntax and cannot be function names.
|
|
86
|
+
const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
87
|
+
function semanticNameFuncs(resolver) {
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const name of resolver.palette.vars.keys()) {
|
|
90
|
+
if (!IDENTIFIER_RE.test(name))
|
|
91
|
+
continue;
|
|
92
|
+
// [LAW:dataflow-not-control-flow] Capture name; same fn shape for every entry.
|
|
93
|
+
const captured = name;
|
|
94
|
+
out[captured] = {
|
|
95
|
+
fn: ((child) => resolveAndApply(resolver, captured, undefined, child)),
|
|
96
|
+
argTypes: ["liftable"],
|
|
97
|
+
returnType: "T",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function makePaletteFunc(resolver) {
|
|
103
|
+
return {
|
|
104
|
+
fn: ((spec, child) => resolveAndApply(resolver, spec, undefined, child)),
|
|
105
|
+
argTypes: ["string", "liftable"],
|
|
106
|
+
returnType: "T",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function makePaletteOverFunc(resolver) {
|
|
110
|
+
return {
|
|
111
|
+
fn: ((spec, bgHex, child) => resolveAndApply(resolver, spec, hexToColorRgba(bgHex), child)),
|
|
112
|
+
argTypes: ["string", "string", "liftable"],
|
|
113
|
+
returnType: "T",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function makeAutoFunc(resolver) {
|
|
117
|
+
return {
|
|
118
|
+
fn: ((bgHex, child) => resolveAndApply(resolver, "auto", hexToColorRgba(bgHex), child)),
|
|
119
|
+
argTypes: ["string", "liftable"],
|
|
120
|
+
returnType: "T",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Build a `FuncMap` exposing the semantic palette of `resolver` as template
|
|
125
|
+
* functions. Merge into `richTextFuncs()` (or pass to `createEngine`) to make
|
|
126
|
+
* palette colors available in templates.
|
|
127
|
+
*
|
|
128
|
+
* Registered functions:
|
|
129
|
+
* - One function per palette variable whose name is a valid Go template
|
|
130
|
+
* identifier: `{{ primary child }}`, `{{ accent child }}`, etc.
|
|
131
|
+
* - `palette "spec" child` — any spec without background context.
|
|
132
|
+
* - `paletteOver "spec" "#bgHex" child` — any spec needing a background (alpha,
|
|
133
|
+
* auto-contrast).
|
|
134
|
+
* - `auto "#bgHex" child` — sugar for `paletteOver "auto" bgHex child`.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* import { createEngine } from "@promptctl/go-template-js";
|
|
139
|
+
* import { GRUVBOX, PaletteResolver, RichText } from "rich-js";
|
|
140
|
+
* import { richTextFuncs, paletteFuncs } from "rich-js/template-bindings";
|
|
141
|
+
*
|
|
142
|
+
* const engine = createEngine({
|
|
143
|
+
* fromString: (s) => new RichText(s),
|
|
144
|
+
* toString: (rt) => rt.plain,
|
|
145
|
+
* funcs: { ...richTextFuncs(), ...paletteFuncs(new PaletteResolver(GRUVBOX.palette)) },
|
|
146
|
+
* });
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export function paletteFuncs(resolver) {
|
|
150
|
+
return {
|
|
151
|
+
...semanticNameFuncs(resolver),
|
|
152
|
+
palette: makePaletteFunc(resolver),
|
|
153
|
+
paletteOver: makePaletteOverFunc(resolver),
|
|
154
|
+
auto: makeAutoFunc(resolver),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=palette-funcs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"palette-funcs.js","sourceRoot":"","sources":["../../src/template-bindings/palette-funcs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-funcs.d.ts","sourceRoot":"","sources":["../../src/template-bindings/style-funcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"style-funcs.d.ts","sourceRoot":"","sources":["../../src/template-bindings/style-funcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,2BAA2B,CAAC;AAqKvE;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAW5C"}
|
|
@@ -18,33 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { Style, ATTRIBUTE_NAMES, ATTRIBUTE_SHORT_ALIASES, } from "../core/style.js";
|
|
20
20
|
import { ColorSpec, ANSI_COLOR_NAMES } from "../core/color.js";
|
|
21
|
-
import {
|
|
22
|
-
// --- Helpers ---
|
|
23
|
-
/**
|
|
24
|
-
* Apply a style on top of an already-styled RichText fragment.
|
|
25
|
-
*
|
|
26
|
-
* The engine's `"liftable"` arg type lifts string literals to
|
|
27
|
-
* `RichText` via the binding's `fromString` *before* the body runs,
|
|
28
|
-
* so by the time we get here, `child` is always a `RichText`. The
|
|
29
|
-
* defensive instanceof check exists because `"liftable"` admits any
|
|
30
|
-
* non-primitive value — the engine cannot prove the object is the
|
|
31
|
-
* binding's own `T`. A misuse (`{{ red someMap }}`) lands here and
|
|
32
|
-
* fails loudly rather than producing a malformed fragment.
|
|
33
|
-
*
|
|
34
|
-
* Conflict resolution follows `Style.add`: the outer (newly applied)
|
|
35
|
-
* style wins. `red (bold "x")` → bold + red; `red (red "x")` → red.
|
|
36
|
-
* Spans inside the inner fragment are preserved as-is — their styles
|
|
37
|
-
* compose with the new wrapper at render time via the segment
|
|
38
|
-
* pipeline's existing additive semantics.
|
|
39
|
-
*/
|
|
40
|
-
function applyStyleToFragment(child, style) {
|
|
41
|
-
if (!(child instanceof RichText)) {
|
|
42
|
-
throw new TypeError(`style function expected a RichText fragment, got ${typeof child === "object" ? Object.prototype.toString.call(child) : typeof child}`);
|
|
43
|
-
}
|
|
44
|
-
const result = child.copy();
|
|
45
|
-
result.style = child.style.add(style);
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
21
|
+
import { applyStyleToFragment } from "./helpers.js";
|
|
48
22
|
function fgFunc(style) {
|
|
49
23
|
return {
|
|
50
24
|
fn: ((child) => applyStyleToFragment(child, style)),
|
|
@@ -123,6 +97,37 @@ function attributeFuncs() {
|
|
|
123
97
|
}
|
|
124
98
|
return out;
|
|
125
99
|
}
|
|
100
|
+
// --- Style spec (multi-attribute one-shot) ---
|
|
101
|
+
//
|
|
102
|
+
// [LAW:one-source-of-truth] `style` accepts the same space-separated grammar
|
|
103
|
+
// `Style.parse` consults — i.e. the inside of `[...]` markup. There is no
|
|
104
|
+
// second parser: a spec that `Style.parse` accepts produces a fragment
|
|
105
|
+
// byte-equivalent to the same spec inside markup, and one that `Style.parse`
|
|
106
|
+
// rejects raises the same `StyleSyntaxError` surface.
|
|
107
|
+
//
|
|
108
|
+
// Motivation: the per-attribute functions (`bold`, `underline`, `hex`, …)
|
|
109
|
+
// compose by nesting. For "apply a fixed set of styles to this child" or
|
|
110
|
+
// "apply this named style set everywhere", nesting is awkward and the
|
|
111
|
+
// style description is fragmented across multiple call sites. `style`
|
|
112
|
+
// collapses that to a single call, and because the spec is a string it
|
|
113
|
+
// flows through Go-template `$vars` and through scope without further
|
|
114
|
+
// machinery:
|
|
115
|
+
//
|
|
116
|
+
// {{ $alert := "bold underline #ff6b6b" }}
|
|
117
|
+
// {{ style $alert "alarm!" }}
|
|
118
|
+
// {{ style $alert .otherField }}
|
|
119
|
+
//
|
|
120
|
+
// [LAW:dataflow-not-control-flow] Same shape as every other style function:
|
|
121
|
+
// a `Style` value (here built by `Style.parse(spec)`) plus a child, in,
|
|
122
|
+
// styled child out. The variability is the spec string; the operation is
|
|
123
|
+
// fixed.
|
|
124
|
+
const styleSpecFunc = {
|
|
125
|
+
fn: ((spec, child) => {
|
|
126
|
+
return applyStyleToFragment(child, Style.parse(spec));
|
|
127
|
+
}),
|
|
128
|
+
argTypes: ["string", "liftable"],
|
|
129
|
+
returnType: "T",
|
|
130
|
+
};
|
|
126
131
|
// --- Hyperlink ---
|
|
127
132
|
//
|
|
128
133
|
// `link` is the cell-splitter for the multi-cell consumer contract.
|
|
@@ -167,6 +172,7 @@ export function richTextStyleFuncs() {
|
|
|
167
172
|
on: onFunc,
|
|
168
173
|
...attributeFuncs(),
|
|
169
174
|
link: linkFunc,
|
|
175
|
+
style: styleSpecFunc,
|
|
170
176
|
};
|
|
171
177
|
}
|
|
172
178
|
//# sourceMappingURL=style-funcs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-funcs.js","sourceRoot":"","sources":["../../src/template-bindings/style-funcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EACL,KAAK,EACL,eAAe,EACf,uBAAuB,GAExB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"style-funcs.js","sourceRoot":"","sources":["../../src/template-bindings/style-funcs.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,4 +20,44 @@ export declare function alphaBlend(fg: ColorRgba, bg: ColorRgba, alpha: number):
|
|
|
20
20
|
* where black and white are equally readable).
|
|
21
21
|
*/
|
|
22
22
|
export declare function contrastFor(bg: ColorRgba): ColorRgba;
|
|
23
|
+
/**
|
|
24
|
+
* WCAG 2.x relative luminance (0..1) of an opaque color. The single
|
|
25
|
+
* luminance function in the codebase — `contrastFor`, `contrastRatio`, and
|
|
26
|
+
* any caller that needs to reason about readability all funnel through it.
|
|
27
|
+
* [LAW:one-source-of-truth]
|
|
28
|
+
*/
|
|
29
|
+
export declare function relativeLuminance(c: ColorRgba): number;
|
|
30
|
+
/**
|
|
31
|
+
* WCAG 2.x contrast ratio between two colors, in [1, 21]. Symmetric — the
|
|
32
|
+
* order of arguments does not matter. 4.5 is the AA threshold for normal
|
|
33
|
+
* text, 3.0 for large text.
|
|
34
|
+
*
|
|
35
|
+
* Assumes opaque inputs: alpha is ignored, since the displayed contrast of a
|
|
36
|
+
* translucent color depends on what it composites over. For a translucent
|
|
37
|
+
* foreground, flatten it first (or use `ensureContrast`, which does).
|
|
38
|
+
*/
|
|
39
|
+
export declare function contrastRatio(a: ColorRgba, b: ColorRgba): number;
|
|
40
|
+
/**
|
|
41
|
+
* Return a foreground guaranteed to clear `minRatio` against `bg`, keeping the
|
|
42
|
+
* color *recognizably itself*. If the themed `fg` already passes it is returned
|
|
43
|
+
* untouched. Otherwise its OKLCH lightness is slid toward the pole that raises
|
|
44
|
+
* contrast — holding hue, and chroma where it stays in gamut (near the poles
|
|
45
|
+
* gamut clamping may reduce chroma, but hue is preserved) — until the ratio is
|
|
46
|
+
* met, so a blue on a dark-blue background becomes a lighter blue, not white.
|
|
47
|
+
* Only when no lightness of that hue can meet the ratio (a mid-toned
|
|
48
|
+
* background where even pure black-or-white tops out below the target) does it
|
|
49
|
+
* fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
|
|
50
|
+
*
|
|
51
|
+
* A translucent `fg` is flattened over `bg` first (the displayed color is
|
|
52
|
+
* `fg` composited over `bg`), so the ratio is measured on what the eye
|
|
53
|
+
* actually sees and the returned color is opaque. `bg` is treated as the
|
|
54
|
+
* opaque substrate.
|
|
55
|
+
*
|
|
56
|
+
* [LAW:single-enforcer] The one place "is this text readable, and if not fix
|
|
57
|
+
* it" is decided. Callers route every fg/bg pair through here and the
|
|
58
|
+
* unreadable state never reaches output. [LAW:dataflow-not-control-flow] the
|
|
59
|
+
* function always runs; the measured ratio (data) decides how far the
|
|
60
|
+
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
61
|
+
*/
|
|
62
|
+
export declare function ensureContrast(fg: ColorRgba, bg: ColorRgba, minRatio?: number): ColorRgba;
|
|
23
63
|
//# sourceMappingURL=colorMath.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colorMath.d.ts","sourceRoot":"","sources":["../../src/themes/colorMath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"colorMath.d.ts","sourceRoot":"","sources":["../../src/themes/colorMath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,kBAAkB,CAAC;AAoEvD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAIlE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAEnE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,MAAM,GACZ,SAAS,CAEX;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAKpD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAMtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,CAMhE;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,SAAS,EACb,QAAQ,SAAM,GACb,SAAS,CA+BX"}
|
package/dist/themes/colorMath.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ColorRgba, blendRgb } from "../core/color.js";
|
|
2
|
+
import { Oklch } from "../core/oklch.js";
|
|
2
3
|
const LEVEL_STEP = 0.1;
|
|
3
4
|
function rgbToHsl(c) {
|
|
4
5
|
const r = c.red / 255;
|
|
@@ -89,11 +90,91 @@ export function contrastFor(bg) {
|
|
|
89
90
|
? new ColorRgba(0, 0, 0)
|
|
90
91
|
: new ColorRgba(255, 255, 255);
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
/**
|
|
94
|
+
* WCAG 2.x relative luminance (0..1) of an opaque color. The single
|
|
95
|
+
* luminance function in the codebase — `contrastFor`, `contrastRatio`, and
|
|
96
|
+
* any caller that needs to reason about readability all funnel through it.
|
|
97
|
+
* [LAW:one-source-of-truth]
|
|
98
|
+
*/
|
|
99
|
+
export function relativeLuminance(c) {
|
|
93
100
|
const ch = (v) => {
|
|
94
101
|
const x = v / 255;
|
|
95
102
|
return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
|
|
96
103
|
};
|
|
97
104
|
return 0.2126 * ch(c.red) + 0.7152 * ch(c.green) + 0.0722 * ch(c.blue);
|
|
98
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* WCAG 2.x contrast ratio between two colors, in [1, 21]. Symmetric — the
|
|
108
|
+
* order of arguments does not matter. 4.5 is the AA threshold for normal
|
|
109
|
+
* text, 3.0 for large text.
|
|
110
|
+
*
|
|
111
|
+
* Assumes opaque inputs: alpha is ignored, since the displayed contrast of a
|
|
112
|
+
* translucent color depends on what it composites over. For a translucent
|
|
113
|
+
* foreground, flatten it first (or use `ensureContrast`, which does).
|
|
114
|
+
*/
|
|
115
|
+
export function contrastRatio(a, b) {
|
|
116
|
+
const la = relativeLuminance(a);
|
|
117
|
+
const lb = relativeLuminance(b);
|
|
118
|
+
const hi = la > lb ? la : lb;
|
|
119
|
+
const lo = la > lb ? lb : la;
|
|
120
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
121
|
+
}
|
|
122
|
+
// Iterations for the lightness bisection below. 20 resolves L to ~1e-6 — far
|
|
123
|
+
// finer than 8-bit quantization or the eye.
|
|
124
|
+
const CONTRAST_ITERS = 20;
|
|
125
|
+
/**
|
|
126
|
+
* Return a foreground guaranteed to clear `minRatio` against `bg`, keeping the
|
|
127
|
+
* color *recognizably itself*. If the themed `fg` already passes it is returned
|
|
128
|
+
* untouched. Otherwise its OKLCH lightness is slid toward the pole that raises
|
|
129
|
+
* contrast — holding hue, and chroma where it stays in gamut (near the poles
|
|
130
|
+
* gamut clamping may reduce chroma, but hue is preserved) — until the ratio is
|
|
131
|
+
* met, so a blue on a dark-blue background becomes a lighter blue, not white.
|
|
132
|
+
* Only when no lightness of that hue can meet the ratio (a mid-toned
|
|
133
|
+
* background where even pure black-or-white tops out below the target) does it
|
|
134
|
+
* fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
|
|
135
|
+
*
|
|
136
|
+
* A translucent `fg` is flattened over `bg` first (the displayed color is
|
|
137
|
+
* `fg` composited over `bg`), so the ratio is measured on what the eye
|
|
138
|
+
* actually sees and the returned color is opaque. `bg` is treated as the
|
|
139
|
+
* opaque substrate.
|
|
140
|
+
*
|
|
141
|
+
* [LAW:single-enforcer] The one place "is this text readable, and if not fix
|
|
142
|
+
* it" is decided. Callers route every fg/bg pair through here and the
|
|
143
|
+
* unreadable state never reaches output. [LAW:dataflow-not-control-flow] the
|
|
144
|
+
* function always runs; the measured ratio (data) decides how far the
|
|
145
|
+
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
146
|
+
*/
|
|
147
|
+
export function ensureContrast(fg, bg, minRatio = 4.5) {
|
|
148
|
+
// Flatten translucency so the guarantee holds for the displayed color, not
|
|
149
|
+
// the raw bytes (e.g. a "#FFFFFF60" text-disabled over a light surface).
|
|
150
|
+
const opaqueFg = fg.compositeOver(bg);
|
|
151
|
+
if (contrastRatio(opaqueFg, bg) >= minRatio)
|
|
152
|
+
return opaqueFg;
|
|
153
|
+
const lab = Oklch.fromRgba(opaqueFg);
|
|
154
|
+
// The pole that increases contrast: lighten toward white on a dark bg, darken
|
|
155
|
+
// toward black on a light one. `contrastFor`'s 0.179 cutoff names it.
|
|
156
|
+
const poleL = relativeLuminance(bg) > 0.179 ? 0 : 1;
|
|
157
|
+
// If even the pole of this hue can't reach the ratio, the hue physically
|
|
158
|
+
// can't — return the true maximum-contrast pick (pure black/white from
|
|
159
|
+
// contrastFor). The gamut-clamped OKLCH pole is only *near* b/w, so
|
|
160
|
+
// contrastFor is at least as strong and is the honest maximum.
|
|
161
|
+
const pole = new Oklch(poleL, lab.c, lab.h, lab.alpha).toRgba();
|
|
162
|
+
if (contrastRatio(pole, bg) < minRatio)
|
|
163
|
+
return contrastFor(bg);
|
|
164
|
+
// Bisect for the lightness nearest the original that still clears the ratio:
|
|
165
|
+
// the smallest perceptual change that achieves accessibility. Contrast is
|
|
166
|
+
// monotone in L over [lab.l, poleL] (everything below the crossing fails),
|
|
167
|
+
// so the search is well-posed.
|
|
168
|
+
let fail = lab.l;
|
|
169
|
+
let pass = poleL;
|
|
170
|
+
for (let i = 0; i < CONTRAST_ITERS; i++) {
|
|
171
|
+
const mid = (fail + pass) / 2;
|
|
172
|
+
const candidate = new Oklch(mid, lab.c, lab.h, lab.alpha).toRgba();
|
|
173
|
+
if (contrastRatio(candidate, bg) >= minRatio)
|
|
174
|
+
pass = mid;
|
|
175
|
+
else
|
|
176
|
+
fail = mid;
|
|
177
|
+
}
|
|
178
|
+
return new Oklch(pass, lab.c, lab.h, lab.alpha).toRgba();
|
|
179
|
+
}
|
|
99
180
|
//# sourceMappingURL=colorMath.js.map
|