@promptctl/rich-js 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +9 -7
  2. package/dist/core/anchor.d.ts +45 -0
  3. package/dist/core/anchor.js +39 -0
  4. package/dist/core/ansi.d.ts +65 -0
  5. package/dist/core/ansi.js +231 -0
  6. package/dist/core/box.d.ts +145 -0
  7. package/dist/core/box.js +372 -0
  8. package/dist/core/cells.d.ts +192 -0
  9. package/dist/core/cells.js +334 -0
  10. package/dist/core/clock.d.ts +48 -0
  11. package/dist/core/clock.js +42 -0
  12. package/dist/core/color.d.ts +326 -0
  13. package/dist/core/color.js +1225 -0
  14. package/dist/core/console.d.ts +196 -0
  15. package/dist/core/console.js +596 -0
  16. package/dist/core/easing.d.ts +86 -0
  17. package/dist/core/easing.js +171 -0
  18. package/dist/core/emoji.d.ts +24 -0
  19. package/dist/core/emoji.js +391 -0
  20. package/dist/core/env.d.ts +16 -0
  21. package/dist/core/env.js +16 -0
  22. package/dist/core/export-html.d.ts +59 -0
  23. package/dist/core/export-html.js +197 -0
  24. package/dist/core/export-lines.d.ts +130 -0
  25. package/dist/core/export-lines.js +159 -0
  26. package/dist/core/export-svg.d.ts +49 -0
  27. package/dist/core/export-svg.js +213 -0
  28. package/dist/core/fnv1a.d.ts +10 -0
  29. package/dist/core/fnv1a.js +17 -0
  30. package/dist/core/highlighter.d.ts +33 -0
  31. package/dist/core/highlighter.js +190 -0
  32. package/dist/core/json.d.ts +27 -0
  33. package/dist/core/json.js +53 -0
  34. package/dist/core/markup.d.ts +140 -0
  35. package/dist/core/markup.js +654 -0
  36. package/dist/core/measure.d.ts +27 -0
  37. package/dist/core/measure.js +67 -0
  38. package/dist/core/memo.d.ts +19 -0
  39. package/dist/core/memo.js +34 -0
  40. package/dist/core/oklch.d.ts +161 -0
  41. package/dist/core/oklch.js +312 -0
  42. package/dist/core/osc8.d.ts +68 -0
  43. package/dist/core/osc8.js +78 -0
  44. package/dist/core/paint.d.ts +107 -0
  45. package/dist/core/paint.js +150 -0
  46. package/dist/core/place.d.ts +30 -0
  47. package/dist/core/place.js +49 -0
  48. package/dist/core/pretty.d.ts +247 -0
  49. package/dist/core/pretty.js +906 -0
  50. package/dist/core/protocol.d.ts +305 -0
  51. package/dist/core/protocol.js +183 -0
  52. package/dist/core/render.d.ts +87 -0
  53. package/dist/core/render.js +105 -0
  54. package/dist/core/segment.d.ts +162 -0
  55. package/dist/core/segment.js +488 -0
  56. package/dist/core/spinnerData.d.ts +12 -0
  57. package/dist/core/spinnerData.js +117 -0
  58. package/dist/core/strip.d.ts +152 -0
  59. package/dist/core/strip.js +326 -0
  60. package/dist/core/style.d.ts +189 -0
  61. package/dist/core/style.js +758 -0
  62. package/dist/core/subscription.d.ts +12 -0
  63. package/dist/core/subscription.js +12 -0
  64. package/dist/core/text.d.ts +283 -0
  65. package/dist/core/text.js +1097 -0
  66. package/dist/core/wrap.d.ts +46 -0
  67. package/dist/core/wrap.js +140 -0
  68. package/dist/host/app.d.ts +133 -0
  69. package/dist/host/app.js +285 -0
  70. package/dist/host/host-environment.d.ts +26 -0
  71. package/dist/host/host-environment.js +44 -0
  72. package/dist/host/index.d.ts +6 -0
  73. package/dist/host/index.js +3 -0
  74. package/dist/host/terminal-host.d.ts +180 -0
  75. package/dist/host/terminal-host.js +186 -0
  76. package/dist/index.d.ts +98 -0
  77. package/dist/index.js +114 -0
  78. package/dist/node/prompt.d.ts +34 -0
  79. package/dist/node/prompt.js +51 -0
  80. package/dist/node/save.d.ts +18 -0
  81. package/dist/node/save.js +24 -0
  82. package/dist/node/terminal-host.d.ts +114 -0
  83. package/dist/node/terminal-host.js +227 -0
  84. package/dist/node/traceback.d.ts +38 -0
  85. package/dist/node/traceback.js +98 -0
  86. package/dist/renderables/align.d.ts +16 -0
  87. package/dist/renderables/align.js +26 -0
  88. package/dist/renderables/columns.d.ts +77 -0
  89. package/dist/renderables/columns.js +191 -0
  90. package/dist/renderables/constrain.d.ts +15 -0
  91. package/dist/renderables/constrain.js +31 -0
  92. package/dist/renderables/effect.d.ts +98 -0
  93. package/dist/renderables/effect.js +160 -0
  94. package/dist/renderables/embed.d.ts +107 -0
  95. package/dist/renderables/embed.js +160 -0
  96. package/dist/renderables/flexStrip.d.ts +41 -0
  97. package/dist/renderables/flexStrip.js +200 -0
  98. package/dist/renderables/group.d.ts +14 -0
  99. package/dist/renderables/group.js +22 -0
  100. package/dist/renderables/layout.d.ts +75 -0
  101. package/dist/renderables/layout.js +223 -0
  102. package/dist/renderables/live.d.ts +74 -0
  103. package/dist/renderables/live.js +188 -0
  104. package/dist/renderables/markdown.d.ts +38 -0
  105. package/dist/renderables/markdown.js +587 -0
  106. package/dist/renderables/padding.d.ts +59 -0
  107. package/dist/renderables/padding.js +147 -0
  108. package/dist/renderables/panel.d.ts +114 -0
  109. package/dist/renderables/panel.js +303 -0
  110. package/dist/renderables/progress.d.ts +178 -0
  111. package/dist/renderables/progress.js +362 -0
  112. package/dist/renderables/progressBar.d.ts +31 -0
  113. package/dist/renderables/progressBar.js +64 -0
  114. package/dist/renderables/prompt.d.ts +71 -0
  115. package/dist/renderables/prompt.js +165 -0
  116. package/dist/renderables/ratio.d.ts +123 -0
  117. package/dist/renderables/ratio.js +213 -0
  118. package/dist/renderables/rule.d.ts +26 -0
  119. package/dist/renderables/rule.js +86 -0
  120. package/dist/renderables/spinner.d.ts +57 -0
  121. package/dist/renderables/spinner.js +103 -0
  122. package/dist/renderables/status.d.ts +29 -0
  123. package/dist/renderables/status.js +46 -0
  124. package/dist/renderables/syntax.d.ts +65 -0
  125. package/dist/renderables/syntax.js +166 -0
  126. package/dist/renderables/table.d.ts +232 -0
  127. package/dist/renderables/table.js +973 -0
  128. package/dist/renderables/traceback.d.ts +32 -0
  129. package/dist/renderables/traceback.js +279 -0
  130. package/dist/renderables/tree.d.ts +76 -0
  131. package/dist/renderables/tree.js +177 -0
  132. package/dist/renderables/viewport.d.ts +114 -0
  133. package/dist/renderables/viewport.js +196 -0
  134. package/dist/template-bindings/color-funcs.d.ts +109 -0
  135. package/dist/template-bindings/color-funcs.js +243 -0
  136. package/dist/template-bindings/helpers.d.ts +26 -0
  137. package/dist/template-bindings/helpers.js +33 -0
  138. package/dist/template-bindings/index.d.ts +119 -0
  139. package/dist/template-bindings/index.js +162 -0
  140. package/dist/template-bindings/palette-funcs.d.ts +96 -0
  141. package/dist/template-bindings/palette-funcs.js +145 -0
  142. package/dist/template-bindings/style-funcs.d.ts +28 -0
  143. package/dist/template-bindings/style-funcs.js +157 -0
  144. package/dist/themes/buildPalette.d.ts +57 -0
  145. package/dist/themes/buildPalette.js +100 -0
  146. package/dist/themes/colorMath.d.ts +94 -0
  147. package/dist/themes/colorMath.js +280 -0
  148. package/dist/themes/colorRef.d.ts +36 -0
  149. package/dist/themes/colorRef.js +78 -0
  150. package/dist/themes/data/ansi-tables.d.ts +14 -0
  151. package/dist/themes/data/ansi-tables.js +49 -0
  152. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  153. package/dist/themes/data/atom-one-dark.js +177 -0
  154. package/dist/themes/data/atom-one-light.d.ts +3 -0
  155. package/dist/themes/data/atom-one-light.js +177 -0
  156. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  157. package/dist/themes/data/catppuccin-frappe.js +177 -0
  158. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  159. package/dist/themes/data/catppuccin-latte.js +177 -0
  160. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  161. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  162. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  163. package/dist/themes/data/catppuccin-mocha.js +177 -0
  164. package/dist/themes/data/cyberpunk.d.ts +17 -0
  165. package/dist/themes/data/cyberpunk.js +193 -0
  166. package/dist/themes/data/default.d.ts +3 -0
  167. package/dist/themes/data/default.js +160 -0
  168. package/dist/themes/data/dracula.d.ts +3 -0
  169. package/dist/themes/data/dracula.js +177 -0
  170. package/dist/themes/data/flexoki.d.ts +3 -0
  171. package/dist/themes/data/flexoki.js +177 -0
  172. package/dist/themes/data/gruvbox.d.ts +3 -0
  173. package/dist/themes/data/gruvbox.js +177 -0
  174. package/dist/themes/data/index.d.ts +35 -0
  175. package/dist/themes/data/index.js +64 -0
  176. package/dist/themes/data/monokai.d.ts +3 -0
  177. package/dist/themes/data/monokai.js +177 -0
  178. package/dist/themes/data/nord.d.ts +3 -0
  179. package/dist/themes/data/nord.js +177 -0
  180. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  181. package/dist/themes/data/rose-pine-dawn.js +177 -0
  182. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  183. package/dist/themes/data/rose-pine-moon.js +177 -0
  184. package/dist/themes/data/rose-pine.d.ts +3 -0
  185. package/dist/themes/data/rose-pine.js +177 -0
  186. package/dist/themes/data/solarized-dark.d.ts +3 -0
  187. package/dist/themes/data/solarized-dark.js +177 -0
  188. package/dist/themes/data/solarized-light.d.ts +3 -0
  189. package/dist/themes/data/solarized-light.js +177 -0
  190. package/dist/themes/data/svg-export.d.ts +3 -0
  191. package/dist/themes/data/svg-export.js +177 -0
  192. package/dist/themes/data/textual-ansi.d.ts +3 -0
  193. package/dist/themes/data/textual-ansi.js +160 -0
  194. package/dist/themes/data/textual-dark.d.ts +3 -0
  195. package/dist/themes/data/textual-dark.js +160 -0
  196. package/dist/themes/data/textual-light.d.ts +3 -0
  197. package/dist/themes/data/textual-light.js +160 -0
  198. package/dist/themes/data/tokyo-night.d.ts +3 -0
  199. package/dist/themes/data/tokyo-night.js +177 -0
  200. package/dist/themes/data/types.d.ts +31 -0
  201. package/dist/themes/data/types.js +10 -0
  202. package/dist/themes/palette.d.ts +45 -0
  203. package/dist/themes/palette.js +71 -0
  204. package/dist/themes/ramp.d.ts +70 -0
  205. package/dist/themes/ramp.js +103 -0
  206. package/dist/themes/registry.d.ts +44 -0
  207. package/dist/themes/registry.js +105 -0
  208. package/dist/themes/terminalThemes.d.ts +49 -0
  209. package/dist/themes/terminalThemes.js +63 -0
  210. package/dist/themes/transpose.d.ts +87 -0
  211. package/dist/themes/transpose.js +138 -0
  212. package/dist/widgets/button.d.ts +45 -0
  213. package/dist/widgets/button.js +177 -0
  214. package/dist/widgets/checkbox.d.ts +38 -0
  215. package/dist/widgets/checkbox.js +135 -0
  216. package/dist/widgets/dropdown.d.ts +64 -0
  217. package/dist/widgets/dropdown.js +416 -0
  218. package/dist/widgets/event-router.d.ts +71 -0
  219. package/dist/widgets/event-router.js +629 -0
  220. package/dist/widgets/focus-manager.d.ts +45 -0
  221. package/dist/widgets/focus-manager.js +191 -0
  222. package/dist/widgets/hit.d.ts +64 -0
  223. package/dist/widgets/hit.js +113 -0
  224. package/dist/widgets/index.d.ts +20 -0
  225. package/dist/widgets/index.js +14 -0
  226. package/dist/widgets/ink.d.ts +22 -0
  227. package/dist/widgets/ink.js +31 -0
  228. package/dist/widgets/slider.d.ts +54 -0
  229. package/dist/widgets/slider.js +247 -0
  230. package/dist/widgets/static-item.d.ts +34 -0
  231. package/dist/widgets/static-item.js +54 -0
  232. package/dist/widgets/text-input.d.ts +283 -0
  233. package/dist/widgets/text-input.js +1174 -0
  234. package/dist/widgets/themed-widget.d.ts +20 -0
  235. package/dist/widgets/themed-widget.js +89 -0
  236. package/dist/widgets/toggle.d.ts +41 -0
  237. package/dist/widgets/toggle.js +157 -0
  238. package/dist/widgets/types.d.ts +87 -0
  239. package/dist/widgets/types.js +30 -0
  240. package/dist/widgets/widget-app.d.ts +65 -0
  241. package/dist/widgets/widget-app.js +206 -0
  242. package/dist/widgets/widget-base.d.ts +42 -0
  243. package/dist/widgets/widget-base.js +172 -0
  244. package/package.json +16 -2
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Strip + Joiner — edge-aware horizontal layout primitive.
3
+ *
4
+ * A `Strip` renders a horizontal sequence of styled items with a `Joiner`
5
+ * deciding how each transition between adjacent items looks. The joiner is
6
+ * a pure function of `(leftItem | null, rightItem | null) -> Renderable`,
7
+ * so endpoint joins (the start and end of the strip) are explicit positions
8
+ * in the protocol — the joiner names what an endpoint looks like rather than
9
+ * the strip guessing.
10
+ *
11
+ * [LAW:one-source-of-truth] The render walk
12
+ * joiner(null, items[0]), items[0],
13
+ * joiner(items[0], items[1]), items[1], ...,
14
+ * joiner(items[N-1], null)
15
+ * is the single authority for how strips lay out. Every joiner participates
16
+ * in the same protocol; "look up the previous segment's bg" is no longer a
17
+ * powerline-specific hack but the contract every joiner shares.
18
+ *
19
+ * [LAW:locality-or-seam] The joiner protocol asks each item only for its
20
+ * *edge* style — the column adjacent to the joiner — not for a single
21
+ * whole-item style. This pushes the bg-uniformity requirement out of the
22
+ * cell type and into the narrowest place that actually needs it: the column
23
+ * boundary. Items with uniform styling report the same style at both edges;
24
+ * items with varying styling report the actual boundary column. There is no
25
+ * single-style invariant on the cell type — the terminal supports per-column
26
+ * styling, so the cell type does too.
27
+ */
28
+ import { Segment } from "./segment.js";
29
+ import { Style } from "./style.js";
30
+ import { type Measurable, type Renderable, type RenderOptions } from "./protocol.js";
31
+ /**
32
+ * Items in a `Strip` expose their per-edge style so joiners can paint the
33
+ * transition glyph at each boundary.
34
+ *
35
+ * `edgeStyle("left", options)` reports the style of the item's leftmost cell
36
+ * column; `edgeStyle("right", options)` reports the rightmost. Joiners read
37
+ * only these — the item's interior may carry any per-column variation without
38
+ * breaking the join.
39
+ *
40
+ * The edge is asked in a render, with that render's options, because a style
41
+ * name only becomes a colour against the render's theme. A joiner therefore
42
+ * reads edges inside the `render` of what it returns, never in `join`.
43
+ */
44
+ export interface StyledRenderable extends Renderable {
45
+ edgeStyle(side: "left" | "right", options: RenderOptions): Style;
46
+ }
47
+ export interface Joiner<T extends StyledRenderable = StyledRenderable> {
48
+ /**
49
+ * `left === null` marks the start endpoint; `right === null` marks the end
50
+ * endpoint. The joiner decides what an endpoint looks like — typically
51
+ * fg-only with no bg so the strip blends into the terminal background.
52
+ */
53
+ join(left: T | null, right: T | null): Renderable;
54
+ }
55
+ export declare class Strip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable {
56
+ readonly items: readonly T[];
57
+ readonly joiner: Joiner<T>;
58
+ constructor(items: readonly T[], joiner: Joiner<T>);
59
+ render(options: RenderOptions): Iterable<Segment>;
60
+ /**
61
+ * [LAW:one-source-of-truth] The width of the one row it draws, and that is
62
+ * both bounds: the row never wraps and no item is offered less than the whole
63
+ * width, so there is no narrower strip to report as a minimum. Read off the
64
+ * drawing rather than summed from the items' measurements, which describe
65
+ * how an item could be drawn, not how the walk draws it.
66
+ */
67
+ measure(options: RenderOptions): {
68
+ minimum: number;
69
+ maximum: number;
70
+ };
71
+ }
72
+ /**
73
+ * The least ΔE_OK two neighbouring backgrounds must differ by for the powerline
74
+ * arrow between them to be seen. Below it the arrow is drawn in a colour the
75
+ * eye cannot tell from its own background, so the joiner draws the divider
76
+ * instead. Twice the ~.02 threshold of a visible difference, because a seam is
77
+ * one cell wide.
78
+ */
79
+ export declare const SEAM_MIN_DELTA_E = 0.04;
80
+ export interface PowerlineJoinerOptions {
81
+ /** Glyph for every join between two coloured items. */
82
+ glyph: string;
83
+ /**
84
+ * Glyph drawn between neighbours whose backgrounds the eye cannot tell apart,
85
+ * in the left item's text colour — the arrow itself would vanish into the
86
+ * shared background.
87
+ */
88
+ divider: string;
89
+ /**
90
+ * Glyph for every join a coloured item is entered from nothing — the strip's
91
+ * start, or a colourless left neighbour — painted in the right item's
92
+ * background. `""` begins a coloured run flat.
93
+ */
94
+ lead: string;
95
+ /**
96
+ * Glyph for every join a coloured item leaves into nothing — the strip's end,
97
+ * or a colourless right neighbour — painted in the left item's background.
98
+ * `""` ends a coloured run flat.
99
+ */
100
+ tail: string;
101
+ }
102
+ /**
103
+ * The powerline set: U+E0B0 (right-arrow) divided by U+E0B1 (thin right-arrow),
104
+ * led by U+E0D7 (Nerd Fonts ple-left_hard_divider_inverse, the right-arrow's
105
+ * inverse) and tailed by the arrow itself. The lead leaves empty a triangle
106
+ * whose base is its cell's left edge and whose point touches the first item, so
107
+ * the run opens notched — as if an arrow before it pointed in — rather than
108
+ * with a shape pointing out. The notch is the terminal's own background showing
109
+ * through, never a colour painted to imitate it, so it matches under a
110
+ * translucent terminal background too. The lead is a Powerline Extra glyph: a
111
+ * classic powerline font lacks it.
112
+ */
113
+ export declare const POWERLINE_JOINER_GLYPHS: Readonly<PowerlineJoinerOptions>;
114
+ export declare class PowerlineJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
115
+ private readonly _glyphs;
116
+ constructor(options?: PowerlineJoinerOptions);
117
+ join(left: T | null, right: T | null): Renderable;
118
+ }
119
+ export interface CapsuleJoinerOptions {
120
+ /** Left-cap glyph (default: U+E0B6, powerline rounded left). */
121
+ left?: string;
122
+ /** Right-cap glyph (default: U+E0B4, powerline rounded right). */
123
+ right?: string;
124
+ /** Separator inserted between adjacent capsules in the middle position. */
125
+ separator?: string;
126
+ }
127
+ export declare class CapsuleJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
128
+ private readonly _caps;
129
+ private readonly _separator;
130
+ constructor(options?: CapsuleJoinerOptions);
131
+ _emit(left: T | null, right: T | null, options: RenderOptions): Iterable<Segment>;
132
+ join(left: T | null, right: T | null): Renderable;
133
+ }
134
+ export interface PlainJoinerOptions {
135
+ separator?: string;
136
+ style?: Style;
137
+ }
138
+ export declare class PlainJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
139
+ private readonly _separator;
140
+ private readonly _style;
141
+ constructor(options?: PlainJoinerOptions);
142
+ join(left: T | null, right: T | null): Renderable;
143
+ }
144
+ export interface GradientJoinerOptions {
145
+ /** Number of cells between adjacent items (default: 4). */
146
+ steps?: number;
147
+ }
148
+ export declare class GradientJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
149
+ private readonly _steps;
150
+ constructor(options?: GradientJoinerOptions);
151
+ join(left: T | null, right: T | null): Renderable;
152
+ }
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Strip + Joiner — edge-aware horizontal layout primitive.
3
+ *
4
+ * A `Strip` renders a horizontal sequence of styled items with a `Joiner`
5
+ * deciding how each transition between adjacent items looks. The joiner is
6
+ * a pure function of `(leftItem | null, rightItem | null) -> Renderable`,
7
+ * so endpoint joins (the start and end of the strip) are explicit positions
8
+ * in the protocol — the joiner names what an endpoint looks like rather than
9
+ * the strip guessing.
10
+ *
11
+ * [LAW:one-source-of-truth] The render walk
12
+ * joiner(null, items[0]), items[0],
13
+ * joiner(items[0], items[1]), items[1], ...,
14
+ * joiner(items[N-1], null)
15
+ * is the single authority for how strips lay out. Every joiner participates
16
+ * in the same protocol; "look up the previous segment's bg" is no longer a
17
+ * powerline-specific hack but the contract every joiner shares.
18
+ *
19
+ * [LAW:locality-or-seam] The joiner protocol asks each item only for its
20
+ * *edge* style — the column adjacent to the joiner — not for a single
21
+ * whole-item style. This pushes the bg-uniformity requirement out of the
22
+ * cell type and into the narrowest place that actually needs it: the column
23
+ * boundary. Items with uniform styling report the same style at both edges;
24
+ * items with varying styling report the actual boundary column. There is no
25
+ * single-style invariant on the cell type — the terminal supports per-column
26
+ * styling, so the cell type does too.
27
+ */
28
+ import { Segment } from "./segment.js";
29
+ import { Style } from "./style.js";
30
+ import { ColorDepth, ColorSpec, SURFACE_BLACK } from "./color.js";
31
+ import { Oklch } from "./oklch.js";
32
+ import { drawable } from "./protocol.js";
33
+ // --- Strip ---
34
+ export class Strip {
35
+ items;
36
+ joiner;
37
+ constructor(items, joiner) {
38
+ this.items = items;
39
+ this.joiner = joiner;
40
+ }
41
+ *render(options) {
42
+ const items = this.items;
43
+ if (items.length === 0)
44
+ return;
45
+ // [LAW:dataflow-not-control-flow] The walk is the same shape every render:
46
+ // start-cap, item, mid-join, item, ..., item, end-cap. Variability lives
47
+ // in `items` and in what the joiner emits at each position — never in
48
+ // whether a join runs.
49
+ //
50
+ // The row sits its items side by side, so none is handed a justify: a
51
+ // `left` fills the canvas, and the first item would pad over the rest of
52
+ // the row. Where the row sits in the width is its container's to say.
53
+ const row = { ...options, justify: undefined };
54
+ yield* this.joiner.join(null, items[0]).render(row);
55
+ for (let i = 0; i < items.length; i++) {
56
+ const item = items[i];
57
+ yield* item.render(row);
58
+ const next = i + 1 < items.length ? items[i + 1] : null;
59
+ yield* this.joiner.join(item, next).render(row);
60
+ }
61
+ // [LAW:one-source-of-truth] `Strip`'s sibling `FlexStrip` ends every line
62
+ // it emits, including the last, and the other block-level renderables
63
+ // (`Panel`/`Rule`/`Table` structurally, `RichText` via `end`) do too — so
64
+ // `Group` can emit children back to back with nothing in between
65
+ // (docs/group.md). A `Strip` that left its last line open broke only
66
+ // under composition: printed alone, Console's own "close whatever the
67
+ // renderable left open" step (console.ts) papered over it, and a `Group`
68
+ // of a Strip and a RichText ran the RichText onto the Strip's line
69
+ // instead of below it. `LINE_ENDS` in test/seam/line-ends.ts is the list
70
+ // of which renderables end their own line and which are fragments.
71
+ yield Segment.line();
72
+ }
73
+ /**
74
+ * [LAW:one-source-of-truth] The width of the one row it draws, and that is
75
+ * both bounds: the row never wraps and no item is offered less than the whole
76
+ * width, so there is no narrower strip to report as a minimum. Read off the
77
+ * drawing rather than summed from the items' measurements, which describe
78
+ * how an item could be drawn, not how the walk draws it.
79
+ */
80
+ measure(options) {
81
+ let width = 0;
82
+ for (const segment of this.render(options))
83
+ width += segment.cellLength;
84
+ return { minimum: width, maximum: width };
85
+ }
86
+ }
87
+ // --- Helpers ---
88
+ const EMPTY = {
89
+ *render(_options) {
90
+ // intentionally empty
91
+ },
92
+ };
93
+ /** A renderable whose segments are computed from the options it is rendered with. */
94
+ function deferred(emit) {
95
+ return { render: emit };
96
+ }
97
+ // Endpoint and powerline join glyphs paint the adjacent item's edge bg
98
+ // *as* their fg. If the edge has no bgcolor, the glyph degrades to the
99
+ // default fg — which renders as nothing visible against the terminal
100
+ // background, the right outcome for an unstyled edge.
101
+ function bgAsFg(edge) {
102
+ return new Style({ color: edge.bgcolor });
103
+ }
104
+ // A bg with a real colour to paint. `undefined` (no bg) and the terminal
105
+ // DEFAULT colour (transparent — identical to the terminal background) are the
106
+ // two representations of "nothing to paint"; the powerline separator treats
107
+ // them the same, so the gate cannot be fooled by an explicit `… on default`.
108
+ function paintableBg(bg) {
109
+ return bg !== undefined && !bg.isDefault ? bg : undefined;
110
+ }
111
+ // A cap, arrow or gradient is its cell continuing: that cell's ground as the
112
+ // writer draws it (`Style.drawnColors`, onto the terminal's black), so a
113
+ // translucent ground is not composited a second time over whatever it enters.
114
+ function drawnGround(bg) {
115
+ return bg.flattenAlpha(SURFACE_BLACK);
116
+ }
117
+ // A cap is its glyph in its cell's ground, and "" is a flat end: no glyph, so
118
+ // no segment — the same output as a join with nothing to paint.
119
+ function* cap(glyph, bg) {
120
+ if (glyph !== "")
121
+ yield new Segment(glyph, new Style({ color: drawnGround(bg) }));
122
+ }
123
+ // --- PowerlineJoiner ---
124
+ /**
125
+ * The least ΔE_OK two neighbouring backgrounds must differ by for the powerline
126
+ * arrow between them to be seen. Below it the arrow is drawn in a colour the
127
+ * eye cannot tell from its own background, so the joiner draws the divider
128
+ * instead. Twice the ~.02 threshold of a visible difference, because a seam is
129
+ * one cell wide.
130
+ */
131
+ export const SEAM_MIN_DELTA_E = 0.04;
132
+ // An arrow the eye cannot tell from the ground it is drawn on. What is
133
+ // measured is what is drawn — `Style.drawnColors`, the colours the writer
134
+ // encodes: the arrow's colour flattened onto its ground and the ground onto
135
+ // the terminal's black, both downgraded to the depth the render encodes at,
136
+ // so two grounds 256 colours round to one cube entry are the one entry they
137
+ // render as. A colour whose RGB is the terminal theme's own (ANSI 0–15, a
138
+ // blend reaching one) has no value here, so two of them are the same only
139
+ // when they are one expression: the same palette slot — its `number`, never
140
+ // the name or the depth that spells it ("red", "color(1)", an EIGHT_BIT spec
141
+ // on 0–15 are one slot) — or the same blend, whose name spells each end by its
142
+ // slot for the same reason.
143
+ function vanishes(arrow, colorSystem) {
144
+ // No colour emitted draws nothing to tell apart; measure what was handed.
145
+ const { color, bgcolor } = arrow.drawnColors(colorSystem ?? ColorDepth.TRUECOLOR);
146
+ if (color === undefined || bgcolor === undefined)
147
+ return false;
148
+ const av = color.fixedValue;
149
+ const bv = bgcolor.fixedValue;
150
+ return av !== undefined && bv !== undefined
151
+ ? Oklch.fromRgba(av).deltaE(Oklch.fromRgba(bv)) < SEAM_MIN_DELTA_E
152
+ : color.number !== undefined ? color.number === bgcolor.number : color.name === bgcolor.name;
153
+ }
154
+ /**
155
+ * The powerline set: U+E0B0 (right-arrow) divided by U+E0B1 (thin right-arrow),
156
+ * led by U+E0D7 (Nerd Fonts ple-left_hard_divider_inverse, the right-arrow's
157
+ * inverse) and tailed by the arrow itself. The lead leaves empty a triangle
158
+ * whose base is its cell's left edge and whose point touches the first item, so
159
+ * the run opens notched — as if an arrow before it pointed in — rather than
160
+ * with a shape pointing out. The notch is the terminal's own background showing
161
+ * through, never a colour painted to imitate it, so it matches under a
162
+ * translucent terminal background too. The lead is a Powerline Extra glyph: a
163
+ * classic powerline font lacks it.
164
+ */
165
+ export const POWERLINE_JOINER_GLYPHS = Object.freeze({
166
+ glyph: "\ue0b0",
167
+ divider: "\ue0b1",
168
+ lead: "\ue0d7",
169
+ tail: "\ue0b0",
170
+ });
171
+ /** The powerline set in ASCII: an arrow for every join, runs begun flat. */
172
+ const ASCII_POWERLINE_GLYPHS = Object.freeze({
173
+ glyph: ">",
174
+ divider: ">",
175
+ lead: "",
176
+ tail: ">",
177
+ });
178
+ const powerlineGlyphs = ({ glyph, divider, lead, tail }) => glyph + divider + lead + tail;
179
+ export class PowerlineJoiner {
180
+ _glyphs;
181
+ constructor(options = POWERLINE_JOINER_GLYPHS) {
182
+ this._glyphs = options;
183
+ }
184
+ join(left, right) {
185
+ // [LAW:dataflow-not-control-flow] One table for all three positions (start
186
+ // cap, mid-join, end cap), read off which side has a colour to paint. The
187
+ // endpoints are not control-flow special cases; they are the DATA cases
188
+ // where a neighbour (hence its bg) is absent:
189
+ // • both bgs — the arrow in the LEFT bg (the colour bleeding rightward)
190
+ // over the RIGHT bg.
191
+ // • left bg only — the end cap, OR a coloured item before a colourless
192
+ // one — the tail bleeds the left colour out over the terminal
193
+ // background (fg = left bg, no bg).
194
+ // • right bg only — the start cap, OR a colourless item before a coloured
195
+ // one — the lead: the right colour painted over the terminal
196
+ // background in the lead's shape (fg = right bg, no bg).
197
+ // • neither — nothing to paint, so the join yields nothing.
198
+ // Equal REAL bgs still emit: a same-bg seam between two distinct items is a
199
+ // structural boundary, never suppressed. The arrow would be drawn in its own
200
+ // background colour there and vanish, so the seam is the DIVIDER instead, in
201
+ // the left item's text colour — the vim-airline convention for neighbours
202
+ // that share a background. "Equal" is perceptual (SEAM_MIN_DELTA_E) and
203
+ // measured on the colours the terminal draws at `options.colorSystem`: an
204
+ // arrow a hair off its background is as invisible as one exactly on it,
205
+ // and two backgrounds 256 colours round to one entry are one background.
206
+ // Background colour is paint, not structure; its ABSENCE (nothing to paint)
207
+ // is the only thing that elides the separator, and that is paint logic.
208
+ // "Absent" = no bg OR the terminal default (transparent) — paintableBg folds
209
+ // both to undefined so an explicit `… on default` cannot smuggle a separator.
210
+ const glyphs = this._glyphs;
211
+ return deferred(function* (options) {
212
+ const { glyph, divider, lead, tail } = drawable(options, glyphs, ASCII_POWERLINE_GLYPHS, powerlineGlyphs);
213
+ const leftEdge = left?.edgeStyle("right", options);
214
+ const leftBg = paintableBg(leftEdge?.bgcolor);
215
+ const rightBg = paintableBg(right?.edgeStyle("left", options).bgcolor);
216
+ if (leftBg === undefined) {
217
+ if (rightBg !== undefined)
218
+ yield* cap(lead, rightBg);
219
+ return;
220
+ }
221
+ if (rightBg === undefined) {
222
+ yield* cap(tail, leftBg);
223
+ return;
224
+ }
225
+ const arrow = new Style({ color: drawnGround(leftBg), bgcolor: rightBg });
226
+ // The divider is the left item's text on the left item's own ground,
227
+ // so it reads exactly as well as that item's text does, at any depth.
228
+ yield vanishes(arrow, options.colorSystem)
229
+ ? new Segment(divider, new Style({ color: leftEdge?.color, bgcolor: leftBg }))
230
+ : new Segment(glyph, arrow);
231
+ });
232
+ }
233
+ }
234
+ const ASCII_CAPS = { left: "(", right: ")" };
235
+ const capGlyphs = ({ left, right }) => left + right;
236
+ export class CapsuleJoiner {
237
+ _caps;
238
+ _separator;
239
+ constructor(options) {
240
+ this._caps = { left: options?.left ?? "", right: options?.right ?? "" };
241
+ this._separator = options?.separator ?? " ";
242
+ }
243
+ *_emit(left, right, options) {
244
+ const caps = drawable(options, this._caps, ASCII_CAPS, capGlyphs);
245
+ const separator = drawable(options, this._separator, " ");
246
+ if (left === null && right === null)
247
+ return;
248
+ if (left === null) {
249
+ yield new Segment(caps.left, bgAsFg(right.edgeStyle("left", options)));
250
+ return;
251
+ }
252
+ if (right === null) {
253
+ yield new Segment(caps.right, bgAsFg(left.edgeStyle("right", options)));
254
+ return;
255
+ }
256
+ // Middle: close the left capsule, separator (unstyled), open the right.
257
+ yield new Segment(caps.right, bgAsFg(left.edgeStyle("right", options)));
258
+ if (separator.length > 0)
259
+ yield new Segment(separator);
260
+ yield new Segment(caps.left, bgAsFg(right.edgeStyle("left", options)));
261
+ }
262
+ join(left, right) {
263
+ return deferred((options) => this._emit(left, right, options));
264
+ }
265
+ }
266
+ export class PlainJoiner {
267
+ _separator;
268
+ _style;
269
+ constructor(options) {
270
+ this._separator = options?.separator ?? " | ";
271
+ this._style = options?.style ?? Style.parse("dim");
272
+ }
273
+ join(left, right) {
274
+ // Endpoints are empty — a fixed separator has no natural cap.
275
+ if (left === null || right === null)
276
+ return EMPTY;
277
+ const separator = this._separator;
278
+ const style = this._style;
279
+ return deferred((options) => [new Segment(drawable(options, separator, " | "), style)]);
280
+ }
281
+ }
282
+ /**
283
+ * Half-block dithering glyph: paints the cell's left half with the foreground
284
+ * colour and the right half with the background colour. Lets each cell carry
285
+ * two colour samples — `2 * steps` samples in `steps` cells — so the gradient
286
+ * looks twice as smooth as one-colour-per-cell at the same width.
287
+ */
288
+ const HALF_BLOCK = "▌"; // ▌
289
+ export class GradientJoiner {
290
+ _steps;
291
+ constructor(options) {
292
+ this._steps = options?.steps ?? 4;
293
+ }
294
+ join(left, right) {
295
+ // [LAW:dataflow-not-control-flow] Endpoints have no opposite anchor to
296
+ // interpolate toward — the data (a missing neighbor) makes the gradient
297
+ // empty. Same for edges with nothing to paint: nothing to blend between.
298
+ if (left === null || right === null)
299
+ return EMPTY;
300
+ const steps = this._steps;
301
+ return deferred(function* (options) {
302
+ const lbg = paintableBg(left.edgeStyle("right", options).bgcolor);
303
+ const rbg = paintableBg(right.edgeStyle("left", options).bgcolor);
304
+ if (!lbg || !rbg)
305
+ return;
306
+ const from = drawnGround(lbg);
307
+ const to = drawnGround(rbg);
308
+ // An ASCII cell carries one sample, the background: the gradient keeps
309
+ // its colours at half the resolution.
310
+ const half = drawable(options, HALF_BLOCK, " ");
311
+ const samples = 2 * steps;
312
+ // Midpoint sampling across `2 * steps` half-cell positions: sample j has
313
+ // t = (j + 0.5) / samples. Cell i takes samples 2i (left half) and 2i+1
314
+ // (right half). No sample ever equals either anchor.
315
+ for (let i = 0; i < steps; i++) {
316
+ const tLeft = (2 * i + 0.5) / samples;
317
+ const tRight = (2 * i + 1.5) / samples;
318
+ // Each sample stays a mix of the two grounds, so a theme that draws
319
+ // `blue` in its own shade draws the ramp from that shade.
320
+ const fg = ColorSpec.blend(from, to, tLeft);
321
+ const bg = ColorSpec.blend(from, to, tRight);
322
+ yield new Segment(half, new Style({ color: fg, bgcolor: bg }));
323
+ }
324
+ });
325
+ }
326
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Immutable style descriptors — colors, text attributes, links, metadata.
3
+ */
4
+ import { ColorSpec, ColorDepth } from "./color.js";
5
+ import { type Anchor } from "./anchor.js";
6
+ /**
7
+ * Canonical text-attribute inventory. Single source of truth consumed by
8
+ * `Style.parse` / `Style.toString` and the template bindings — adding an
9
+ * attribute here propagates to every styling surface without a second
10
+ * list to keep in sync. [LAW:one-source-of-truth]
11
+ */
12
+ export declare const ATTRIBUTE_NAMES: readonly ["bold", "dim", "italic", "underline", "blink", "blink2", "reverse", "conceal", "strike", "underline2", "frame", "encircle", "overline"];
13
+ export type AttributeName = (typeof ATTRIBUTE_NAMES)[number];
14
+ export declare const ATTRIBUTE_SGR: Record<AttributeName, number>;
15
+ /**
16
+ * Canonical short-attribute aliases (`b` → `bold`, `i` → `italic`, …).
17
+ * Single source of truth consumed by `Style.parse` and the template
18
+ * bindings — adding an alias here makes it available everywhere.
19
+ * [LAW:one-source-of-truth]
20
+ */
21
+ export declare const ATTRIBUTE_SHORT_ALIASES: Record<string, AttributeName>;
22
+ export interface StyleOptions {
23
+ color?: string | ColorSpec;
24
+ bgcolor?: string | ColorSpec;
25
+ bold?: boolean;
26
+ dim?: boolean;
27
+ italic?: boolean;
28
+ underline?: boolean;
29
+ blink?: boolean;
30
+ blink2?: boolean;
31
+ reverse?: boolean;
32
+ conceal?: boolean;
33
+ strike?: boolean;
34
+ underline2?: boolean;
35
+ frame?: boolean;
36
+ encircle?: boolean;
37
+ overline?: boolean;
38
+ link?: string;
39
+ meta?: Record<string, unknown>;
40
+ anchor?: Anchor;
41
+ }
42
+ export declare class StyleSyntaxError extends Error {
43
+ constructor(message: string, options?: ErrorOptions);
44
+ }
45
+ export declare class Style {
46
+ readonly color: ColorSpec | undefined;
47
+ readonly bgcolor: ColorSpec | undefined;
48
+ readonly bold: boolean | undefined;
49
+ readonly dim: boolean | undefined;
50
+ readonly italic: boolean | undefined;
51
+ readonly underline: boolean | undefined;
52
+ readonly blink: boolean | undefined;
53
+ readonly blink2: boolean | undefined;
54
+ readonly reverse: boolean | undefined;
55
+ readonly conceal: boolean | undefined;
56
+ readonly strike: boolean | undefined;
57
+ readonly underline2: boolean | undefined;
58
+ readonly frame: boolean | undefined;
59
+ readonly encircle: boolean | undefined;
60
+ readonly overline: boolean | undefined;
61
+ readonly link: string | undefined;
62
+ readonly meta: Record<string, unknown> | undefined;
63
+ /**
64
+ * Where this style's cells sat in the output of whatever drew them; see
65
+ * `./anchor.ts`. It draws nothing, and it makes two styles that draw alike
66
+ * unequal, because they came from different places.
67
+ */
68
+ readonly anchor: Anchor | undefined;
69
+ constructor(options?: StyleOptions);
70
+ get isNull(): boolean;
71
+ get transparentBackground(): boolean;
72
+ get backgroundStyle(): Style;
73
+ /**
74
+ * This style with `overrides` in place of its own fields.
75
+ *
76
+ * [LAW:one-source-of-truth] The field list is the class's own fields, so a
77
+ * field added to `Style` is carried by every copy without being named here.
78
+ */
79
+ private with;
80
+ get withoutColor(): Style;
81
+ withLink(link: string | undefined): Style;
82
+ clearMetaAndLinks(): Style;
83
+ /**
84
+ * The same look, drawn from the place `anchor` names, or from nowhere.
85
+ */
86
+ withAnchor(anchor: Anchor | undefined): Style;
87
+ /**
88
+ * This style on the cell `cells` to the right: its anchor shifted along its
89
+ * row, or the style itself when it has no anchor.
90
+ */
91
+ shiftedBy(cells: number): Style;
92
+ /**
93
+ * Merges two styles. `other`'s values take priority.
94
+ */
95
+ add(other: Style | undefined): Style;
96
+ equals(other: Style): boolean;
97
+ /**
98
+ * The colours this style puts on screen at `colorSystem`: each flattened
99
+ * onto what lies beneath it — the background onto the terminal's black, the
100
+ * foreground onto that background — then downgraded to the depth. The one
101
+ * account of what a style draws: `toSgrCodes` encodes exactly these, and a
102
+ * renderable choosing between two ways of drawing measures them.
103
+ */
104
+ drawnColors(colorSystem?: ColorDepth): {
105
+ readonly color: ColorSpec | undefined;
106
+ readonly bgcolor: ColorSpec | undefined;
107
+ };
108
+ /**
109
+ * Returns the SGR parameter list this style emits (e.g. `"1;31;48;2;0;0;255"`),
110
+ * or `""` when the style has no SGR contribution. Excludes OSC 8 link bytes —
111
+ * links are not SGR. The segment encoder (`segmentToString`) wraps it in
112
+ * `\x1b[...m`; styled text reaches the wire only through that encoder.
113
+ *
114
+ * [LAW:one-source-of-truth] One computation of SGR codes.
115
+ */
116
+ toSgrCodes(colorSystem: ColorDepth): string;
117
+ /**
118
+ * Serializes to a canonical string that round-trips through `Style.parse()`.
119
+ */
120
+ toString(): string;
121
+ static null(): Style;
122
+ static fromColor(color?: ColorSpec, bgcolor?: ColorSpec): Style;
123
+ static fromMeta(meta: Record<string, unknown>): Style;
124
+ static pickFirst(...args: (Style | undefined)[]): Style;
125
+ static combine(styles: (Style | undefined)[]): Style;
126
+ static chain(...styles: (Style | undefined)[]): Style;
127
+ static normalize(definition: string): string;
128
+ /**
129
+ * Parse a space-separated style definition. Memoized (`Memo`).
130
+ *
131
+ * A definition only: `"bold red"` parses, `"repr.number"` does not. Names
132
+ * belong to a `Theme`, and `Theme.resolve` is where one is looked up.
133
+ */
134
+ static parse(definition: string): Style;
135
+ }
136
+ export declare const NULL_STYLE: Style;
137
+ export declare class StyleStack {
138
+ private readonly stack;
139
+ constructor(base?: Style);
140
+ get current(): Style;
141
+ push(style: Style): void;
142
+ pop(): Style;
143
+ }
144
+ /**
145
+ * A `Style` added over a style name or definition a render has yet to
146
+ * resolve. Which it is cannot be told before then: a theme's names shadow
147
+ * definitions (`Theme.resolve`), so the addition waits for the render whose
148
+ * theme says what `under` stands for, and draws as `under` resolved with
149
+ * `over` added on top.
150
+ */
151
+ export interface LayeredStyle {
152
+ readonly under: string;
153
+ readonly over: Style;
154
+ }
155
+ /** A style as a RichText stores it, base or span: resolved when it is drawn. */
156
+ export type TextStyle = string | Style | LayeredStyle;
157
+ /**
158
+ * `over` added to `base`, the outer style winning as `Style.add` has it. A
159
+ * `Style` base takes the addition now, and so does `""`, the definition that
160
+ * adds nothing; any other string keeps it as a `LayeredStyle` for the render
161
+ * that resolves the string.
162
+ */
163
+ export declare function layerStyle(base: TextStyle, over: Style): TextStyle;
164
+ export declare class Theme {
165
+ private readonly styles;
166
+ constructor(definitions?: Record<string, string>, options?: {
167
+ inherit?: boolean;
168
+ });
169
+ get(name: string): Style | undefined;
170
+ /**
171
+ * The style a `TextStyle` stands for under this theme: a name this theme
172
+ * defines, otherwise a definition for `Style.parse`, which throws
173
+ * `StyleSyntaxError` when the string is neither. A `LayeredStyle` is its
174
+ * `under` resolved here with its `over` added on top.
175
+ *
176
+ * [LAW:one-source-of-truth] The theme is the one map from names to styles.
177
+ * The lookup stays outside `Style.parse`'s process-wide cache on purpose:
178
+ * cached there, the first console to resolve `repr.number` would decide it
179
+ * for every console after it, whatever theme each was given.
180
+ */
181
+ resolve(style: TextStyle): Style;
182
+ has(name: string): boolean;
183
+ }
184
+ export declare const DEFAULT_STYLES: Record<string, Style>;
185
+ /**
186
+ * The theme a render resolves names against when nothing supplied one — what
187
+ * a renderable sees outside a `Console`, as in `renderToString`.
188
+ */
189
+ export declare const DEFAULT_THEME: Theme;