@promptctl/rich-js 0.17.0 → 0.18.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 (206) hide show
  1. package/README.md +13 -26
  2. package/package.json +7 -3
  3. package/dist/core/box.d.ts +0 -144
  4. package/dist/core/box.js +0 -391
  5. package/dist/core/cells.d.ts +0 -148
  6. package/dist/core/cells.js +0 -266
  7. package/dist/core/color.d.ts +0 -250
  8. package/dist/core/color.js +0 -1029
  9. package/dist/core/console.d.ts +0 -156
  10. package/dist/core/console.js +0 -472
  11. package/dist/core/emoji.d.ts +0 -24
  12. package/dist/core/emoji.js +0 -391
  13. package/dist/core/export-html.d.ts +0 -24
  14. package/dist/core/export-html.js +0 -106
  15. package/dist/core/export-lines.d.ts +0 -121
  16. package/dist/core/export-lines.js +0 -141
  17. package/dist/core/highlighter.d.ts +0 -33
  18. package/dist/core/highlighter.js +0 -124
  19. package/dist/core/json.d.ts +0 -27
  20. package/dist/core/json.js +0 -53
  21. package/dist/core/markup.d.ts +0 -97
  22. package/dist/core/markup.js +0 -619
  23. package/dist/core/measure.d.ts +0 -27
  24. package/dist/core/measure.js +0 -61
  25. package/dist/core/oklch.d.ts +0 -161
  26. package/dist/core/oklch.js +0 -312
  27. package/dist/core/osc8.d.ts +0 -68
  28. package/dist/core/osc8.js +0 -90
  29. package/dist/core/pretty.d.ts +0 -141
  30. package/dist/core/pretty.js +0 -472
  31. package/dist/core/protocol.d.ts +0 -137
  32. package/dist/core/protocol.js +0 -101
  33. package/dist/core/render.d.ts +0 -84
  34. package/dist/core/render.js +0 -145
  35. package/dist/core/segment.d.ts +0 -142
  36. package/dist/core/segment.js +0 -433
  37. package/dist/core/spinnerData.d.ts +0 -10
  38. package/dist/core/spinnerData.js +0 -115
  39. package/dist/core/strip.d.ts +0 -145
  40. package/dist/core/strip.js +0 -308
  41. package/dist/core/style.d.ts +0 -144
  42. package/dist/core/style.js +0 -723
  43. package/dist/core/subscription.d.ts +0 -12
  44. package/dist/core/subscription.js +0 -12
  45. package/dist/core/text.d.ts +0 -255
  46. package/dist/core/text.js +0 -990
  47. package/dist/core/wrap.d.ts +0 -41
  48. package/dist/core/wrap.js +0 -127
  49. package/dist/host/host-stream.d.ts +0 -30
  50. package/dist/host/host-stream.js +0 -35
  51. package/dist/host/index.d.ts +0 -3
  52. package/dist/host/index.js +0 -2
  53. package/dist/host/terminal-host.d.ts +0 -143
  54. package/dist/host/terminal-host.js +0 -132
  55. package/dist/index.d.ts +0 -87
  56. package/dist/index.js +0 -104
  57. package/dist/node/prompt.d.ts +0 -21
  58. package/dist/node/prompt.js +0 -30
  59. package/dist/node/save.d.ts +0 -17
  60. package/dist/node/save.js +0 -21
  61. package/dist/node/terminal-host.d.ts +0 -84
  62. package/dist/node/terminal-host.js +0 -160
  63. package/dist/node/traceback.d.ts +0 -27
  64. package/dist/node/traceback.js +0 -112
  65. package/dist/renderables/align.d.ts +0 -16
  66. package/dist/renderables/align.js +0 -46
  67. package/dist/renderables/columns.d.ts +0 -61
  68. package/dist/renderables/columns.js +0 -168
  69. package/dist/renderables/constrain.d.ts +0 -15
  70. package/dist/renderables/constrain.js +0 -37
  71. package/dist/renderables/flexStrip.d.ts +0 -41
  72. package/dist/renderables/flexStrip.js +0 -200
  73. package/dist/renderables/group.d.ts +0 -11
  74. package/dist/renderables/group.js +0 -15
  75. package/dist/renderables/layout.d.ts +0 -92
  76. package/dist/renderables/layout.js +0 -315
  77. package/dist/renderables/live.d.ts +0 -47
  78. package/dist/renderables/live.js +0 -131
  79. package/dist/renderables/markdown.d.ts +0 -24
  80. package/dist/renderables/markdown.js +0 -182
  81. package/dist/renderables/padding.d.ts +0 -38
  82. package/dist/renderables/padding.js +0 -140
  83. package/dist/renderables/panel.d.ts +0 -116
  84. package/dist/renderables/panel.js +0 -322
  85. package/dist/renderables/progress.d.ts +0 -90
  86. package/dist/renderables/progress.js +0 -241
  87. package/dist/renderables/progressBar.d.ts +0 -31
  88. package/dist/renderables/progressBar.js +0 -52
  89. package/dist/renderables/prompt.d.ts +0 -49
  90. package/dist/renderables/prompt.js +0 -125
  91. package/dist/renderables/rule.d.ts +0 -24
  92. package/dist/renderables/rule.js +0 -90
  93. package/dist/renderables/spinner.d.ts +0 -29
  94. package/dist/renderables/spinner.js +0 -64
  95. package/dist/renderables/status.d.ts +0 -23
  96. package/dist/renderables/status.js +0 -60
  97. package/dist/renderables/syntax.d.ts +0 -35
  98. package/dist/renderables/syntax.js +0 -114
  99. package/dist/renderables/table.d.ts +0 -182
  100. package/dist/renderables/table.js +0 -697
  101. package/dist/renderables/traceback.d.ts +0 -31
  102. package/dist/renderables/traceback.js +0 -109
  103. package/dist/renderables/tree.d.ts +0 -54
  104. package/dist/renderables/tree.js +0 -159
  105. package/dist/template-bindings/color-funcs.d.ts +0 -87
  106. package/dist/template-bindings/color-funcs.js +0 -219
  107. package/dist/template-bindings/helpers.d.ts +0 -24
  108. package/dist/template-bindings/helpers.js +0 -48
  109. package/dist/template-bindings/index.d.ts +0 -107
  110. package/dist/template-bindings/index.js +0 -144
  111. package/dist/template-bindings/palette-funcs.d.ts +0 -94
  112. package/dist/template-bindings/palette-funcs.js +0 -151
  113. package/dist/template-bindings/style-funcs.d.ts +0 -28
  114. package/dist/template-bindings/style-funcs.js +0 -151
  115. package/dist/themes/buildPalette.d.ts +0 -36
  116. package/dist/themes/buildPalette.js +0 -56
  117. package/dist/themes/colorMath.d.ts +0 -82
  118. package/dist/themes/colorMath.js +0 -254
  119. package/dist/themes/colorRef.d.ts +0 -51
  120. package/dist/themes/colorRef.js +0 -96
  121. package/dist/themes/data/atom-one-dark.d.ts +0 -3
  122. package/dist/themes/data/atom-one-dark.js +0 -158
  123. package/dist/themes/data/atom-one-light.d.ts +0 -3
  124. package/dist/themes/data/atom-one-light.js +0 -158
  125. package/dist/themes/data/catppuccin-frappe.d.ts +0 -3
  126. package/dist/themes/data/catppuccin-frappe.js +0 -158
  127. package/dist/themes/data/catppuccin-latte.d.ts +0 -3
  128. package/dist/themes/data/catppuccin-latte.js +0 -158
  129. package/dist/themes/data/catppuccin-macchiato.d.ts +0 -3
  130. package/dist/themes/data/catppuccin-macchiato.js +0 -158
  131. package/dist/themes/data/catppuccin-mocha.d.ts +0 -3
  132. package/dist/themes/data/catppuccin-mocha.js +0 -158
  133. package/dist/themes/data/cyberpunk.d.ts +0 -17
  134. package/dist/themes/data/cyberpunk.js +0 -191
  135. package/dist/themes/data/default.d.ts +0 -3
  136. package/dist/themes/data/default.js +0 -158
  137. package/dist/themes/data/dracula.d.ts +0 -3
  138. package/dist/themes/data/dracula.js +0 -158
  139. package/dist/themes/data/flexoki.d.ts +0 -3
  140. package/dist/themes/data/flexoki.js +0 -158
  141. package/dist/themes/data/gruvbox.d.ts +0 -3
  142. package/dist/themes/data/gruvbox.js +0 -158
  143. package/dist/themes/data/index.d.ts +0 -35
  144. package/dist/themes/data/index.js +0 -64
  145. package/dist/themes/data/monokai.d.ts +0 -3
  146. package/dist/themes/data/monokai.js +0 -158
  147. package/dist/themes/data/nord.d.ts +0 -3
  148. package/dist/themes/data/nord.js +0 -158
  149. package/dist/themes/data/rose-pine-dawn.d.ts +0 -3
  150. package/dist/themes/data/rose-pine-dawn.js +0 -158
  151. package/dist/themes/data/rose-pine-moon.d.ts +0 -3
  152. package/dist/themes/data/rose-pine-moon.js +0 -158
  153. package/dist/themes/data/rose-pine.d.ts +0 -3
  154. package/dist/themes/data/rose-pine.js +0 -158
  155. package/dist/themes/data/solarized-dark.d.ts +0 -3
  156. package/dist/themes/data/solarized-dark.js +0 -158
  157. package/dist/themes/data/solarized-light.d.ts +0 -3
  158. package/dist/themes/data/solarized-light.js +0 -158
  159. package/dist/themes/data/svg-export.d.ts +0 -3
  160. package/dist/themes/data/svg-export.js +0 -158
  161. package/dist/themes/data/textual-ansi.d.ts +0 -3
  162. package/dist/themes/data/textual-ansi.js +0 -158
  163. package/dist/themes/data/textual-dark.d.ts +0 -3
  164. package/dist/themes/data/textual-dark.js +0 -158
  165. package/dist/themes/data/textual-light.d.ts +0 -3
  166. package/dist/themes/data/textual-light.js +0 -158
  167. package/dist/themes/data/tokyo-night.d.ts +0 -3
  168. package/dist/themes/data/tokyo-night.js +0 -158
  169. package/dist/themes/data/types.d.ts +0 -15
  170. package/dist/themes/data/types.js +0 -1
  171. package/dist/themes/palette.d.ts +0 -21
  172. package/dist/themes/palette.js +0 -28
  173. package/dist/themes/ramp.d.ts +0 -83
  174. package/dist/themes/ramp.js +0 -122
  175. package/dist/themes/registry.d.ts +0 -43
  176. package/dist/themes/registry.js +0 -82
  177. package/dist/themes/terminalThemes.d.ts +0 -54
  178. package/dist/themes/terminalThemes.js +0 -68
  179. package/dist/themes/transpose.d.ts +0 -87
  180. package/dist/themes/transpose.js +0 -138
  181. package/dist/widgets/button.d.ts +0 -49
  182. package/dist/widgets/button.js +0 -206
  183. package/dist/widgets/checkbox.d.ts +0 -41
  184. package/dist/widgets/checkbox.js +0 -155
  185. package/dist/widgets/dropdown.d.ts +0 -67
  186. package/dist/widgets/dropdown.js +0 -453
  187. package/dist/widgets/event-router.d.ts +0 -78
  188. package/dist/widgets/event-router.js +0 -622
  189. package/dist/widgets/focus-manager.d.ts +0 -26
  190. package/dist/widgets/focus-manager.js +0 -193
  191. package/dist/widgets/index.d.ts +0 -22
  192. package/dist/widgets/index.js +0 -15
  193. package/dist/widgets/screen.d.ts +0 -98
  194. package/dist/widgets/screen.js +0 -530
  195. package/dist/widgets/slider.d.ts +0 -57
  196. package/dist/widgets/slider.js +0 -270
  197. package/dist/widgets/static-item.d.ts +0 -40
  198. package/dist/widgets/static-item.js +0 -60
  199. package/dist/widgets/text-input.d.ts +0 -274
  200. package/dist/widgets/text-input.js +0 -1162
  201. package/dist/widgets/toggle.d.ts +0 -44
  202. package/dist/widgets/toggle.js +0 -179
  203. package/dist/widgets/types.d.ts +0 -108
  204. package/dist/widgets/types.js +0 -31
  205. package/dist/widgets/widget-base.d.ts +0 -42
  206. package/dist/widgets/widget-base.js +0 -173
