@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,87 @@
1
+ /**
2
+ * Color *values* for the rich-js template binding.
3
+ *
4
+ * ### Why colors are values
5
+ *
6
+ * Every other function in this binding layer takes a child fragment and
7
+ * returns a styled fragment. These do not: they take colors and return a
8
+ * color. That is the whole point. Before this module existed, a color could
9
+ * not survive between two operations — every color function resolved and
10
+ * applied in one breath — so composing two color operations had to be spelled
11
+ * as a *string grammar* (`"primary-darken-3 50%"`), a second language
12
+ * reimplementing function application inside a string literal. Every new color
13
+ * operation meant a new grammar production. [LAW:no-mode-explosion]
14
+ *
15
+ * With colors as values the surface is one shape that grows by nesting:
16
+ *
17
+ * ```
18
+ * {{ fg (color "primary") .branch }}
19
+ * {{ fg (darken (color "primary") 2) .branch }}
20
+ * {{ fg (mix (color "foreground") (color "surface") 65) .label }}
21
+ * ```
22
+ *
23
+ * The simple case is a literal prefix of the composed case — an author adding
24
+ * a `darken` to working code wraps it, rather than rewriting it into an
25
+ * unfamiliar form. [LAW:composability]
26
+ *
27
+ * ### The carrier is a hex string, deliberately
28
+ *
29
+ * A color crosses the template seam as `#RRGGBB`, not as an opaque object,
30
+ * for three reasons:
31
+ *
32
+ * 1. **The type separates.** `"string"` is the strictest arg slot the template
33
+ * engine has — it *refuses* typed `T`. So a color slot cannot silently
34
+ * accept a text fragment. An opaque object would land in `"T"`/`"liftable"`,
35
+ * both of which admit any non-primitive, and `{{ bold (color "primary") }}`
36
+ * would type-check its way into nonsense. [LAW:types-are-the-program]
37
+ * 2. **It flows through the language.** `{{ $muted := mix … }}` holds it,
38
+ * `printf` prints it, `eq` compares it — no engine changes required. Naming
39
+ * a color once and using it ten times is the ergonomic payoff.
40
+ * 3. **Misuse is visible.** A color that lands in text position renders as
41
+ * `#7aa2f7` — wrong in a way an author immediately sees, never a silently
42
+ * dropped style. [LAW:no-silent-failure]
43
+ *
44
+ * No precision is given up: `darken`, `blendRgb`, and `contrastFor` already
45
+ * take and return 8-bit `ColorRgba`. The one caveat is OKLCH: each of the
46
+ * axis functions below round-trips sRGB→OKLCH→sRGB, so chaining three or more
47
+ * of them quantizes visibly. Compose two, or reach for `transposePalette` when
48
+ * a whole palette needs adapting — the same caveat that already applies to
49
+ * chained transpositions.
50
+ *
51
+ * ### Palette-free on purpose
52
+ *
53
+ * Nothing here knows what a palette is. `color` and `ramp` (the two
54
+ * palette-dependent functions, in `palette-funcs.ts`) turn names into hex;
55
+ * from that point on the math is pure. A consumer with no theme system at all still gets
56
+ * the full color vocabulary by feeding it hex literals. [LAW:one-way-deps]
57
+ */
58
+ import type { FuncMap, TemplateFunc } from "@promptctl/go-template-js";
59
+ import { ColorDepth } from "../core/color.js";
60
+ /**
61
+ * The `readableOn` binding, measuring at the depth `drawnAt` names when it is
62
+ * evaluated — a consumer that renders at a per-call depth registers its own.
63
+ */
64
+ export declare const readableOnFunc: (drawnAt: () => ColorDepth) => TemplateFunc;
65
+ /**
66
+ * The palette-free color vocabulary: every function takes colors and returns a
67
+ * color, so they compose by nesting.
68
+ *
69
+ * | function | meaning |
70
+ * |---|---|
71
+ * | `darken c n` / `lighten c n` | slide HSL lightness by N 10% levels |
72
+ * | `mix a b pct` | blend `a` toward `b` by `pct`% (0 → `a`, 100 → `b`) |
73
+ * | `contrastOn bg` | black or white, whichever is readable on `bg` |
74
+ * | `readableOn fg bg ratio` | `fg` nudged in OKLCH lightness until it clears `ratio` on `bg`, hue preserved |
75
+ * | `shiftHue c deg` | rotate hue in OKLCH |
76
+ * | `scaleChroma c f` | multiply chroma (0 → gray, 1 → identity) |
77
+ * | `scaleLightness c f` | multiply lightness (1 → identity, -1 → invert) |
78
+ * | `shiftLightness c d` | add to lightness, after any scale |
79
+ *
80
+ * Pair with `paletteFuncs()` to name colors from a theme, and with
81
+ * `richTextStyleFuncs()`'s `fg`/`bg` to paint them onto text.
82
+ *
83
+ * @param drawnAt the depth the terminal will draw at, read on every
84
+ * `readableOn` evaluation so a host that learns its depth per render passes
85
+ * one getter; defaults to truecolor.
86
+ */
87
+ export declare function colorFuncs(drawnAt?: () => ColorDepth): FuncMap;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Color *values* for the rich-js template binding.
3
+ *
4
+ * ### Why colors are values
5
+ *
6
+ * Every other function in this binding layer takes a child fragment and
7
+ * returns a styled fragment. These do not: they take colors and return a
8
+ * color. That is the whole point. Before this module existed, a color could
9
+ * not survive between two operations — every color function resolved and
10
+ * applied in one breath — so composing two color operations had to be spelled
11
+ * as a *string grammar* (`"primary-darken-3 50%"`), a second language
12
+ * reimplementing function application inside a string literal. Every new color
13
+ * operation meant a new grammar production. [LAW:no-mode-explosion]
14
+ *
15
+ * With colors as values the surface is one shape that grows by nesting:
16
+ *
17
+ * ```
18
+ * {{ fg (color "primary") .branch }}
19
+ * {{ fg (darken (color "primary") 2) .branch }}
20
+ * {{ fg (mix (color "foreground") (color "surface") 65) .label }}
21
+ * ```
22
+ *
23
+ * The simple case is a literal prefix of the composed case — an author adding
24
+ * a `darken` to working code wraps it, rather than rewriting it into an
25
+ * unfamiliar form. [LAW:composability]
26
+ *
27
+ * ### The carrier is a hex string, deliberately
28
+ *
29
+ * A color crosses the template seam as `#RRGGBB`, not as an opaque object,
30
+ * for three reasons:
31
+ *
32
+ * 1. **The type separates.** `"string"` is the strictest arg slot the template
33
+ * engine has — it *refuses* typed `T`. So a color slot cannot silently
34
+ * accept a text fragment. An opaque object would land in `"T"`/`"liftable"`,
35
+ * both of which admit any non-primitive, and `{{ bold (color "primary") }}`
36
+ * would type-check its way into nonsense. [LAW:types-are-the-program]
37
+ * 2. **It flows through the language.** `{{ $muted := mix … }}` holds it,
38
+ * `printf` prints it, `eq` compares it — no engine changes required. Naming
39
+ * a color once and using it ten times is the ergonomic payoff.
40
+ * 3. **Misuse is visible.** A color that lands in text position renders as
41
+ * `#7aa2f7` — wrong in a way an author immediately sees, never a silently
42
+ * dropped style. [LAW:no-silent-failure]
43
+ *
44
+ * No precision is given up: `darken`, `blendRgb`, and `contrastFor` already
45
+ * take and return 8-bit `ColorRgba`. The one caveat is OKLCH: each of the
46
+ * axis functions below round-trips sRGB→OKLCH→sRGB, so chaining three or more
47
+ * of them quantizes visibly. Compose two, or reach for `transposePalette` when
48
+ * a whole palette needs adapting — the same caveat that already applies to
49
+ * chained transpositions.
50
+ *
51
+ * ### Palette-free on purpose
52
+ *
53
+ * Nothing here knows what a palette is. `color` and `ramp` (the two
54
+ * palette-dependent functions, in `palette-funcs.ts`) turn names into hex;
55
+ * from that point on the math is pure. A consumer with no theme system at all still gets
56
+ * the full color vocabulary by feeding it hex literals. [LAW:one-way-deps]
57
+ */
58
+ import { blendRgb, ColorDepth } from "../core/color.js";
59
+ import { Oklch, IDENTITY } from "../core/oklch.js";
60
+ import { HEX_COLOR_RE, parseHexColor } from "../themes/colorRef.js";
61
+ import { darken, contrastFor, ensureContrast } from "../themes/colorMath.js";
62
+ /**
63
+ * The gate every color argument crosses. [LAW:parse-dont-validate] — the
64
+ * function bodies below receive `ColorRgba` and never re-check.
65
+ *
66
+ * Parsing itself is `parseHexColor`'s job; this wrapper exists only to replace
67
+ * its error *wording*, because the mistake it catches is almost always the
68
+ * same one — a palette name passed where a color belongs (`darken "primary" 2`)
69
+ * — and an author who reads "wrap it: darken (color "primary")" is corrected in
70
+ * one step, where "expected #RRGGBB" leaves them guessing.
71
+ * [LAW:no-silent-failure]
72
+ */
73
+ function asColor(value, func) {
74
+ if (!HEX_COLOR_RE.test(value.trim())) {
75
+ throw new TypeError(`${func} expected a color (#RRGGBB or #RRGGBBAA), got ${JSON.stringify(value)}` +
76
+ ` — to use a palette name here, wrap it: ${func} (color ${JSON.stringify(value)}) …`);
77
+ }
78
+ return parseHexColor(value);
79
+ }
80
+ // [LAW:single-enforcer] Every colour function is fixed-arity, so the engine's
81
+ // arity gate refuses a missing or surplus argument before the body runs.
82
+ function colorFunc(argTypes, fn) {
83
+ return { fn, argTypes, arity: { kind: "exact" }, returnType: "string" };
84
+ }
85
+ /**
86
+ * The gate every *numeric* argument crosses, with an optional inclusive range.
87
+ *
88
+ * [LAW:single-enforcer] One check for every `"float"` slot. An `"int"` slot is
89
+ * a safe integer by the engine's gate and needs none, but a `"float"` slot admits
90
+ * NaN and Infinity (legitimate IEEE-754 values), which would otherwise surface
91
+ * as `ColorRgba.red must be an integer in [0, 255]; got NaN` — loud, but
92
+ * pointing at a channel the author never wrote. [LAW:no-silent-failure]
93
+ */
94
+ function asAmount(value, func, param, range) {
95
+ const inRange = range === undefined || (value >= range.min && value <= range.max);
96
+ if (!Number.isFinite(value) || !inRange) {
97
+ const bound = range === undefined
98
+ ? "a finite number"
99
+ : `within ${range.min}..${range.max} (${range.note})`;
100
+ throw new RangeError(`${func}'s ${param} must be ${bound}, got ${value}`);
101
+ }
102
+ return value;
103
+ }
104
+ // --- HSL lightness ---
105
+ //
106
+ // `lighten` is `darken` with the sign flipped — the same operation, not a
107
+ // second one (colorMath's `lighten` is literally `darken(c, -levels)`). Both
108
+ // names ship because "lighten by 2" and "darken by -2" are not equally
109
+ // readable at a call site, and the operation is genuinely bidirectional.
110
+ const darkenFunc = colorFunc(["string", "int"], ((hex, levels) => darken(asColor(hex, "darken"), levels).hex));
111
+ const lightenFunc = colorFunc(["string", "int"], ((hex, levels) => darken(asColor(hex, "lighten"), -levels).hex));
112
+ // --- Blending ---
113
+ //
114
+ // [LAW:one-type-per-behavior] There is no separate `alphaBlend` binding.
115
+ // `alphaBlend(fg, bg, a)` in colorMath is *defined* as `blendRgb(bg, fg, a)` —
116
+ // one operation wearing two names. Compositing a translucent color over a
117
+ // background and mixing two colors by a percentage are the same arithmetic,
118
+ // so they get one function. Terminals have no alpha channel, which is also why
119
+ // no function here *produces* a translucent color: it could not be rendered,
120
+ // and a color that silently does nothing in the sink is worse than no
121
+ // function at all.
122
+ const MIX_PCT_MAX = 100;
123
+ const MIX_RANGE = { min: 0, max: MIX_PCT_MAX, note: "0 = from, 100 = toward" };
124
+ const mixFunc = colorFunc(["string", "string", "float"], ((fromHex, towardHex, pct) => blendRgb(asColor(fromHex, "mix"), asColor(towardHex, "mix"), asAmount(pct, "mix", "percentage", MIX_RANGE) / MIX_PCT_MAX).hex));
125
+ // --- Readability ---
126
+ //
127
+ // Two different questions, so two functions rather than one with a flag:
128
+ // `contrastOn` asks "what is maximally readable here" (black or white, WCAG
129
+ // relative-luminance cutoff); `readableOn` asks "keep this color recognizably
130
+ // itself, but guarantee it clears `ratio`" — it slides OKLCH lightness and
131
+ // preserves hue, so a blue on dark blue becomes a lighter blue rather than
132
+ // white.
133
+ //
134
+ // [LAW:no-silent-failure] `ratio` is required, not defaulted to WCAG AA's 4.5.
135
+ // The threshold is the whole decision this function makes, and the right value
136
+ // genuinely differs by intent — 4.5 for body text, 3.0 for large or
137
+ // deliberately recessive text. A hidden default would silently govern an
138
+ // accessibility outcome the author never saw, and would quietly over-correct a
139
+ // caller whose *point* was to be quiet: de-emphasized text floored at 4.5 is no
140
+ // longer de-emphasized.
141
+ // WCAG 2.x contrast ratios live in [1, 21] — 1 is a color on itself.
142
+ const RATIO_RANGE = {
143
+ min: 1,
144
+ max: 21,
145
+ note: "4.5 = AA body text, 3 = AA large text",
146
+ };
147
+ const contrastOnFunc = colorFunc(["string"], ((bgHex) => contrastFor(asColor(bgHex, "contrastOn")).hex));
148
+ /**
149
+ * The `readableOn` binding, measuring at the depth `drawnAt` names when it is
150
+ * evaluated — a consumer that renders at a per-call depth registers its own.
151
+ */
152
+ export const readableOnFunc = (drawnAt) => colorFunc(["string", "string", "float"], ((fgHex, bgHex, ratio) => ensureContrast(asColor(fgHex, "readableOn"), asColor(bgHex, "readableOn"), asAmount(ratio, "readableOn", "ratio", RATIO_RANGE), drawnAt()).hex));
153
+ // --- OKLCH axes ---
154
+ //
155
+ // [LAW:one-source-of-truth] One function per `ThemeKey` axis, and the table is
156
+ // keyed BY the axis so the compiler proves it total: add a field to `ThemeKey`
157
+ // and this object stops satisfying `Record<keyof ThemeKey, string>` until a
158
+ // binding name is chosen for it. The same derive-registrations-from-the-real-
159
+ // inventory trick `attributeFuncs()` uses over `ATTRIBUTE_NAMES`.
160
+ //
161
+ // Naming follows the axis semantics rather than the field spelling: `shift`
162
+ // for the additive axes, `scale` for the multiplicative ones, so a reader can
163
+ // tell from the call site whether `1` means "no change" or "one unit".
164
+ const OKLCH_AXIS_FUNCS = {
165
+ hueShift: "shiftHue",
166
+ chromaScale: "scaleChroma",
167
+ lightnessScale: "scaleLightness",
168
+ lightnessShift: "shiftLightness",
169
+ };
170
+ function oklchAxisFuncs() {
171
+ const out = {};
172
+ for (const [axis, funcName] of Object.entries(OKLCH_AXIS_FUNCS)) {
173
+ // [LAW:dataflow-not-control-flow] Every axis takes the identical path —
174
+ // splice one value into the identity key and apply it. The axis is data.
175
+ const key = axis;
176
+ out[funcName] = colorFunc(["string", "float"], ((hex, amount) => {
177
+ const themeKey = {
178
+ ...IDENTITY,
179
+ [key]: asAmount(amount, funcName, "amount"),
180
+ };
181
+ return Oklch.fromRgba(asColor(hex, funcName)).applyKey(themeKey).toRgba().hex;
182
+ }));
183
+ }
184
+ return out;
185
+ }
186
+ /**
187
+ * The palette-free color vocabulary: every function takes colors and returns a
188
+ * color, so they compose by nesting.
189
+ *
190
+ * | function | meaning |
191
+ * |---|---|
192
+ * | `darken c n` / `lighten c n` | slide HSL lightness by N 10% levels |
193
+ * | `mix a b pct` | blend `a` toward `b` by `pct`% (0 → `a`, 100 → `b`) |
194
+ * | `contrastOn bg` | black or white, whichever is readable on `bg` |
195
+ * | `readableOn fg bg ratio` | `fg` nudged in OKLCH lightness until it clears `ratio` on `bg`, hue preserved |
196
+ * | `shiftHue c deg` | rotate hue in OKLCH |
197
+ * | `scaleChroma c f` | multiply chroma (0 → gray, 1 → identity) |
198
+ * | `scaleLightness c f` | multiply lightness (1 → identity, -1 → invert) |
199
+ * | `shiftLightness c d` | add to lightness, after any scale |
200
+ *
201
+ * Pair with `paletteFuncs()` to name colors from a theme, and with
202
+ * `richTextStyleFuncs()`'s `fg`/`bg` to paint them onto text.
203
+ *
204
+ * @param drawnAt the depth the terminal will draw at, read on every
205
+ * `readableOn` evaluation so a host that learns its depth per render passes
206
+ * one getter; defaults to truecolor.
207
+ */
208
+ export function colorFuncs(drawnAt = () => ColorDepth.TRUECOLOR) {
209
+ return {
210
+ darken: darkenFunc,
211
+ lighten: lightenFunc,
212
+ mix: mixFunc,
213
+ contrastOn: contrastOnFunc,
214
+ readableOn: readableOnFunc(drawnAt),
215
+ ...oklchAxisFuncs(),
216
+ };
217
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared internal helpers for the rich-js template binding layer.
3
+ *
4
+ * [LAW:one-source-of-truth] `applyStyleToFragment` is the single implementation
5
+ * of "apply a Style to an existing RichText fragment." Both the style-function
6
+ * registrations and the palette-function registrations use this same operation;
7
+ * keeping one copy prevents the two from drifting.
8
+ */
9
+ import { Style } from "../core/style.js";
10
+ import { RichText } from "../core/text.js";
11
+ /**
12
+ * Apply a style on top of an already-styled RichText fragment.
13
+ *
14
+ * [LAW:single-enforcer] Type validation lives here; callers trust the result.
15
+ * The `"liftable"` arg type lifts string literals to `RichText` via the
16
+ * engine's `fromString` before the body runs, so `child` is always a
17
+ * `RichText` by the time this runs. The instanceof check exists because
18
+ * `"liftable"` admits any non-primitive — the engine cannot prove the object
19
+ * is the binding's own `T`. Misuse (`{{ red someMap }}`) fails loudly here
20
+ * rather than producing a malformed fragment.
21
+ *
22
+ * Conflict resolution: `Style.add` — the outer (newly applied) style wins.
23
+ */
24
+ export declare function applyStyleToFragment(child: unknown, style: Style): RichText;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared internal helpers for the rich-js template binding layer.
3
+ *
4
+ * [LAW:one-source-of-truth] `applyStyleToFragment` is the single implementation
5
+ * of "apply a Style to an existing RichText fragment." Both the style-function
6
+ * registrations and the palette-function registrations use this same operation;
7
+ * keeping one copy prevents the two from drifting.
8
+ */
9
+ import { Style, StyleSyntaxError } from "../core/style.js";
10
+ import { RichText } from "../core/text.js";
11
+ /**
12
+ * Apply a style on top of an already-styled RichText fragment.
13
+ *
14
+ * [LAW:single-enforcer] Type validation lives here; callers trust the result.
15
+ * The `"liftable"` arg type lifts string literals to `RichText` via the
16
+ * engine's `fromString` before the body runs, so `child` is always a
17
+ * `RichText` by the time this runs. The instanceof check exists because
18
+ * `"liftable"` admits any non-primitive — the engine cannot prove the object
19
+ * is the binding's own `T`. Misuse (`{{ red someMap }}`) fails loudly here
20
+ * rather than producing a malformed fragment.
21
+ *
22
+ * Conflict resolution: `Style.add` — the outer (newly applied) style wins.
23
+ */
24
+ export function applyStyleToFragment(child, style) {
25
+ if (!(child instanceof RichText)) {
26
+ throw new TypeError(`template function expected a RichText fragment, got ${typeof child === "object" ? Object.prototype.toString.call(child) : typeof child}`);
27
+ }
28
+ const result = child.copy();
29
+ result.style = baseStyleOf(child.style).add(style);
30
+ return result;
31
+ }
32
+ /**
33
+ * A fragment's base style as a `Style`. A string must parse as a definition: a
34
+ * name resolves only against the theme of the render that draws it, and a
35
+ * template runs before any render exists.
36
+ */
37
+ function baseStyleOf(style) {
38
+ if (style instanceof Style)
39
+ return style;
40
+ try {
41
+ return Style.parse(style);
42
+ }
43
+ catch (err) {
44
+ if (!(err instanceof StyleSyntaxError))
45
+ throw err;
46
+ throw new TypeError(`template function cannot style a fragment whose base style "${style}" is not a style definition: a style name resolves only against the theme of a render`, { cause: err });
47
+ }
48
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * rich-js template bindings — public entry point.
3
+ *
4
+ * [LAW:one-source-of-truth] This module is the public entry point for
5
+ * rich-js's styling vocabulary as `@promptctl/go-template-js` template
6
+ * functions. The split between the two exported registrations is exactly the
7
+ * split between what needs a theme and what does not:
8
+ *
9
+ * - `richTextFuncs()` — the colour sinks (`fg`/`bg`), the palette-free colour
10
+ * math (`darken`, `mix`, `contrastOn`, the OKLCH axes …), text attributes,
11
+ * and `link`. Needs no configuration; safe to register unconditionally. A
12
+ * consumer with no theme system still gets the complete colour vocabulary by
13
+ * feeding it hex literals.
14
+ * - `paletteFuncs(getPalette)` — `color` and `ramp`, the two functions that
15
+ * resolve a palette name. This is the *only* registration that knows a
16
+ * palette exists, which is why it is the only one that takes an argument.
17
+ * [LAW:one-way-deps]
18
+ *
19
+ * `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
20
+ * palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
21
+ * and merge it into their own engine config.
22
+ *
23
+ * Colours compose by nesting, like every other function:
24
+ * `{{ fg (darken (color "primary") 2) (bold "x") }}`. There is no second
25
+ * markup grammar and no colour-spec mini-language — the template *is* the
26
+ * composition mechanism.
27
+ *
28
+ * Fragment type: `RichText`. Chosen because it is the library's primary
29
+ * text type (implements `Renderable` + `Measurable`), composes via
30
+ * `append`, and flows directly into the existing render pipeline with no
31
+ * adapter. Top-level template expressions emit as `RichText[]` — natural
32
+ * for cases where a single template produces multiple independently
33
+ * addressable fragments (e.g. multiple top-level `link` calls, each
34
+ * carrying its own URL).
35
+ */
36
+ import { type Engine, type FuncMap } from "@promptctl/go-template-js";
37
+ import { RichText } from "../core/text.js";
38
+ import { Segment } from "../core/segment.js";
39
+ import { ColorDepth } from "../core/color.js";
40
+ export { paletteFuncs } from "./palette-funcs.js";
41
+ export { colorFuncs, readableOnFunc } from "./color-funcs.js";
42
+ /**
43
+ * Funcs registered by the rich-js binding — the colour sinks, the palette-free
44
+ * colour math, text attributes, and the `link` cell-splitter. Nothing here
45
+ * needs a theme; the two that do (`color`, `ramp`) ship separately via
46
+ * `paletteFuncs(getPalette)`, merged consumer-side. `drawnAt` is forwarded to
47
+ * `colorFuncs` — the depth `readableOn` measures at, truecolor when omitted.
48
+ *
49
+ * `FuncMap` is not parameterised over `T` in `@promptctl/go-template-js` — the engine's
50
+ * `T` lives on the `Engine`/`EngineConfig`, and per-function input/output
51
+ * types are carried as runtime `argTypes` on each `TemplateFunc`. The map
52
+ * therefore *type-checks* against any `EngineConfig<T>.funcs`, but the
53
+ * functions returned here are only *runtime-compatible* with
54
+ * `Engine<RichText>`: each style function returns a `RichText` and
55
+ * requires its lifted child to be a `RichText` (enforced by an
56
+ * `instanceof` check that throws on misuse). Consumers merging this map
57
+ * into a wider engine must keep `T = RichText`; merging into an engine
58
+ * whose `T` is something else will compile but fail at evaluation time.
59
+ */
60
+ export declare function richTextFuncs(drawnAt?: () => ColorDepth): FuncMap;
61
+ /**
62
+ * Construct an `Engine<RichText>` with the rich-js style-function set
63
+ * registered. Consumers that already manage their own engine should call
64
+ * `richTextFuncs()` and merge the result into their own `createEngine`
65
+ * configuration; consumers that just want rich-js styling can use this
66
+ * factory directly.
67
+ *
68
+ * `fromString` lifts text literals into `RichText`. `toString` flattens a
69
+ * `RichText` to its plain text — the engine's no-silent-flatten guard uses
70
+ * this only for `printf "%s"` / `"%q"` and `print*` slots, where ANSI is
71
+ * not desired anyway.
72
+ */
73
+ export declare function createRichTextEngine(): Engine<RichText>;
74
+ /**
75
+ * Compile a template source against `engine` and render the result to a
76
+ * flat `Segment[]`. The 90%-case convenience over chaining `engine.compile`,
77
+ * `RichText.fromFragments`, and `.render` by hand:
78
+ *
79
+ * - Runs `engine.compile(source)(scope)` to get the engine's `RichText[]`.
80
+ * - Flattens that fragment list into a single styled `RichText` via
81
+ * `RichText.fromFragments` so every fragment's wrapping style survives.
82
+ * - Renders to a `Segment[]` at the requested `maxWidth`.
83
+ * - Wraps the whole flow in a try/catch — on parse/evaluate failure,
84
+ * emits a single dim styled `[error: <message>]` segment the caller can
85
+ * drop into their layout. No bespoke fallback wiring required at every
86
+ * call site.
87
+ *
88
+ * [LAW:single-enforcer] One place owns "render a template to segments,
89
+ * degrade gracefully on errors" — every consumer that wants this exact
90
+ * shape reads from here rather than re-implementing the same try/catch +
91
+ * error-formatting glue.
92
+ *
93
+ * For the 10% — custom error UX, intermediate access to the `RichText`,
94
+ * pre-compiled templates re-used many times — call the engine directly
95
+ * and use `RichText.fromFragments` to flatten. This helper is sugar for
96
+ * the live-render case (e.g. a preview pane), not a replacement for the
97
+ * compile-once-evaluate-many pattern.
98
+ *
99
+ * @param maxWidth defaults to 400 — large enough that downstream `splitLines`
100
+ * / `adjustLineLength` clipping decides actual width, matching the typical
101
+ * "render wide, fit on output" pipeline.
102
+ * @param errorStyle is a `Style.parse` spec (default `"red dim"`).
103
+ */
104
+ export declare function renderTemplate(engine: Engine<RichText>, source: string, scope?: unknown, options?: {
105
+ maxWidth?: number;
106
+ errorStyle?: string;
107
+ }): Segment[];
@@ -0,0 +1,146 @@
1
+ /**
2
+ * rich-js template bindings — public entry point.
3
+ *
4
+ * [LAW:one-source-of-truth] This module is the public entry point for
5
+ * rich-js's styling vocabulary as `@promptctl/go-template-js` template
6
+ * functions. The split between the two exported registrations is exactly the
7
+ * split between what needs a theme and what does not:
8
+ *
9
+ * - `richTextFuncs()` — the colour sinks (`fg`/`bg`), the palette-free colour
10
+ * math (`darken`, `mix`, `contrastOn`, the OKLCH axes …), text attributes,
11
+ * and `link`. Needs no configuration; safe to register unconditionally. A
12
+ * consumer with no theme system still gets the complete colour vocabulary by
13
+ * feeding it hex literals.
14
+ * - `paletteFuncs(getPalette)` — `color` and `ramp`, the two functions that
15
+ * resolve a palette name. This is the *only* registration that knows a
16
+ * palette exists, which is why it is the only one that takes an argument.
17
+ * [LAW:one-way-deps]
18
+ *
19
+ * `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
20
+ * palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
21
+ * and merge it into their own engine config.
22
+ *
23
+ * Colours compose by nesting, like every other function:
24
+ * `{{ fg (darken (color "primary") 2) (bold "x") }}`. There is no second
25
+ * markup grammar and no colour-spec mini-language — the template *is* the
26
+ * composition mechanism.
27
+ *
28
+ * Fragment type: `RichText`. Chosen because it is the library's primary
29
+ * text type (implements `Renderable` + `Measurable`), composes via
30
+ * `append`, and flows directly into the existing render pipeline with no
31
+ * adapter. Top-level template expressions emit as `RichText[]` — natural
32
+ * for cases where a single template produces multiple independently
33
+ * addressable fragments (e.g. multiple top-level `link` calls, each
34
+ * carrying its own URL).
35
+ */
36
+ import { createEngine } from "@promptctl/go-template-js";
37
+ import { RichText } from "../core/text.js";
38
+ import { Style } from "../core/style.js";
39
+ import { Segment } from "../core/segment.js";
40
+ import { richTextStyleFuncs } from "./style-funcs.js";
41
+ import { colorFuncs } from "./color-funcs.js";
42
+ export { paletteFuncs } from "./palette-funcs.js";
43
+ export { colorFuncs, readableOnFunc } from "./color-funcs.js";
44
+ /**
45
+ * Funcs registered by the rich-js binding — the colour sinks, the palette-free
46
+ * colour math, text attributes, and the `link` cell-splitter. Nothing here
47
+ * needs a theme; the two that do (`color`, `ramp`) ship separately via
48
+ * `paletteFuncs(getPalette)`, merged consumer-side. `drawnAt` is forwarded to
49
+ * `colorFuncs` — the depth `readableOn` measures at, truecolor when omitted.
50
+ *
51
+ * `FuncMap` is not parameterised over `T` in `@promptctl/go-template-js` — the engine's
52
+ * `T` lives on the `Engine`/`EngineConfig`, and per-function input/output
53
+ * types are carried as runtime `argTypes` on each `TemplateFunc`. The map
54
+ * therefore *type-checks* against any `EngineConfig<T>.funcs`, but the
55
+ * functions returned here are only *runtime-compatible* with
56
+ * `Engine<RichText>`: each style function returns a `RichText` and
57
+ * requires its lifted child to be a `RichText` (enforced by an
58
+ * `instanceof` check that throws on misuse). Consumers merging this map
59
+ * into a wider engine must keep `T = RichText`; merging into an engine
60
+ * whose `T` is something else will compile but fail at evaluation time.
61
+ */
62
+ // [LAW:one-source-of-truth] The truecolor default lives on `colorFuncs` alone;
63
+ // an omitted `drawnAt` is forwarded as omitted.
64
+ export function richTextFuncs(drawnAt) {
65
+ return { ...richTextStyleFuncs(), ...colorFuncs(drawnAt) };
66
+ }
67
+ /**
68
+ * Construct an `Engine<RichText>` with the rich-js style-function set
69
+ * registered. Consumers that already manage their own engine should call
70
+ * `richTextFuncs()` and merge the result into their own `createEngine`
71
+ * configuration; consumers that just want rich-js styling can use this
72
+ * factory directly.
73
+ *
74
+ * `fromString` lifts text literals into `RichText`. `toString` flattens a
75
+ * `RichText` to its plain text — the engine's no-silent-flatten guard uses
76
+ * this only for `printf "%s"` / `"%q"` and `print*` slots, where ANSI is
77
+ * not desired anyway.
78
+ */
79
+ export function createRichTextEngine() {
80
+ return createEngine({
81
+ fromString: (s) => new RichText(s),
82
+ toString: (rt) => rt.plain,
83
+ funcs: richTextFuncs(),
84
+ });
85
+ }
86
+ /**
87
+ * Compile a template source against `engine` and render the result to a
88
+ * flat `Segment[]`. The 90%-case convenience over chaining `engine.compile`,
89
+ * `RichText.fromFragments`, and `.render` by hand:
90
+ *
91
+ * - Runs `engine.compile(source)(scope)` to get the engine's `RichText[]`.
92
+ * - Flattens that fragment list into a single styled `RichText` via
93
+ * `RichText.fromFragments` so every fragment's wrapping style survives.
94
+ * - Renders to a `Segment[]` at the requested `maxWidth`.
95
+ * - Wraps the whole flow in a try/catch — on parse/evaluate failure,
96
+ * emits a single dim styled `[error: <message>]` segment the caller can
97
+ * drop into their layout. No bespoke fallback wiring required at every
98
+ * call site.
99
+ *
100
+ * [LAW:single-enforcer] One place owns "render a template to segments,
101
+ * degrade gracefully on errors" — every consumer that wants this exact
102
+ * shape reads from here rather than re-implementing the same try/catch +
103
+ * error-formatting glue.
104
+ *
105
+ * For the 10% — custom error UX, intermediate access to the `RichText`,
106
+ * pre-compiled templates re-used many times — call the engine directly
107
+ * and use `RichText.fromFragments` to flatten. This helper is sugar for
108
+ * the live-render case (e.g. a preview pane), not a replacement for the
109
+ * compile-once-evaluate-many pattern.
110
+ *
111
+ * @param maxWidth defaults to 400 — large enough that downstream `splitLines`
112
+ * / `adjustLineLength` clipping decides actual width, matching the typical
113
+ * "render wide, fit on output" pipeline.
114
+ * @param errorStyle is a `Style.parse` spec (default `"red dim"`).
115
+ */
116
+ export function renderTemplate(engine, source, scope = {}, options) {
117
+ try {
118
+ const frags = engine.compile(source)(scope);
119
+ const rt = RichText.fromFragments(frags);
120
+ return Array.from(rt.render({
121
+ maxWidth: options?.maxWidth ?? 400,
122
+ isTerminal: true,
123
+ }));
124
+ }
125
+ catch (e) {
126
+ // An engine error's `message` is its one-line summary; `String(e)` adds a
127
+ // multi-line code frame, which would break this single segment across lines.
128
+ const summary = e instanceof Error ? e.message : String(e);
129
+ return [new Segment(`[error: ${summary.slice(0, 80)}]`, safeErrorStyle(options?.errorStyle))];
130
+ }
131
+ }
132
+ // [LAW:single-enforcer] The error-path Style must not itself throw, or
133
+ // the whole "degrade gracefully" promise of renderTemplate is broken. A
134
+ // bogus user-supplied `errorStyle` spec falls back to a hard-coded safe
135
+ // Style — never propagates the parse failure to the caller.
136
+ const FALLBACK_ERROR_STYLE = new Style({ color: "red", dim: true });
137
+ function safeErrorStyle(spec) {
138
+ if (spec === undefined)
139
+ return FALLBACK_ERROR_STYLE;
140
+ try {
141
+ return Style.parse(spec);
142
+ }
143
+ catch {
144
+ return FALLBACK_ERROR_STYLE;
145
+ }
146
+ }