@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
package/README.md CHANGED
@@ -24,7 +24,7 @@ Most of the library comes from the package name itself — `Console`, `Table`, `
24
24
  |---|---|---|---|
25
25
  | `@promptctl/rich-js/widgets` | Button, Checkbox, Toggle, TextInput, Dropdown, Slider, and the screen that mounts them | `mobx` | Carries a third-party runtime dependency of its own — MobX, for widget state |
26
26
  | `@promptctl/rich-js/template-bindings` | The styling vocabulary as Go-template functions, so styled text can be authored as a template | `@promptctl/go-template-js` | Carries a third-party runtime dependency of its own — the Go-template engine, for parsing and evaluating the templates |
27
- | `@promptctl/rich-js/host` | `TerminalHost`, `BrowserTerminalHost`, `hostStream` — the seam between rendering and a terminal | Nothing | A program that just wants to write bytes through a host shouldn't pay for the widget set to do it |
27
+ | `@promptctl/rich-js/host` | `TerminalHost`, `BrowserTerminalHost`, `hostEnvironment` — the seam between rendering and a terminal | Nothing | A program that just wants to write bytes through a host shouldn't pay for the widget set to do it |
28
28
  | `@promptctl/rich-js/node/save`, `/node/prompt`, `/node/traceback`, `/node/terminal-host` | File export, readline input, the crash handler, and the node TTY host | Nothing | Each one reads node built-ins, and keeping them off the main entry point is what keeps that entry point browser-safe |
29
29
 
30
30
  Those two peer dependencies are yours to install — `npm install @promptctl/rich-js` deliberately fetches neither, because a program that prints a table shouldn't acquire a state library or a template engine to do it. Add the ones you need:
@@ -293,45 +293,31 @@ installTraceback();
293
293
  Everything above draws once and returns. Widgets stay on screen and respond — a button that highlights under the cursor, a text field with a cursor you can move, a dropdown you filter by typing. They come from the `widgets` subpath and need MobX installed alongside the package (see [Entry points](#entry-points)).
294
294
 
295
295
  ```typescript
296
- import { Button, TextInput, DefaultScreen, EventRouter } from "@promptctl/rich-js/widgets";
296
+ import { Group } from "@promptctl/rich-js";
297
+ import { Button, TextInput, WidgetApp } from "@promptctl/rich-js/widgets";
297
298
  import { NodeTerminalHost } from "@promptctl/rich-js/node/terminal-host";
298
299
 
299
- const host = new NodeTerminalHost();
300
- const screen = new DefaultScreen({ host });
301
- const router = new EventRouter({ screen, host });
302
-
303
300
  const name = new TextInput({ placeholder: "your name" });
304
301
  const submit = new Button({ label: "Submit", variant: "primary" });
302
+ const form = new Group(name, submit);
305
303
 
306
- const quit = (): void => {
307
- router.stop();
308
- screen.stop();
309
- host.write("\n");
310
- };
304
+ const app = new WidgetApp({ host: new NodeTerminalHost(), surface: "alternate", view: () => form });
311
305
 
312
- submit.onSubmit(() => {
313
- quit();
314
- host.write(`hello, ${name.value}\n`);
315
- process.exit(0);
316
- });
306
+ submit.onSubmit(() => app.stop());
317
307
 
318
308
  // Raw mode swallows Ctrl+C, so the app has to handle it itself.
