@promptctl/rich-js 0.18.1 → 0.20.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 (169) hide show
  1. package/README.md +11 -8
  2. package/dist/core/box.d.ts +3 -1
  3. package/dist/core/box.js +6 -13
  4. package/dist/core/cells.d.ts +61 -28
  5. package/dist/core/cells.js +171 -124
  6. package/dist/core/clock.d.ts +48 -0
  7. package/dist/core/clock.js +42 -0
  8. package/dist/core/color.d.ts +99 -18
  9. package/dist/core/color.js +334 -146
  10. package/dist/core/console.d.ts +38 -7
  11. package/dist/core/console.js +159 -90
  12. package/dist/core/easing.d.ts +86 -0
  13. package/dist/core/easing.js +171 -0
  14. package/dist/core/env.d.ts +16 -0
  15. package/dist/core/env.js +16 -0
  16. package/dist/core/export-html.js +1 -4
  17. package/dist/core/export-lines.d.ts +22 -13
  18. package/dist/core/export-lines.js +37 -19
  19. package/dist/core/export-svg.d.ts +49 -0
  20. package/dist/core/export-svg.js +213 -0
  21. package/dist/core/fnv1a.d.ts +10 -0
  22. package/dist/core/fnv1a.js +17 -0
  23. package/dist/core/highlighter.d.ts +2 -2
  24. package/dist/core/highlighter.js +113 -47
  25. package/dist/core/json.js +1 -1
  26. package/dist/core/markup.d.ts +28 -7
  27. package/dist/core/markup.js +235 -312
  28. package/dist/core/measure.d.ts +3 -3
  29. package/dist/core/measure.js +12 -7
  30. package/dist/core/memo.d.ts +19 -0
  31. package/dist/core/memo.js +34 -0
  32. package/dist/core/noise.d.ts +31 -0
  33. package/dist/core/noise.js +94 -0
  34. package/dist/core/osc8.d.ts +6 -6
  35. package/dist/core/osc8.js +9 -21
  36. package/dist/core/paint.d.ts +107 -0
  37. package/dist/core/paint.js +161 -0
  38. package/dist/core/place.d.ts +1 -1
  39. package/dist/core/place.js +4 -6
  40. package/dist/core/pretty.d.ts +126 -2
  41. package/dist/core/pretty.js +571 -105
  42. package/dist/core/protocol.d.ts +43 -3
  43. package/dist/core/protocol.js +23 -2
  44. package/dist/core/render.d.ts +33 -30
  45. package/dist/core/render.js +43 -83
  46. package/dist/core/segment.d.ts +6 -5
  47. package/dist/core/segment.js +11 -15
  48. package/dist/core/selection.d.ts +12 -0
  49. package/dist/core/selection.js +14 -0
  50. package/dist/core/strip.d.ts +116 -74
  51. package/dist/core/strip.js +266 -217
  52. package/dist/core/style.d.ts +27 -7
  53. package/dist/core/style.js +106 -41
  54. package/dist/core/text.d.ts +43 -16
  55. package/dist/core/text.js +180 -59
  56. package/dist/core/wrap.d.ts +5 -0
  57. package/dist/core/wrap.js +16 -3
  58. package/dist/host/app.d.ts +95 -17
  59. package/dist/host/app.js +201 -61
  60. package/dist/host/index.d.ts +4 -1
  61. package/dist/host/index.js +1 -0
  62. package/dist/host/input.d.ts +84 -0
  63. package/dist/host/input.js +498 -0
  64. package/dist/host/terminal-host.d.ts +15 -2
  65. package/dist/host/terminal-host.js +2 -0
  66. package/dist/index.d.ts +26 -14
  67. package/dist/index.js +17 -7
  68. package/dist/node/prompt.d.ts +13 -0
  69. package/dist/node/prompt.js +30 -9
  70. package/dist/node/save.d.ts +4 -3
  71. package/dist/node/save.js +6 -3
  72. package/dist/node/terminal-host.d.ts +10 -2
  73. package/dist/node/terminal-host.js +7 -0
  74. package/dist/node/traceback.d.ts +2 -1
  75. package/dist/node/traceback.js +3 -39
  76. package/dist/renderables/align.js +2 -9
  77. package/dist/renderables/columns.d.ts +45 -29
  78. package/dist/renderables/columns.js +142 -103
  79. package/dist/renderables/constrain.js +6 -12
  80. package/dist/renderables/effect.d.ts +113 -0
  81. package/dist/renderables/effect.js +182 -0
  82. package/dist/renderables/effects.d.ts +252 -0
  83. package/dist/renderables/effects.js +565 -0
  84. package/dist/renderables/embed.d.ts +94 -31
  85. package/dist/renderables/embed.js +141 -45
  86. package/dist/renderables/group.d.ts +6 -3
  87. package/dist/renderables/group.js +5 -0
  88. package/dist/renderables/layout.d.ts +6 -23
  89. package/dist/renderables/layout.js +24 -113
  90. package/dist/renderables/list-view.d.ts +42 -0
  91. package/dist/renderables/list-view.js +78 -0
  92. package/dist/renderables/live.d.ts +40 -16
  93. package/dist/renderables/live.js +96 -83
  94. package/dist/renderables/markdown.d.ts +16 -2
  95. package/dist/renderables/markdown.js +521 -118
  96. package/dist/renderables/padding.d.ts +22 -1
  97. package/dist/renderables/padding.js +30 -24
  98. package/dist/renderables/panel.d.ts +31 -33
  99. package/dist/renderables/panel.js +137 -141
  100. package/dist/renderables/progress.d.ts +110 -16
  101. package/dist/renderables/progress.js +212 -81
  102. package/dist/renderables/progressBar.d.ts +24 -4
  103. package/dist/renderables/progressBar.js +78 -23
  104. package/dist/renderables/prompt.d.ts +37 -15
  105. package/dist/renderables/prompt.js +125 -85
  106. package/dist/renderables/ratio.d.ts +123 -0
  107. package/dist/renderables/ratio.js +213 -0
  108. package/dist/renderables/rule.d.ts +3 -2
  109. package/dist/renderables/rule.js +50 -52
  110. package/dist/renderables/spinner.d.ts +28 -2
  111. package/dist/renderables/spinner.js +52 -19
  112. package/dist/renderables/status.d.ts +13 -7
  113. package/dist/renderables/status.js +15 -29
  114. package/dist/renderables/syntax.d.ts +37 -7
  115. package/dist/renderables/syntax.js +134 -83
  116. package/dist/renderables/table.d.ts +58 -20
  117. package/dist/renderables/table.js +373 -197
  118. package/dist/renderables/traceback.d.ts +5 -4
  119. package/dist/renderables/traceback.js +239 -68
  120. package/dist/renderables/tree.d.ts +10 -2
  121. package/dist/renderables/tree.js +61 -34
  122. package/dist/renderables/viewport.d.ts +30 -0
  123. package/dist/renderables/viewport.js +61 -7
  124. package/dist/template-bindings/color-funcs.d.ts +31 -9
  125. package/dist/template-bindings/color-funcs.js +40 -14
  126. package/dist/template-bindings/helpers.d.ts +4 -2
  127. package/dist/template-bindings/helpers.js +5 -20
  128. package/dist/template-bindings/index.d.ts +29 -17
  129. package/dist/template-bindings/index.js +36 -20
  130. package/dist/template-bindings/palette-funcs.d.ts +3 -1
  131. package/dist/template-bindings/palette-funcs.js +11 -7
  132. package/dist/template-bindings/style-funcs.js +4 -2
  133. package/dist/themes/buildPalette.d.ts +24 -3
  134. package/dist/themes/buildPalette.js +52 -8
  135. package/dist/themes/colorMath.d.ts +29 -17
  136. package/dist/themes/colorMath.js +74 -48
  137. package/dist/themes/colorRef.d.ts +2 -17
  138. package/dist/themes/colorRef.js +13 -31
  139. package/dist/themes/palette.d.ts +24 -0
  140. package/dist/themes/palette.js +46 -3
  141. package/dist/themes/ramp.d.ts +21 -34
  142. package/dist/themes/ramp.js +23 -42
  143. package/dist/themes/registry.js +39 -28
  144. package/dist/widgets/button.d.ts +2 -6
  145. package/dist/widgets/button.js +9 -40
  146. package/dist/widgets/checkbox.d.ts +2 -5
  147. package/dist/widgets/checkbox.js +9 -31
  148. package/dist/widgets/dropdown.d.ts +2 -5
  149. package/dist/widgets/dropdown.js +17 -51
  150. package/dist/widgets/event-router.d.ts +31 -40
  151. package/dist/widgets/event-router.js +52 -483
  152. package/dist/widgets/index.d.ts +2 -1
  153. package/dist/widgets/ink.d.ts +22 -0
  154. package/dist/widgets/ink.js +31 -0
  155. package/dist/widgets/slider.d.ts +2 -5
  156. package/dist/widgets/slider.js +11 -34
  157. package/dist/widgets/text-input.d.ts +19 -10
  158. package/dist/widgets/text-input.js +156 -147
  159. package/dist/widgets/themed-widget.d.ts +20 -0
  160. package/dist/widgets/themed-widget.js +89 -0
  161. package/dist/widgets/toggle.d.ts +2 -5
  162. package/dist/widgets/toggle.js +8 -31
  163. package/dist/widgets/types.d.ts +8 -25
  164. package/dist/widgets/types.js +8 -0
  165. package/dist/widgets/widget-app.d.ts +24 -7
  166. package/dist/widgets/widget-app.js +35 -18
  167. package/dist/widgets/widget-base.d.ts +2 -0
  168. package/dist/widgets/widget-base.js +9 -2
  169. package/package.json +31 -17
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ThemedWidget — a widget that draws every cell it owns from one theme's
3
+ * palette: the interactive widgets, as against `StaticItem`, which shows
4
+ * content it did not draw.
5
+ *
6
+ * [LAW:one-type-per-behavior] The theme, how it is swapped, what a disabled
7
+ * widget looks like, and the ground a padded row stands on are the same for
8
+ * every such widget, so they live here once.
9
+ */
10
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
11
+ var useValue = arguments.length > 2;
12
+ for (var i = 0; i < initializers.length; i++) {
13
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
14
+ }
15
+ return useValue ? value : void 0;
16
+ };
17
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
18
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
19
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
20
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
21
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
22
+ var _, done = false;
23
+ for (var i = decorators.length - 1; i >= 0; i--) {
24
+ var context = {};
25
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
26
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
27
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
28
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
29
+ if (kind === "accessor") {
30
+ if (result === void 0) continue;
31
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
32
+ if (_ = accept(result.get)) descriptor.get = _;
33
+ if (_ = accept(result.set)) descriptor.set = _;
34
+ if (_ = accept(result.init)) initializers.unshift(_);
35
+ }
36
+ else if (_ = accept(result)) {
37
+ if (kind === "field") initializers.unshift(_);
38
+ else descriptor[key] = _;
39
+ }
40
+ }
41
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
42
+ done = true;
43
+ };
44
+ import { observableRef, action } from "mobx";
45
+ import { Style } from "../core/style.js";
46
+ import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
47
+ import { WidgetBase } from "./widget-base.js";
48
+ import { ink } from "./ink.js";
49
+ let ThemedWidget = (() => {
50
+ let _classSuper = WidgetBase;
51
+ let _instanceExtraInitializers = [];
52
+ let _theme_decorators;
53
+ let _theme_initializers = [];
54
+ let _theme_extraInitializers = [];
55
+ let _setTheme_decorators;
56
+ return class ThemedWidget extends _classSuper {
57
+ static {
58
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
59
+ _theme_decorators = [observableRef];
60
+ _setTheme_decorators = [action];
61
+ __esDecorate(this, null, _theme_decorators, { kind: "accessor", name: "theme", static: false, private: false, access: { has: obj => "theme" in obj, get: obj => obj.theme, set: (obj, value) => { obj.theme = value; } }, metadata: _metadata }, _theme_initializers, _theme_extraInitializers);
62
+ __esDecorate(this, null, _setTheme_decorators, { kind: "method", name: "setTheme", static: false, private: false, access: { has: obj => "setTheme" in obj, get: obj => obj.setTheme }, metadata: _metadata }, null, _instanceExtraInitializers);
63
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
64
+ }
65
+ #theme_accessor_storage = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _theme_initializers, void 0));
66
+ // [LAW:types-are-the-program] @observableRef so setTheme() triggers a
67
+ // re-render: every draw reads its colours through this reference.
68
+ get theme() { return this.#theme_accessor_storage; }
69
+ set theme(value) { this.#theme_accessor_storage = value; }
70
+ constructor(theme) {
71
+ super();
72
+ __runInitializers(this, _theme_extraInitializers);
73
+ this.theme = theme ?? DEFAULT_TERMINAL_THEME;
74
+ }
75
+ setTheme(theme) {
76
+ this.theme = theme;
77
+ }
78
+ get ground() {
79
+ return new Style(ink(this.theme, "foreground", "background"));
80
+ }
81
+ // [LAW:one-source-of-truth] How every widget looks while disabled: the
82
+ // palette's de-emphasized text on its ground. WCAG sets no contrast floor for
83
+ // an inactive control, and this sits under the one for text on purpose.
84
+ get disabledInk() {
85
+ return ink(this.theme, "foreground-muted", "background");
86
+ }
87
+ };
88
+ })();
89
+ export { ThemedWidget };
@@ -13,7 +13,7 @@
13
13
  import { Segment } from "../core/segment.js";
14
14
  import type { RenderOptions } from "../core/protocol.js";
15
15
  import type { TerminalTheme } from "../core/color.js";
16
- import { WidgetBase } from "./widget-base.js";
16
+ import { ThemedWidget } from "./themed-widget.js";
17
17
  import type { KeyEvent, WidgetMouseEvent } from "./types.js";
18
18
  export type ToggleVariant = "default" | "primary" | "success" | "warning" | "danger";
19
19
  export interface ToggleOptions {
@@ -24,15 +24,13 @@ export interface ToggleOptions {
24
24
  theme?: TerminalTheme;
25
25
  variant?: ToggleVariant;
26
26
  }
27
- export declare class Toggle extends WidgetBase {
27
+ export declare class Toggle extends ThemedWidget {
28
28
  readonly id: string;
29
29
  readonly focusable = true;
30
30
  accessor label: string;
31
31
  accessor on: boolean;
32
32
  accessor variant: ToggleVariant;
33
- private accessor _theme;
34
33
  constructor(options: ToggleOptions);
35
- setTheme(theme: TerminalTheme): void;
36
34
  handleKey(event: KeyEvent): void;
37
35
  handleMouse(event: WidgetMouseEvent): void;
38
36
  protected draw(_options: RenderOptions): Iterable<Segment>;
@@ -40,5 +38,4 @@ export declare class Toggle extends WidgetBase {
40
38
  minimum: number;
41
39
  maximum: number;
42
40
  };
43
- private resolvePalette;
44
41
  }
@@ -47,10 +47,9 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
47
47
  import { observable, action, observableRef } from "mobx";
48
48
  import { Segment } from "../core/segment.js";
49
49
  import { Style } from "../core/style.js";
50
- import { ColorSpec } from "../core/color.js";
51
50
  import { cellLen } from "../core/cells.js";
52
- import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
53
- import { WidgetBase } from "./widget-base.js";
51
+ import { ThemedWidget } from "./themed-widget.js";
52
+ import { ink } from "./ink.js";
54
53
  // onFg uses on-${accent} (WCAG-correct contrast, picked once at palette
55
54
  // build time) so the ON state stays readable on a full accent bg. Earlier
56
55
  // versions used text-${accent} here, which is mostly-accent and clashed
@@ -64,7 +63,7 @@ const VARIANT_KEYS = {
64
63
  danger: { onBg: "error", onFg: "on-error", offBg: "error-muted", offFg: "text-error" },
65
64
  };
66
65
  let Toggle = (() => {
67
- let _classSuper = WidgetBase;
66
+ let _classSuper = ThemedWidget;
68
67
  let _instanceExtraInitializers = [];
69
68
  let _label_decorators;
70
69
  let _label_initializers = [];
@@ -75,10 +74,6 @@ let Toggle = (() => {
75
74
  let _variant_decorators;
76
75
  let _variant_initializers = [];
77
76
  let _variant_extraInitializers = [];
78
- let __theme_decorators;
79
- let __theme_initializers = [];
80
- let __theme_extraInitializers = [];
81
- let _setTheme_decorators;
82
77
  let _handleKey_decorators;
83
78
  let _handleMouse_decorators;
84
79
  return class Toggle extends _classSuper {
@@ -87,15 +82,11 @@ let Toggle = (() => {
87
82
  _label_decorators = [observable];
88
83
  _on_decorators = [observable];
89
84
  _variant_decorators = [observableRef];
90
- __theme_decorators = [observableRef];
91
- _setTheme_decorators = [action];
92
85
  _handleKey_decorators = [action];
93
86
  _handleMouse_decorators = [action];
94
87
  __esDecorate(this, null, _label_decorators, { kind: "accessor", name: "label", static: false, private: false, access: { has: obj => "label" in obj, get: obj => obj.label, set: (obj, value) => { obj.label = value; } }, metadata: _metadata }, _label_initializers, _label_extraInitializers);
95
88
  __esDecorate(this, null, _on_decorators, { kind: "accessor", name: "on", static: false, private: false, access: { has: obj => "on" in obj, get: obj => obj.on, set: (obj, value) => { obj.on = value; } }, metadata: _metadata }, _on_initializers, _on_extraInitializers);
96
89
  __esDecorate(this, null, _variant_decorators, { kind: "accessor", name: "variant", static: false, private: false, access: { has: obj => "variant" in obj, get: obj => obj.variant, set: (obj, value) => { obj.variant = value; } }, metadata: _metadata }, _variant_initializers, _variant_extraInitializers);
97
- __esDecorate(this, null, __theme_decorators, { kind: "accessor", name: "_theme", static: false, private: false, access: { has: obj => "_theme" in obj, get: obj => obj._theme, set: (obj, value) => { obj._theme = value; } }, metadata: _metadata }, __theme_initializers, __theme_extraInitializers);
98
- __esDecorate(this, null, _setTheme_decorators, { kind: "method", name: "setTheme", static: false, private: false, access: { has: obj => "setTheme" in obj, get: obj => obj.setTheme }, metadata: _metadata }, null, _instanceExtraInitializers);
99
90
  __esDecorate(this, null, _handleKey_decorators, { kind: "method", name: "handleKey", static: false, private: false, access: { has: obj => "handleKey" in obj, get: obj => obj.handleKey }, metadata: _metadata }, null, _instanceExtraInitializers);
100
91
  __esDecorate(this, null, _handleMouse_decorators, { kind: "method", name: "handleMouse", static: false, private: false, access: { has: obj => "handleMouse" in obj, get: obj => obj.handleMouse }, metadata: _metadata }, null, _instanceExtraInitializers);
101
92
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -111,22 +102,15 @@ let Toggle = (() => {
111
102
  #variant_accessor_storage = (__runInitializers(this, _on_extraInitializers), __runInitializers(this, _variant_initializers, void 0));
112
103
  get variant() { return this.#variant_accessor_storage; }
113
104
  set variant(value) { this.#variant_accessor_storage = value; }
114
- #_theme_accessor_storage = (__runInitializers(this, _variant_extraInitializers), __runInitializers(this, __theme_initializers, void 0));
115
- // [LAW:types-are-the-program] @observableRef so setTheme() triggers a
116
- // re-render — see slider.ts.
117
- get _theme() { return this.#_theme_accessor_storage; }
118
- set _theme(value) { this.#_theme_accessor_storage = value; }
119
105
  constructor(options) {
120
- super();
121
- __runInitializers(this, __theme_extraInitializers);
106
+ super(options.theme);
107
+ __runInitializers(this, _variant_extraInitializers);
122
108
  this.id = options.id ?? `toggle-${options.label.toLowerCase().replace(/\s+/g, "-")}`;
123
109
  this.label = options.label;
124
110
  this.on = options.on ?? false;
125
111
  this.variant = options.variant ?? "default";
126
112
  this.disabled = options.disabled ?? false;
127
- this._theme = options.theme ?? DEFAULT_TERMINAL_THEME;
128
113
  }
129
- setTheme(theme) { this._theme = theme; }
130
114
  // --- Event handlers ---
131
115
  handleKey(event) {
132
116
  if (this.disabled)
@@ -157,24 +141,17 @@ let Toggle = (() => {
157
141
  const indicator = this.on ? "[ON] " : "[OFF]";
158
142
  const text = `${indicator} ${this.label}`;
159
143
  if (this.disabled) {
160
- return [new Segment(text, new Style({ color: "#666666", bgcolor: "#333333", dim: true }))];
144
+ return [new Segment(text, new Style(this.disabledInk))];
161
145
  }
162
146
  const keys = VARIANT_KEYS[this.variant];
163
- const fg = this.resolvePalette(this.on ? keys.onFg : keys.offFg);
164
- const bg = this.resolvePalette(this.on ? keys.onBg : keys.offBg);
165
- return [new Segment(text, new Style({ color: fg, bgcolor: bg, underline: this.focused }))];
147
+ const colors = this.on ? ink(this.theme, keys.onFg, keys.onBg) : ink(this.theme, keys.offFg, keys.offBg);
148
+ return [new Segment(text, new Style({ ...colors, underline: this.focused }))];
166
149
  }
167
150
  measure(_options) {
168
151
  // [LAW:one-source-of-truth] cellLen — see button.ts.
169
152
  const width = 5 + 1 + cellLen(this.label);
170
153
  return { minimum: width, maximum: width };
171
154
  }
172
- // --- Palette resolution ---
173
- resolvePalette(key) {
174
- const rgba = this._theme.palette.get(key);
175
- // [LAW:no-defensive-null-guards] palette must contain all keys; missing is a construction bug.
176
- return ColorSpec.fromRgba(rgba);
177
- }
178
155
  };
179
156
  })();
180
157
  export { Toggle };
@@ -8,21 +8,15 @@
8
8
  import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
9
9
  import type { Segment } from "../core/segment.js";
10
10
  import type { Unsubscribe } from "../core/subscription.js";
11
- export interface KeyEventInit {
12
- key: string;
13
- character: string;
14
- shift: boolean;
15
- ctrl: boolean;
16
- meta: boolean;
17
- }
18
- export declare class KeyEvent {
11
+ import type { KeyPress, ScreenMouseEvent } from "../host/input.js";
12
+ export declare class KeyEvent implements KeyPress {
19
13
  readonly key: string;
20
14
  readonly character: string;
21
15
  readonly shift: boolean;
22
16
  readonly ctrl: boolean;
23
17
  readonly meta: boolean;
24
18
  private _stopped;
25
- constructor(init: KeyEventInit);
19
+ constructor(init: KeyPress);
26
20
  get stopped(): boolean;
27
21
  stop(): void;
28
22
  }
@@ -30,21 +24,11 @@ export type KeyHandlerPriority = "high" | "normal";
30
24
  export interface KeyHandlerOptions {
31
25
  priority?: KeyHandlerPriority;
32
26
  }
33
- export type ScreenMouseEvent = {
34
- [T in MouseEventType]: MouseEventOf<T>;
35
- }[MouseEventType];
36
- type MouseEventType = "mouse_down" | "mouse_up" | "mouse_move" | "scroll_up" | "scroll_down";
37
- interface MouseEventOf<Type extends string> {
38
- type: Type;
39
- x: number;
40
- y: number;
41
- button: number;
42
- shift: boolean;
43
- ctrl: boolean;
44
- }
45
- export interface WidgetMouseEvent extends MouseEventOf<"mouse_down" | "mouse_up" | "mouse_move"> {
46
- over: boolean;
47
- }
27
+ export type WidgetMouseEvent = Extract<ScreenMouseEvent, {
28
+ type: "mouse_down" | "mouse_up" | "mouse_move";
29
+ }> & {
30
+ readonly over: boolean;
31
+ };
48
32
  export interface WidgetFocusEvent {
49
33
  type: "focus" | "blur";
50
34
  }
@@ -84,4 +68,3 @@ export interface FrameSource {
84
68
  readonly frame: readonly (readonly Segment[])[];
85
69
  refresh(): void;
86
70
  }
87
- export {};
@@ -5,6 +5,14 @@
5
5
  * producing Segment[] from current state. They have no knowledge of stdin,
6
6
  * terminal escape sequences, or their host environment.
7
7
  */
8
+ // --- Event types ---
9
+ // [LAW:types-are-the-program] KeyEvent is a `KeyPress` the app heard, plus
10
+ // the say over who else hears it. It carries a single mutable signal
11
+ // (`stopped`) that participants in the dispatch chain set by calling
12
+ // `stop()`. Treating it as an interface would force every handler to
13
+ // shuttle a return value upward and the router to interpret it — the
14
+ // class collapses that into one self-describing value flowing through
15
+ // the chain.
8
16
  export class KeyEvent {
9
17
  key;
10
18
  character;
@@ -20,12 +20,21 @@
20
20
  *
21
21
  * [LAW:locality-or-seam] `App` knows nothing of widgets — `host/` depends on
22
22
  * `core/` alone — and this reaches it only through the seams it already has:
23
- * the view is a renderable, and `onPaint` hears the frame.
23
+ * the view is a renderable, `onPaint` hears the frame, and `onKey`/`onMouse`
24
+ * hear the input.
24
25
  */
25
26
  import { Segment } from "../core/segment.js";
26
27
  import type { Unsubscribe } from "../core/subscription.js";
27
- import { type AppOptions, type AppPhase } from "../host/app.js";
28
- import type { FocusManager, FrameSource, KeyEvent, KeyHandlerOptions, ScreenMouseEvent } from "./types.js";
28
+ import { type AppOptions, type AppPhase, type Wheel } from "../host/app.js";
29
+ import type { FocusManager, FrameSource, KeyEvent, KeyHandlerOptions } from "./types.js";
30
+ import type { AppMouseEvent } from "../host/app.js";
31
+ export interface WidgetAppOptions extends AppOptions {
32
+ /**
33
+ * Whether a left-button press focuses the widget under it, before it hears the
34
+ * press. `true` unless given.
35
+ */
36
+ readonly focusOnClick?: boolean;
37
+ }
29
38
  export declare class WidgetApp implements FrameSource {
30
39
  readonly focusManager: FocusManager;
31
40
  private readonly focus;
@@ -33,7 +42,7 @@ export declare class WidgetApp implements FrameSource {
33
42
  private readonly router;
34
43
  private readonly reaction;
35
44
  private drawn;
36
- constructor(options: AppOptions);
45
+ constructor(options: WidgetAppOptions);
37
46
  get phase(): AppPhase;
38
47
  /** The rows on screen now, as painted: what a pointer event lands on. */
39
48
  get frame(): readonly (readonly Segment[])[];
@@ -51,13 +60,21 @@ export declare class WidgetApp implements FrameSource {
51
60
  */
52
61
  refresh(): void;
53
62
  suspend(): Promise<void>;
63
+ /** Paint at the app's rate until the returned function is called, as `App.animate` does. */
64
+ animate(): Unsubscribe;
54
65
  /**
55
- * Hear every key. A `high` handler hears it before the focused widget, a
66
+ * Hear every key the app does not quit on. A `high` handler hears it before the focused widget, a
56
67
  * `normal` one after it — and after Tab has moved focus — and a handler
57
68
  * that calls `event.stop()` ends the key there.
58
69
  */
59
70
  onKey(handler: (event: KeyEvent) => void, options?: KeyHandlerOptions): Unsubscribe;
60
- /** Hear every pointer event, in terminal cells, before any widget does. */
61
- onMouse(handler: (event: ScreenMouseEvent) => void): Unsubscribe;
71
+ /** Hear every pointer event but the vertical wheel, in terminal cells, before any widget does. */
72
+ onMouse(handler: (event: AppMouseEvent) => void): Unsubscribe;
73
+ /**
74
+ * Offer each wheel turn the viewport under the pointer did not move with to
75
+ * `handler`, as `App.onWheel` does: it returns whether it moved something,
76
+ * and a turn nothing moved with is passed outward.
77
+ */
78
+ onWheel(handler: (wheel: Wheel) => boolean): Unsubscribe;
62
79
  private compose;
63
80
  }
@@ -20,7 +20,8 @@
20
20
  *
21
21
  * [LAW:locality-or-seam] `App` knows nothing of widgets — `host/` depends on
22
22
  * `core/` alone — and this reaches it only through the seams it already has:
23
- * the view is a renderable, and `onPaint` hears the frame.
23
+ * the view is a renderable, `onPaint` hears the frame, and `onKey`/`onMouse`
24
+ * hear the input.
24
25
  */
25
26
  import { Reaction } from "mobx";
26
27
  import { Segment } from "../core/segment.js";
@@ -40,16 +41,23 @@ export class WidgetApp {
40
41
  // The widgets the view drew for the frame on screen, in document order.
41
42
  drawn = [];
42
43
  constructor(options) {
43
- const { view } = options;
44
+ const { view, focusOnClick = true } = options;
44
45
  this.reaction = new Reaction("WidgetApp", () => this.app.refresh());
45
- const root = { render: (renderOptions) => this.compose(view, renderOptions) };
46
- this.app = new App({ ...options, view: () => root });
46
+ this.app = new App({
47
+ ...options,
48
+ view: (t) => ({ render: (renderOptions) => this.compose(() => view(t), renderOptions) }),
49
+ });
47
50
  this.focus = new DefaultFocusManager(() => {
48
51
  const owners = ownersOn(this.app.frame);
49
52
  return this.drawn.filter((widget) => owners.has(widget));
50
53
  });
51
54
  this.focusManager = this.focus;
52
- this.router = new EventRouter({ source: this, host: options.host });
55
+ this.router = new EventRouter({ source: this, focusOnClick });
56
+ // [LAW:single-enforcer] The app reads the terminal; the router routes
57
+ // what it hears, after the app's quit keys.
58
+ this.app.onKey((press) => this.router.key(press));
59
+ this.app.onMouse((event) => this.router.mouse(event));
60
+ this.app.onWheel((wheel) => this.router.wheel(wheel));
53
61
  this.app.onPaint(() => {
54
62
  this.focus.settle(this.drawn);
55
63
  this.router.framePainted();
@@ -70,18 +78,14 @@ export class WidgetApp {
70
78
  * until the app stops — `App.run`, with the input.
71
79
  */
72
80
  async run() {
73
- // App refuses a second run; this one must not end the input of the first.
81
+ // App refuses a second run; this one must not end the state of the first.
74
82
  if (this.app.phase !== "idle")
75
83
  return this.app.run();
76
- // Listening before the app starts, so a first frame that throws — and
77
- // stops the host — is not followed by a subscription that restarts it.
78
- this.router.start();
79
- const running = this.app.run();
80
84
  try {
81
- await running;
85
+ await this.app.run();
82
86
  }
83
87
  finally {
84
- this.router.stop();
88
+ this.router.reset();
85
89
  this.reaction.dispose();
86
90
  }
87
91
  }
@@ -102,18 +106,30 @@ export class WidgetApp {
102
106
  suspend() {
103
107
  return this.app.suspend();
104
108
  }
109
+ /** Paint at the app's rate until the returned function is called, as `App.animate` does. */
110
+ animate() {
111
+ return this.app.animate();
112
+ }
105
113
  /**
106
- * Hear every key. A `high` handler hears it before the focused widget, a
114
+ * Hear every key the app does not quit on. A `high` handler hears it before the focused widget, a
107
115
  * `normal` one after it — and after Tab has moved focus — and a handler
108
116
  * that calls `event.stop()` ends the key there.
109
117
  */
110
118
  onKey(handler, options) {
111
119
  return this.router.onKey(handler, options);
112
120
  }
113
- /** Hear every pointer event, in terminal cells, before any widget does. */
121
+ /** Hear every pointer event but the vertical wheel, in terminal cells, before any widget does. */
114
122
  onMouse(handler) {
115
123
  return this.router.onMouse(handler);
116
124
  }
125
+ /**
126
+ * Offer each wheel turn the viewport under the pointer did not move with to
127
+ * `handler`, as `App.onWheel` does: it returns whether it moved something,
128
+ * and a turn nothing moved with is passed outward.
129
+ */
130
+ onWheel(handler) {
131
+ return this.app.onWheel(handler);
132
+ }
117
133
  compose(view, options) {
118
134
  const drawn = new Set();
119
135
  // [LAW:parse-dont-validate] An owner is any object; only a `WidgetBase`
@@ -176,12 +192,13 @@ function paintOverlay(lines, widget, options) {
176
192
  const at = footprintOf(lines, widget);
177
193
  if (!at)
178
194
  return;
179
- // An overlay floats over the frame; no region holds it.
180
- const overlay = widget.renderOverlay({ ...options, height: undefined });
195
+ // An overlay floats over the frame; no region holds it, and the cells right
196
+ // of its column are all it can reach.
197
+ const width = Math.max(0, options.maxWidth - at.x);
198
+ const overlay = widget.renderOverlay({ ...options, maxWidth: width, height: undefined });
181
199
  if (overlay === null)
182
200
  return;
183
- const width = Math.max(0, options.maxWidth - at.x);
184
- const rows = Segment.anchorLines(Segment.splitLines(overlay), widget, at.rows).map((line) => Segment.adjustLineLength(line, width, undefined, false));
201
+ const rows = Segment.anchorLines(Segment.splitLines(Segment.cropLines(overlay, width)), widget, at.rows);
185
202
  paintLines(lines, rows, at.x, at.y + at.rows);
186
203
  }
187
204
  // Paint `rows` into `lines` with its top-left cell at (x, y), growing `lines`
@@ -8,6 +8,7 @@
8
8
  * makeObservable call and subclasses add observables the same way.
9
9
  */
10
10
  import { Segment } from "../core/segment.js";
11
+ import { type Style } from "../core/style.js";
11
12
  import type { RenderOptions } from "../core/protocol.js";
12
13
  import type { InteractiveWidget, KeyEvent, WidgetMouseEvent, WidgetFocusEvent } from "./types.js";
13
14
  import type { Unsubscribe } from "../core/subscription.js";
@@ -32,6 +33,7 @@ export declare abstract class WidgetBase implements InteractiveWidget {
32
33
  protected emitChange(): void;
33
34
  protected emitSubmit(): void;
34
35
  render(options: RenderOptions): Iterable<Segment>;
36
+ protected get ground(): Style;
35
37
  protected abstract draw(options: RenderOptions): Iterable<Segment>;
36
38
  abstract measure(options: RenderOptions): {
37
39
  minimum: number;
@@ -43,6 +43,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
43
43
  };
44
44
  import { observable, action } from "mobx";
45
45
  import { Segment } from "../core/segment.js";
46
+ import { NULL_STYLE } from "../core/style.js";
46
47
  let WidgetBase = (() => {
47
48
  let _instanceExtraInitializers = [];
48
49
  let _focused_decorators;
@@ -150,16 +151,22 @@ let WidgetBase = (() => {
150
151
  // anchor, so stamping has to follow every text layout `draw` does.
151
152
  // Every row is padded to the widest, so the widget owns its whole rectangle
152
153
  // and a click past the end of a short row still reaches it; every row ends
153
- // in a newline, so a blank last row survives the next `splitLines`.
154
+ // in a newline, so a blank last row survives the next `splitLines`. The
155
+ // padding stands on `ground`, whatever the widget draws its own cells on.
154
156
  // Reporting itself before it draws is what puts a widget ahead of the
155
157
  // widgets nested in it in document order — the order focus traverses.
156
158
  render(options) {
157
159
  options.onDraw?.(this);
158
160
  const drawn = Segment.splitLines(this.draw(options));
159
161
  const [width] = Segment.getShape(drawn);
160
- const lines = Segment.anchorLines(drawn.map((line) => Segment.adjustLineLength(line, width)), this);
162
+ const lines = Segment.anchorLines(drawn.map((line) => Segment.adjustLineLength(line, width, this.ground)), this);
161
163
  return lines.flatMap((line) => [...line, Segment.line()]);
162
164
  }
165
+ // What a padded row stands on. None by default: the padding shows whatever
166
+ // the widget is drawn over, as the cells of content it did not draw do.
167
+ get ground() {
168
+ return NULL_STYLE;
169
+ }
163
170
  };
164
171
  })();
165
172
  export { WidgetBase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptctl/rich-js",
3
- "version": "0.18.1",
3
+ "version": "0.20.0",
4
4
  "description": "Rich text and beautiful formatting in the terminal — a TypeScript port of Python's Rich",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,25 +61,27 @@
61
61
  "build": "npm run clean && tsc",
62
62
  "test": "vitest run",
63
63
  "test:watch": "vitest",
64
+ "prelint": "node scripts/stage-repo-files.ts",
64
65
  "lint": "tsc --noEmit && tsc -p tsconfig.scripts.json && tsc -p tsconfig.test.json",
65
66
  "clean": "rm -rf dist",
66
- "docs:dev": "npm run demos:build && vitepress dev docs",
67
- "docs:build": "npm run demos:build && vitepress build docs",
67
+ "predocs:dev": "node scripts/stage-repo-files.ts",
68
+ "docs:dev": "vitepress dev docs",
69
+ "predocs:build": "node scripts/stage-repo-files.ts",
70
+ "docs:build": "vitepress build docs",
68
71
  "docs:preview": "vitepress preview docs",
69
- "demo": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-explore/index.js",
70
- "demo:build": "tsc -p tsconfig.demo.json",
71
- "sessions": "tsc -p tsconfig.demo.json && node dist-demo/examples/claude-sessions/index.js",
72
- "strip": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-strip/index.js",
73
- "viewport": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-viewport/index.js",
74
- "markup-plugins": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-markup-plugins/index.js",
75
- "demo-inputs": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-config/index.js",
76
- "demo:dropdown": "tsc -p tsconfig.demo.json && node dist-demo/examples/dropdown-demo/index.js",
77
- "dash": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-dash/index.js",
78
- "template-bindings": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-template-bindings/index.js",
79
- "themes-and-color-studio": "tsc -p tsconfig.demo.json && node dist-demo/examples/themes-and-color-studio/index.js",
80
- "harness:browser-host": "tsc -p tsconfig.demo.json && echo 'Serving harness at http://localhost:8080/examples/browser-terminal-host-harness/' && npx --yes http-server@14.1.1 -p 8080 -c-1 -s .",
81
- "demos:build": "tsc -p tsconfig.demo.json && vite build --config vite.config.demos.ts",
82
- "demos:preview": "vite preview --config vite.config.demos.ts",
72
+ "demo": "node scripts/build-demos.ts && node dist-demo/examples/rich-explore/node.js",
73
+ "demo:build": "node scripts/build-demos.ts",
74
+ "sessions": "node scripts/build-demos.ts && node dist-demo/examples/claude-sessions/node.js",
75
+ "strip": "node scripts/build-demos.ts && node dist-demo/examples/rich-strip/main.js",
76
+ "effects-feel": "node scripts/build-demos.ts && node dist-demo/examples/effects-feel/index.js",
77
+ "viewport": "node scripts/build-demos.ts && node dist-demo/examples/rich-viewport/main.js",
78
+ "markup-plugins": "node scripts/build-demos.ts && node dist-demo/examples/rich-markup-plugins/main.js",
79
+ "demo-inputs": "node scripts/build-demos.ts && node dist-demo/examples/rich-config/main.js",
80
+ "demo:dropdown": "node scripts/build-demos.ts && node dist-demo/examples/dropdown-demo/main.js",
81
+ "dash": "node scripts/build-demos.ts && node dist-demo/examples/rich-dash/node.js",
82
+ "template-bindings": "node scripts/build-demos.ts && node dist-demo/examples/rich-template-bindings/main.js",
83
+ "themes-and-color-studio": "node scripts/build-demos.ts && node dist-demo/examples/themes-and-color-studio/node.js",
84
+ "terminal-host": "node scripts/build-demos.ts && node dist-demo/examples/terminal-host/main.js",
83
85
  "test:demos": "playwright test"
84
86
  },
85
87
  "keywords": [
@@ -96,20 +98,32 @@
96
98
  "cli"
97
99
  ],
98
100
  "license": "MIT",
101
+ "devEngines": {
102
+ "runtime": { "name": "node", "version": "^22.18.0 || >=23.6.0", "onFail": "error" }
103
+ },
99
104
  "engines": {
100
105
  "node": ">=20"
101
106
  },
102
107
  "devDependencies": {
108
+ "@codemirror/commands": "^6.11.1",
109
+ "@codemirror/lang-javascript": "^6.2.5",
110
+ "@codemirror/language": "^6.12.4",
111
+ "@codemirror/state": "^6.7.6",
112
+ "@codemirror/view": "^6.43.13",
113
+ "@lezer/highlight": "^1.2.5",
103
114
  "@playwright/test": "^1.63.0",
104
115
  "@promptctl/go-template-js": "^0.10.0",
105
116
  "@types/markdown-it": "^14.2.0",
106
117
  "@types/node": "^26.5.1",
107
118
  "@types/semver": "^7.8.0",
119
+ "@xterm/headless": "^6.0.0",
108
120
  "markdown-it": "^14.3.2",
109
121
  "markdown-it-container": "^4.0.0",
110
122
  "mobx": "^7.0.3",
123
+ "node-inspect-extracted": "^3.3.3",
111
124
  "semver": "^7.8.5",
112
125
  "strip-ansi": "^7.2.0",
126
+ "sucrase": "^3.35.1",
113
127
  "typescript": "^6.0.3",
114
128
  "vite": "^8.3.0",
115
129
  "vitepress": "^1.6.4",