@promptctl/rich-js 0.18.0 → 0.18.1

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 (221) hide show
  1. package/dist/core/anchor.d.ts +45 -0
  2. package/dist/core/anchor.js +39 -0
  3. package/dist/core/ansi.d.ts +65 -0
  4. package/dist/core/ansi.js +231 -0
  5. package/dist/core/box.d.ts +143 -0
  6. package/dist/core/box.js +379 -0
  7. package/dist/core/cells.d.ts +159 -0
  8. package/dist/core/cells.js +287 -0
  9. package/dist/core/color.d.ts +246 -0
  10. package/dist/core/color.js +1038 -0
  11. package/dist/core/console.d.ts +165 -0
  12. package/dist/core/console.js +527 -0
  13. package/dist/core/emoji.d.ts +24 -0
  14. package/dist/core/emoji.js +391 -0
  15. package/dist/core/export-html.d.ts +59 -0
  16. package/dist/core/export-html.js +200 -0
  17. package/dist/core/export-lines.d.ts +121 -0
  18. package/dist/core/export-lines.js +141 -0
  19. package/dist/core/highlighter.d.ts +33 -0
  20. package/dist/core/highlighter.js +124 -0
  21. package/dist/core/json.d.ts +27 -0
  22. package/dist/core/json.js +53 -0
  23. package/dist/core/markup.d.ts +119 -0
  24. package/dist/core/markup.js +731 -0
  25. package/dist/core/measure.d.ts +27 -0
  26. package/dist/core/measure.js +62 -0
  27. package/dist/core/oklch.d.ts +161 -0
  28. package/dist/core/oklch.js +312 -0
  29. package/dist/core/osc8.d.ts +68 -0
  30. package/dist/core/osc8.js +90 -0
  31. package/dist/core/place.d.ts +30 -0
  32. package/dist/core/place.js +51 -0
  33. package/dist/core/pretty.d.ts +144 -0
  34. package/dist/core/pretty.js +476 -0
  35. package/dist/core/protocol.d.ts +267 -0
  36. package/dist/core/protocol.js +163 -0
  37. package/dist/core/render.d.ts +84 -0
  38. package/dist/core/render.js +145 -0
  39. package/dist/core/segment.d.ts +161 -0
  40. package/dist/core/segment.js +492 -0
  41. package/dist/core/spinnerData.d.ts +12 -0
  42. package/dist/core/spinnerData.js +117 -0
  43. package/dist/core/strip.d.ts +141 -0
  44. package/dist/core/strip.js +305 -0
  45. package/dist/core/style.d.ts +169 -0
  46. package/dist/core/style.js +695 -0
  47. package/dist/core/subscription.d.ts +12 -0
  48. package/dist/core/subscription.js +12 -0
  49. package/dist/core/text.d.ts +256 -0
  50. package/dist/core/text.js +976 -0
  51. package/dist/core/wrap.d.ts +41 -0
  52. package/dist/core/wrap.js +127 -0
  53. package/dist/host/app.d.ts +121 -0
  54. package/dist/host/app.js +255 -0
  55. package/dist/host/host-environment.d.ts +26 -0
  56. package/dist/host/host-environment.js +44 -0
  57. package/dist/host/index.d.ts +5 -0
  58. package/dist/host/index.js +3 -0
  59. package/dist/host/terminal-host.d.ts +179 -0
  60. package/dist/host/terminal-host.js +186 -0
  61. package/dist/index.d.ts +91 -0
  62. package/dist/index.js +107 -0
  63. package/dist/node/prompt.d.ts +21 -0
  64. package/dist/node/prompt.js +30 -0
  65. package/dist/node/save.d.ts +17 -0
  66. package/dist/node/save.js +21 -0
  67. package/dist/node/terminal-host.d.ts +113 -0
  68. package/dist/node/terminal-host.js +227 -0
  69. package/dist/node/traceback.d.ts +37 -0
  70. package/dist/node/traceback.js +134 -0
  71. package/dist/renderables/align.d.ts +16 -0
  72. package/dist/renderables/align.js +33 -0
  73. package/dist/renderables/columns.d.ts +61 -0
  74. package/dist/renderables/columns.js +152 -0
  75. package/dist/renderables/constrain.d.ts +15 -0
  76. package/dist/renderables/constrain.js +37 -0
  77. package/dist/renderables/embed.d.ts +50 -0
  78. package/dist/renderables/embed.js +71 -0
  79. package/dist/renderables/flexStrip.d.ts +41 -0
  80. package/dist/renderables/flexStrip.js +200 -0
  81. package/dist/renderables/group.d.ts +11 -0
  82. package/dist/renderables/group.js +17 -0
  83. package/dist/renderables/layout.d.ts +92 -0
  84. package/dist/renderables/layout.js +312 -0
  85. package/dist/renderables/live.d.ts +50 -0
  86. package/dist/renderables/live.js +175 -0
  87. package/dist/renderables/markdown.d.ts +24 -0
  88. package/dist/renderables/markdown.js +184 -0
  89. package/dist/renderables/padding.d.ts +38 -0
  90. package/dist/renderables/padding.js +141 -0
  91. package/dist/renderables/panel.d.ts +116 -0
  92. package/dist/renderables/panel.js +307 -0
  93. package/dist/renderables/progress.d.ts +90 -0
  94. package/dist/renderables/progress.js +238 -0
  95. package/dist/renderables/progressBar.d.ts +31 -0
  96. package/dist/renderables/progressBar.js +53 -0
  97. package/dist/renderables/prompt.d.ts +49 -0
  98. package/dist/renderables/prompt.js +125 -0
  99. package/dist/renderables/rule.d.ts +25 -0
  100. package/dist/renderables/rule.js +90 -0
  101. package/dist/renderables/spinner.d.ts +31 -0
  102. package/dist/renderables/spinner.js +70 -0
  103. package/dist/renderables/status.d.ts +23 -0
  104. package/dist/renderables/status.js +60 -0
  105. package/dist/renderables/syntax.d.ts +35 -0
  106. package/dist/renderables/syntax.js +115 -0
  107. package/dist/renderables/table.d.ts +194 -0
  108. package/dist/renderables/table.js +795 -0
  109. package/dist/renderables/traceback.d.ts +31 -0
  110. package/dist/renderables/traceback.js +108 -0
  111. package/dist/renderables/tree.d.ts +68 -0
  112. package/dist/renderables/tree.js +150 -0
  113. package/dist/renderables/viewport.d.ts +114 -0
  114. package/dist/renderables/viewport.js +198 -0
  115. package/dist/template-bindings/color-funcs.d.ts +87 -0
  116. package/dist/template-bindings/color-funcs.js +217 -0
  117. package/dist/template-bindings/helpers.d.ts +24 -0
  118. package/dist/template-bindings/helpers.js +48 -0
  119. package/dist/template-bindings/index.d.ts +107 -0
  120. package/dist/template-bindings/index.js +146 -0
  121. package/dist/template-bindings/palette-funcs.d.ts +94 -0
  122. package/dist/template-bindings/palette-funcs.js +141 -0
  123. package/dist/template-bindings/style-funcs.d.ts +28 -0
  124. package/dist/template-bindings/style-funcs.js +155 -0
  125. package/dist/themes/buildPalette.d.ts +36 -0
  126. package/dist/themes/buildPalette.js +56 -0
  127. package/dist/themes/colorMath.d.ts +82 -0
  128. package/dist/themes/colorMath.js +254 -0
  129. package/dist/themes/colorRef.d.ts +51 -0
  130. package/dist/themes/colorRef.js +96 -0
  131. package/dist/themes/data/ansi-tables.d.ts +14 -0
  132. package/dist/themes/data/ansi-tables.js +49 -0
  133. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  134. package/dist/themes/data/atom-one-dark.js +177 -0
  135. package/dist/themes/data/atom-one-light.d.ts +3 -0
  136. package/dist/themes/data/atom-one-light.js +177 -0
  137. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  138. package/dist/themes/data/catppuccin-frappe.js +177 -0
  139. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  140. package/dist/themes/data/catppuccin-latte.js +177 -0
  141. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  142. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  143. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  144. package/dist/themes/data/catppuccin-mocha.js +177 -0
  145. package/dist/themes/data/cyberpunk.d.ts +17 -0
  146. package/dist/themes/data/cyberpunk.js +193 -0
  147. package/dist/themes/data/default.d.ts +3 -0
  148. package/dist/themes/data/default.js +160 -0
  149. package/dist/themes/data/dracula.d.ts +3 -0
  150. package/dist/themes/data/dracula.js +177 -0
  151. package/dist/themes/data/flexoki.d.ts +3 -0
  152. package/dist/themes/data/flexoki.js +177 -0
  153. package/dist/themes/data/gruvbox.d.ts +3 -0
  154. package/dist/themes/data/gruvbox.js +177 -0
  155. package/dist/themes/data/index.d.ts +35 -0
  156. package/dist/themes/data/index.js +64 -0
  157. package/dist/themes/data/monokai.d.ts +3 -0
  158. package/dist/themes/data/monokai.js +177 -0
  159. package/dist/themes/data/nord.d.ts +3 -0
  160. package/dist/themes/data/nord.js +177 -0
  161. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  162. package/dist/themes/data/rose-pine-dawn.js +177 -0
  163. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  164. package/dist/themes/data/rose-pine-moon.js +177 -0
  165. package/dist/themes/data/rose-pine.d.ts +3 -0
  166. package/dist/themes/data/rose-pine.js +177 -0
  167. package/dist/themes/data/solarized-dark.d.ts +3 -0
  168. package/dist/themes/data/solarized-dark.js +177 -0
  169. package/dist/themes/data/solarized-light.d.ts +3 -0
  170. package/dist/themes/data/solarized-light.js +177 -0
  171. package/dist/themes/data/svg-export.d.ts +3 -0
  172. package/dist/themes/data/svg-export.js +177 -0
  173. package/dist/themes/data/textual-ansi.d.ts +3 -0
  174. package/dist/themes/data/textual-ansi.js +160 -0
  175. package/dist/themes/data/textual-dark.d.ts +3 -0
  176. package/dist/themes/data/textual-dark.js +160 -0
  177. package/dist/themes/data/textual-light.d.ts +3 -0
  178. package/dist/themes/data/textual-light.js +160 -0
  179. package/dist/themes/data/tokyo-night.d.ts +3 -0
  180. package/dist/themes/data/tokyo-night.js +177 -0
  181. package/dist/themes/data/types.d.ts +31 -0
  182. package/dist/themes/data/types.js +10 -0
  183. package/dist/themes/palette.d.ts +21 -0
  184. package/dist/themes/palette.js +28 -0
  185. package/dist/themes/ramp.d.ts +83 -0
  186. package/dist/themes/ramp.js +122 -0
  187. package/dist/themes/registry.d.ts +44 -0
  188. package/dist/themes/registry.js +94 -0
  189. package/dist/themes/terminalThemes.d.ts +49 -0
  190. package/dist/themes/terminalThemes.js +63 -0
  191. package/dist/themes/transpose.d.ts +87 -0
  192. package/dist/themes/transpose.js +138 -0
  193. package/dist/widgets/button.d.ts +49 -0
  194. package/dist/widgets/button.js +208 -0
  195. package/dist/widgets/checkbox.d.ts +41 -0
  196. package/dist/widgets/checkbox.js +157 -0
  197. package/dist/widgets/dropdown.d.ts +67 -0
  198. package/dist/widgets/dropdown.js +450 -0
  199. package/dist/widgets/event-router.d.ts +71 -0
  200. package/dist/widgets/event-router.js +629 -0
  201. package/dist/widgets/focus-manager.d.ts +45 -0
  202. package/dist/widgets/focus-manager.js +191 -0
  203. package/dist/widgets/hit.d.ts +64 -0
  204. package/dist/widgets/hit.js +113 -0
  205. package/dist/widgets/index.d.ts +20 -0
  206. package/dist/widgets/index.js +14 -0
  207. package/dist/widgets/slider.d.ts +57 -0
  208. package/dist/widgets/slider.js +270 -0
  209. package/dist/widgets/static-item.d.ts +34 -0
  210. package/dist/widgets/static-item.js +54 -0
  211. package/dist/widgets/text-input.d.ts +274 -0
  212. package/dist/widgets/text-input.js +1165 -0
  213. package/dist/widgets/toggle.d.ts +44 -0
  214. package/dist/widgets/toggle.js +180 -0
  215. package/dist/widgets/types.d.ts +87 -0
  216. package/dist/widgets/types.js +30 -0
  217. package/dist/widgets/widget-app.d.ts +63 -0
  218. package/dist/widgets/widget-app.js +199 -0
  219. package/dist/widgets/widget-base.d.ts +40 -0
  220. package/dist/widgets/widget-base.js +165 -0
  221. package/package.json +6 -2
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The palette-dependent template functions: `color`, and `ramp` whose stops
3
+ * are color references.
4
+ *
5
+ * ### Why `color` is the one name resolver
6
+ *
7
+ * This module used to register four kinds of thing: one function per
8
+ * identifier-safe palette variable (`{{ primary child }}`, `{{ accent child }}`,
9
+ * …), a general `palette "spec" child`, a `paletteOver "spec" "#bg" child` for
10
+ * specs needing a background, and an `auto "#bg" child` sugar. That surface had
11
+ * two defects worth recording, because both are easy to reintroduce.
12
+ *
13
+ * **The name family could not cover its own domain.** A themed palette carries
14
+ * ~150 variables; roughly 14 of them are legal Go-template identifiers. So the
15
+ * generated functions reached under a tenth of the palette, and the other nine
16
+ * tenths needed `palette "text-primary" child` — a *different expression shape*
17
+ * for the same intent. An author who learned `{{ primary x }}` and reasonably
18
+ * tried `{{ text_primary x }}` got a FuncNotFound. [LAW:composability] — the
19
+ * `filterByStatus`/`filterByOwner` shape: names cannot enumerate a domain, and
20
+ * the N+1 case always needs a form you have to learn separately.
21
+ *
22
+ * **Resolution and application were fused.** Every one of those functions
23
+ * consumed its color instantly into a styled fragment, so a color could never
24
+ * be held or passed. Composition therefore had to happen inside the spec
25
+ * *string* — hence the old `name-darken-N alpha%` grammar. See
26
+ * `color-funcs.ts` for the full argument; the short version is that a string
27
+ * grammar is function application with the function calls spelled as
28
+ * punctuation, and it grows a new production for every operation.
29
+ *
30
+ * What replaces all of it: `color "name-or-hex"` produces a color value, the
31
+ * functions in `color-funcs.ts` transform colors, and `fg`/`bg` in
32
+ * `style-funcs.ts` paint them. One shape, total over the palette, open to
33
+ * arbitrary composition. [LAW:one-type-per-behavior]
34
+ *
35
+ * ### Why `ramp` lives here and not with the color math
36
+ *
37
+ * `ramp` is the one function whose input is a *number*, and its stops are
38
+ * spelled as color references — `ramp .pct "step" 0 "panel" 50 "warning" 80
39
+ * "error"` — resolved through the same `resolveColorRef` that `color` crosses.
40
+ * Requiring `(color "panel")` around every stop would put the boilerplate
41
+ * back that this binding exists to remove, and an author who writes a hex
42
+ * literal loses nothing: the resolver is idempotent on hex. The arithmetic
43
+ * itself is `ColorRamp` in `themes/ramp.ts`, palette-free; only the
44
+ * reference resolution is here. [LAW:one-way-deps]
45
+ *
46
+ * ### Why a getter, not a palette
47
+ *
48
+ * `paletteFuncs` takes `() => Palette` rather than a `Palette`. A consumer
49
+ * whose theme can change at runtime (a live preview, a theme picker, a
50
+ * status-line that recolors on click) would otherwise be frozen to whichever
51
+ * palette happened to be current when the engine was constructed — and since
52
+ * templates are parsed once and evaluated many times, that freeze outlives
53
+ * every subsequent theme change while the *rest* of the consumer's colors move
54
+ * on. Two palettes, one render: [LAW:one-source-of-truth] violated by a
55
+ * captured reference.
56
+ *
57
+ * The getter costs nothing structurally. `FuncMap` entries are data in the
58
+ * engine and their bodies run at *evaluate* time, so reading the palette
59
+ * through a getter leaves parse-once/evaluate-many completely intact. What the
60
+ * getter must not change is *which functions exist* — and it cannot, because
61
+ * the two names here do not depend on the palette's contents. That was not
62
+ * true of the generated per-variable functions, which is the second reason
63
+ * they are gone.
64
+ */
65
+ import type { FuncMap } from "@promptctl/go-template-js";
66
+ import type { Palette } from "../themes/palette.js";
67
+ /**
68
+ * Register `color "name-or-hex"` against a live palette.
69
+ *
70
+ * `color` resolves a palette variable name to a `#RRGGBB` string, and passes
71
+ * an already-literal color through unchanged. That second half is not a
72
+ * convenience — it makes `color` **idempotent**, which is what lets consumers
73
+ * apply it unconditionally to any author-written color string without first
74
+ * asking whether it is a name or already a color. [LAW:dataflow-not-control-flow]
75
+ *
76
+ * An unknown name throws, carrying near-miss suggestions from the live
77
+ * palette. In a template that surfaces as an evaluation error at the exact
78
+ * call site, which is the signal an author (or an agent editing a config) needs
79
+ * to fix it. [LAW:no-silent-failure]
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const engine = createEngine({
84
+ * fromString: (s) => new RichText(s),
85
+ * toString: (rt) => rt.plain,
86
+ * funcs: {
87
+ * ...richTextFuncs(),
88
+ * ...colorFuncs(),
89
+ * ...paletteFuncs(() => currentTheme.palette),
90
+ * },
91
+ * });
92
+ * ```
93
+ */
94
+ export declare function paletteFuncs(getPalette: () => Palette): FuncMap;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The palette-dependent template functions: `color`, and `ramp` whose stops
3
+ * are color references.
4
+ *
5
+ * ### Why `color` is the one name resolver
6
+ *
7
+ * This module used to register four kinds of thing: one function per
8
+ * identifier-safe palette variable (`{{ primary child }}`, `{{ accent child }}`,
9
+ * …), a general `palette "spec" child`, a `paletteOver "spec" "#bg" child` for
10
+ * specs needing a background, and an `auto "#bg" child` sugar. That surface had
11
+ * two defects worth recording, because both are easy to reintroduce.
12
+ *
13
+ * **The name family could not cover its own domain.** A themed palette carries
14
+ * ~150 variables; roughly 14 of them are legal Go-template identifiers. So the
15
+ * generated functions reached under a tenth of the palette, and the other nine
16
+ * tenths needed `palette "text-primary" child` — a *different expression shape*
17
+ * for the same intent. An author who learned `{{ primary x }}` and reasonably
18
+ * tried `{{ text_primary x }}` got a FuncNotFound. [LAW:composability] — the
19
+ * `filterByStatus`/`filterByOwner` shape: names cannot enumerate a domain, and
20
+ * the N+1 case always needs a form you have to learn separately.
21
+ *
22
+ * **Resolution and application were fused.** Every one of those functions
23
+ * consumed its color instantly into a styled fragment, so a color could never
24
+ * be held or passed. Composition therefore had to happen inside the spec
25
+ * *string* — hence the old `name-darken-N alpha%` grammar. See
26
+ * `color-funcs.ts` for the full argument; the short version is that a string
27
+ * grammar is function application with the function calls spelled as
28
+ * punctuation, and it grows a new production for every operation.
29
+ *
30
+ * What replaces all of it: `color "name-or-hex"` produces a color value, the
31
+ * functions in `color-funcs.ts` transform colors, and `fg`/`bg` in
32
+ * `style-funcs.ts` paint them. One shape, total over the palette, open to
33
+ * arbitrary composition. [LAW:one-type-per-behavior]
34
+ *
35
+ * ### Why `ramp` lives here and not with the color math
36
+ *
37
+ * `ramp` is the one function whose input is a *number*, and its stops are
38
+ * spelled as color references — `ramp .pct "step" 0 "panel" 50 "warning" 80
39
+ * "error"` — resolved through the same `resolveColorRef` that `color` crosses.
40
+ * Requiring `(color "panel")` around every stop would put the boilerplate
41
+ * back that this binding exists to remove, and an author who writes a hex
42
+ * literal loses nothing: the resolver is idempotent on hex. The arithmetic
43
+ * itself is `ColorRamp` in `themes/ramp.ts`, palette-free; only the
44
+ * reference resolution is here. [LAW:one-way-deps]
45
+ *
46
+ * ### Why a getter, not a palette
47
+ *
48
+ * `paletteFuncs` takes `() => Palette` rather than a `Palette`. A consumer
49
+ * whose theme can change at runtime (a live preview, a theme picker, a
50
+ * status-line that recolors on click) would otherwise be frozen to whichever
51
+ * palette happened to be current when the engine was constructed — and since
52
+ * templates are parsed once and evaluated many times, that freeze outlives
53
+ * every subsequent theme change while the *rest* of the consumer's colors move
54
+ * on. Two palettes, one render: [LAW:one-source-of-truth] violated by a
55
+ * captured reference.
56
+ *
57
+ * The getter costs nothing structurally. `FuncMap` entries are data in the
58
+ * engine and their bodies run at *evaluate* time, so reading the palette
59
+ * through a getter leaves parse-once/evaluate-many completely intact. What the
60
+ * getter must not change is *which functions exist* — and it cannot, because
61
+ * the two names here do not depend on the palette's contents. That was not
62
+ * true of the generated per-variable functions, which is the second reason
63
+ * they are gone.
64
+ */
65
+ import { resolveColorRef } from "../themes/colorRef.js";
66
+ import { ColorRamp, parseRampEasing } from "../themes/ramp.js";
67
+ /**
68
+ * The `(position, color-ref)` pairs a `ramp` call's tail spells, as stops.
69
+ *
70
+ * The engine's `alternating` gate has already typed every even slot as a
71
+ * float and every odd slot as a string; what it cannot see is the *pairing*
72
+ * — a trailing position with no color — nor that the string is a color
73
+ * reference. Both are settled here, once, and `ColorRamp` receives resolved
74
+ * stops it never re-checks. [LAW:parse-dont-validate]
75
+ *
76
+ * Each reference resolves against the palette of *this* evaluation, so a
77
+ * ramp over palette names follows the theme exactly as `color` does.
78
+ */
79
+ function colorStops(tail, palette) {
80
+ if (tail.length % 2 !== 0) {
81
+ throw new RangeError(`ramp's last stop (position ${String(tail[tail.length - 1])}) has no color — ` +
82
+ `stops are <position> <color> pairs`);
83
+ }
84
+ const stops = [];
85
+ for (let i = 0; i < tail.length; i += 2) {
86
+ stops.push({
87
+ at: tail[i],
88
+ color: resolveColorRef(palette, tail[i + 1]),
89
+ });
90
+ }
91
+ return stops;
92
+ }
93
+ /**
94
+ * Register `color "name-or-hex"` against a live palette.
95
+ *
96
+ * `color` resolves a palette variable name to a `#RRGGBB` string, and passes
97
+ * an already-literal color through unchanged. That second half is not a
98
+ * convenience — it makes `color` **idempotent**, which is what lets consumers
99
+ * apply it unconditionally to any author-written color string without first
100
+ * asking whether it is a name or already a color. [LAW:dataflow-not-control-flow]
101
+ *
102
+ * An unknown name throws, carrying near-miss suggestions from the live
103
+ * palette. In a template that surfaces as an evaluation error at the exact
104
+ * call site, which is the signal an author (or an agent editing a config) needs
105
+ * to fix it. [LAW:no-silent-failure]
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * const engine = createEngine({
110
+ * fromString: (s) => new RichText(s),
111
+ * toString: (rt) => rt.plain,
112
+ * funcs: {
113
+ * ...richTextFuncs(),
114
+ * ...colorFuncs(),
115
+ * ...paletteFuncs(() => currentTheme.palette),
116
+ * },
117
+ * });
118
+ * ```
119
+ */
120
+ export function paletteFuncs(getPalette) {
121
+ const colorFunc = {
122
+ fn: ((ref) => resolveColorRef(getPalette(), ref).hex),
123
+ argTypes: ["string"],
124
+ arity: { kind: "exact" },
125
+ returnType: "string",
126
+ };
127
+ // `ramp <value> <easing> <position> <color> …` — the argument list is one
128
+ // float/string cycle end to end (value, easing, then each stop's position
129
+ // and color), so the engine's `alternating` gate types every slot; the
130
+ // pairing and the references are parsed in `colorStops`. Its minimum is the
131
+ // value, the easing, and one stop, so the gate refuses a stopless call.
132
+ // [LAW:types-are-the-program]
133
+ const rampFunc = {
134
+ fn: ((value, easing, ...tail) => new ColorRamp(parseRampEasing(easing), colorStops(tail, getPalette())).at(value)
135
+ .hex),
136
+ argTypes: ["float", "string"],
137
+ arity: { kind: "alternating", minimum: 4 },
138
+ returnType: "string",
139
+ };
140
+ return { color: colorFunc, ramp: rampFunc };
141
+ }
@@ -0,0 +1,28 @@
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 type { FuncMap } from "@promptctl/go-template-js";
19
+ /**
20
+ * Text-styling registrations: the two colour sinks (`fg`, `bg`), text
21
+ * attributes (canonical names, short aliases, and `not_*` negations), the
22
+ * hyperlink cell-splitter (`link`), and the multi-attribute `style` spec.
23
+ *
24
+ * Colours themselves come from elsewhere: `colorFuncs()` for the palette-free
25
+ * math, `paletteFuncs()` for naming a theme colour. This module only paints.
26
+ * [LAW:one-way-deps] — nothing here imports a palette.
27
+ */
28
+ export declare function richTextStyleFuncs(): FuncMap;
@@ -0,0 +1,155 @@
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 a template-built fragment 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
+ // `fragment.style.link` being truthy. `Style.add` propagates `link`
121
+ // through any outer wrapping call, so `{{ fg "red" (link "u" "x") }}` and
122
+ // `{{ link "u" "x" }}` produce shapes that both qualify as cells from
123
+ // the consumer's perspective. Outer-wins on nested links comes for free
124
+ // from `Style.add`'s right-wins-on-conflict rule.
125
+ //
126
+ // [LAW:dataflow-not-control-flow] No special-case AST node, no
127
+ // `LinkFragment` subclass, no second emitter path — the variability is
128
+ // the value of `style.link`, not whether some control-flow branch ran.
129
+ const linkFunc = {
130
+ fn: ((url, child) => {
131
+ return applyStyleToFragment(child, new Style({ link: url }));
132
+ }),
133
+ argTypes: ["string", "liftable"],
134
+ arity: { kind: "exact" },
135
+ returnType: "T",
136
+ };
137
+ // --- Public assembly ---
138
+ /**
139
+ * Text-styling registrations: the two colour sinks (`fg`, `bg`), text
140
+ * attributes (canonical names, short aliases, and `not_*` negations), the
141
+ * hyperlink cell-splitter (`link`), and the multi-attribute `style` spec.
142
+ *
143
+ * Colours themselves come from elsewhere: `colorFuncs()` for the palette-free
144
+ * math, `paletteFuncs()` for naming a theme colour. This module only paints.
145
+ * [LAW:one-way-deps] — nothing here imports a palette.
146
+ */
147
+ export function richTextStyleFuncs() {
148
+ return {
149
+ fg: colorSinkFunc("color"),
150
+ bg: colorSinkFunc("bgcolor"),
151
+ ...attributeFuncs(),
152
+ link: linkFunc,
153
+ style: styleSpecFunc,
154
+ };
155
+ }
@@ -0,0 +1,36 @@
1
+ import { ColorRgba } from "../core/color.js";
2
+ import { Palette } from "./palette.js";
3
+ /**
4
+ * Base colors required to build a full semantic palette.
5
+ * All are ColorRgba — callers construct from whatever source (hex, HSL, etc.).
6
+ */
7
+ export interface BaseColors {
8
+ primary: ColorRgba;
9
+ secondary: ColorRgba;
10
+ accent: ColorRgba;
11
+ success: ColorRgba;
12
+ warning: ColorRgba;
13
+ error: ColorRgba;
14
+ background: ColorRgba;
15
+ foreground: ColorRgba;
16
+ }
17
+ /**
18
+ * Build a full semantic palette from base colors.
19
+ *
20
+ * Derived entries follow Textual's formulas:
21
+ * - `*-muted` = color blended 70% toward its opposite base role. Defined for
22
+ * every accent AND for the two base roles themselves
23
+ * (`foreground-muted` blends toward `background`,
24
+ * `background-muted` blends toward `foreground`) — a caller
25
+ * de-emphasizing body/structural text reaches for
26
+ * `foreground-muted` the same way it reaches for
27
+ * `primary-muted` to de-emphasize an accent.
28
+ * - `text-*` = contrast text tinted 66% with the accent color (use as
29
+ * foreground in muted/background-tinted contexts)
30
+ * - `on-*` = WCAG-correct contrast colour (black or white) for use as
31
+ * foreground when the FULL accent is the background. Picked
32
+ * by relative luminance — single source of truth so widgets
33
+ * never need to invert / reverse fg/bg to get readable text.
34
+ * - `surface` = background blended 5% toward foreground
35
+ */
36
+ export declare function buildPalette(name: string, dark: boolean, base: BaseColors): Palette;
@@ -0,0 +1,56 @@
1
+ import { blendRgb } from "../core/color.js";
2
+ import { alphaBlend, contrastFor } from "./colorMath.js";
3
+ import { Palette } from "./palette.js";
4
+ const MUTED_BLEND = 0.7;
5
+ const TEXT_ALPHA = 0.66;
6
+ const SURFACE_LIFT = 0.05;
7
+ const ACCENT_KEYS = ["primary", "secondary", "accent", "success", "warning", "error"];
8
+ /**
9
+ * Build a full semantic palette from base colors.
10
+ *
11
+ * Derived entries follow Textual's formulas:
12
+ * - `*-muted` = color blended 70% toward its opposite base role. Defined for
13
+ * every accent AND for the two base roles themselves
14
+ * (`foreground-muted` blends toward `background`,
15
+ * `background-muted` blends toward `foreground`) — a caller
16
+ * de-emphasizing body/structural text reaches for
17
+ * `foreground-muted` the same way it reaches for
18
+ * `primary-muted` to de-emphasize an accent.
19
+ * - `text-*` = contrast text tinted 66% with the accent color (use as
20
+ * foreground in muted/background-tinted contexts)
21
+ * - `on-*` = WCAG-correct contrast colour (black or white) for use as
22
+ * foreground when the FULL accent is the background. Picked
23
+ * by relative luminance — single source of truth so widgets
24
+ * never need to invert / reverse fg/bg to get readable text.
25
+ * - `surface` = background blended 5% toward foreground
26
+ */
27
+ export function buildPalette(name, dark, base) {
28
+ const vars = new Map();
29
+ // Base entries
30
+ vars.set("background", base.background);
31
+ vars.set("foreground", base.foreground);
32
+ for (const key of ACCENT_KEYS) {
33
+ vars.set(key, base[key]);
34
+ }
35
+ // Muted variants of the two base roles, same "blend 70% toward
36
+ // background" formula as the accent `*-muted` family below — every
37
+ // base color the palette defines gets a de-emphasized variant, not just
38
+ // the accents. `foreground-muted` is what a caller reaches for to draw
39
+ // structural/contextual text (labels, punctuation, ids) at reduced visual
40
+ // weight without a terminal-support-dependent SGR attribute.
41
+ vars.set("foreground-muted", blendRgb(base.foreground, base.background, MUTED_BLEND));
42
+ vars.set("background-muted", blendRgb(base.background, base.foreground, MUTED_BLEND));
43
+ // Surface — subtle lift from background
44
+ vars.set("surface", blendRgb(base.background, base.foreground, SURFACE_LIFT));
45
+ // Derived: muted, text-, and on- for each accent.
46
+ // [LAW:single-enforcer] contrastFor is the only place that decides
47
+ // black-vs-white for "text on this accent" — widgets read on-${accent}.
48
+ const contrastText = contrastFor(base.background);
49
+ for (const key of ACCENT_KEYS) {
50
+ const color = base[key];
51
+ vars.set(`${key}-muted`, blendRgb(color, base.background, MUTED_BLEND));
52
+ vars.set(`text-${key}`, alphaBlend(color, contrastText, TEXT_ALPHA));
53
+ vars.set(`on-${key}`, contrastFor(color));
54
+ }
55
+ return new Palette(name, dark, vars);
56
+ }
@@ -0,0 +1,82 @@
1
+ import { ColorDepth, ColorRgba, contrastRatio, relativeLuminance } from "../core/color.js";
2
+ /**
3
+ * Darken a color by N levels, where each level reduces HSL lightness by 10%.
4
+ * Negative levels lighten. Level 0 returns an equivalent triplet (after the
5
+ * RGB↔HSL roundtrip; values may differ by ±1 due to rounding).
6
+ */
7
+ export declare function darken(color: ColorRgba, levels: number): ColorRgba;
8
+ /**
9
+ * Lighten a color by N levels. Equivalent to `darken(color, -levels)`.
10
+ */
11
+ export declare function lighten(color: ColorRgba, levels: number): ColorRgba;
12
+ /**
13
+ * Composite `fg` over `bg` with the given alpha (0..1). At alpha=0 returns bg;
14
+ * at alpha=1 returns fg.
15
+ */
16
+ export declare function alphaBlend(fg: ColorRgba, bg: ColorRgba, alpha: number): ColorRgba;
17
+ /**
18
+ * Pick a contrasting foreground (black or white) for a background, using the
19
+ * WCAG relative-luminance threshold of 0.179 (the perceptually correct cutoff
20
+ * where black and white are equally readable). A translucent `bg` is judged
21
+ * as drawn: composited over `substrate` (see `drawnBackground`).
22
+ */
23
+ export declare function contrastFor(bg: ColorRgba, substrate?: ColorRgba): ColorRgba;
24
+ export { relativeLuminance, contrastRatio };
25
+ /**
26
+ * Return a foreground guaranteed to clear `minRatio` against `bg`, keeping the
27
+ * color *recognizably itself*. If the themed `fg` already passes it is returned
28
+ * untouched. Otherwise its OKLCH lightness is slid toward the pole that raises
29
+ * contrast — holding hue, and chroma where it stays in gamut (near the poles
30
+ * gamut clamping may reduce chroma, but hue is preserved) — until the ratio is
31
+ * met, so a blue on a dark-blue background becomes a lighter blue, not white.
32
+ * Only when no lightness of that hue can meet the ratio (a mid-toned
33
+ * background where even pure black-or-white tops out below the target) does it
34
+ * fall back to `contrastFor`'s black/white — the true maximum-contrast pick.
35
+ *
36
+ * A translucent `bg` is measured as it is drawn — composited over
37
+ * `substrate`, the SGR writer's black by default — and a translucent `fg` is
38
+ * then flattened over that drawn background, the order the writer composites
39
+ * in, so the ratio is measured on what the eye actually sees and the returned
40
+ * color is opaque.
41
+ *
42
+ * `drawnAt` is the depth the terminal will draw the pair at. At 256 colours
43
+ * the terminal rounds text and background independently, and two roundings
44
+ * can meet in the middle, so the ratio is measured on the drawn pair: a
45
+ * colour that loses the floor there is replaced by the nearest cube/grey entry
46
+ * that clears it (whose own rounding is itself). At ANSI the terminal draws
47
+ * its own theme's colours, so the ratio is measured on the table's nominal
48
+ * ones (`DRAWN_FROM`): they say which side of the ground text belongs on, and
49
+ * text on its background's index measures 1:1 and is always replaced.
50
+ * Truecolor draws the colour chosen.
51
+ *
52
+ * [LAW:single-enforcer] The one place "is this text readable, and if not fix
53
+ * it" is decided. Callers route every fg/bg pair through here and the
54
+ * unreadable state never reaches output. [LAW:dataflow-not-control-flow] the
55
+ * function always runs; the measured ratio (data) decides how far the
56
+ * lightness moves — there is no caller-side "should I check contrast" branch.
57
+ */
58
+ export declare function ensureContrast(fg: ColorRgba, bg: ColorRgba, minRatio?: number, // WCAG AA for normal text
59
+ drawnAt?: ColorDepth, substrate?: ColorRgba): ColorRgba;
60
+ /**
61
+ * `chosen` (composited opaque over `substrate`), or — when the depth the
62
+ * terminal draws at rounds it to a colour `accept` refuses — the nearest colour that depth draws as itself which
63
+ * `accept` takes. `accept` sees the candidate as drawn, and `drawn`, the same
64
+ * rounding for any other colour it measures against, so the caller states a
65
+ * floor once and it holds on the colours the terminal shows — at ANSI, on
66
+ * the table's nominal colours, as `ensureContrast` measures there. Truecolor
67
+ * draws from no table, so a colour refused there has no replacement;
68
+ * `undefined` means nothing the depth draws is accepted.
69
+ *
70
+ * `ensureContrast` is this with a contrast ratio as the floor; this is for a
71
+ * floor that is not text on its background (an open state standing off every
72
+ * closed cell, two planes standing off each other).
73
+ */
74
+ export declare function ensureDrawn(chosen: ColorRgba, drawnAt: ColorDepth, accept: (candidate: ColorRgba, drawn: (c: ColorRgba) => ColorRgba) => boolean, substrate?: ColorRgba): ColorRgba | undefined;
75
+ /**
76
+ * The colour a ground is shown as at `drawnAt`: composited over `substrate`
77
+ * (the SGR writer's black by default), then rounded to the table that depth
78
+ * draws from — at ANSI, that table's nominal colour stands in for the
79
+ * theme's. The one account `ensureDrawn` and `ensureContrast` measure by, for
80
+ * a caller that measures a floor of its own.
81
+ */
82
+ export declare function drawnColour(colour: ColorRgba, drawnAt: ColorDepth, substrate?: ColorRgba): ColorRgba;