319
- router.onKey(
309
+ app.onKey(
320
310
  (event) => {
321
- if (event.ctrl && event.key === "c") {
322
- quit();
323
- process.exit(0);
324
- }
311
+ if (event.ctrl && event.key === "c") app.stop();
325
312
  },
326
313
  { priority: "high" },
327
314
  );
328
315
 
329
- screen.mount(name, submit);
330
- screen.start();
331
- router.start();
316
+ await app.run();
317
+ console.log(`hello, ${name.value}`);
332
318
  ```
333
319
 
334
- `DefaultScreen` builds a focus manager when you don't pass one, so the `TextInput` has focus before the user touches anything and Tab moves between the two widgets. The screen re-renders through a MobX reaction: change a widget's state and the frame redraws itself, with no explicit repaint call anywhere. See [docs/widgets.md](docs/widgets.md) for the full widget set, key dispatch, layout placements, and how to write your own.
320
+ Widgets go anywhere a renderable goes — a `Group`, a `Panel`, a `Layout` pane — and the app finds them on the frame it paints. The `TextInput` has focus before the user touches anything, Tab moves between the two widgets in the order they are drawn, and a change to a widget's state repaints with no explicit call. `app.stop()` — or a crash, or a signal — hands the terminal back. See [docs/widgets.md](docs/widgets.md) for the full widget set, key dispatch, layout, and how to write your own.
335
321
 
336
322
  </details>
337
323
 
@@ -401,6 +387,7 @@ npm run demo
401
387
  | `npm run themes-and-color-studio` | themes-and-color-studio | A printed tour of colours, palettes, bundled themes, and contrast. Prints once and exits; set `EXPORT_HTML=out.html` to save it as HTML too. |
402
388
  | `npm run strip` | rich-strip | Every built-in `Joiner`, printed side by side. Prints once and exits. |
403
389
  | `npm run markup-plugins` | rich-markup-plugins | Custom markup tags registered through `MarkupRegistry`. Prints once and exits. |
390
+ | `npm run viewport` | rich-viewport | A cursor walking a list taller than its window, kept in view by `Viewport.ensureVisible`. |
404
391
 
405
392
  `Ctrl-C` quits any demo that stays running.
406
393
 
@@ -410,5 +397,5 @@ npm run demo
410
397
  |---|---|
411
398
  | `NO_COLOR` | Disable all color (hyperlinks still print) |
412
399
  | `FORCE_COLOR` | Enable color regardless of `TERM` |
413
- | `TERM=dumb` | Disable color and style |
400
+ | `TERM=dumb` | Disable color, style, and hyperlinks |
414
401
  | `COLUMNS` / `LINES` | Override terminal dimensions |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptctl/rich-js",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Rich text and beautiful formatting in the terminal — a TypeScript port of Python's Rich",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -66,6 +66,7 @@
66
66
  "demo:build": "tsc -p tsconfig.demo.json",
67
67
  "sessions": "tsc -p tsconfig.demo.json && node dist-demo/examples/claude-sessions/index.js",
68
68
  "strip": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-strip/index.js",
69
+ "viewport": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-viewport/index.js",
69
70
  "markup-plugins": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-markup-plugins/index.js",
70
71
  "demo-inputs": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-config/index.js",
71
72
  "demo:dropdown": "tsc -p tsconfig.demo.json && node dist-demo/examples/dropdown-demo/index.js",
@@ -96,9 +97,12 @@
96
97
  },
97
98
  "devDependencies": {
98
99
  "@playwright/test": "^1.63.0",
99
- "@promptctl/go-template-js": "^0.9.0",
100
+ "@promptctl/go-template-js": "^0.10.0",
101
+ "@types/markdown-it": "^14.2.0",
100
102
  "@types/node": "^26.5.1",
101
103
  "@types/semver": "^7.8.0",
104
+ "markdown-it": "^14.3.2",
105
+ "markdown-it-container": "^4.0.0",
102
106
  "mobx": "^7.0.3",
103
107
  "semver": "^7.8.5",
104
108
  "strip-ansi": "^7.2.0",
@@ -111,7 +115,7 @@
111
115
  "string-width": "^8.2.2"
112
116
  },
113
117
  "peerDependencies": {
114
- "@promptctl/go-template-js": "^0.9.0",
118
+ "@promptctl/go-template-js": "^0.10.0",
115
119
  "mobx": "^7.0.3"
116
120
  },
117
121
  "peerDependenciesMeta": {
@@ -1,144 +0,0 @@
1
- /**
2
- * Box-drawing character sets for borders and table grids.
3
- *
4
- * A style is declared as an 8x4 character grid — one line per row a table can
5
- * draw, one column per position within that row:
6
- *
7
- * ┌─┬┐ top border
8
- * │ ││ header content
9
- * ├─┼┤ header separator
10
- * │ ││ body content
11
- * ├─┼┤ row separator
12
- * ├─┼┤ footer separator
13
- * │ ││ footer content
14
- * └─┴┘ bottom border
15
- *
16
- * [LAW:one-source-of-truth] The grid is the shape the reference implementation
17
- * (Python Rich's `box.py`) publishes these glyphs in, so a constant below can
18
- * be diffed against it character for character. The eighteen named fields this
19
- * replaced were a second, differently-shaped map of the same territory, and it
20
- * had drifted: seven of the nineteen constants carried wrong glyphs, and the
21
- * field named `mid` held the reference's line 3 while the reference's `mid_*`
22
- * is line 4 — so reading the reference name-for-name swapped a separator for a
23
- * content row. Named row fields are gone for that reason; a row is reached by
24
- * what it is (`getRow`, `getContentChars`), never by a name that can be
25
- * mismatched to a line.
26
- */
27
- import { Segment } from "./segment.js";
28
- import type { Style } from "./style.js";
29
- /** A rule spanning the table: the top and bottom borders, and every separator. */
30
- export interface EdgeChars {
31
- left: string;
32
- horizontal: string;
33
- cross: string;
34
- right: string;
35
- }
36
- /**
37
- * The verticals framing a row of cells. A content line of the grid has no
38
- * horizontal of its own — its fill column is a placeholder the cells occupy.
39
- */
40
- export interface ContentChars {
41
- left: string;
42
- vertical: string;
43
- right: string;
44
- }
45
- export type RowLevel = "head" | "row" | "foot" | "mid";
46
- export interface SubstituteOptions {
47
- asciiOnly?: boolean;
48
- safe?: boolean;
49
- }
50
- export declare class Box {
51
- readonly top: EdgeChars;
52
- readonly bottom: EdgeChars;
53
- private readonly grid;
54
- private readonly ascii;
55
- private readonly headContent;
56
- private readonly headSeparator;
57
- private readonly bodyContent;
58
- private readonly rowSeparator;
59
- private readonly footSeparator;
60
- private readonly footContent;
61
- /**
62
- * [LAW:parse-dont-validate] The one crossing between a grid string and box
63
- * glyphs. A `Box` cannot exist without eight rows of four characters, so no
64
- * consumer below ever re-checks the shape of the data it reads.
65
- */
66
- constructor(grid: string);
67
- /**
68
- * Renders the top border row for given column widths.
69
- */
70
- getTop(widths: readonly number[], style?: Style, edge?: boolean): Segment[];
71
- /**
72
- * Renders the separator drawn *above* a row at `level` — the head separator
73
- * under the header, the row separator between body rows, the foot separator
74
- * above the footer.
75
- */
76
- getRow(widths: readonly number[], level: RowLevel, style?: Style, edge?: boolean): Segment[];
77
- /**
78
- * The verticals that frame a content row at `level` — the counterpart to
79
- * `getRow`, which draws the separator between two such rows.
80
- */
81
- getContentChars(level: RowLevel): ContentChars;
82
- /**
83
- * Renders the bottom border row.
84
- */
85
- getBottom(widths: readonly number[], style?: Style, edge?: boolean): Segment[];
86
- /**
87
- * The box to draw with when the platform cannot render this one as written.
88
- *
89
- * `asciiOnly` gives up a box that spends non-ASCII glyphs for `ASCII`, and
90
- * leaves the four already-ASCII styles as they are — a caller that chose
91
- * MARKDOWN or ASCII_DOUBLE_HEAD asked for that frame and it is already
92
- * drawable, so answering with `ASCII` would trade a frame the terminal
93
- * supports for a different one it equally supports. `safe` squares off the
94
- * rounded corners a legacy Windows terminal draws as blanks.
95
- */
96
- substitute(options?: SubstituteOptions): Box;
97
- private safeSubstitute;
98
- /**
99
- * The nearest box that spends no special glyphs on a header — what a table
100
- * with `showHeader: false` draws with. A box whose head row already matches
101
- * its body is its own answer, so this is the identity for fourteen of the
102
- * nineteen shipped styles.
103
- *
104
- * It stands beside `substitute` rather than joining it because the two ask
105
- * unrelated questions: `substitute` asks what the *platform* can draw, this
106
- * asks what the *table* contains. Nothing correlates them, so a caller that
107
- * wants both wants both, and a shared options bag would only multiply the
108
- * combinations either one has to reason about. [LAW:no-mode-explosion]
109
- *
110
- * [LAW:types-are-the-program] The relation is keyed on the grid, not on
111
- * object identity as the reference's dict is. A `Box` is wholly determined by
112
- * its grid — that is what the constructor takes and all eight rows derive
113
- * from — so two boxes with one grid must answer this alike. Identity keying
114
- * would say otherwise the moment a box arrived by any route but the shipped
115
- * constant, and `safeSubstitute` above builds exactly such a box: a fresh
116
- * instance carrying an unchanged grid.
117
- */
118
- plainHeaded(): Box;
119
- /**
120
- * [LAW:dataflow-not-control-flow] Every full-width rule the box can draw is
121
- * this one loop; which rule it is arrives as four characters, not a branch.
122
- */
123
- private getEdge;
124
- private getRowChars;
125
- }
126
- export declare const ASCII: Box;
127
- export declare const ASCII2: Box;
128
- export declare const ASCII_DOUBLE_HEAD: Box;
129
- export declare const SQUARE: Box;
130
- export declare const SQUARE_DOUBLE_HEAD: Box;
131
- export declare const MINIMAL: Box;
132
- export declare const MINIMAL_HEAVY_HEAD: Box;
133
- export declare const MINIMAL_DOUBLE_HEAD: Box;
134
- export declare const SIMPLE: Box;
135
- export declare const SIMPLE_HEAD: Box;
136
- export declare const SIMPLE_HEAVY: Box;
137
- export declare const HORIZONTALS: Box;
138
- export declare const ROUNDED: Box;
139
- export declare const HEAVY: Box;
140
- export declare const HEAVY_EDGE: Box;
141
- export declare const HEAVY_HEAD: Box;
142
- export declare const DOUBLE: Box;
143
- export declare const DOUBLE_EDGE: Box;
144
- export declare const MARKDOWN: Box;
package/dist/core/box.js DELETED
@@ -1,391 +0,0 @@
1
- /**
2
- * Box-drawing character sets for borders and table grids.
3
- *
4
- * A style is declared as an 8x4 character grid — one line per row a table can
5
- * draw, one column per position within that row:
6
- *
7
- * ┌─┬┐ top border
8
- * │ ││ header content
9
- * ├─┼┤ header separator
10
- * │ ││ body content
11
- * ├─┼┤ row separator
12
- * ├─┼┤ footer separator
13
- * │ ││ footer content
14
- * └─┴┘ bottom border
15
- *
16
- * [LAW:one-source-of-truth] The grid is the shape the reference implementation
17
- * (Python Rich's `box.py`) publishes these glyphs in, so a constant below can
18
- * be diffed against it character for character. The eighteen named fields this
19
- * replaced were a second, differently-shaped map of the same territory, and it
20
- * had drifted: seven of the nineteen constants carried wrong glyphs, and the
21
- * field named `mid` held the reference's line 3 while the reference's `mid_*`
22
- * is line 4 — so reading the reference name-for-name swapped a separator for a
23
- * content row. Named row fields are gone for that reason; a row is reached by
24
- * what it is (`getRow`, `getContentChars`), never by a name that can be
25
- * mismatched to a line.
26
- */
27
- import { cellLen } from "./cells.js";
28
- import { Segment } from "./segment.js";
29
- const GRID_ROWS = 8;
30
- const GRID_COLUMNS = 4;
31
- /**
32
- * A grid a terminal without unicode support can already draw as written.
33
- *
34
- * [LAW:one-source-of-truth] Derived from the grid rather than declared per
35
- * constant the way the reference's `ascii=True` is. A `Box` is wholly its grid,
36
- * so the grid already answers this; a second, hand-written answer could
37
- * disagree with it. It also reaches boxes no constant can speak for —
38
- * `safeSubstitute` builds a fresh `Box` from an edited grid, and a flag carried
39
- * on the shipped constants would say nothing about that one.
40
- */
41
- const ASCII_GRID = /^[\x00-\x7F]*$/;
42
- /** Corners that a legacy Windows terminal cannot draw, and their square kin. */
43
- const SAFE_SUBSTITUTIONS = {
44
- "╭": "┌",
45
- "╮": "┐",
46
- "╰": "└",
47
- "╯": "┘",
48
- };
49
- const edgeOf = (row) => ({
50
- left: row[0],
51
- horizontal: row[1],
52
- cross: row[2],
53
- right: row[3],
54
- });
55
- const contentOf = (row) => ({
56
- left: row[0],
57
- vertical: row[2],
58
- right: row[3],
59
- });
60
- // [LAW:one-type-per-behavior] Box is a single type for all border styles — instances differ by data
61
- export class Box {
62
- top;
63
- bottom;
64
- grid;
65
- ascii;
66
- headContent;
67
- headSeparator;
68
- bodyContent;
69
- rowSeparator;
70
- footSeparator;
71
- footContent;
72
- /**
73
- * [LAW:parse-dont-validate] The one crossing between a grid string and box
74
- * glyphs. A `Box` cannot exist without eight rows of four characters, so no
75
- * consumer below ever re-checks the shape of the data it reads.
76
- */
77
- constructor(grid) {
78
- const lines = grid.split("\n");
79
- // A row is four characters because `getEdge` indexes the four positions,
80
- // and four cells because that is what the terminal draws — a single astral
81
- // glyph satisfies the first and breaks every frame the box appears in.
82
- // [LAW:single-enforcer] `cellLen` is the cell-width authority.
83
- const malformed = lines.length !== GRID_ROWS
84
- || lines.some((line) => Array.from(line).length !== GRID_COLUMNS)
85
- || lines.some((line) => cellLen(line) !== GRID_COLUMNS);
86
- if (malformed) {
87
- throw new Error(`A box grid is ${GRID_ROWS} lines of ${GRID_COLUMNS} single-cell `
88
- + `characters; got ${lines.length} line(s) measuring `
89
- + lines.map((line) => `${Array.from(line).length}/${cellLen(line)}`).join(", ")
90
- + " characters/cells");
91
- }
92
- const rows = lines.map((line) => Array.from(line));
93
- this.grid = grid;
94
- this.ascii = ASCII_GRID.test(grid);
95
- this.top = edgeOf(rows[0]);
96
- this.headContent = contentOf(rows[1]);
97
- this.headSeparator = edgeOf(rows[2]);
98
- this.bodyContent = contentOf(rows[3]);
99
- this.rowSeparator = edgeOf(rows[4]);
100
- this.footSeparator = edgeOf(rows[5]);
101
- this.footContent = contentOf(rows[6]);
102
- this.bottom = edgeOf(rows[7]);
103
- }
104
- /**
105
- * Renders the top border row for given column widths.
106
- */
107
- getTop(widths, style, edge = true) {
108
- return this.getEdge(widths, this.top, style, edge);
109
- }
110
- /**
111
- * Renders the separator drawn *above* a row at `level` — the head separator
112
- * under the header, the row separator between body rows, the foot separator
113
- * above the footer.
114
- */
115
- getRow(widths, level, style, edge = true) {
116
- return this.getEdge(widths, this.getRowChars(level), style, edge);
117
- }
118
- /**
119
- * The verticals that frame a content row at `level` — the counterpart to
120
- * `getRow`, which draws the separator between two such rows.
121
- */
122
- getContentChars(level) {
123
- switch (level) {
124
- case "head":
125
- return this.headContent;
126
- // `mid` is a spacer between body rows, so it is framed as one.
127
- case "row":
128
- case "mid":
129
- return this.bodyContent;
130
- case "foot":
131
- return this.footContent;
132
- }
133
- }
134
- /**
135
- * Renders the bottom border row.
136
- */
137
- getBottom(widths, style, edge = true) {
138
- return this.getEdge(widths, this.bottom, style, edge);
139
- }
140
- /**
141
- * The box to draw with when the platform cannot render this one as written.
142
- *
143
- * `asciiOnly` gives up a box that spends non-ASCII glyphs for `ASCII`, and
144
- * leaves the four already-ASCII styles as they are — a caller that chose
145
- * MARKDOWN or ASCII_DOUBLE_HEAD asked for that frame and it is already
146
- * drawable, so answering with `ASCII` would trade a frame the terminal
147
- * supports for a different one it equally supports. `safe` squares off the
148
- * rounded corners a legacy Windows terminal draws as blanks.
149
- */
150
- substitute(options = {}) {
151
- if (options.asciiOnly && !this.ascii)
152
- return ASCII;
153
- if (options.safe)
154
- return this.safeSubstitute();
155
- return this;
156
- }
157
- safeSubstitute() {
158
- return new Box(Array.from(this.grid, (char) => SAFE_SUBSTITUTIONS[char] ?? char).join(""));
159
- }
160
- /**
161
- * The nearest box that spends no special glyphs on a header — what a table
162
- * with `showHeader: false` draws with. A box whose head row already matches
163
- * its body is its own answer, so this is the identity for fourteen of the
164
- * nineteen shipped styles.
165
- *
166
- * It stands beside `substitute` rather than joining it because the two ask
167
- * unrelated questions: `substitute` asks what the *platform* can draw, this
168
- * asks what the *table* contains. Nothing correlates them, so a caller that
169
- * wants both wants both, and a shared options bag would only multiply the
170
- * combinations either one has to reason about. [LAW:no-mode-explosion]
171
- *
172
- * [LAW:types-are-the-program] The relation is keyed on the grid, not on
173
- * object identity as the reference's dict is. A `Box` is wholly determined by
174
- * its grid — that is what the constructor takes and all eight rows derive
175
- * from — so two boxes with one grid must answer this alike. Identity keying
176
- * would say otherwise the moment a box arrived by any route but the shipped
177
- * constant, and `safeSubstitute` above builds exactly such a box: a fresh
178
- * instance carrying an unchanged grid.
179
- */
180
- plainHeaded() {
181
- // `private` is scoped to the class, so the pairs below can be read by grid
182
- // without exposing one.
183
- return PLAIN_HEADED_SUBSTITUTIONS.find(([headed]) => headed.grid === this.grid)?.[1]
184
- ?? this;
185
- }
186
- /**
187
- * [LAW:dataflow-not-control-flow] Every full-width rule the box can draw is
188
- * this one loop; which rule it is arrives as four characters, not a branch.
189
- */
190
- getEdge(widths, chars, style, edge) {
191
- const segments = [];
192
- if (edge)
193
- segments.push(new Segment(chars.left, style));
194
- for (let i = 0; i < widths.length; i++) {
195
- if (i > 0)
196
- segments.push(new Segment(chars.cross, style));
197
- segments.push(new Segment(chars.horizontal.repeat(widths[i]), style));
198
- }
199
- if (edge)
200
- segments.push(new Segment(chars.right, style));
201
- segments.push(Segment.line());
202
- return segments;
203
- }
204
- getRowChars(level) {
205
- switch (level) {
206
- case "head":
207
- return this.headSeparator;
208
- case "row":
209
- return this.rowSeparator;
210
- case "foot":
211
- return this.footSeparator;
212
- // The reference's spacer row, not a rule: blank fill between the body
213
- // row's verticals. Nothing draws it yet — rich-table-jx1 has the details.
214
- case "mid":
215
- return {
216
- left: this.bodyContent.left,
217
- horizontal: " ",
218
- cross: this.bodyContent.vertical,
219
- right: this.bodyContent.right,
220
- };
221
- }
222
- }
223
- }
224
- // --- Pre-built box styles ---
225
- // Transcribed from Python Rich's `rich/box.py` and verified grid-for-grid
226
- // against it; see this module's header for the shape and why it is this shape.
227
- export const ASCII = new Box("+--+\n" +
228
- "| ||\n" +
229
- "|-+|\n" +
230
- "| ||\n" +
231
- "|-+|\n" +
232
- "|-+|\n" +
233
- "| ||\n" +
234
- "+--+");
235
- export const ASCII2 = new Box("+-++\n" +
236
- "| ||\n" +
237
- "+-++\n" +
238
- "| ||\n" +
239
- "+-++\n" +
240
- "+-++\n" +
241
- "| ||\n" +
242
- "+-++");
243
- export const ASCII_DOUBLE_HEAD = new Box("+-++\n" +
244
- "| ||\n" +
245
- "+=++\n" +
246
- "| ||\n" +
247
- "+-++\n" +
248
- "+-++\n" +
249
- "| ||\n" +
250
- "+-++");
251
- export const SQUARE = new Box("┌─┬┐\n" +
252
- "│ ││\n" +
253
- "├─┼┤\n" +
254
- "│ ││\n" +
255
- "├─┼┤\n" +
256
- "├─┼┤\n" +
257
- "│ ││\n" +
258
- "└─┴┘");
259
- export const SQUARE_DOUBLE_HEAD = new Box("┌─┬┐\n" +
260
- "│ ││\n" +
261
- "╞═╪╡\n" +
262
- "│ ││\n" +
263
- "├─┼┤\n" +
264
- "├─┼┤\n" +
265
- "│ ││\n" +
266
- "└─┴┘");
267
- export const MINIMAL = new Box(" ╷ \n" +
268
- " │ \n" +
269
- "╶─┼╴\n" +
270
- " │ \n" +
271
- "╶─┼╴\n" +
272
- "╶─┼╴\n" +
273
- " │ \n" +
274
- " ╵ ");
275
- export const MINIMAL_HEAVY_HEAD = new Box(" ╷ \n" +
276
- " │ \n" +
277
- "╺━┿╸\n" +
278
- " │ \n" +
279
- "╶─┼╴\n" +
280
- "╶─┼╴\n" +
281
- " │ \n" +
282
- " ╵ ");
283
- export const MINIMAL_DOUBLE_HEAD = new Box(" ╷ \n" +
284
- " │ \n" +
285
- " ═╪ \n" +
286
- " │ \n" +
287
- " ─┼ \n" +
288
- " ─┼ \n" +
289
- " │ \n" +
290
- " ╵ ");
291
- export const SIMPLE = new Box(" \n" +
292
- " \n" +
293
- " ── \n" +
294
- " \n" +
295
- " \n" +
296
- " ── \n" +
297
- " \n" +
298
- " ");
299
- export const SIMPLE_HEAD = new Box(" \n" +
300
- " \n" +
301
- " ── \n" +
302
- " \n" +
303
- " \n" +
304
- " \n" +
305
- " \n" +
306
- " ");
307
- export const SIMPLE_HEAVY = new Box(" \n" +
308
- " \n" +
309
- " ━━ \n" +
310
- " \n" +
311
- " \n" +
312
- " ━━ \n" +
313
- " \n" +
314
- " ");
315
- export const HORIZONTALS = new Box(" ── \n" +
316
- " \n" +
317
- " ── \n" +
318
- " \n" +
319
- " ── \n" +
320
- " ── \n" +
321
- " \n" +
322
- " ── ");
323
- export const ROUNDED = new Box("╭─┬╮\n" +
324
- "│ ││\n" +
325
- "├─┼┤\n" +
326
- "│ ││\n" +
327
- "├─┼┤\n" +
328
- "├─┼┤\n" +
329
- "│ ││\n" +
330
- "╰─┴╯");
331
- export const HEAVY = new Box("┏━┳┓\n" +
332
- "┃ ┃┃\n" +
333
- "┣━╋┫\n" +
334
- "┃ ┃┃\n" +
335
- "┣━╋┫\n" +
336
- "┣━╋┫\n" +
337
- "┃ ┃┃\n" +
338
- "┗━┻┛");
339
- export const HEAVY_EDGE = new Box("┏━┯┓\n" +
340
- "┃ │┃\n" +
341
- "┠─┼┨\n" +
342
- "┃ │┃\n" +
343
- "┠─┼┨\n" +
344
- "┠─┼┨\n" +
345
- "┃ │┃\n" +
346
- "┗━┷┛");
347
- export const HEAVY_HEAD = new Box("┏━┳┓\n" +
348
- "┃ ┃┃\n" +
349
- "┡━╇┩\n" +
350
- "│ ││\n" +
351
- "├─┼┤\n" +
352
- "├─┼┤\n" +
353
- "│ ││\n" +
354
- "└─┴┘");
355
- export const DOUBLE = new Box("╔═╦╗\n" +
356
- "║ ║║\n" +
357
- "╠═╬╣\n" +
358
- "║ ║║\n" +
359
- "╠═╬╣\n" +
360
- "╠═╬╣\n" +
361
- "║ ║║\n" +
362
- "╚═╩╝");
363
- export const DOUBLE_EDGE = new Box("╔═╤╗\n" +
364
- "║ │║\n" +
365
- "╟─┼╢\n" +
366
- "║ │║\n" +
367
- "╟─┼╢\n" +
368
- "╟─┼╢\n" +
369
- "║ │║\n" +
370
- "╚═╧╝");
371
- export const MARKDOWN = new Box(" \n" +
372
- "| ||\n" +
373
- "|-||\n" +
374
- "| ||\n" +
375
- "|-||\n" +
376
- "|-||\n" +
377
- "| ||\n" +
378
- " ");
379
- /**
380
- * Boxes whose header glyphs differ from their body's, paired with the kin that
381
- * draws the same frame without them. Transcribed from the reference's
382
- * `PLAIN_HEADED_SUBSTITUTIONS`; the fourteen styles absent here already draw a
383
- * plain head, and `plainHeaded` returns them unchanged.
384
- */
385
- const PLAIN_HEADED_SUBSTITUTIONS = [
386
- [HEAVY_HEAD, SQUARE],
387
- [SQUARE_DOUBLE_HEAD, SQUARE],
388
- [MINIMAL_HEAVY_HEAD, MINIMAL],
389
- [MINIMAL_DOUBLE_HEAD, MINIMAL],
390
- [ASCII_DOUBLE_HEAD, ASCII2],
391
- ];