@promptctl/rich-js 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/core/anchor.d.ts +45 -0
- package/dist/core/anchor.js +39 -0
- package/dist/core/ansi.d.ts +65 -0
- package/dist/core/ansi.js +231 -0
- package/dist/core/box.d.ts +145 -0
- package/dist/core/box.js +372 -0
- package/dist/core/cells.d.ts +192 -0
- package/dist/core/cells.js +334 -0
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +326 -0
- package/dist/core/color.js +1225 -0
- package/dist/core/console.d.ts +196 -0
- package/dist/core/console.js +596 -0
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/emoji.d.ts +24 -0
- package/dist/core/emoji.js +391 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.d.ts +59 -0
- package/dist/core/export-html.js +197 -0
- package/dist/core/export-lines.d.ts +130 -0
- package/dist/core/export-lines.js +159 -0
- package/dist/core/export-svg.d.ts +49 -0
- package/dist/core/export-svg.js +213 -0
- package/dist/core/fnv1a.d.ts +10 -0
- package/dist/core/fnv1a.js +17 -0
- package/dist/core/highlighter.d.ts +33 -0
- package/dist/core/highlighter.js +190 -0
- package/dist/core/json.d.ts +27 -0
- package/dist/core/json.js +53 -0
- package/dist/core/markup.d.ts +140 -0
- package/dist/core/markup.js +654 -0
- package/dist/core/measure.d.ts +27 -0
- package/dist/core/measure.js +67 -0
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/oklch.d.ts +161 -0
- package/dist/core/oklch.js +312 -0
- package/dist/core/osc8.d.ts +68 -0
- package/dist/core/osc8.js +78 -0
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +150 -0
- package/dist/core/place.d.ts +30 -0
- package/dist/core/place.js +49 -0
- package/dist/core/pretty.d.ts +247 -0
- package/dist/core/pretty.js +906 -0
- package/dist/core/protocol.d.ts +305 -0
- package/dist/core/protocol.js +183 -0
- package/dist/core/render.d.ts +87 -0
- package/dist/core/render.js +105 -0
- package/dist/core/segment.d.ts +162 -0
- package/dist/core/segment.js +488 -0
- package/dist/core/spinnerData.d.ts +12 -0
- package/dist/core/spinnerData.js +117 -0
- package/dist/core/strip.d.ts +152 -0
- package/dist/core/strip.js +326 -0
- package/dist/core/style.d.ts +189 -0
- package/dist/core/style.js +758 -0
- package/dist/core/subscription.d.ts +12 -0
- package/dist/core/subscription.js +12 -0
- package/dist/core/text.d.ts +283 -0
- package/dist/core/text.js +1097 -0
- package/dist/core/wrap.d.ts +46 -0
- package/dist/core/wrap.js +140 -0
- package/dist/host/app.d.ts +133 -0
- package/dist/host/app.js +285 -0
- package/dist/host/host-environment.d.ts +26 -0
- package/dist/host/host-environment.js +44 -0
- package/dist/host/index.d.ts +6 -0
- package/dist/host/index.js +3 -0
- package/dist/host/terminal-host.d.ts +180 -0
- package/dist/host/terminal-host.js +186 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +114 -0
- package/dist/node/prompt.d.ts +34 -0
- package/dist/node/prompt.js +51 -0
- package/dist/node/save.d.ts +18 -0
- package/dist/node/save.js +24 -0
- package/dist/node/terminal-host.d.ts +114 -0
- package/dist/node/terminal-host.js +227 -0
- package/dist/node/traceback.d.ts +38 -0
- package/dist/node/traceback.js +98 -0
- package/dist/renderables/align.d.ts +16 -0
- package/dist/renderables/align.js +26 -0
- package/dist/renderables/columns.d.ts +77 -0
- package/dist/renderables/columns.js +191 -0
- package/dist/renderables/constrain.d.ts +15 -0
- package/dist/renderables/constrain.js +31 -0
- package/dist/renderables/effect.d.ts +98 -0
- package/dist/renderables/effect.js +160 -0
- package/dist/renderables/embed.d.ts +107 -0
- package/dist/renderables/embed.js +160 -0
- package/dist/renderables/flexStrip.d.ts +41 -0
- package/dist/renderables/flexStrip.js +200 -0
- package/dist/renderables/group.d.ts +14 -0
- package/dist/renderables/group.js +22 -0
- package/dist/renderables/layout.d.ts +75 -0
- package/dist/renderables/layout.js +223 -0
- package/dist/renderables/live.d.ts +74 -0
- package/dist/renderables/live.js +188 -0
- package/dist/renderables/markdown.d.ts +38 -0
- package/dist/renderables/markdown.js +587 -0
- package/dist/renderables/padding.d.ts +59 -0
- package/dist/renderables/padding.js +147 -0
- package/dist/renderables/panel.d.ts +114 -0
- package/dist/renderables/panel.js +303 -0
- package/dist/renderables/progress.d.ts +178 -0
- package/dist/renderables/progress.js +362 -0
- package/dist/renderables/progressBar.d.ts +31 -0
- package/dist/renderables/progressBar.js +64 -0
- package/dist/renderables/prompt.d.ts +71 -0
- package/dist/renderables/prompt.js +165 -0
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +26 -0
- package/dist/renderables/rule.js +86 -0
- package/dist/renderables/spinner.d.ts +57 -0
- package/dist/renderables/spinner.js +103 -0
- package/dist/renderables/status.d.ts +29 -0
- package/dist/renderables/status.js +46 -0
- package/dist/renderables/syntax.d.ts +65 -0
- package/dist/renderables/syntax.js +166 -0
- package/dist/renderables/table.d.ts +232 -0
- package/dist/renderables/table.js +973 -0
- package/dist/renderables/traceback.d.ts +32 -0
- package/dist/renderables/traceback.js +279 -0
- package/dist/renderables/tree.d.ts +76 -0
- package/dist/renderables/tree.js +177 -0
- package/dist/renderables/viewport.d.ts +114 -0
- package/dist/renderables/viewport.js +196 -0
- package/dist/template-bindings/color-funcs.d.ts +109 -0
- package/dist/template-bindings/color-funcs.js +243 -0
- package/dist/template-bindings/helpers.d.ts +26 -0
- package/dist/template-bindings/helpers.js +33 -0
- package/dist/template-bindings/index.d.ts +119 -0
- package/dist/template-bindings/index.js +162 -0
- package/dist/template-bindings/palette-funcs.d.ts +96 -0
- package/dist/template-bindings/palette-funcs.js +145 -0
- package/dist/template-bindings/style-funcs.d.ts +28 -0
- package/dist/template-bindings/style-funcs.js +157 -0
- package/dist/themes/buildPalette.d.ts +57 -0
- package/dist/themes/buildPalette.js +100 -0
- package/dist/themes/colorMath.d.ts +94 -0
- package/dist/themes/colorMath.js +280 -0
- package/dist/themes/colorRef.d.ts +36 -0
- package/dist/themes/colorRef.js +78 -0
- package/dist/themes/data/ansi-tables.d.ts +14 -0
- package/dist/themes/data/ansi-tables.js +49 -0
- package/dist/themes/data/atom-one-dark.d.ts +3 -0
- package/dist/themes/data/atom-one-dark.js +177 -0
- package/dist/themes/data/atom-one-light.d.ts +3 -0
- package/dist/themes/data/atom-one-light.js +177 -0
- package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
- package/dist/themes/data/catppuccin-frappe.js +177 -0
- package/dist/themes/data/catppuccin-latte.d.ts +3 -0
- package/dist/themes/data/catppuccin-latte.js +177 -0
- package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
- package/dist/themes/data/catppuccin-macchiato.js +177 -0
- package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
- package/dist/themes/data/catppuccin-mocha.js +177 -0
- package/dist/themes/data/cyberpunk.d.ts +17 -0
- package/dist/themes/data/cyberpunk.js +193 -0
- package/dist/themes/data/default.d.ts +3 -0
- package/dist/themes/data/default.js +160 -0
- package/dist/themes/data/dracula.d.ts +3 -0
- package/dist/themes/data/dracula.js +177 -0
- package/dist/themes/data/flexoki.d.ts +3 -0
- package/dist/themes/data/flexoki.js +177 -0
- package/dist/themes/data/gruvbox.d.ts +3 -0
- package/dist/themes/data/gruvbox.js +177 -0
- package/dist/themes/data/index.d.ts +35 -0
- package/dist/themes/data/index.js +64 -0
- package/dist/themes/data/monokai.d.ts +3 -0
- package/dist/themes/data/monokai.js +177 -0
- package/dist/themes/data/nord.d.ts +3 -0
- package/dist/themes/data/nord.js +177 -0
- package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
- package/dist/themes/data/rose-pine-dawn.js +177 -0
- package/dist/themes/data/rose-pine-moon.d.ts +3 -0
- package/dist/themes/data/rose-pine-moon.js +177 -0
- package/dist/themes/data/rose-pine.d.ts +3 -0
- package/dist/themes/data/rose-pine.js +177 -0
- package/dist/themes/data/solarized-dark.d.ts +3 -0
- package/dist/themes/data/solarized-dark.js +177 -0
- package/dist/themes/data/solarized-light.d.ts +3 -0
- package/dist/themes/data/solarized-light.js +177 -0
- package/dist/themes/data/svg-export.d.ts +3 -0
- package/dist/themes/data/svg-export.js +177 -0
- package/dist/themes/data/textual-ansi.d.ts +3 -0
- package/dist/themes/data/textual-ansi.js +160 -0
- package/dist/themes/data/textual-dark.d.ts +3 -0
- package/dist/themes/data/textual-dark.js +160 -0
- package/dist/themes/data/textual-light.d.ts +3 -0
- package/dist/themes/data/textual-light.js +160 -0
- package/dist/themes/data/tokyo-night.d.ts +3 -0
- package/dist/themes/data/tokyo-night.js +177 -0
- package/dist/themes/data/types.d.ts +31 -0
- package/dist/themes/data/types.js +10 -0
- package/dist/themes/palette.d.ts +45 -0
- package/dist/themes/palette.js +71 -0
- package/dist/themes/ramp.d.ts +70 -0
- package/dist/themes/ramp.js +103 -0
- package/dist/themes/registry.d.ts +44 -0
- package/dist/themes/registry.js +105 -0
- package/dist/themes/terminalThemes.d.ts +49 -0
- package/dist/themes/terminalThemes.js +63 -0
- package/dist/themes/transpose.d.ts +87 -0
- package/dist/themes/transpose.js +138 -0
- package/dist/widgets/button.d.ts +45 -0
- package/dist/widgets/button.js +177 -0
- package/dist/widgets/checkbox.d.ts +38 -0
- package/dist/widgets/checkbox.js +135 -0
- package/dist/widgets/dropdown.d.ts +64 -0
- package/dist/widgets/dropdown.js +416 -0
- package/dist/widgets/event-router.d.ts +71 -0
- package/dist/widgets/event-router.js +629 -0
- package/dist/widgets/focus-manager.d.ts +45 -0
- package/dist/widgets/focus-manager.js +191 -0
- package/dist/widgets/hit.d.ts +64 -0
- package/dist/widgets/hit.js +113 -0
- package/dist/widgets/index.d.ts +20 -0
- package/dist/widgets/index.js +14 -0
- package/dist/widgets/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +54 -0
- package/dist/widgets/slider.js +247 -0
- package/dist/widgets/static-item.d.ts +34 -0
- package/dist/widgets/static-item.js +54 -0
- package/dist/widgets/text-input.d.ts +283 -0
- package/dist/widgets/text-input.js +1174 -0
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +41 -0
- package/dist/widgets/toggle.js +157 -0
- package/dist/widgets/types.d.ts +87 -0
- package/dist/widgets/types.js +30 -0
- package/dist/widgets/widget-app.d.ts +65 -0
- package/dist/widgets/widget-app.js +206 -0
- package/dist/widgets/widget-base.d.ts +42 -0
- package/dist/widgets/widget-base.js +172 -0
- package/package.json +16 -2
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
arity: { kind: "exact" },
|
|
26
|
+
returnType: "T",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// --- Colour sinks ---
|
|
30
|
+
//
|
|
31
|
+
// `fg` and `bg` are the only two colour-applying functions, and they are the
|
|
32
|
+
// terminal step of the colour pipeline: `color` names a colour, the functions
|
|
33
|
+
// in `color-funcs.ts` transform it, these paint it onto text.
|
|
34
|
+
//
|
|
35
|
+
// [LAW:composability] They replace five separate families — one function per
|
|
36
|
+
// ANSI colour name (`red`, `bright_blue`, …), `hex`, `rgb`, `color` (256-index),
|
|
37
|
+
// and `on` (background). Every one of those encoded *which colour* in the
|
|
38
|
+
// function's identity, so the vocabulary could only grow by adding names, and
|
|
39
|
+
// a colour computed at render time could not be applied at all. Here the colour
|
|
40
|
+
// is an argument, so the sink admits every colour that exists and every colour
|
|
41
|
+
// that will ever exist. Two functions, unbounded reach.
|
|
42
|
+
//
|
|
43
|
+
// [LAW:types-are-the-program] The slot accepts the full `ColorSpec.parse`
|
|
44
|
+
// vocabulary, which is wider than the hex the colour math produces — and that
|
|
45
|
+
// width is deliberate, not laxity. `#7aa2f7` is a *concrete* colour; `"red"`
|
|
46
|
+
// and `"color(42)"` are *symbolic* ones the terminal resolves against its own
|
|
47
|
+
// theme. Only concrete colours can be darkened or blended, which is why the
|
|
48
|
+
// colour-math functions take hex alone; but both kinds can be painted, so the
|
|
49
|
+
// sink takes the union. The type of each slot is exactly the set of values that
|
|
50
|
+
// slot can mean something for.
|
|
51
|
+
function colorSinkFunc(slot) {
|
|
52
|
+
return {
|
|
53
|
+
fn: ((spec, child) => applyStyleToFragment(child, new Style({ [slot]: ColorSpec.parse(spec) }))),
|
|
54
|
+
argTypes: ["string", "liftable"],
|
|
55
|
+
arity: { kind: "exact" },
|
|
56
|
+
returnType: "T",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// --- Text attributes ---
|
|
60
|
+
//
|
|
61
|
+
// [LAW:one-source-of-truth] The attribute and short-alias inventories
|
|
62
|
+
// come from `core/style.ts`. This file derives the template registration
|
|
63
|
+
// set from the same data `Style.parse` consults — adding an attribute or
|
|
64
|
+
// alias in one place propagates here automatically.
|
|
65
|
+
function attrStyle(name, value) {
|
|
66
|
+
return new Style({ [name]: value });
|
|
67
|
+
}
|
|
68
|
+
function attributeFuncs() {
|
|
69
|
+
const out = {};
|
|
70
|
+
for (const name of ATTRIBUTE_NAMES) {
|
|
71
|
+
out[name] = attrFunc(attrStyle(name, true));
|
|
72
|
+
out[`not_${name}`] = attrFunc(attrStyle(name, false));
|
|
73
|
+
}
|
|
74
|
+
for (const [alias, canonical] of Object.entries(ATTRIBUTE_SHORT_ALIASES)) {
|
|
75
|
+
out[alias] = attrFunc(attrStyle(canonical, true));
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
// --- Style spec (multi-attribute one-shot) ---
|
|
80
|
+
//
|
|
81
|
+
// [LAW:one-source-of-truth] `style` accepts the same space-separated grammar
|
|
82
|
+
// `Style.parse` consults — i.e. the inside of `[...]` markup. There is no
|
|
83
|
+
// second parser: a spec that `Style.parse` accepts produces a fragment
|
|
84
|
+
// byte-equivalent to the same spec inside markup, and one that `Style.parse`
|
|
85
|
+
// rejects raises the same `StyleSyntaxError` surface.
|
|
86
|
+
//
|
|
87
|
+
// Motivation: the per-attribute functions (`bold`, `underline`, `fg`, …)
|
|
88
|
+
// compose by nesting. For "apply a fixed set of styles to this child" or
|
|
89
|
+
// "apply this named style set everywhere", nesting is awkward and the
|
|
90
|
+
// style description is fragmented across multiple call sites. `style`
|
|
91
|
+
// collapses that to a single call, and because the spec is a string it
|
|
92
|
+
// flows through Go-template `$vars` and through scope without further
|
|
93
|
+
// machinery:
|
|
94
|
+
//
|
|
95
|
+
// {{ $alert := "bold underline #ff6b6b" }}
|
|
96
|
+
// {{ style $alert "alarm!" }}
|
|
97
|
+
// {{ style $alert .otherField }}
|
|
98
|
+
//
|
|
99
|
+
// [LAW:dataflow-not-control-flow] Same shape as every other style function:
|
|
100
|
+
// a `Style` value (here built by `Style.parse(spec)`) plus a child, in,
|
|
101
|
+
// styled child out. The variability is the spec string; the operation is
|
|
102
|
+
// fixed.
|
|
103
|
+
const styleSpecFunc = {
|
|
104
|
+
fn: ((spec, child) => {
|
|
105
|
+
return applyStyleToFragment(child, Style.parse(spec));
|
|
106
|
+
}),
|
|
107
|
+
argTypes: ["string", "liftable"],
|
|
108
|
+
arity: { kind: "exact" },
|
|
109
|
+
returnType: "T",
|
|
110
|
+
};
|
|
111
|
+
// --- Hyperlink ---
|
|
112
|
+
//
|
|
113
|
+
// `link` is the cell-splitter for the multi-cell consumer contract.
|
|
114
|
+
// Implementation-wise it is the same shape as any other style function:
|
|
115
|
+
// it sets the `link` slot of `Style` exactly as the existing string-form
|
|
116
|
+
// `link URL` does, so `{{ link "u" "x" }}` is byte-equivalent to
|
|
117
|
+
// `RichText("x", { style: Style.parse("link u") })`.
|
|
118
|
+
//
|
|
119
|
+
// The cell-boundary signal that consumers (cc-candybar et al.) walk is
|
|
120
|
+
// `theme.resolve(fragment.style).link` being truthy: a fragment whose base
|
|
121
|
+
// style is a string keeps the link in a `LayeredStyle` until a theme says
|
|
122
|
+
// what the string stands for. `Style.add` propagates `link`
|
|
123
|
+
// through any outer wrapping call, so `{{ fg "red" (link "u" "x") }}` and
|
|
124
|
+
// `{{ link "u" "x" }}` produce shapes that both qualify as cells from
|
|
125
|
+
// the consumer's perspective. Outer-wins on nested links comes for free
|
|
126
|
+
// from `Style.add`'s right-wins-on-conflict rule.
|
|
127
|
+
//
|
|
128
|
+
// [LAW:dataflow-not-control-flow] No special-case AST node, no
|
|
129
|
+
// `LinkFragment` subclass, no second emitter path — the variability is
|
|
130
|
+
// the value of `style.link`, not whether some control-flow branch ran.
|
|
131
|
+
const linkFunc = {
|
|
132
|
+
fn: ((url, child) => {
|
|
133
|
+
return applyStyleToFragment(child, new Style({ link: url }));
|
|
134
|
+
}),
|
|
135
|
+
argTypes: ["string", "liftable"],
|
|
136
|
+
arity: { kind: "exact" },
|
|
137
|
+
returnType: "T",
|
|
138
|
+
};
|
|
139
|
+
// --- Public assembly ---
|
|
140
|
+
/**
|
|
141
|
+
* Text-styling registrations: the two colour sinks (`fg`, `bg`), text
|
|
142
|
+
* attributes (canonical names, short aliases, and `not_*` negations), the
|
|
143
|
+
* hyperlink cell-splitter (`link`), and the multi-attribute `style` spec.
|
|
144
|
+
*
|
|
145
|
+
* Colours themselves come from elsewhere: `colorFuncs()` for the palette-free
|
|
146
|
+
* math, `paletteFuncs()` for naming a theme colour. This module only paints.
|
|
147
|
+
* [LAW:one-way-deps] — nothing here imports a palette.
|
|
148
|
+
*/
|
|
149
|
+
export function richTextStyleFuncs() {
|
|
150
|
+
return {
|
|
151
|
+
fg: colorSinkFunc("color"),
|
|
152
|
+
bg: colorSinkFunc("bgcolor"),
|
|
153
|
+
...attributeFuncs(),
|
|
154
|
+
link: linkFunc,
|
|
155
|
+
style: styleSpecFunc,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
type AccentKey = "primary" | "secondary" | "accent" | "success" | "warning" | "error";
|
|
18
|
+
export declare const ACCENT_KEYS: readonly AccentKey[];
|
|
19
|
+
/**
|
|
20
|
+
* Build a full semantic palette from base colors.
|
|
21
|
+
*
|
|
22
|
+
* Derived entries follow Textual's formulas:
|
|
23
|
+
* - `*-muted` = color blended 70% toward its opposite base role. Defined for
|
|
24
|
+
* every accent AND for the two base roles themselves
|
|
25
|
+
* (`foreground-muted` blends toward `background`,
|
|
26
|
+
* `background-muted` blends toward `foreground`) — a caller
|
|
27
|
+
* de-emphasizing body/structural text reaches for
|
|
28
|
+
* `foreground-muted` the same way it reaches for
|
|
29
|
+
* `primary-muted` to de-emphasize an accent.
|
|
30
|
+
* - `text-*` = contrast text tinted 66% with the accent color, then held
|
|
31
|
+
* to WCAG AA (4.5:1) on `*-muted` — the foreground for a
|
|
32
|
+
* `*-muted` ground
|
|
33
|
+
* - `on-*` = WCAG-correct contrast colour (black or white) for use as
|
|
34
|
+
* foreground when the FULL accent is the background. Picked
|
|
35
|
+
* by relative luminance — single source of truth so widgets
|
|
36
|
+
* never need to invert / reverse fg/bg to get readable text.
|
|
37
|
+
* - `surface` = background blended 5% toward foreground
|
|
38
|
+
*
|
|
39
|
+
* A translucent base colour is drawn onto `background` before anything is
|
|
40
|
+
* derived from it, so `on-*` is chosen against the colour the accent is drawn
|
|
41
|
+
* as and every derived entry is opaque.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildPalette(name: string, dark: boolean, given: BaseColors): Palette;
|
|
44
|
+
/**
|
|
45
|
+
* A palette's `text-*`: its tint held to WCAG AA on `*-muted`, moved toward
|
|
46
|
+
* the palette's own text side so it also reads on `background`, where widgets
|
|
47
|
+
* draw it too. The muted floor is the promise: where no lightness on the text
|
|
48
|
+
* side clears it, the colour that does is on the other side, and the
|
|
49
|
+
* background pair is the one given up. Every bundled palette clears both
|
|
50
|
+
* (test/themes/text-on-muted.test.ts).
|
|
51
|
+
*
|
|
52
|
+
* [LAW:single-enforcer] The one rule for the pair, for the palettes derived
|
|
53
|
+
* here and the authored ones the registry hydrates: the tint is the theme's,
|
|
54
|
+
* whether it reads is decided here.
|
|
55
|
+
*/
|
|
56
|
+
export declare function accentText(tint: ColorRgba, muted: ColorRgba, background: ColorRgba): ColorRgba;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { blendRgb } from "../core/color.js";
|
|
2
|
+
import { alphaBlend, contrastFor, ensureContrastToward } from "./colorMath.js";
|
|
3
|
+
import { Palette, drawnOn } from "./palette.js";
|
|
4
|
+
const MUTED_BLEND = 0.7;
|
|
5
|
+
const TEXT_ALPHA = 0.66;
|
|
6
|
+
const SURFACE_LIFT = 0.05;
|
|
7
|
+
// WCAG AA for body text: `text-*` is the label drawn on `*-muted`.
|
|
8
|
+
const TEXT_ON_MUTED = 4.5;
|
|
9
|
+
export const ACCENT_KEYS = ["primary", "secondary", "accent", "success", "warning", "error"];
|
|
10
|
+
/**
|
|
11
|
+
* Build a full semantic palette from base colors.
|
|
12
|
+
*
|
|
13
|
+
* Derived entries follow Textual's formulas:
|
|
14
|
+
* - `*-muted` = color blended 70% toward its opposite base role. Defined for
|
|
15
|
+
* every accent AND for the two base roles themselves
|
|
16
|
+
* (`foreground-muted` blends toward `background`,
|
|
17
|
+
* `background-muted` blends toward `foreground`) — a caller
|
|
18
|
+
* de-emphasizing body/structural text reaches for
|
|
19
|
+
* `foreground-muted` the same way it reaches for
|
|
20
|
+
* `primary-muted` to de-emphasize an accent.
|
|
21
|
+
* - `text-*` = contrast text tinted 66% with the accent color, then held
|
|
22
|
+
* to WCAG AA (4.5:1) on `*-muted` — the foreground for a
|
|
23
|
+
* `*-muted` ground
|
|
24
|
+
* - `on-*` = WCAG-correct contrast colour (black or white) for use as
|
|
25
|
+
* foreground when the FULL accent is the background. Picked
|
|
26
|
+
* by relative luminance — single source of truth so widgets
|
|
27
|
+
* never need to invert / reverse fg/bg to get readable text.
|
|
28
|
+
* - `surface` = background blended 5% toward foreground
|
|
29
|
+
*
|
|
30
|
+
* A translucent base colour is drawn onto `background` before anything is
|
|
31
|
+
* derived from it, so `on-*` is chosen against the colour the accent is drawn
|
|
32
|
+
* as and every derived entry is opaque.
|
|
33
|
+
*/
|
|
34
|
+
export function buildPalette(name, dark, given) {
|
|
35
|
+
const drawn = drawnOn(name, given.background);
|
|
36
|
+
// Every base colour is derived from, and a fully transparent one draws no
|
|
37
|
+
// colour of its own to derive a muted shade or a contrast from.
|
|
38
|
+
// [LAW:no-silent-failure]
|
|
39
|
+
const draw = (key) => {
|
|
40
|
+
const colour = given[key];
|
|
41
|
+
if (colour.alpha === 0) {
|
|
42
|
+
throw new RangeError(`palette ${JSON.stringify(name)}: ${key} is fully transparent (${colour.hex}), so there is no colour to derive the palette from`);
|
|
43
|
+
}
|
|
44
|
+
return drawn(colour);
|
|
45
|
+
};
|
|
46
|
+
const base = {
|
|
47
|
+
primary: draw("primary"),
|
|
48
|
+
secondary: draw("secondary"),
|
|
49
|
+
accent: draw("accent"),
|
|
50
|
+
success: draw("success"),
|
|
51
|
+
warning: draw("warning"),
|
|
52
|
+
error: draw("error"),
|
|
53
|
+
background: given.background,
|
|
54
|
+
foreground: draw("foreground"),
|
|
55
|
+
};
|
|
56
|
+
const vars = new Map();
|
|
57
|
+
// Base entries
|
|
58
|
+
vars.set("background", base.background);
|
|
59
|
+
vars.set("foreground", base.foreground);
|
|
60
|
+
for (const key of ACCENT_KEYS) {
|
|
61
|
+
vars.set(key, base[key]);
|
|
62
|
+
}
|
|
63
|
+
// Muted variants of the two base roles, same "blend 70% toward
|
|
64
|
+
// background" formula as the accent `*-muted` family below — every
|
|
65
|
+
// base color the palette defines gets a de-emphasized variant, not just
|
|
66
|
+
// the accents. `foreground-muted` is what a caller reaches for to draw
|
|
67
|
+
// structural/contextual text (labels, punctuation, ids) at reduced visual
|
|
68
|
+
// weight without a terminal-support-dependent SGR attribute.
|
|
69
|
+
vars.set("foreground-muted", blendRgb(base.foreground, base.background, MUTED_BLEND));
|
|
70
|
+
vars.set("background-muted", blendRgb(base.background, base.foreground, MUTED_BLEND));
|
|
71
|
+
// Surface — subtle lift from background
|
|
72
|
+
vars.set("surface", blendRgb(base.background, base.foreground, SURFACE_LIFT));
|
|
73
|
+
// Derived: muted, text-, and on- for each accent.
|
|
74
|
+
// [LAW:single-enforcer] contrastFor is the only place that decides
|
|
75
|
+
// black-vs-white for "text on this accent" — widgets read on-${accent}.
|
|
76
|
+
const contrastText = contrastFor(base.background);
|
|
77
|
+
for (const key of ACCENT_KEYS) {
|
|
78
|
+
const color = base[key];
|
|
79
|
+
const muted = blendRgb(color, base.background, MUTED_BLEND);
|
|
80
|
+
vars.set(`${key}-muted`, muted);
|
|
81
|
+
vars.set(`text-${key}`, accentText(alphaBlend(color, contrastText, TEXT_ALPHA), muted, base.background));
|
|
82
|
+
vars.set(`on-${key}`, contrastFor(color));
|
|
83
|
+
}
|
|
84
|
+
return new Palette(name, dark, vars);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A palette's `text-*`: its tint held to WCAG AA on `*-muted`, moved toward
|
|
88
|
+
* the palette's own text side so it also reads on `background`, where widgets
|
|
89
|
+
* draw it too. The muted floor is the promise: where no lightness on the text
|
|
90
|
+
* side clears it, the colour that does is on the other side, and the
|
|
91
|
+
* background pair is the one given up. Every bundled palette clears both
|
|
92
|
+
* (test/themes/text-on-muted.test.ts).
|
|
93
|
+
*
|
|
94
|
+
* [LAW:single-enforcer] The one rule for the pair, for the palettes derived
|
|
95
|
+
* here and the authored ones the registry hydrates: the tint is the theme's,
|
|
96
|
+
* whether it reads is decided here.
|
|
97
|
+
*/
|
|
98
|
+
export function accentText(tint, muted, background) {
|
|
99
|
+
return ensureContrastToward(tint, muted, TEXT_ON_MUTED, contrastFor(background));
|
|
100
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ColorDepth, ColorRgba, contrastRatio, relativeLuminance, type TerminalTheme } 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
|
+
* each index in its own theme's colour, so the ratio is measured on
|
|
48
|
+
* `terminal`'s sixteen: a theme whose black is light and white dark (Rosé Pine
|
|
49
|
+
* Dawn) turns the nominal table's sides over. A replacement is the colour the
|
|
50
|
+
* writer emits as the index nearest `fg` that clears the floor as `terminal`
|
|
51
|
+
* draws it. Truecolor draws the colour chosen.
|
|
52
|
+
*
|
|
53
|
+
* [LAW:single-enforcer] The one place "is this text readable, and if not fix
|
|
54
|
+
* it" is decided. Callers route every fg/bg pair through here and the
|
|
55
|
+
* unreadable state never reaches output. [LAW:dataflow-not-control-flow] the
|
|
56
|
+
* function always runs; the measured ratio (data) decides how far the
|
|
57
|
+
* lightness moves — there is no caller-side "should I check contrast" branch.
|
|
58
|
+
*/
|
|
59
|
+
export declare function ensureContrast(fg: ColorRgba, bg: ColorRgba, minRatio?: number, // WCAG AA for normal text
|
|
60
|
+
drawnAt?: ColorDepth, substrate?: ColorRgba, terminal?: TerminalTheme): ColorRgba;
|
|
61
|
+
/**
|
|
62
|
+
* `chosen` (composited opaque over `substrate`), or — when the depth the
|
|
63
|
+
* terminal draws at rounds it to a colour `accept` refuses — the colour the
|
|
64
|
+
* writer emits as the nearest entry `accept` takes. `accept` sees the
|
|
65
|
+
* candidate as drawn, and `drawn`, the same rounding for any other colour it
|
|
66
|
+
* measures against, so the caller states a floor once and it holds on the
|
|
67
|
+
* colours the terminal shows — at ANSI, `terminal`'s own sixteen, as
|
|
68
|
+
* `ensureContrast` measures there. Truecolor
|
|
69
|
+
* draws from no table, so a colour refused there has no replacement;
|
|
70
|
+
* `undefined` means nothing the depth draws is accepted.
|
|
71
|
+
*
|
|
72
|
+
* `ensureContrast` is this with a contrast ratio as the floor; this is for a
|
|
73
|
+
* floor that is not text on its background (an open state standing off every
|
|
74
|
+
* closed cell, two planes standing off each other).
|
|
75
|
+
*/
|
|
76
|
+
export declare function ensureDrawn(chosen: ColorRgba, drawnAt: ColorDepth, accept: (candidate: ColorRgba, drawn: (c: ColorRgba) => ColorRgba) => boolean, substrate?: ColorRgba, terminal?: TerminalTheme): ColorRgba | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* The colour a ground is shown as at `drawnAt`: composited over `substrate`
|
|
79
|
+
* (the SGR writer's black by default), rounded to the index the writer emits
|
|
80
|
+
* at that depth, and shown as the terminal shows that index — at ANSI, in
|
|
81
|
+
* `terminal`'s own colour. The one account `ensureDrawn` and `ensureContrast`
|
|
82
|
+
* measure by, for a caller that measures a floor of its own.
|
|
83
|
+
*/
|
|
84
|
+
export declare function drawnColour(colour: ColorRgba, drawnAt: ColorDepth, substrate?: ColorRgba, terminal?: TerminalTheme): ColorRgba;
|
|
85
|
+
/**
|
|
86
|
+
* `fg` held to `minRatio` on `bg` in truecolor, moving in lightness toward
|
|
87
|
+
* `toward` (black or white). `ensureContrast` moves toward the pole `bg`
|
|
88
|
+
* itself contrasts with; a colour that already has a side (a palette's
|
|
89
|
+
* `text-*` is its text, tinted) passes that side, since on a ground near the
|
|
90
|
+
* 0.179 cutoff both poles clear and only one also reads on everything else
|
|
91
|
+
* the colour is drawn on. Where `toward` cannot clear `minRatio` on `bg`,
|
|
92
|
+
* the floor wins over the side: the answer is `contrastFor(bg)`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function ensureContrastToward(fg: ColorRgba, bg: ColorRgba, minRatio: number, toward: ColorRgba): ColorRgba;
|