@promptctl/rich-js 0.16.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 +14 -27
  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 -229
  8. package/dist/core/color.js +0 -992
  9. package/dist/core/console.d.ts +0 -142
  10. package/dist/core/console.js +0 -477
  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 -69
  34. package/dist/core/render.js +0 -140
  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 -139
  40. package/dist/core/strip.js +0 -302
  41. package/dist/core/style.d.ts +0 -149
  42. package/dist/core/style.js +0 -741
  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 -542
  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,322 +0,0 @@
1
- /**
2
- * Panel — a bordered box that wraps content, with optional title and subtitle.
3
- */
4
- import { cellLen, setCellSize, asCellCol, cellCount } from "../core/cells.js";
5
- import { Segment } from "../core/segment.js";
6
- import { NULL_STYLE } from "../core/style.js";
7
- import { ROUNDED } from "../core/box.js";
8
- import { Measurement } from "../core/measure.js";
9
- import { RichText } from "../core/text.js";
10
- import { normalizePadding } from "./padding.js";
11
- import { getStyle, isMeasurable, withBoundedWidth, withCellWidth } from "../core/protocol.js";
12
- function layoutPanel(outerWidth, padding) {
13
- const [, padRightWanted, , padLeftWanted] = padding;
14
- // Panel's one division point, and the width reaching it is not always one
15
- // this file produced: `_getPanelWidth` derives the fit-mode width from
16
- // `Measurement.get` on an arbitrary `Measurable`, so a renderable reporting a
17
- // negative maximum arrives here unparsed and reaches `repeat` as a negative
18
- // count.
19
- let budget = cellCount(outerWidth);
20
- const take = (want) => {
21
- const got = Math.min(want, budget);
22
- budget -= got;
23
- return got;
24
- };
25
- const left = take(1);
26
- const right = take(1);
27
- const firstContentCell = take(1);
28
- const padLeft = take(padLeftWanted);
29
- const padRight = take(padRightWanted);
30
- const contentWidth = firstContentCell + budget;
31
- return {
32
- left,
33
- right,
34
- padLeft,
35
- contentWidth,
36
- padRight,
37
- spanWidth: padLeft + contentWidth + padRight,
38
- };
39
- }
40
- /**
41
- * Every cell of a panel that is not content canvas. Read off the geometry
42
- * rather than recomputed as `2 + padLeft + padRight`, because the two differ
43
- * exactly where this panel is squeezed: a width that cannot afford its right
44
- * frame column or its padding does not spend cells on them, and a measurement
45
- * that assumed it did would report a minimum larger than its own maximum.
46
- */
47
- function frameOverhead(geometry) {
48
- return geometry.left + geometry.right + geometry.padLeft + geometry.padRight;
49
- }
50
- /**
51
- * The style of text set into a border: its own when one was given, the
52
- * border's otherwise — the one rule for "what colour is the title text in".
53
- */
54
- function borderTextStyle(options, own, border) {
55
- return own === undefined ? border : getStyle(options, own);
56
- }
57
- function toRenderable(content) {
58
- // [LAW:one-type-per-behavior] Content embedded in a panel is a line, not a
59
- // printed unit — the same reason columns, table cells, tree labels, and
60
- // every other embedding site build their RichText with `end: ""`. Now that
61
- // `RichText.render` honors `end` uniformly (rich-text-5ai), leaving this at
62
- // the default "\n" would draw a trailing blank row whenever content ends
63
- // with a literal newline.
64
- if (typeof content === "string")
65
- return new RichText(content, { end: "" });
66
- if (content instanceof RichText) {
67
- const copy = content.copy();
68
- copy.end = "";
69
- return copy;
70
- }
71
- return content;
72
- }
73
- export class Panel {
74
- renderable;
75
- box;
76
- title;
77
- subtitle;
78
- bottomRightAccessory;
79
- expand;
80
- style;
81
- borderStyle;
82
- titleStyle;
83
- subtitleStyle;
84
- width;
85
- padding;
86
- constructor(content, options) {
87
- this.renderable = toRenderable(content);
88
- this.box = options?.box ?? ROUNDED;
89
- this.title = options?.title;
90
- this.subtitle = options?.subtitle;
91
- this.bottomRightAccessory = options?.bottomRightAccessory;
92
- this.expand = options?.expand !== false;
93
- this.style = options?.style ?? NULL_STYLE;
94
- this.borderStyle = options?.borderStyle ?? NULL_STYLE;
95
- this.titleStyle = options?.titleStyle;
96
- this.subtitleStyle = options?.subtitleStyle;
97
- this.width = options?.width;
98
- this.padding = normalizePadding(options?.padding ?? [0, 1, 0, 1]);
99
- }
100
- *render(rawOptions) {
101
- const options = withBoundedWidth(rawOptions, this);
102
- const box = options.asciiOnly ? this.box.substitute({ asciiOnly: true }) : this.box;
103
- const borderStyle = getStyle(options, this.borderStyle);
104
- const style = getStyle(options, this.style);
105
- const border = borderStyle.isNull ? undefined : borderStyle;
106
- const contentStyle = style.isNull ? undefined : style;
107
- const geometry = layoutPanel(this._getPanelWidth(options), this.padding);
108
- const [padTop, , padBottom] = this.padding;
109
- const contentLines = this._renderContent(options, geometry.contentWidth);
110
- // Top border (with optional title)
111
- yield* this._renderTopBorder(options, box, geometry, border);
112
- // Top padding — a padding row is a content row whose content is nothing.
113
- for (let i = 0; i < padTop; i++) {
114
- yield* this._renderRow(box, geometry, [], border, contentStyle);
115
- }
116
- for (const line of contentLines) {
117
- yield* this._renderRow(box, geometry, line, border, contentStyle);
118
- }
119
- for (let i = 0; i < padBottom; i++) {
120
- yield* this._renderRow(box, geometry, [], border, contentStyle);
121
- }
122
- // Bottom border (with optional subtitle)
123
- yield* this._renderBottomBorder(options, box, geometry, border);
124
- }
125
- /**
126
- * The wrapped renderable's lines, laid out on a canvas `contentWidth` cells
127
- * wide. Below width 3 a panel is all frame and the canvas holds no lines —
128
- * but the render still happens, because a `bottomRightAccessory` thunk
129
- * fires at every width and reads state this render populates.
130
- */
131
- _renderContent(options, contentWidth) {
132
- const innerOptions = {
133
- ...options,
134
- maxWidth: contentWidth,
135
- };
136
- const lines = Segment.splitLines([...this.renderable.render(innerOptions)]);
137
- return contentWidth === 0 ? [] : lines;
138
- }
139
- /**
140
- * One row of the panel body: frame column, span, frame column.
141
- *
142
- * [LAW:single-enforcer] `adjustLineLength` is the one place a width is
143
- * decided here, and it runs twice against two different widths. The first
144
- * pass crops content that rendered wider than the canvas it was handed (a
145
- * `Table` at its natural width, say) back to the canvas, so an oversized
146
- * child is trimmed rather than allowed to eat the right-hand padding. The
147
- * second fills the rest of the span, which is the trailing padding and any
148
- * shortfall in one stroke.
149
- */
150
- *_renderRow(box, geometry, line, border, contentStyle) {
151
- const frame = box.getContentChars("row");
152
- yield new Segment(frame.left.repeat(geometry.left), border);
153
- const content = Segment.adjustLineLength(line, geometry.contentWidth, contentStyle, false);
154
- const span = [new Segment(" ".repeat(geometry.padLeft), contentStyle), ...content];
155
- yield* Segment.adjustLineLength(span, geometry.spanWidth, contentStyle);
156
- yield new Segment(frame.right.repeat(geometry.right), border);
157
- yield Segment.line();
158
- }
159
- measure(rawOptions) {
160
- const options = withCellWidth(rawOptions);
161
- const declared = this._declaredWidth;
162
- if (declared !== undefined) {
163
- // A declared width is not a ceiling on the content, it is the answer:
164
- // `_getPanelWidth` returns it before measuring anything, so the panel
165
- // draws at this width whatever the content wants. Reported with a
166
- // content-derived floor instead, `{width: 20}` around "hi" offered 100
167
- // cells answered 6..20 and then drew 20 every time, and a parent
168
- // dividing space from the floor under-provisioned it.
169
- const width = Math.min(options.maxWidth, declared);
170
- return { minimum: width, maximum: width };
171
- }
172
- return this._fitRange(options);
173
- }
174
- /**
175
- * The width this panel wants when nothing declared one for it: its content
176
- * plus its own frame, both read off the division it will render against.
177
- *
178
- * [LAW:one-source-of-truth] `measure` and `_getPanelWidth` ask this same
179
- * question, and each used to work it out itself — the same `layoutPanel`, the
180
- * same `frameOverhead`, the same `Math.min(maxWidth, maximum + overhead)`,
181
- * written twice. That is the pattern `_declaredWidth` below was extracted to
182
- * stop, left standing for the fit case; changing how overhead is derived in
183
- * one copy is all it would take to put `measure` and `render` back into the
184
- * disagreement this epic spent itself closing.
185
- */
186
- _fitRange(options) {
187
- const geometry = layoutPanel(options.maxWidth, this.padding);
188
- const overhead = frameOverhead(geometry);
189
- if (isMeasurable(this.renderable)) {
190
- const innerOptions = {
191
- ...options,
192
- maxWidth: geometry.contentWidth,
193
- };
194
- const measurement = Measurement.get(innerOptions, this.renderable);
195
- const maximum = Math.min(options.maxWidth, measurement.maximum + overhead);
196
- return {
197
- minimum: Math.min(measurement.minimum + overhead, maximum),
198
- maximum,
199
- };
200
- }
201
- // Content that cannot measure itself leaves the panel with nothing to want,
202
- // so it wants the offer — unbounded included, which `withBoundedWidth`
203
- // reports rather than turning into a number nobody can defend.
204
- return { minimum: Math.min(overhead, options.maxWidth), maximum: options.maxWidth };
205
- }
206
- /**
207
- * The width this panel was told to be, as a count of cells.
208
- *
209
- * [LAW:one-source-of-truth] `measure` and `_getPanelWidth` answer the same
210
- * question about the same field, so they read it from here. Answered
211
- * separately, `measure` reported nine cells of content while `render` drew the
212
- * declared twelve, and the parent that divided space from the range got a
213
- * panel three cells wider than the share it granted.
214
- */
215
- get _declaredWidth() {
216
- return this.width === undefined ? undefined : cellCount(this.width);
217
- }
218
- _getPanelWidth(options) {
219
- const declared = this._declaredWidth;
220
- if (declared !== undefined)
221
- return Math.min(declared, options.maxWidth);
222
- if (this.expand)
223
- return options.maxWidth;
224
- return this._fitRange(options).maximum;
225
- }
226
- *_renderTopBorder(options, box, geometry, border) {
227
- const innerBorderWidth = geometry.spanWidth;
228
- if (!this.title) {
229
- yield new Segment(box.top.left.repeat(geometry.left), border);
230
- yield new Segment(box.top.horizontal.repeat(innerBorderWidth), border);
231
- yield new Segment(box.top.right.repeat(geometry.right), border);
232
- yield Segment.line();
233
- return;
234
- }
235
- const titleText = typeof this.title === "string" ? this.title : this.title.plain;
236
- const titleDisplay = ` ${titleText} `;
237
- const titleWidth = cellLen(titleDisplay);
238
- const titleSeg = borderTextStyle(options, this.titleStyle, border);
239
- yield new Segment(box.top.left.repeat(geometry.left), border);
240
- if (titleWidth >= innerBorderWidth) {
241
- // Title fills the border. [LAW:one-source-of-truth] cellLen / setCellSize
242
- // are the cell-width authority — plain .slice would miscount wide chars
243
- // and break border alignment.
244
- yield new Segment(setCellSize(titleDisplay, asCellCol(innerBorderWidth)), titleSeg);
245
- }
246
- else {
247
- // Center the title in the top border
248
- const leftRuleWidth = Math.floor((innerBorderWidth - titleWidth) / 2);
249
- const rightRuleWidth = innerBorderWidth - titleWidth - leftRuleWidth;
250
- if (leftRuleWidth > 0)
251
- yield new Segment(box.top.horizontal.repeat(leftRuleWidth), border);
252
- yield new Segment(titleDisplay, titleSeg);
253
- if (rightRuleWidth > 0)
254
- yield new Segment(box.top.horizontal.repeat(rightRuleWidth), border);
255
- }
256
- yield new Segment(box.top.right.repeat(geometry.right), border);
257
- yield Segment.line();
258
- }
259
- *_renderBottomBorder(options, box, geometry, border) {
260
- const innerBorderWidth = geometry.spanWidth;
261
- // Resolve the right accessory *now*. Function form evaluates after
262
- // content has been rendered (Panel.render collects content segments
263
- // before yielding any borders), so the thunk sees fresh widget state.
264
- const accessory = this._resolveAccessory(this.bottomRightAccessory);
265
- const accessoryDisplay = accessory === undefined
266
- ? ""
267
- : typeof accessory === "string"
268
- ? ` ${accessory} `
269
- : ` ${accessory.plain} `;
270
- const accessoryWidth = cellLen(accessoryDisplay);
271
- const accessoryOwn = accessory instanceof RichText ? accessory.resolvedStyle(options) : NULL_STYLE;
272
- const accessoryStyle = accessoryOwn.isNull ? border : accessoryOwn;
273
- yield new Segment(box.bottom.left.repeat(geometry.left), border);
274
- // Space available for the centered subtitle / rule fill — the accessory
275
- // (if any) hugs the right edge and the subtitle treats the remainder
276
- // as its centering canvas.
277
- const centerWidth = Math.max(0, innerBorderWidth - accessoryWidth);
278
- if (!this.subtitle) {
279
- if (centerWidth > 0)
280
- yield new Segment(box.bottom.horizontal.repeat(centerWidth), border);
281
- }
282
- else {
283
- const subtitleText = typeof this.subtitle === "string" ? this.subtitle : this.subtitle.plain;
284
- const subtitleDisplay = ` ${subtitleText} `;
285
- const subtitleWidth = cellLen(subtitleDisplay);
286
- const subtitleSeg = borderTextStyle(options, this.subtitleStyle, border);
287
- if (subtitleWidth >= centerWidth) {
288
- // Cell-aware clip — see _renderTopBorder.
289
- yield new Segment(setCellSize(subtitleDisplay, asCellCol(centerWidth)), subtitleSeg);
290
- }
291
- else {
292
- const leftRuleWidth = Math.floor((centerWidth - subtitleWidth) / 2);
293
- const rightRuleWidth = centerWidth - subtitleWidth - leftRuleWidth;
294
- if (leftRuleWidth > 0)
295
- yield new Segment(box.bottom.horizontal.repeat(leftRuleWidth), border);
296
- yield new Segment(subtitleDisplay, subtitleSeg);
297
- if (rightRuleWidth > 0)
298
- yield new Segment(box.bottom.horizontal.repeat(rightRuleWidth), border);
299
- }
300
- }
301
- if (accessoryWidth > 0) {
302
- // Cell-aware clip — see _renderTopBorder.
303
- const fit = accessoryWidth > innerBorderWidth
304
- ? setCellSize(accessoryDisplay, asCellCol(innerBorderWidth))
305
- : accessoryDisplay;
306
- yield new Segment(fit, accessoryStyle);
307
- }
308
- yield new Segment(box.bottom.right.repeat(geometry.right), border);
309
- yield Segment.line();
310
- }
311
- _resolveAccessory(a) {
312
- if (a === undefined)
313
- return undefined;
314
- if (typeof a === "function")
315
- return a();
316
- return a;
317
- }
318
- // --- Static factory ---
319
- static fit(content, options) {
320
- return new Panel(content, { ...options, expand: false });
321
- }
322
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Progress — displays continuously updated progress bars.
3
- */
4
- import { Segment } from "../core/segment.js";
5
- import { Console } from "../core/console.js";
6
- import type { Renderable, RenderOptions } from "../core/protocol.js";
7
- export interface TaskOptions {
8
- total?: number;
9
- start?: boolean;
10
- visible?: boolean;
11
- }
12
- export interface TaskUpdateOptions {
13
- completed?: number;
14
- advance?: number;
15
- description?: string;
16
- visible?: boolean;
17
- refresh?: boolean;
18
- }
19
- interface Task {
20
- id: number;
21
- description: string;
22
- total: number | undefined;
23
- completed: number;
24
- started: boolean;
25
- visible: boolean;
26
- startTime: number;
27
- elapsed: number;
28
- }
29
- export interface ProgressColumn extends Renderable {
30
- render(options: RenderOptions, task?: Task): Iterable<Segment>;
31
- }
32
- export declare class TextColumn implements ProgressColumn {
33
- readonly format: string;
34
- constructor(format?: string);
35
- render(options: RenderOptions, task?: Task): Iterable<Segment>;
36
- }
37
- export declare class BarColumn implements ProgressColumn {
38
- readonly barWidth: number;
39
- constructor(barWidth?: number);
40
- render(_options: RenderOptions, task?: Task): Iterable<Segment>;
41
- }
42
- export declare class TaskProgressColumn implements ProgressColumn {
43
- render(options: RenderOptions, task?: Task): Iterable<Segment>;
44
- }
45
- export declare class TimeRemainingColumn implements ProgressColumn {
46
- render(options: RenderOptions, task?: Task): Iterable<Segment>;
47
- }
48
- export declare class TimeElapsedColumn implements ProgressColumn {
49
- render(options: RenderOptions, task?: Task): Iterable<Segment>;
50
- }
51
- export declare class SpinnerColumn implements ProgressColumn {
52
- private _spinner;
53
- constructor(spinnerName?: string);
54
- render(options: RenderOptions, _task?: Task): Iterable<Segment>;
55
- }
56
- export declare class MofNCompleteColumn implements ProgressColumn {
57
- render(_options: RenderOptions, task?: Task): Iterable<Segment>;
58
- }
59
- export interface ProgressOptions {
60
- refreshPerSecond?: number;
61
- autoRefresh?: boolean;
62
- transient?: boolean;
63
- expand?: boolean;
64
- console?: Console;
65
- }
66
- export declare class Progress implements Renderable {
67
- private _columns;
68
- private _tasks;
69
- private _nextId;
70
- private _live;
71
- private _console;
72
- readonly expand: boolean;
73
- constructor(...columns: (ProgressColumn | ProgressOptions)[]);
74
- get console(): Console;
75
- get finished(): boolean;
76
- static getDefaultColumns(): ProgressColumn[];
77
- addTask(description: string, options?: TaskOptions): number;
78
- updateTask(taskId: number, options: TaskUpdateOptions): void;
79
- startTask(taskId: number): void;
80
- start(): void;
81
- stop(): void;
82
- refresh(): void;
83
- render(options: RenderOptions): Iterable<Segment>;
84
- }
85
- export declare function track<T>(iterable: Iterable<T>, options?: {
86
- description?: string;
87
- total?: number;
88
- console?: Console;
89
- }): Iterable<T>;
90
- export {};
@@ -1,241 +0,0 @@
1
- /**
2
- * Progress — displays continuously updated progress bars.
3
- */
4
- import { Segment } from "../core/segment.js";
5
- import { RichText } from "../core/text.js";
6
- import { Console } from "../core/console.js";
7
- import { escape as escapeMarkup, renderMarkup } from "../core/markup.js";
8
- import { ProgressBar } from "./progressBar.js";
9
- import { Spinner } from "./spinner.js";
10
- import { Live } from "./live.js";
11
- import { Table } from "./table.js";
12
- import { getStyle } from "../core/protocol.js";
13
- export class TextColumn {
14
- format;
15
- constructor(format) {
16
- this.format = format ?? "{task.description}";
17
- }
18
- *render(options, task) {
19
- // [LAW:single-enforcer] Format strings flow through the markup parser so
20
- // tags like `[progress.description]` become Style spans, not literal text.
21
- // Task descriptions are escaped first to prevent injection of stray tags.
22
- const description = escapeMarkup(task?.description ?? "");
23
- // Use a callback so `$&`/`$1`/`$$` in the task description aren't
24
- // reinterpreted by String.replace as replacement patterns.
25
- const formatted = this.format.replace(/\{task\.description\}/g, () => description);
26
- // [LAW:one-type-per-behavior] A column is a row fragment, not a printed
27
- // line, so it clears `end` the same way `toCellText` does in table.ts —
28
- // `renderMarkup` returns a fresh `RichText` each call, so it is safe to
29
- // mutate directly rather than copy.
30
- const text = renderMarkup(formatted);
31
- text.end = "";
32
- yield* text.render(options);
33
- }
34
- }
35
- export class BarColumn {
36
- barWidth;
37
- constructor(barWidth) {
38
- this.barWidth = barWidth ?? 40;
39
- }
40
- *render(_options, task) {
41
- const bar = new ProgressBar({
42
- total: task?.total ?? 100,
43
- completed: task?.completed ?? 0,
44
- width: this.barWidth,
45
- });
46
- yield* bar.render(_options);
47
- }
48
- }
49
- export class TaskProgressColumn {
50
- *render(options, task) {
51
- const percent = task && task.total
52
- ? Math.min(100, Math.round((task.completed / task.total) * 100))
53
- : 0;
54
- yield new Segment(`${percent}%`, getStyle(options, "progress.percentage"));
55
- }
56
- }
57
- export class TimeRemainingColumn {
58
- *render(options, task) {
59
- const style = getStyle(options, "progress.remaining");
60
- if (!task || !task.total || !task.started || task.completed <= 0) {
61
- yield new Segment("-:--:--", style);
62
- return;
63
- }
64
- const elapsed = (Date.now() - task.startTime) / 1000;
65
- const rate = task.completed / elapsed;
66
- const remaining = (task.total - task.completed) / rate;
67
- yield new Segment(formatTime(remaining), style);
68
- }
69
- }
70
- export class TimeElapsedColumn {
71
- *render(options, task) {
72
- const style = getStyle(options, "progress.elapsed");
73
- if (!task || !task.started) {
74
- yield new Segment("0:00:00", style);
75
- return;
76
- }
77
- const elapsed = (Date.now() - task.startTime) / 1000;
78
- yield new Segment(formatTime(elapsed), style);
79
- }
80
- }
81
- export class SpinnerColumn {
82
- _spinner;
83
- constructor(spinnerName) {
84
- this._spinner = new Spinner(spinnerName);
85
- }
86
- *render(options, _task) {
87
- yield* this._spinner.render(options);
88
- }
89
- }
90
- export class MofNCompleteColumn {
91
- *render(_options, task) {
92
- const completed = task?.completed ?? 0;
93
- const total = task?.total ?? "?";
94
- yield new Segment(`${completed}/${total}`);
95
- }
96
- }
97
- function formatTime(seconds) {
98
- const h = Math.floor(seconds / 3600);
99
- const m = Math.floor((seconds % 3600) / 60);
100
- const s = Math.floor(seconds % 60);
101
- return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
102
- }
103
- export class Progress {
104
- _columns;
105
- _tasks;
106
- _nextId;
107
- _live;
108
- _console;
109
- expand;
110
- constructor(...columns) {
111
- // Last arg might be options
112
- let opts = {};
113
- const cols = [];
114
- for (const arg of columns) {
115
- if ("render" in arg) {
116
- cols.push(arg);
117
- }
118
- else {
119
- opts = arg;
120
- }
121
- }
122
- if (cols.length === 0) {
123
- cols.push(new TextColumn("[progress.description]{task.description}"), new BarColumn(), new TaskProgressColumn(), new TimeRemainingColumn());
124
- }
125
- this._columns = cols;
126
- this._tasks = new Map();
127
- this._nextId = 1;
128
- this._console = opts.console ?? new Console({ forceTerminal: true });
129
- this.expand = opts.expand ?? false;
130
- this._live = new Live(this, {
131
- console: this._console,
132
- refreshPerSecond: opts.refreshPerSecond ?? 10,
133
- autoRefresh: opts.autoRefresh,
134
- transient: opts.transient,
135
- });
136
- }
137
- get console() {
138
- return this._console;
139
- }
140
- get finished() {
141
- for (const task of this._tasks.values()) {
142
- if (!task.started || (task.total !== undefined && task.completed < task.total)) {
143
- return false;
144
- }
145
- }
146
- return this._tasks.size > 0;
147
- }
148
- static getDefaultColumns() {
149
- return [
150
- new TextColumn("[progress.description]{task.description}"),
151
- new BarColumn(),
152
- new TaskProgressColumn(),
153
- new TimeRemainingColumn(),
154
- ];
155
- }
156
- addTask(description, options) {
157
- const id = this._nextId++;
158
- const task = {
159
- id,
160
- description,
161
- total: options?.total,
162
- completed: 0,
163
- started: options?.start !== false,
164
- visible: options?.visible !== false,
165
- startTime: Date.now(),
166
- elapsed: 0,
167
- };
168
- this._tasks.set(id, task);
169
- return id;
170
- }
171
- updateTask(taskId, options) {
172
- const task = this._tasks.get(taskId);
173
- if (!task)
174
- return;
175
- if (options.completed !== undefined)
176
- task.completed = options.completed;
177
- if (options.advance !== undefined)
178
- task.completed += options.advance;
179
- if (options.description !== undefined)
180
- task.description = options.description;
181
- if (options.visible !== undefined)
182
- task.visible = options.visible;
183
- if (options.refresh) {
184
- this._live.update(this, { refresh: true });
185
- }
186
- }
187
- startTask(taskId) {
188
- const task = this._tasks.get(taskId);
189
- if (task) {
190
- task.started = true;
191
- task.startTime = Date.now();
192
- }
193
- }
194
- start() {
195
- this._live.start();
196
- }
197
- stop() {
198
- this._live.stop();
199
- }
200
- refresh() {
201
- this._live.refresh();
202
- }
203
- *render(options) {
204
- const table = Table.grid({ expand: this.expand });
205
- for (const _col of this._columns) {
206
- table.addColumn();
207
- }
208
- for (const task of this._tasks.values()) {
209
- if (!task.visible)
210
- continue;
211
- const cells = this._columns.map((col) => {
212
- const segs = [...col.render(options, task)];
213
- // Build a RichText that preserves segment styles as spans
214
- const text = new RichText("", { end: "" });
215
- for (const seg of segs) {
216
- text.append(seg.text, seg.style);
217
- }
218
- return text;
219
- });
220
- table.addRow(...cells);
221
- }
222
- yield* table.render(options);
223
- }
224
- }
225
- // --- track ---
226
- export function* track(iterable, options) {
227
- const items = [...iterable];
228
- const total = options?.total ?? items.length;
229
- const progress = new Progress({ console: options?.console });
230
- const taskId = progress.addTask(options?.description ?? "Working...", { total });
231
- progress.start();
232
- try {
233
- for (let i = 0; i < items.length; i++) {
234
- yield items[i];
235
- progress.updateTask(taskId, { completed: i + 1 });
236
- }
237
- }
238
- finally {
239
- progress.stop();
240
- }
241
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * ProgressBar — a visual progress bar rendered with block characters.
3
- */
4
- import { Segment } from "../core/segment.js";
5
- import { Style } from "../core/style.js";
6
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
7
- export interface ProgressBarOptions {
8
- total?: number;
9
- completed?: number;
10
- width?: number;
11
- pulse?: boolean;
12
- style?: string | Style;
13
- completeStyle?: string | Style;
14
- finishedStyle?: string | Style;
15
- }
16
- export declare class ProgressBar implements Renderable, Measurable {
17
- total: number;
18
- completed: number;
19
- readonly width: number | undefined;
20
- readonly pulse: boolean;
21
- readonly style: string | Style;
22
- readonly completeStyle: string | Style;
23
- readonly finishedStyle: string | Style;
24
- constructor(options?: ProgressBarOptions);
25
- get percentComplete(): number;
26
- render(options: RenderOptions): Iterable<Segment>;
27
- measure(_options: RenderOptions): {
28
- minimum: number;
29
- maximum: number;
30
- };
31
- }