@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.
Files changed (244) hide show
  1. package/README.md +9 -7
  2. package/dist/core/anchor.d.ts +45 -0
  3. package/dist/core/anchor.js +39 -0
  4. package/dist/core/ansi.d.ts +65 -0
  5. package/dist/core/ansi.js +231 -0
  6. package/dist/core/box.d.ts +145 -0
  7. package/dist/core/box.js +372 -0
  8. package/dist/core/cells.d.ts +192 -0
  9. package/dist/core/cells.js +334 -0
  10. package/dist/core/clock.d.ts +48 -0
  11. package/dist/core/clock.js +42 -0
  12. package/dist/core/color.d.ts +326 -0
  13. package/dist/core/color.js +1225 -0
  14. package/dist/core/console.d.ts +196 -0
  15. package/dist/core/console.js +596 -0
  16. package/dist/core/easing.d.ts +86 -0
  17. package/dist/core/easing.js +171 -0
  18. package/dist/core/emoji.d.ts +24 -0
  19. package/dist/core/emoji.js +391 -0
  20. package/dist/core/env.d.ts +16 -0
  21. package/dist/core/env.js +16 -0
  22. package/dist/core/export-html.d.ts +59 -0
  23. package/dist/core/export-html.js +197 -0
  24. package/dist/core/export-lines.d.ts +130 -0
  25. package/dist/core/export-lines.js +159 -0
  26. package/dist/core/export-svg.d.ts +49 -0
  27. package/dist/core/export-svg.js +213 -0
  28. package/dist/core/fnv1a.d.ts +10 -0
  29. package/dist/core/fnv1a.js +17 -0
  30. package/dist/core/highlighter.d.ts +33 -0
  31. package/dist/core/highlighter.js +190 -0
  32. package/dist/core/json.d.ts +27 -0
  33. package/dist/core/json.js +53 -0
  34. package/dist/core/markup.d.ts +140 -0
  35. package/dist/core/markup.js +654 -0
  36. package/dist/core/measure.d.ts +27 -0
  37. package/dist/core/measure.js +67 -0
  38. package/dist/core/memo.d.ts +19 -0
  39. package/dist/core/memo.js +34 -0
  40. package/dist/core/oklch.d.ts +161 -0
  41. package/dist/core/oklch.js +312 -0
  42. package/dist/core/osc8.d.ts +68 -0
  43. package/dist/core/osc8.js +78 -0
  44. package/dist/core/paint.d.ts +107 -0
  45. package/dist/core/paint.js +150 -0
  46. package/dist/core/place.d.ts +30 -0
  47. package/dist/core/place.js +49 -0
  48. package/dist/core/pretty.d.ts +247 -0
  49. package/dist/core/pretty.js +906 -0
  50. package/dist/core/protocol.d.ts +305 -0
  51. package/dist/core/protocol.js +183 -0
  52. package/dist/core/render.d.ts +87 -0
  53. package/dist/core/render.js +105 -0
  54. package/dist/core/segment.d.ts +162 -0
  55. package/dist/core/segment.js +488 -0
  56. package/dist/core/spinnerData.d.ts +12 -0
  57. package/dist/core/spinnerData.js +117 -0
  58. package/dist/core/strip.d.ts +152 -0
  59. package/dist/core/strip.js +326 -0
  60. package/dist/core/style.d.ts +189 -0
  61. package/dist/core/style.js +758 -0
  62. package/dist/core/subscription.d.ts +12 -0
  63. package/dist/core/subscription.js +12 -0
  64. package/dist/core/text.d.ts +283 -0
  65. package/dist/core/text.js +1097 -0
  66. package/dist/core/wrap.d.ts +46 -0
  67. package/dist/core/wrap.js +140 -0
  68. package/dist/host/app.d.ts +133 -0
  69. package/dist/host/app.js +285 -0
  70. package/dist/host/host-environment.d.ts +26 -0
  71. package/dist/host/host-environment.js +44 -0
  72. package/dist/host/index.d.ts +6 -0
  73. package/dist/host/index.js +3 -0
  74. package/dist/host/terminal-host.d.ts +180 -0
  75. package/dist/host/terminal-host.js +186 -0
  76. package/dist/index.d.ts +98 -0
  77. package/dist/index.js +114 -0
  78. package/dist/node/prompt.d.ts +34 -0
  79. package/dist/node/prompt.js +51 -0
  80. package/dist/node/save.d.ts +18 -0
  81. package/dist/node/save.js +24 -0
  82. package/dist/node/terminal-host.d.ts +114 -0
  83. package/dist/node/terminal-host.js +227 -0
  84. package/dist/node/traceback.d.ts +38 -0
  85. package/dist/node/traceback.js +98 -0
  86. package/dist/renderables/align.d.ts +16 -0
  87. package/dist/renderables/align.js +26 -0
  88. package/dist/renderables/columns.d.ts +77 -0
  89. package/dist/renderables/columns.js +191 -0
  90. package/dist/renderables/constrain.d.ts +15 -0
  91. package/dist/renderables/constrain.js +31 -0
  92. package/dist/renderables/effect.d.ts +98 -0
  93. package/dist/renderables/effect.js +160 -0
  94. package/dist/renderables/embed.d.ts +107 -0
  95. package/dist/renderables/embed.js +160 -0
  96. package/dist/renderables/flexStrip.d.ts +41 -0
  97. package/dist/renderables/flexStrip.js +200 -0
  98. package/dist/renderables/group.d.ts +14 -0
  99. package/dist/renderables/group.js +22 -0
  100. package/dist/renderables/layout.d.ts +75 -0
  101. package/dist/renderables/layout.js +223 -0
  102. package/dist/renderables/live.d.ts +74 -0
  103. package/dist/renderables/live.js +188 -0
  104. package/dist/renderables/markdown.d.ts +38 -0
  105. package/dist/renderables/markdown.js +587 -0
  106. package/dist/renderables/padding.d.ts +59 -0
  107. package/dist/renderables/padding.js +147 -0
  108. package/dist/renderables/panel.d.ts +114 -0
  109. package/dist/renderables/panel.js +303 -0
  110. package/dist/renderables/progress.d.ts +178 -0
  111. package/dist/renderables/progress.js +362 -0
  112. package/dist/renderables/progressBar.d.ts +31 -0
  113. package/dist/renderables/progressBar.js +64 -0
  114. package/dist/renderables/prompt.d.ts +71 -0
  115. package/dist/renderables/prompt.js +165 -0
  116. package/dist/renderables/ratio.d.ts +123 -0
  117. package/dist/renderables/ratio.js +213 -0
  118. package/dist/renderables/rule.d.ts +26 -0
  119. package/dist/renderables/rule.js +86 -0
  120. package/dist/renderables/spinner.d.ts +57 -0
  121. package/dist/renderables/spinner.js +103 -0
  122. package/dist/renderables/status.d.ts +29 -0
  123. package/dist/renderables/status.js +46 -0
  124. package/dist/renderables/syntax.d.ts +65 -0
  125. package/dist/renderables/syntax.js +166 -0
  126. package/dist/renderables/table.d.ts +232 -0
  127. package/dist/renderables/table.js +973 -0
  128. package/dist/renderables/traceback.d.ts +32 -0
  129. package/dist/renderables/traceback.js +279 -0
  130. package/dist/renderables/tree.d.ts +76 -0
  131. package/dist/renderables/tree.js +177 -0
  132. package/dist/renderables/viewport.d.ts +114 -0
  133. package/dist/renderables/viewport.js +196 -0
  134. package/dist/template-bindings/color-funcs.d.ts +109 -0
  135. package/dist/template-bindings/color-funcs.js +243 -0
  136. package/dist/template-bindings/helpers.d.ts +26 -0
  137. package/dist/template-bindings/helpers.js +33 -0
  138. package/dist/template-bindings/index.d.ts +119 -0
  139. package/dist/template-bindings/index.js +162 -0
  140. package/dist/template-bindings/palette-funcs.d.ts +96 -0
  141. package/dist/template-bindings/palette-funcs.js +145 -0
  142. package/dist/template-bindings/style-funcs.d.ts +28 -0
  143. package/dist/template-bindings/style-funcs.js +157 -0
  144. package/dist/themes/buildPalette.d.ts +57 -0
  145. package/dist/themes/buildPalette.js +100 -0
  146. package/dist/themes/colorMath.d.ts +94 -0
  147. package/dist/themes/colorMath.js +280 -0
  148. package/dist/themes/colorRef.d.ts +36 -0
  149. package/dist/themes/colorRef.js +78 -0
  150. package/dist/themes/data/ansi-tables.d.ts +14 -0
  151. package/dist/themes/data/ansi-tables.js +49 -0
  152. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  153. package/dist/themes/data/atom-one-dark.js +177 -0
  154. package/dist/themes/data/atom-one-light.d.ts +3 -0
  155. package/dist/themes/data/atom-one-light.js +177 -0
  156. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  157. package/dist/themes/data/catppuccin-frappe.js +177 -0
  158. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  159. package/dist/themes/data/catppuccin-latte.js +177 -0
  160. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  161. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  162. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  163. package/dist/themes/data/catppuccin-mocha.js +177 -0
  164. package/dist/themes/data/cyberpunk.d.ts +17 -0
  165. package/dist/themes/data/cyberpunk.js +193 -0
  166. package/dist/themes/data/default.d.ts +3 -0
  167. package/dist/themes/data/default.js +160 -0
  168. package/dist/themes/data/dracula.d.ts +3 -0
  169. package/dist/themes/data/dracula.js +177 -0
  170. package/dist/themes/data/flexoki.d.ts +3 -0
  171. package/dist/themes/data/flexoki.js +177 -0
  172. package/dist/themes/data/gruvbox.d.ts +3 -0
  173. package/dist/themes/data/gruvbox.js +177 -0
  174. package/dist/themes/data/index.d.ts +35 -0
  175. package/dist/themes/data/index.js +64 -0
  176. package/dist/themes/data/monokai.d.ts +3 -0
  177. package/dist/themes/data/monokai.js +177 -0
  178. package/dist/themes/data/nord.d.ts +3 -0
  179. package/dist/themes/data/nord.js +177 -0
  180. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  181. package/dist/themes/data/rose-pine-dawn.js +177 -0
  182. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  183. package/dist/themes/data/rose-pine-moon.js +177 -0
  184. package/dist/themes/data/rose-pine.d.ts +3 -0
  185. package/dist/themes/data/rose-pine.js +177 -0
  186. package/dist/themes/data/solarized-dark.d.ts +3 -0
  187. package/dist/themes/data/solarized-dark.js +177 -0
  188. package/dist/themes/data/solarized-light.d.ts +3 -0
  189. package/dist/themes/data/solarized-light.js +177 -0
  190. package/dist/themes/data/svg-export.d.ts +3 -0
  191. package/dist/themes/data/svg-export.js +177 -0
  192. package/dist/themes/data/textual-ansi.d.ts +3 -0
  193. package/dist/themes/data/textual-ansi.js +160 -0
  194. package/dist/themes/data/textual-dark.d.ts +3 -0
  195. package/dist/themes/data/textual-dark.js +160 -0
  196. package/dist/themes/data/textual-light.d.ts +3 -0
  197. package/dist/themes/data/textual-light.js +160 -0
  198. package/dist/themes/data/tokyo-night.d.ts +3 -0
  199. package/dist/themes/data/tokyo-night.js +177 -0
  200. package/dist/themes/data/types.d.ts +31 -0
  201. package/dist/themes/data/types.js +10 -0
  202. package/dist/themes/palette.d.ts +45 -0
  203. package/dist/themes/palette.js +71 -0
  204. package/dist/themes/ramp.d.ts +70 -0
  205. package/dist/themes/ramp.js +103 -0
  206. package/dist/themes/registry.d.ts +44 -0
  207. package/dist/themes/registry.js +105 -0
  208. package/dist/themes/terminalThemes.d.ts +49 -0
  209. package/dist/themes/terminalThemes.js +63 -0
  210. package/dist/themes/transpose.d.ts +87 -0
  211. package/dist/themes/transpose.js +138 -0
  212. package/dist/widgets/button.d.ts +45 -0
  213. package/dist/widgets/button.js +177 -0
  214. package/dist/widgets/checkbox.d.ts +38 -0
  215. package/dist/widgets/checkbox.js +135 -0
  216. package/dist/widgets/dropdown.d.ts +64 -0
  217. package/dist/widgets/dropdown.js +416 -0
  218. package/dist/widgets/event-router.d.ts +71 -0
  219. package/dist/widgets/event-router.js +629 -0
  220. package/dist/widgets/focus-manager.d.ts +45 -0
  221. package/dist/widgets/focus-manager.js +191 -0
  222. package/dist/widgets/hit.d.ts +64 -0
  223. package/dist/widgets/hit.js +113 -0
  224. package/dist/widgets/index.d.ts +20 -0
  225. package/dist/widgets/index.js +14 -0
  226. package/dist/widgets/ink.d.ts +22 -0
  227. package/dist/widgets/ink.js +31 -0
  228. package/dist/widgets/slider.d.ts +54 -0
  229. package/dist/widgets/slider.js +247 -0
  230. package/dist/widgets/static-item.d.ts +34 -0
  231. package/dist/widgets/static-item.js +54 -0
  232. package/dist/widgets/text-input.d.ts +283 -0
  233. package/dist/widgets/text-input.js +1174 -0
  234. package/dist/widgets/themed-widget.d.ts +20 -0
  235. package/dist/widgets/themed-widget.js +89 -0
  236. package/dist/widgets/toggle.d.ts +41 -0
  237. package/dist/widgets/toggle.js +157 -0
  238. package/dist/widgets/types.d.ts +87 -0
  239. package/dist/widgets/types.js +30 -0
  240. package/dist/widgets/widget-app.d.ts +65 -0
  241. package/dist/widgets/widget-app.js +206 -0
  242. package/dist/widgets/widget-base.d.ts +42 -0
  243. package/dist/widgets/widget-base.js +172 -0
  244. 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;