@@ -1,31 +0,0 @@
1
- /**
2
- * Traceback — renders error tracebacks with formatting.
3
- *
4
- * [LAW:effects-at-boundaries] Pure rendering: an `Error` in, `Segment`s out.
5
- * Installing this as the process-wide crash handler touches `process.on` and
6
- * `process.exit`, so that lives behind the node seam as `installTraceback` in
7
- * `src/node/traceback.ts` — which is what keeps this module, and therefore the
8
- * main barrel, importable in a browser.
9
- *
10
- * [LAW:types-are-the-program] `TracebackOptions` names only what the renderer
11
- * reads. There is no `showLocals`: an `Error` carries a stack of locations and
12
- * nothing of the values in scope at them. The one way to get those in node —
13
- * an inspector session pausing on exceptions — would have to pause on every
14
- * throw, caught ones included, to serve `new Traceback(caughtError)`, and it
15
- * cannot exist in a browser at all. Nor is there a `width` or `theme`: those
16
- * sized and coloured a source-code excerpt this renderer does not produce.
17
- */
18
- import { Segment } from "../core/segment.js";
19
- import type { Renderable, RenderOptions } from "../core/protocol.js";
20
- export interface TracebackOptions {
21
- suppress?: string[];
22
- maxFrames?: number;
23
- }
24
- export declare class Traceback implements Renderable {
25
- readonly error: Error;
26
- readonly maxFrames: number;
27
- readonly suppress: string[];
28
- constructor(error: Error, options?: TracebackOptions);
29
- render(options: RenderOptions): Iterable<Segment>;
30
- private _renderFrame;
31
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * Traceback — renders error tracebacks with formatting.
3
- *
4
- * [LAW:effects-at-boundaries] Pure rendering: an `Error` in, `Segment`s out.
5
- * Installing this as the process-wide crash handler touches `process.on` and
6
- * `process.exit`, so that lives behind the node seam as `installTraceback` in
7
- * `src/node/traceback.ts` — which is what keeps this module, and therefore the
8
- * main barrel, importable in a browser.
9
- *
10
- * [LAW:types-are-the-program] `TracebackOptions` names only what the renderer
11
- * reads. There is no `showLocals`: an `Error` carries a stack of locations and
12
- * nothing of the values in scope at them. The one way to get those in node —
13
- * an inspector session pausing on exceptions — would have to pause on every
14
- * throw, caught ones included, to serve `new Traceback(caughtError)`, and it
15
- * cannot exist in a browser at all. Nor is there a `width` or `theme`: those
16
- * sized and coloured a source-code excerpt this renderer does not produce.
17
- */
18
- import { Segment } from "../core/segment.js";
19
- import { Style } from "../core/style.js";
20
- import { getStyle } from "../core/protocol.js";
21
- function parseStack(error) {
22
- const stack = error.stack ?? "";
23
- const lines = stack.split("\n");
24
- const frames = [];
25
- for (const line of lines) {
26
- const trimmed = line.trim();
27
- // Node.js format: " at functionName (file:line:column)"
28
- // or: " at file:line:column"
29
- const match = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?/.exec(trimmed);
30
- if (match) {
31
- frames.push({
32
- function: match[1] || undefined,
33
- file: match[2],
34
- line: parseInt(match[3], 10),
35
- column: parseInt(match[4], 10),
36
- });
37
- }
38
- }
39
- return frames;
40
- }
41
- export class Traceback {
42
- error;
43
- maxFrames;
44
- suppress;
45
- constructor(error, options) {
46
- this.error = error;
47
- this.maxFrames = options?.maxFrames ?? 100;
48
- this.suppress = options?.suppress ?? [];
49
- }
50
- *render(options) {
51
- const excTypeStyle = getStyle(options, "traceback.exc_type");
52
- const textStyle = getStyle(options, "traceback.text");
53
- // Error type and message
54
- const errorName = this.error.name || "Error";
55
- const errorMessage = this.error.message || "";
56
- yield new Segment(errorName, excTypeStyle);
57
- yield new Segment(": ");
58
- yield new Segment(errorMessage, textStyle);
59
- yield Segment.line();
60
- yield Segment.line();
61
- // Stack frames — suppressed frames show file/line only (not removed)
62
- const frames = parseStack(this.error);
63
- const filteredFrames = this.suppress.length > 0
64
- ? frames.map((f) => ({
65
- ...f,
66
- suppressed: this.suppress.some((s) => f.file.includes(s)),
67
- }))
68
- : frames;
69
- let displayFrames = filteredFrames;
70
- if (this.maxFrames > 0 && displayFrames.length > this.maxFrames) {
71
- // Spend the budget exactly: the tail takes `maxFrames - head` rather than
72
- // a second `head`, so an odd budget shows every frame it counts, and it
73
- // slices from an index because `slice(-0)` is the whole array.
74
- const head = Math.floor(this.maxFrames / 2);
75
- const first = displayFrames.slice(0, head);
76
- const last = displayFrames.slice(displayFrames.length - (this.maxFrames - head));
77
- const omitted = displayFrames.length - this.maxFrames;
78
- for (const frame of first) {
79
- yield* this._renderFrame(frame, options);
80
- }
81
- yield new Segment(` ... ${omitted} frames omitted ...`, textStyle);
82
- yield Segment.line();
83
- for (const frame of last) {
84
- yield* this._renderFrame(frame, options);
85
- }
86
- }
87
- else {
88
- for (const frame of displayFrames) {
89
- yield* this._renderFrame(frame, options);
90
- }
91
- }
92
- }
93
- *_renderFrame(frame, options) {
94
- const pathStyle = Style.parse("dim");
95
- const lineNoStyle = getStyle(options, "traceback.offset");
96
- yield new Segment(" ");
97
- // Spec: suppressed frames show file and line only — no function name
98
- if (frame.function && !frame.suppressed) {
99
- yield new Segment(frame.function, Style.parse("bold"));
100
- yield new Segment(" ");
101
- }
102
- yield new Segment(frame.file, pathStyle);
103
- if (frame.line !== undefined) {
104
- yield new Segment(":");
105
- yield new Segment(String(frame.line), lineNoStyle);
106
- }
107
- yield Segment.line();
108
- }
109
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * Tree — hierarchical view with guide lines.
3
- */
4
- import { Segment } from "../core/segment.js";
5
- import { Style } from "../core/style.js";
6
- import { RichText } from "../core/text.js";
7
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
8
- export interface TreeOptions {
9
- expanded?: boolean;
10
- hideRoot?: boolean;
11
- guide_style?: string | Style;
12
- style?: string | Style;
13
- }
14
- export declare class Tree implements Renderable, Measurable {
15
- readonly label: Renderable;
16
- readonly children: Tree[];
17
- expanded: boolean;
18
- readonly hideRoot: boolean;
19
- readonly guideStyle: string | Style;
20
- readonly style: string | Style;
21
- constructor(label: string | RichText | Renderable, options?: TreeOptions);
22
- add(label: string | RichText | Renderable, options?: TreeOptions): Tree;
23
- render(rawOptions: RenderOptions): Iterable<Segment>;
24
- /**
25
- * The tree walked into the rows it emits, one row per label, guides first.
26
- *
27
- * [LAW:one-source-of-truth] The walk that decides which rows exist and what
28
- * leads them is here and only here. `render` turns each row into segments and
29
- * `measure` reads each row's width off the same sequence, so the two cannot
30
- * come to disagree about how many rows there are or how wide the guides on
31
- * them run — which they would the moment a second walk existed.
32
- */
33
- private _rows;
34
- /**
35
- * One row of the tree: its guides, then its label in whatever width the
36
- * guides left.
37
- *
38
- * [LAW:single-enforcer] The row's width is divided in exactly one place, here.
39
- * Before this, each call site emitted its guides and then handed the label
40
- * `options` unchanged — the label was told it had the whole outer width while
41
- * the guides had already spent four cells of it, so a tree at maxWidth 5
42
- * emitted rows of 9. That is the same defect Panel and Table were fixed for,
43
- * and the same fix: divide the requested width once and let every part read
44
- * its share off the division. `options` arrives parsed from `render`.
45
- *
46
- * [LAW:single-enforcer] Tree also owns its row boundaries here rather than
47
- * depending on label renderables to invent trailing newlines.
48
- */
49
- private _renderRow;
50
- measure(options: RenderOptions): {
51
- minimum: number;
52
- maximum: number;
53
- };
54
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * Tree — hierarchical view with guide lines.
3
- */
4
- import { Segment } from "../core/segment.js";
5
- import { cellFit, cellLen, asCellCol } from "../core/cells.js";
6
- import { NULL_STYLE } from "../core/style.js";
7
- import { RichText } from "../core/text.js";
8
- import { getStyle, isMeasurable, withBoundedWidth, withCellWidth } from "../core/protocol.js";
9
- import { Measurement } from "../core/measure.js";
10
- // Guide characters
11
- const GUIDE_BRANCH = "├── ";
12
- const GUIDE_LAST = "└── ";
13
- const GUIDE_VERT = "│ ";
14
- const GUIDE_SPACE = " ";
15
- // ASCII fallback
16
- const GUIDE_BRANCH_ASCII = "+-- ";
17
- const GUIDE_LAST_ASCII = "+-- ";
18
- const GUIDE_VERT_ASCII = "| ";
19
- function toRenderable(label) {
20
- if (typeof label === "string")
21
- return new RichText(label, { end: "" });
22
- if (label instanceof RichText) {
23
- // Ensure no trailing newline in tree labels
24
- const copy = label.copy();
25
- copy.end = "";
26
- return copy;
27
- }
28
- return label;
29
- }
30
- /**
31
- * How wide a label wants to be. A label with no `measure` cannot say, and the
32
- * offer is the honest stand-in — including when the offer is unbounded, which
33
- * is where `withBoundedWidth` reports it rather than guessing a number.
34
- */
35
- function labelWidth(options, label) {
36
- if (!isMeasurable(label))
37
- return options.maxWidth;
38
- return Measurement.get(options, label).maximum;
39
- }
40
- export class Tree {
41
- label;
42
- children;
43
- expanded;
44
- hideRoot;
45
- guideStyle;
46
- style;
47
- constructor(label, options) {
48
- this.label = toRenderable(label);
49
- this.children = [];
50
- this.expanded = options?.expanded !== false;
51
- this.hideRoot = options?.hideRoot ?? false;
52
- this.guideStyle = options?.guide_style ?? NULL_STYLE;
53
- this.style = options?.style ?? NULL_STYLE;
54
- }
55
- add(label, options) {
56
- const child = new Tree(label, {
57
- guide_style: options?.guide_style,
58
- style: options?.style,
59
- expanded: options?.expanded,
60
- });
61
- this.children.push(child);
62
- return child;
63
- }
64
- *render(rawOptions) {
65
- const options = withBoundedWidth(rawOptions, this);
66
- for (const row of this._rows(options, [], !this.hideRoot)) {
67
- yield* this._renderRow(options, row);
68
- }
69
- }
70
- /**
71
- * The tree walked into the rows it emits, one row per label, guides first.
72
- *
73
- * [LAW:one-source-of-truth] The walk that decides which rows exist and what
74
- * leads them is here and only here. `render` turns each row into segments and
75
- * `measure` reads each row's width off the same sequence, so the two cannot
76
- * come to disagree about how many rows there are or how wide the guides on
77
- * them run — which they would the moment a second walk existed.
78
- */
79
- *_rows(options, prefixes, showLabel) {
80
- const ascii = options.asciiOnly ?? false;
81
- if (showLabel) {
82
- yield { guides: prefixes, guideStyles: [this.guideStyle], label: this.label };
83
- }
84
- if (!this.expanded)
85
- return;
86
- for (let i = 0; i < this.children.length; i++) {
87
- const child = this.children[i];
88
- const isLast = i === this.children.length - 1;
89
- const branch = ascii
90
- ? (isLast ? GUIDE_LAST_ASCII : GUIDE_BRANCH_ASCII)
91
- : (isLast ? GUIDE_LAST : GUIDE_BRANCH);
92
- const continuation = ascii
93
- ? (isLast ? GUIDE_SPACE : GUIDE_VERT_ASCII)
94
- : (isLast ? GUIDE_SPACE : GUIDE_VERT);
95
- // Child label with branch guide
96
- yield {
97
- guides: [...prefixes, branch],
98
- guideStyles: [child.guideStyle, this.guideStyle],
99
- label: child.label,
100
- };
101
- // Grandchildren with continuation prefix
102
- if (child.expanded && child.children.length > 0) {
103
- const grandPrefixes = [...prefixes, continuation];
104
- for (let j = 0; j < child.children.length; j++) {
105
- yield* child.children[j]._rows(options, grandPrefixes, true);
106
- }
107
- }
108
- }
109
- }
110
- /**
111
- * One row of the tree: its guides, then its label in whatever width the
112
- * guides left.
113
- *
114
- * [LAW:single-enforcer] The row's width is divided in exactly one place, here.
115
- * Before this, each call site emitted its guides and then handed the label
116
- * `options` unchanged — the label was told it had the whole outer width while
117
- * the guides had already spent four cells of it, so a tree at maxWidth 5
118
- * emitted rows of 9. That is the same defect Panel and Table were fixed for,
119
- * and the same fix: divide the requested width once and let every part read
120
- * its share off the division. `options` arrives parsed from `render`.
121
- *
122
- * [LAW:single-enforcer] Tree also owns its row boundaries here rather than
123
- * depending on label renderables to invent trailing newlines.
124
- */
125
- *_renderRow(options, row) {
126
- const guideStyle = row.guideStyles.map((style) => getStyle(options, style)).find((style) => !style.isNull);
127
- let left = options.maxWidth;
128
- for (const text of row.guides) {
129
- const piece = cellFit(text, asCellCol(left));
130
- if (piece.length > 0)
131
- yield new Segment(piece, guideStyle);
132
- left -= cellLen(piece);
133
- }
134
- // The guides above are cropped by `cellFit`; the label was not, so a label
135
- // that ignores the width it is handed pushed the row past the offer the
136
- // guides had just been fitted into.
137
- yield* Segment.cropLines(row.label.render({ ...options, maxWidth: left }), left);
138
- yield Segment.line();
139
- }
140
- measure(options) {
141
- // The ceiling is parsed for the same reason the rows are: an unparsed NaN
142
- // ceiling yields a range no parent layout can divide.
143
- const parsed = withCellWidth(options);
144
- const ceiling = parsed.maxWidth;
145
- // The widest row the walk emits, not the offer. Reported as the offer, a
146
- // tree told any parent that asked "I want all of it" — so `Panel` in fit
147
- // mode drew a 40-cell frame around nine cells of tree, and an unbounded
148
- // offer came back unbounded.
149
- let natural = 0;
150
- for (const row of this._rows(parsed, [], !this.hideRoot)) {
151
- const guideWidth = row.guides.reduce((sum, text) => sum + cellLen(text), 0);
152
- natural = Math.max(natural, guideWidth + labelWidth(parsed, row.label));
153
- }
154
- const maximum = Math.min(natural, ceiling);
155
- // A row is its guides plus at least one cell of label — but never more than
156
- // this tree turned out to want, or the floor would sit above its own ceiling.
157
- return { minimum: Math.min(4, maximum), maximum };
158
- }
159
- }
@@ -1,87 +0,0 @@
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;
@@ -1,219 +0,0 @@
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
- function colorFunc(argTypes, fn) {
81
- return { fn, argTypes, returnType: "string" };
82
- }
83
- /**
84
- * The gate every *numeric* argument crosses, with an optional inclusive range.
85
- *
86
- * [LAW:single-enforcer] One check for the whole family. The template engine
87
- * treats a trailing slot as variadic, so an under-supplied call
88
- * (`{{ darken "#102030" }}`) reaches the body with `undefined` rather than
89
- * failing at the gate. Without this, that `undefined` propagated into the color
90
- * math and surfaced as `ColorRgba.red must be an integer in [0, 255]; got NaN`
91
- * — loud, but pointing at a channel the author never wrote, three layers from
92
- * the missing argument. An error that does not locate its cause is barely
93
- * better than a silent one. [LAW:no-silent-failure]
94
- */
95
- function asAmount(value, func, param, range) {
96
- const inRange = range === undefined || (value >= range.min && value <= range.max);
97
- if (!Number.isFinite(value) || !inRange) {
98
- const bound = range === undefined
99
- ? "a finite number"
100
- : `within ${range.min}..${range.max} (${range.note})`;
101
- throw new RangeError(`${func}'s ${param} must be ${bound}, got ${value}` +
102
- (value === undefined ? ` — ${func} takes it as its last argument` : ""));
103
- }
104
- return value;
105
- }
106
- // --- HSL lightness ---
107
- //
108
- // `lighten` is `darken` with the sign flipped — the same operation, not a
109
- // second one (colorMath's `lighten` is literally `darken(c, -levels)`). Both
110
- // names ship because "lighten by 2" and "darken by -2" are not equally
111
- // readable at a call site, and the operation is genuinely bidirectional.
112
- const darkenFunc = colorFunc(["string", "int"], ((hex, levels) => darken(asColor(hex, "darken"), asAmount(levels, "darken", "levels")).hex));
113
- const lightenFunc = colorFunc(["string", "int"], ((hex, levels) => darken(asColor(hex, "lighten"), -asAmount(levels, "lighten", "levels")).hex));
114
- // --- Blending ---
115
- //
116
- // [LAW:one-type-per-behavior] There is no separate `alphaBlend` binding.
117
- // `alphaBlend(fg, bg, a)` in colorMath is *defined* as `blendRgb(bg, fg, a)` —
118
- // one operation wearing two names. Compositing a translucent color over a
119
- // background and mixing two colors by a percentage are the same arithmetic,
120
- // so they get one function. Terminals have no alpha channel, which is also why
121
- // no function here *produces* a translucent color: it could not be rendered,
122
- // and a color that silently does nothing in the sink is worse than no
123
- // function at all.
124
- const MIX_PCT_MAX = 100;
125
- const MIX_RANGE = { min: 0, max: MIX_PCT_MAX, note: "0 = from, 100 = toward" };
126
- 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));
127
- // --- Readability ---
128
- //
129
- // Two different questions, so two functions rather than one with a flag:
130
- // `contrastOn` asks "what is maximally readable here" (black or white, WCAG
131
- // relative-luminance cutoff); `readableOn` asks "keep this color recognizably
132
- // itself, but guarantee it clears `ratio`" — it slides OKLCH lightness and
133
- // preserves hue, so a blue on dark blue becomes a lighter blue rather than
134
- // white.
135
- //
136
- // [LAW:no-silent-failure] `ratio` is required, not defaulted to WCAG AA's 4.5.
137
- // The threshold is the whole decision this function makes, and the right value
138
- // genuinely differs by intent — 4.5 for body text, 3.0 for large or
139
- // deliberately recessive text. A hidden default would silently govern an
140
- // accessibility outcome the author never saw, and would quietly over-correct a
141
- // caller whose *point* was to be quiet: de-emphasized text floored at 4.5 is no
142
- // longer de-emphasized.
143
- // WCAG 2.x contrast ratios live in [1, 21] — 1 is a color on itself.
144
- const RATIO_RANGE = {
145
- min: 1,
146
- max: 21,
147
- note: "4.5 = AA body text, 3 = AA large text",
148
- };
149
- const contrastOnFunc = colorFunc(["string"], ((bgHex) => contrastFor(asColor(bgHex, "contrastOn")).hex));
150
- /**
151
- * The `readableOn` binding, measuring at the depth `drawnAt` names when it is
152
- * evaluated — a consumer that renders at a per-call depth registers its own.
153
- */
154
- 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));
155
- // --- OKLCH axes ---
156
- //
157
- // [LAW:one-source-of-truth] One function per `ThemeKey` axis, and the table is
158
- // keyed BY the axis so the compiler proves it total: add a field to `ThemeKey`
159
- // and this object stops satisfying `Record<keyof ThemeKey, string>` until a
160
- // binding name is chosen for it. The same derive-registrations-from-the-real-
161
- // inventory trick `attributeFuncs()` uses over `ATTRIBUTE_NAMES`.
162
- //
163
- // Naming follows the axis semantics rather than the field spelling: `shift`
164
- // for the additive axes, `scale` for the multiplicative ones, so a reader can
165
- // tell from the call site whether `1` means "no change" or "one unit".
166
- const OKLCH_AXIS_FUNCS = {
167
- hueShift: "shiftHue",
168
- chromaScale: "scaleChroma",
169
- lightnessScale: "scaleLightness",
170
- lightnessShift: "shiftLightness",
171
- };
172
- function oklchAxisFuncs() {
173
- const out = {};
174
- for (const [axis, funcName] of Object.entries(OKLCH_AXIS_FUNCS)) {
175
- // [LAW:dataflow-not-control-flow] Every axis takes the identical path —
176
- // splice one value into the identity key and apply it. The axis is data.
177
- const key = axis;
178
- out[funcName] = colorFunc(["string", "float"], ((hex, amount) => {
179
- const themeKey = {
180
- ...IDENTITY,
181
- [key]: asAmount(amount, funcName, "amount"),
182
- };
183
- return Oklch.fromRgba(asColor(hex, funcName)).applyKey(themeKey).toRgba().hex;
184
- }));
185
- }
186
- return out;
187
- }
188
- /**
189
- * The palette-free color vocabulary: every function takes colors and returns a
190
- * color, so they compose by nesting.
191
- *
192
- * | function | meaning |
193
- * |---|---|
194
- * | `darken c n` / `lighten c n` | slide HSL lightness by N 10% levels |
195
- * | `mix a b pct` | blend `a` toward `b` by `pct`% (0 → `a`, 100 → `b`) |
196
- * | `contrastOn bg` | black or white, whichever is readable on `bg` |
197
- * | `readableOn fg bg ratio` | `fg` nudged in OKLCH lightness until it clears `ratio` on `bg`, hue preserved |
198
- * | `shiftHue c deg` | rotate hue in OKLCH |
199
- * | `scaleChroma c f` | multiply chroma (0 → gray, 1 → identity) |
200
- * | `scaleLightness c f` | multiply lightness (1 → identity, -1 → invert) |
201
- * | `shiftLightness c d` | add to lightness, after any scale |
202
- *
203
- * Pair with `paletteFuncs()` to name colors from a theme, and with
204
- * `richTextStyleFuncs()`'s `fg`/`bg` to paint them onto text.
205
- *
206
- * @param drawnAt the depth the terminal will draw at, read on every
207
- * `readableOn` evaluation so a host that learns its depth per render passes
208
- * one getter; defaults to truecolor.
209
- */
210
- export function colorFuncs(drawnAt = () => ColorDepth.TRUECOLOR) {
211
- return {
212
- darken: darkenFunc,
213
- lighten: lightenFunc,
214
- mix: mixFunc,
215
- contrastOn: contrastOnFunc,
216
- readableOn: readableOnFunc(drawnAt),
217
- ...oklchAxisFuncs(),
218
- };
219
- }