@promptctl/rich-js 0.18.0 → 0.18.1

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 (221) hide show
  1. package/dist/core/anchor.d.ts +45 -0
  2. package/dist/core/anchor.js +39 -0
  3. package/dist/core/ansi.d.ts +65 -0
  4. package/dist/core/ansi.js +231 -0
  5. package/dist/core/box.d.ts +143 -0
  6. package/dist/core/box.js +379 -0
  7. package/dist/core/cells.d.ts +159 -0
  8. package/dist/core/cells.js +287 -0
  9. package/dist/core/color.d.ts +246 -0
  10. package/dist/core/color.js +1038 -0
  11. package/dist/core/console.d.ts +165 -0
  12. package/dist/core/console.js +527 -0
  13. package/dist/core/emoji.d.ts +24 -0
  14. package/dist/core/emoji.js +391 -0
  15. package/dist/core/export-html.d.ts +59 -0
  16. package/dist/core/export-html.js +200 -0
  17. package/dist/core/export-lines.d.ts +121 -0
  18. package/dist/core/export-lines.js +141 -0
  19. package/dist/core/highlighter.d.ts +33 -0
  20. package/dist/core/highlighter.js +124 -0
  21. package/dist/core/json.d.ts +27 -0
  22. package/dist/core/json.js +53 -0
  23. package/dist/core/markup.d.ts +119 -0
  24. package/dist/core/markup.js +731 -0
  25. package/dist/core/measure.d.ts +27 -0
  26. package/dist/core/measure.js +62 -0
  27. package/dist/core/oklch.d.ts +161 -0
  28. package/dist/core/oklch.js +312 -0
  29. package/dist/core/osc8.d.ts +68 -0
  30. package/dist/core/osc8.js +90 -0
  31. package/dist/core/place.d.ts +30 -0
  32. package/dist/core/place.js +51 -0
  33. package/dist/core/pretty.d.ts +144 -0
  34. package/dist/core/pretty.js +476 -0
  35. package/dist/core/protocol.d.ts +267 -0
  36. package/dist/core/protocol.js +163 -0
  37. package/dist/core/render.d.ts +84 -0
  38. package/dist/core/render.js +145 -0
  39. package/dist/core/segment.d.ts +161 -0
  40. package/dist/core/segment.js +492 -0
  41. package/dist/core/spinnerData.d.ts +12 -0
  42. package/dist/core/spinnerData.js +117 -0
  43. package/dist/core/strip.d.ts +141 -0
  44. package/dist/core/strip.js +305 -0
  45. package/dist/core/style.d.ts +169 -0
  46. package/dist/core/style.js +695 -0
  47. package/dist/core/subscription.d.ts +12 -0
  48. package/dist/core/subscription.js +12 -0
  49. package/dist/core/text.d.ts +256 -0
  50. package/dist/core/text.js +976 -0
  51. package/dist/core/wrap.d.ts +41 -0
  52. package/dist/core/wrap.js +127 -0
  53. package/dist/host/app.d.ts +121 -0
  54. package/dist/host/app.js +255 -0
  55. package/dist/host/host-environment.d.ts +26 -0
  56. package/dist/host/host-environment.js +44 -0
  57. package/dist/host/index.d.ts +5 -0
  58. package/dist/host/index.js +3 -0
  59. package/dist/host/terminal-host.d.ts +179 -0
  60. package/dist/host/terminal-host.js +186 -0
  61. package/dist/index.d.ts +91 -0
  62. package/dist/index.js +107 -0
  63. package/dist/node/prompt.d.ts +21 -0
  64. package/dist/node/prompt.js +30 -0
  65. package/dist/node/save.d.ts +17 -0
  66. package/dist/node/save.js +21 -0
  67. package/dist/node/terminal-host.d.ts +113 -0
  68. package/dist/node/terminal-host.js +227 -0
  69. package/dist/node/traceback.d.ts +37 -0
  70. package/dist/node/traceback.js +134 -0
  71. package/dist/renderables/align.d.ts +16 -0
  72. package/dist/renderables/align.js +33 -0
  73. package/dist/renderables/columns.d.ts +61 -0
  74. package/dist/renderables/columns.js +152 -0
  75. package/dist/renderables/constrain.d.ts +15 -0
  76. package/dist/renderables/constrain.js +37 -0
  77. package/dist/renderables/embed.d.ts +50 -0
  78. package/dist/renderables/embed.js +71 -0
  79. package/dist/renderables/flexStrip.d.ts +41 -0
  80. package/dist/renderables/flexStrip.js +200 -0
  81. package/dist/renderables/group.d.ts +11 -0
  82. package/dist/renderables/group.js +17 -0
  83. package/dist/renderables/layout.d.ts +92 -0
  84. package/dist/renderables/layout.js +312 -0
  85. package/dist/renderables/live.d.ts +50 -0
  86. package/dist/renderables/live.js +175 -0
  87. package/dist/renderables/markdown.d.ts +24 -0
  88. package/dist/renderables/markdown.js +184 -0
  89. package/dist/renderables/padding.d.ts +38 -0
  90. package/dist/renderables/padding.js +141 -0
  91. package/dist/renderables/panel.d.ts +116 -0
  92. package/dist/renderables/panel.js +307 -0
  93. package/dist/renderables/progress.d.ts +90 -0
  94. package/dist/renderables/progress.js +238 -0
  95. package/dist/renderables/progressBar.d.ts +31 -0
  96. package/dist/renderables/progressBar.js +53 -0
  97. package/dist/renderables/prompt.d.ts +49 -0
  98. package/dist/renderables/prompt.js +125 -0
  99. package/dist/renderables/rule.d.ts +25 -0
  100. package/dist/renderables/rule.js +90 -0
  101. package/dist/renderables/spinner.d.ts +31 -0
  102. package/dist/renderables/spinner.js +70 -0
  103. package/dist/renderables/status.d.ts +23 -0
  104. package/dist/renderables/status.js +60 -0
  105. package/dist/renderables/syntax.d.ts +35 -0
  106. package/dist/renderables/syntax.js +115 -0
  107. package/dist/renderables/table.d.ts +194 -0
  108. package/dist/renderables/table.js +795 -0
  109. package/dist/renderables/traceback.d.ts +31 -0
  110. package/dist/renderables/traceback.js +108 -0
  111. package/dist/renderables/tree.d.ts +68 -0
  112. package/dist/renderables/tree.js +150 -0
  113. package/dist/renderables/viewport.d.ts +114 -0
  114. package/dist/renderables/viewport.js +198 -0
  115. package/dist/template-bindings/color-funcs.d.ts +87 -0
  116. package/dist/template-bindings/color-funcs.js +217 -0
  117. package/dist/template-bindings/helpers.d.ts +24 -0
  118. package/dist/template-bindings/helpers.js +48 -0
  119. package/dist/template-bindings/index.d.ts +107 -0
  120. package/dist/template-bindings/index.js +146 -0
  121. package/dist/template-bindings/palette-funcs.d.ts +94 -0
  122. package/dist/template-bindings/palette-funcs.js +141 -0
  123. package/dist/template-bindings/style-funcs.d.ts +28 -0
  124. package/dist/template-bindings/style-funcs.js +155 -0
  125. package/dist/themes/buildPalette.d.ts +36 -0
  126. package/dist/themes/buildPalette.js +56 -0
  127. package/dist/themes/colorMath.d.ts +82 -0
  128. package/dist/themes/colorMath.js +254 -0
  129. package/dist/themes/colorRef.d.ts +51 -0
  130. package/dist/themes/colorRef.js +96 -0
  131. package/dist/themes/data/ansi-tables.d.ts +14 -0
  132. package/dist/themes/data/ansi-tables.js +49 -0
  133. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  134. package/dist/themes/data/atom-one-dark.js +177 -0
  135. package/dist/themes/data/atom-one-light.d.ts +3 -0
  136. package/dist/themes/data/atom-one-light.js +177 -0
  137. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  138. package/dist/themes/data/catppuccin-frappe.js +177 -0
  139. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  140. package/dist/themes/data/catppuccin-latte.js +177 -0
  141. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  142. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  143. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  144. package/dist/themes/data/catppuccin-mocha.js +177 -0
  145. package/dist/themes/data/cyberpunk.d.ts +17 -0
  146. package/dist/themes/data/cyberpunk.js +193 -0
  147. package/dist/themes/data/default.d.ts +3 -0
  148. package/dist/themes/data/default.js +160 -0
  149. package/dist/themes/data/dracula.d.ts +3 -0
  150. package/dist/themes/data/dracula.js +177 -0
  151. package/dist/themes/data/flexoki.d.ts +3 -0
  152. package/dist/themes/data/flexoki.js +177 -0
  153. package/dist/themes/data/gruvbox.d.ts +3 -0
  154. package/dist/themes/data/gruvbox.js +177 -0
  155. package/dist/themes/data/index.d.ts +35 -0
  156. package/dist/themes/data/index.js +64 -0
  157. package/dist/themes/data/monokai.d.ts +3 -0
  158. package/dist/themes/data/monokai.js +177 -0
  159. package/dist/themes/data/nord.d.ts +3 -0
  160. package/dist/themes/data/nord.js +177 -0
  161. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  162. package/dist/themes/data/rose-pine-dawn.js +177 -0
  163. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  164. package/dist/themes/data/rose-pine-moon.js +177 -0
  165. package/dist/themes/data/rose-pine.d.ts +3 -0
  166. package/dist/themes/data/rose-pine.js +177 -0
  167. package/dist/themes/data/solarized-dark.d.ts +3 -0
  168. package/dist/themes/data/solarized-dark.js +177 -0
  169. package/dist/themes/data/solarized-light.d.ts +3 -0
  170. package/dist/themes/data/solarized-light.js +177 -0
  171. package/dist/themes/data/svg-export.d.ts +3 -0
  172. package/dist/themes/data/svg-export.js +177 -0
  173. package/dist/themes/data/textual-ansi.d.ts +3 -0
  174. package/dist/themes/data/textual-ansi.js +160 -0
  175. package/dist/themes/data/textual-dark.d.ts +3 -0
  176. package/dist/themes/data/textual-dark.js +160 -0
  177. package/dist/themes/data/textual-light.d.ts +3 -0
  178. package/dist/themes/data/textual-light.js +160 -0
  179. package/dist/themes/data/tokyo-night.d.ts +3 -0
  180. package/dist/themes/data/tokyo-night.js +177 -0
  181. package/dist/themes/data/types.d.ts +31 -0
  182. package/dist/themes/data/types.js +10 -0
  183. package/dist/themes/palette.d.ts +21 -0
  184. package/dist/themes/palette.js +28 -0
  185. package/dist/themes/ramp.d.ts +83 -0
  186. package/dist/themes/ramp.js +122 -0
  187. package/dist/themes/registry.d.ts +44 -0
  188. package/dist/themes/registry.js +94 -0
  189. package/dist/themes/terminalThemes.d.ts +49 -0
  190. package/dist/themes/terminalThemes.js +63 -0
  191. package/dist/themes/transpose.d.ts +87 -0
  192. package/dist/themes/transpose.js +138 -0
  193. package/dist/widgets/button.d.ts +49 -0
  194. package/dist/widgets/button.js +208 -0
  195. package/dist/widgets/checkbox.d.ts +41 -0
  196. package/dist/widgets/checkbox.js +157 -0
  197. package/dist/widgets/dropdown.d.ts +67 -0
  198. package/dist/widgets/dropdown.js +450 -0
  199. package/dist/widgets/event-router.d.ts +71 -0
  200. package/dist/widgets/event-router.js +629 -0
  201. package/dist/widgets/focus-manager.d.ts +45 -0
  202. package/dist/widgets/focus-manager.js +191 -0
  203. package/dist/widgets/hit.d.ts +64 -0
  204. package/dist/widgets/hit.js +113 -0
  205. package/dist/widgets/index.d.ts +20 -0
  206. package/dist/widgets/index.js +14 -0
  207. package/dist/widgets/slider.d.ts +57 -0
  208. package/dist/widgets/slider.js +270 -0
  209. package/dist/widgets/static-item.d.ts +34 -0
  210. package/dist/widgets/static-item.js +54 -0
  211. package/dist/widgets/text-input.d.ts +274 -0
  212. package/dist/widgets/text-input.js +1165 -0
  213. package/dist/widgets/toggle.d.ts +44 -0
  214. package/dist/widgets/toggle.js +180 -0
  215. package/dist/widgets/types.d.ts +87 -0
  216. package/dist/widgets/types.js +30 -0
  217. package/dist/widgets/widget-app.d.ts +63 -0
  218. package/dist/widgets/widget-app.js +199 -0
  219. package/dist/widgets/widget-base.d.ts +40 -0
  220. package/dist/widgets/widget-base.js +165 -0
  221. package/package.json +6 -2
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Toggle widget — on/off switch with label and variant colour.
3
+ * [LAW:dataflow-not-control-flow] one Segment of fixed width every render;
4
+ * indicator and palette keys come from observable state via lookup tables.
5
+ * [LAW:one-type-per-behavior] shared infrastructure inherited from WidgetBase.
6
+ *
7
+ * Visual states:
8
+ * off — "[OFF] label" with muted variant background
9
+ * on — "[ON] label" with full variant accent background
10
+ * focused — underline on the segment (no width change)
11
+ * disabled — dimmed
12
+ */
13
+ import { Segment } from "../core/segment.js";
14
+ import type { RenderOptions } from "../core/protocol.js";
15
+ import type { TerminalTheme } from "../core/color.js";
16
+ import { WidgetBase } from "./widget-base.js";
17
+ import type { KeyEvent, WidgetMouseEvent } from "./types.js";
18
+ export type ToggleVariant = "default" | "primary" | "success" | "warning" | "danger";
19
+ export interface ToggleOptions {
20
+ label: string;
21
+ on?: boolean;
22
+ id?: string;
23
+ disabled?: boolean;
24
+ theme?: TerminalTheme;
25
+ variant?: ToggleVariant;
26
+ }
27
+ export declare class Toggle extends WidgetBase {
28
+ readonly id: string;
29
+ readonly focusable = true;
30
+ accessor label: string;
31
+ accessor on: boolean;
32
+ accessor variant: ToggleVariant;
33
+ private accessor _theme;
34
+ constructor(options: ToggleOptions);
35
+ setTheme(theme: TerminalTheme): void;
36
+ handleKey(event: KeyEvent): void;
37
+ handleMouse(event: WidgetMouseEvent): void;
38
+ protected draw(_options: RenderOptions): Iterable<Segment>;
39
+ measure(_options: RenderOptions): {
40
+ minimum: number;
41
+ maximum: number;
42
+ };
43
+ private resolvePalette;
44
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Toggle widget — on/off switch with label and variant colour.
3
+ * [LAW:dataflow-not-control-flow] one Segment of fixed width every render;
4
+ * indicator and palette keys come from observable state via lookup tables.
5
+ * [LAW:one-type-per-behavior] shared infrastructure inherited from WidgetBase.
6
+ *
7
+ * Visual states:
8
+ * off — "[OFF] label" with muted variant background
9
+ * on — "[ON] label" with full variant accent background
10
+ * focused — underline on the segment (no width change)
11
+ * disabled — dimmed
12
+ */
13
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
14
+ var useValue = arguments.length > 2;
15
+ for (var i = 0; i < initializers.length; i++) {
16
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
17
+ }
18
+ return useValue ? value : void 0;
19
+ };
20
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
21
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
22
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
23
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
24
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
25
+ var _, done = false;
26
+ for (var i = decorators.length - 1; i >= 0; i--) {
27
+ var context = {};
28
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
29
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
30
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
31
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
32
+ if (kind === "accessor") {
33
+ if (result === void 0) continue;
34
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
35
+ if (_ = accept(result.get)) descriptor.get = _;
36
+ if (_ = accept(result.set)) descriptor.set = _;
37
+ if (_ = accept(result.init)) initializers.unshift(_);
38
+ }
39
+ else if (_ = accept(result)) {
40
+ if (kind === "field") initializers.unshift(_);
41
+ else descriptor[key] = _;
42
+ }
43
+ }
44
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
45
+ done = true;
46
+ };
47
+ import { observable, action, observableRef } from "mobx";
48
+ import { Segment } from "../core/segment.js";
49
+ import { Style } from "../core/style.js";
50
+ import { ColorSpec } from "../core/color.js";
51
+ import { cellLen } from "../core/cells.js";
52
+ import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
53
+ import { WidgetBase } from "./widget-base.js";
54
+ // onFg uses on-${accent} (WCAG-correct contrast, picked once at palette
55
+ // build time) so the ON state stays readable on a full accent bg. Earlier
56
+ // versions used text-${accent} here, which is mostly-accent and clashed
57
+ // with the same accent as background. offFg keeps text-${accent} because
58
+ // offBg is mostly-bg-tinted and the contrast holds.
59
+ const VARIANT_KEYS = {
60
+ default: { onBg: "primary", onFg: "on-primary", offBg: "surface", offFg: "foreground" },
61
+ primary: { onBg: "primary", onFg: "on-primary", offBg: "primary-muted", offFg: "text-primary" },
62
+ success: { onBg: "success", onFg: "on-success", offBg: "success-muted", offFg: "text-success" },
63
+ warning: { onBg: "warning", onFg: "on-warning", offBg: "warning-muted", offFg: "text-warning" },
64
+ danger: { onBg: "error", onFg: "on-error", offBg: "error-muted", offFg: "text-error" },
65
+ };
66
+ let Toggle = (() => {
67
+ let _classSuper = WidgetBase;
68
+ let _instanceExtraInitializers = [];
69
+ let _label_decorators;
70
+ let _label_initializers = [];
71
+ let _label_extraInitializers = [];
72
+ let _on_decorators;
73
+ let _on_initializers = [];
74
+ let _on_extraInitializers = [];
75
+ let _variant_decorators;
76
+ let _variant_initializers = [];
77
+ let _variant_extraInitializers = [];
78
+ let __theme_decorators;
79
+ let __theme_initializers = [];
80
+ let __theme_extraInitializers = [];
81
+ let _setTheme_decorators;
82
+ let _handleKey_decorators;
83
+ let _handleMouse_decorators;
84
+ return class Toggle extends _classSuper {
85
+ static {
86
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
87
+ _label_decorators = [observable];
88
+ _on_decorators = [observable];
89
+ _variant_decorators = [observableRef];
90
+ __theme_decorators = [observableRef];
91
+ _setTheme_decorators = [action];
92
+ _handleKey_decorators = [action];
93
+ _handleMouse_decorators = [action];
94
+ __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
+ __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
+ __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
+ __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
+ __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
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
102
+ }
103
+ id = __runInitializers(this, _instanceExtraInitializers);
104
+ focusable = true;
105
+ #label_accessor_storage = __runInitializers(this, _label_initializers, void 0);
106
+ get label() { return this.#label_accessor_storage; }
107
+ set label(value) { this.#label_accessor_storage = value; }
108
+ #on_accessor_storage = (__runInitializers(this, _label_extraInitializers), __runInitializers(this, _on_initializers, void 0));
109
+ get on() { return this.#on_accessor_storage; }
110
+ set on(value) { this.#on_accessor_storage = value; }
111
+ #variant_accessor_storage = (__runInitializers(this, _on_extraInitializers), __runInitializers(this, _variant_initializers, void 0));
112
+ get variant() { return this.#variant_accessor_storage; }
113
+ 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
+ constructor(options) {
120
+ super();
121
+ __runInitializers(this, __theme_extraInitializers);
122
+ this.id = options.id ?? `toggle-${options.label.toLowerCase().replace(/\s+/g, "-")}`;
123
+ this.label = options.label;
124
+ this.on = options.on ?? false;
125
+ this.variant = options.variant ?? "default";
126
+ this.disabled = options.disabled ?? false;
127
+ this._theme = options.theme ?? DEFAULT_TERMINAL_THEME;
128
+ }
129
+ setTheme(theme) { this._theme = theme; }
130
+ // --- Event handlers ---
131
+ handleKey(event) {
132
+ if (this.disabled)
133
+ return;
134
+ if (event.key === "space") {
135
+ this.on = !this.on;
136
+ this.emitChange();
137
+ event.stop();
138
+ return;
139
+ }
140
+ if (event.key === "enter") {
141
+ this.emitSubmit();
142
+ event.stop();
143
+ }
144
+ }
145
+ handleMouse(event) {
146
+ if (this.disabled)
147
+ return;
148
+ // A release that dragged off the toggle cancels the press.
149
+ if (event.type === "mouse_up" && event.over) {
150
+ this.on = !this.on;
151
+ this.emitChange();
152
+ }
153
+ }
154
+ // --- Rendering ---
155
+ draw(_options) {
156
+ // Both indicators are exactly 5 cells: "[ON] " and "[OFF]".
157
+ const indicator = this.on ? "[ON] " : "[OFF]";
158
+ const text = `${indicator} ${this.label}`;
159
+ if (this.disabled) {
160
+ return [new Segment(text, new Style({ color: "#666666", bgcolor: "#333333", dim: true }))];
161
+ }
162
+ 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 }))];
166
+ }
167
+ measure(_options) {
168
+ // [LAW:one-source-of-truth] cellLen — see button.ts.
169
+ const width = 5 + 1 + cellLen(this.label);
170
+ return { minimum: width, maximum: width };
171
+ }
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
+ };
179
+ })();
180
+ export { Toggle };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Interactive widget event types and core interfaces.
3
+ * [LAW:one-source-of-truth] These types are the single authority for the widget contract.
4
+ * Widgets are MobX-observable state machines that implement Renderable,
5
+ * producing Segment[] from current state. They have no knowledge of stdin,
6
+ * terminal escape sequences, or their host environment.
7
+ */
8
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
9
+ import type { Segment } from "../core/segment.js";
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 {
19
+ readonly key: string;
20
+ readonly character: string;
21
+ readonly shift: boolean;
22
+ readonly ctrl: boolean;
23
+ readonly meta: boolean;
24
+ private _stopped;
25
+ constructor(init: KeyEventInit);
26
+ get stopped(): boolean;
27
+ stop(): void;
28
+ }
29
+ export type KeyHandlerPriority = "high" | "normal";
30
+ export interface KeyHandlerOptions {
31
+ priority?: KeyHandlerPriority;
32
+ }
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
+ }
48
+ export interface WidgetFocusEvent {
49
+ type: "focus" | "blur";
50
+ }
51
+ export interface InteractiveWidget extends Renderable, Measurable {
52
+ readonly id: string;
53
+ readonly focusable: boolean;
54
+ focused: boolean;
55
+ hovered: boolean;
56
+ active: boolean;
57
+ disabled: boolean;
58
+ handleKey(event: KeyEvent): void;
59
+ handleMouse(event: WidgetMouseEvent): void;
60
+ handleFocus(event: WidgetFocusEvent): void;
61
+ focus(): void;
62
+ blur(): void;
63
+ setDisabled(value: boolean): void;
64
+ setHovered(value: boolean): void;
65
+ onChange(handler: (widget: InteractiveWidget) => void): Unsubscribe;
66
+ onSubmit(handler: (widget: InteractiveWidget) => void): Unsubscribe;
67
+ }
68
+ export interface OverlayRenderable {
69
+ renderOverlay(options: RenderOptions): Iterable<Segment> | null;
70
+ }
71
+ export declare function hasOverlay(value: object): value is OverlayRenderable;
72
+ export interface FocusManager {
73
+ readonly current: InteractiveWidget | null;
74
+ /** The widgets Tab moves between, in the order it moves. */
75
+ readonly widgets: readonly InteractiveWidget[];
76
+ next(): void;
77
+ prev(): void;
78
+ focus(widget: InteractiveWidget): void;
79
+ handleKey(event: KeyEvent): void;
80
+ onChange(handler: (current: InteractiveWidget | null) => void): Unsubscribe;
81
+ }
82
+ export interface FrameSource {
83
+ readonly focusManager: FocusManager;
84
+ readonly frame: readonly (readonly Segment[])[];
85
+ refresh(): void;
86
+ }
87
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Interactive widget event types and core interfaces.
3
+ * [LAW:one-source-of-truth] These types are the single authority for the widget contract.
4
+ * Widgets are MobX-observable state machines that implement Renderable,
5
+ * producing Segment[] from current state. They have no knowledge of stdin,
6
+ * terminal escape sequences, or their host environment.
7
+ */
8
+ export class KeyEvent {
9
+ key;
10
+ character;
11
+ shift;
12
+ ctrl;
13
+ meta;
14
+ _stopped = false;
15
+ constructor(init) {
16
+ this.key = init.key;
17
+ this.character = init.character;
18
+ this.shift = init.shift;
19
+ this.ctrl = init.ctrl;
20
+ this.meta = init.meta;
21
+ }
22
+ get stopped() { return this._stopped; }
23
+ // Claim this key. Halts further chain dispatch — no high/normal handler
24
+ // and no focused-widget handler downstream of the caller will see it.
25
+ stop() { this._stopped = true; }
26
+ }
27
+ export function hasOverlay(value) {
28
+ return ("renderOverlay" in value &&
29
+ typeof value.renderOverlay === "function");
30
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * WidgetApp — an `App` whose view has widgets in it: keys reach the focused
3
+ * widget, the pointer reaches the widget that drew the cell under it, and a
4
+ * change to anything a frame read paints the next one.
5
+ *
6
+ * A widget goes wherever a renderable goes — in a `Panel`, a `Layout` pane, a
7
+ * `Table` cell — and nothing lays it out a second time. Everything added here
8
+ * reads the frame the view drew:
9
+ *
10
+ * - Focus moves in document order: the order widgets start to render in
11
+ * (`RenderOptions.onDraw`), among those with a cell on the frame on screen.
12
+ * Not reading order — in a `Layout` split into columns, reading order
13
+ * zigzags between the columns row by row.
14
+ * - Overlays are painted over the view's frame, in document order, directly
15
+ * below their owner's footprint (`OverlayRenderable`). Paint order is the
16
+ * z-order: a later overlay covers an earlier one.
17
+ * - A pointer event goes to the innermost widget that drew the cell under it
18
+ * (`widgetAt`), in that widget's own coordinates; the wheel goes to the
19
+ * innermost `Viewport` that can move (`scrollTargetAt`).
20
+ *
21
+ * [LAW:locality-or-seam] `App` knows nothing of widgets — `host/` depends on
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.
24
+ */
25
+ import { Segment } from "../core/segment.js";
26
+ 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";
29
+ export declare class WidgetApp implements FrameSource {
30
+ readonly focusManager: FocusManager;
31
+ private readonly focus;
32
+ private readonly app;
33
+ private readonly router;
34
+ private readonly reaction;
35
+ private drawn;
36
+ constructor(options: AppOptions);
37
+ get phase(): AppPhase;
38
+ /** The rows on screen now, as painted: what a pointer event lands on. */
39
+ get frame(): readonly (readonly Segment[])[];
40
+ /**
41
+ * Take the terminal, paint the first frame, and route input to its widgets
42
+ * until the app stops — `App.run`, with the input.
43
+ */
44
+ run(): Promise<void>;
45
+ stop(): void;
46
+ fail(error: unknown): void;
47
+ /**
48
+ * Paint the next frame, as `App.refresh` does. A change to an observable
49
+ * the view read needs none; one no observable carries — a `Viewport`
50
+ * scrolled from a key handler — does.
51
+ */
52
+ refresh(): void;
53
+ suspend(): Promise<void>;
54
+ /**
55
+ * Hear every key. A `high` handler hears it before the focused widget, a
56
+ * `normal` one after it — and after Tab has moved focus — and a handler
57
+ * that calls `event.stop()` ends the key there.
58
+ */
59
+ 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;
62
+ private compose;
63
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * WidgetApp — an `App` whose view has widgets in it: keys reach the focused
3
+ * widget, the pointer reaches the widget that drew the cell under it, and a
4
+ * change to anything a frame read paints the next one.
5
+ *
6
+ * A widget goes wherever a renderable goes — in a `Panel`, a `Layout` pane, a
7
+ * `Table` cell — and nothing lays it out a second time. Everything added here
8
+ * reads the frame the view drew:
9
+ *
10
+ * - Focus moves in document order: the order widgets start to render in
11
+ * (`RenderOptions.onDraw`), among those with a cell on the frame on screen.
12
+ * Not reading order — in a `Layout` split into columns, reading order
13
+ * zigzags between the columns row by row.
14
+ * - Overlays are painted over the view's frame, in document order, directly
15
+ * below their owner's footprint (`OverlayRenderable`). Paint order is the
16
+ * z-order: a later overlay covers an earlier one.
17
+ * - A pointer event goes to the innermost widget that drew the cell under it
18
+ * (`widgetAt`), in that widget's own coordinates; the wheel goes to the
19
+ * innermost `Viewport` that can move (`scrollTargetAt`).
20
+ *
21
+ * [LAW:locality-or-seam] `App` knows nothing of widgets — `host/` depends on
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.
24
+ */
25
+ import { Reaction } from "mobx";
26
+ import { Segment } from "../core/segment.js";
27
+ import { App } from "../host/app.js";
28
+ import { DefaultFocusManager } from "./focus-manager.js";
29
+ import { EventRouter } from "./event-router.js";
30
+ import { footprintOf, ownersOn } from "./hit.js";
31
+ import { hasOverlay } from "./types.js";
32
+ import { WidgetBase } from "./widget-base.js";
33
+ export class WidgetApp {
34
+ focusManager;
35
+ focus;
36
+ app;
37
+ router;
38
+ // Paints the next frame when anything the last one read changes.
39
+ reaction;
40
+ // The widgets the view drew for the frame on screen, in document order.
41
+ drawn = [];
42
+ constructor(options) {
43
+ const { view } = options;
44
+ 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 });
47
+ this.focus = new DefaultFocusManager(() => {
48
+ const owners = ownersOn(this.app.frame);
49
+ return this.drawn.filter((widget) => owners.has(widget));
50
+ });
51
+ this.focusManager = this.focus;
52
+ this.router = new EventRouter({ source: this, host: options.host });
53
+ this.app.onPaint(() => {
54
+ this.focus.settle(this.drawn);
55
+ this.router.framePainted();
56
+ });
57
+ // [LAW:single-enforcer] `settle` judges every focus move, so every move
58
+ // paints — including one onto a widget no frame has read yet.
59
+ this.focus.onChange(() => this.app.refresh());
60
+ }
61
+ get phase() {
62
+ return this.app.phase;
63
+ }
64
+ /** The rows on screen now, as painted: what a pointer event lands on. */
65
+ get frame() {
66
+ return this.app.frame;
67
+ }
68
+ /**
69
+ * Take the terminal, paint the first frame, and route input to its widgets
70
+ * until the app stops — `App.run`, with the input.
71
+ */
72
+ async run() {
73
+ // App refuses a second run; this one must not end the input of the first.
74
+ if (this.app.phase !== "idle")
75
+ 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
+ try {
81
+ await running;
82
+ }
83
+ finally {
84
+ this.router.stop();
85
+ this.reaction.dispose();
86
+ }
87
+ }
88
+ stop() {
89
+ this.app.stop();
90
+ }
91
+ fail(error) {
92
+ this.app.fail(error);
93
+ }
94
+ /**
95
+ * Paint the next frame, as `App.refresh` does. A change to an observable
96
+ * the view read needs none; one no observable carries — a `Viewport`
97
+ * scrolled from a key handler — does.
98
+ */
99
+ refresh() {
100
+ this.app.refresh();
101
+ }
102
+ suspend() {
103
+ return this.app.suspend();
104
+ }
105
+ /**
106
+ * Hear every key. A `high` handler hears it before the focused widget, a
107
+ * `normal` one after it — and after Tab has moved focus — and a handler
108
+ * that calls `event.stop()` ends the key there.
109
+ */
110
+ onKey(handler, options) {
111
+ return this.router.onKey(handler, options);
112
+ }
113
+ /** Hear every pointer event, in terminal cells, before any widget does. */
114
+ onMouse(handler) {
115
+ return this.router.onMouse(handler);
116
+ }
117
+ compose(view, options) {
118
+ const drawn = new Set();
119
+ // [LAW:parse-dont-validate] An owner is any object; only a `WidgetBase`
120
+ // reports itself as a widget, so this is where `object` becomes one.
121
+ const onDraw = (owner) => {
122
+ if (owner instanceof WidgetBase)
123
+ drawn.add(owner);
124
+ };
125
+ // The view, its render and the overlays are all tracked, so an observable
126
+ // any of them reads — a label, `focused`, the state that picks what the
127
+ // view shows — paints the next frame when it changes.
128
+ // An overlay reports what it draws too: a widget in it is on the frame,
129
+ // and visiting `drawn` in insertion order paints its overlay in turn.
130
+ const render = { ...options, onDraw };
131
+ const lines = tracked(this.reaction, () => {
132
+ const base = Segment.splitLines(view().render(render));
133
+ for (const widget of drawn)
134
+ paintOverlay(base, widget, render);
135
+ return base;
136
+ });
137
+ this.drawn = [...drawn];
138
+ return lines.flatMap((line) => [...line, Segment.line()]);
139
+ }
140
+ }
141
+ /**
142
+ * `fn`, with every observable it reads subscribed to by `reaction`.
143
+ *
144
+ * [LAW:no-silent-failure] mobx reports an error thrown inside `track` and
145
+ * carries on, which would paint an empty frame for a view that threw. The
146
+ * error is carried out instead, and fails the app as `App` fails on any frame
147
+ * that throws.
148
+ */
149
+ function tracked(reaction, fn) {
150
+ // Written inside `track`, which the compiler cannot see run.
151
+ let outcome = {
152
+ ok: false,
153
+ error: new Error("WidgetApp: a frame drawn after the app stopped"),
154
+ };
155
+ reaction.track(() => {
156
+ try {
157
+ outcome = { ok: true, value: fn() };
158
+ }
159
+ catch (error) {
160
+ outcome = { ok: false, error };
161
+ }
162
+ });
163
+ if (!outcome.ok)
164
+ throw outcome.error;
165
+ return outcome.value;
166
+ }
167
+ /**
168
+ * Paint `widget`'s overlay over `lines`, below its footprint and at the column
169
+ * of its row 0, each overlay row stamped as the widget's row below its last —
170
+ * so a click there reaches the widget, in its own coordinates. A widget whose
171
+ * row 0 is not on the frame has nowhere to hang an overlay, and paints none.
172
+ */
173
+ function paintOverlay(lines, widget, options) {
174
+ if (!hasOverlay(widget))
175
+ return;
176
+ const at = footprintOf(lines, widget);
177
+ if (!at)
178
+ return;
179
+ // An overlay floats over the frame; no region holds it.
180
+ const overlay = widget.renderOverlay({ ...options, height: undefined });
181
+ if (overlay === null)
182
+ 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));
185
+ paintLines(lines, rows, at.x, at.y + at.rows);
186
+ }
187
+ // Paint `rows` into `lines` with its top-left cell at (x, y), growing `lines`
188
+ // as needed; each painted cell replaces the one beneath it.
189
+ function paintLines(lines, rows, x, y) {
190
+ while (lines.length < y + rows.length)
191
+ lines.push([]);
192
+ rows.forEach((source, i) => {
193
+ const row = lines[y + i];
194
+ // Padded to `x`, so a row that ends short of the overlay still reaches it.
195
+ const gap = Math.max(0, x - Segment.getLineLength(row));
196
+ const [before = [], , after = []] = Segment.divide([...row, new Segment(" ".repeat(gap))], [x, x + Segment.getLineLength(source)]);
197
+ lines[y + i] = [...before, ...source, ...after];
198
+ });
199
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * WidgetBase — shared MobX observable foundation for interactive widgets.
3
+ * [LAW:one-type-per-behavior] All widgets share the same base; differences
4
+ * are in configuration and state, not in infrastructure.
5
+ *
6
+ * Reactive state is declared with TC39 decorators, which MobX 7 requires on
7
+ * `accessor` members. Each decorator registers its own member, so there is no
8
+ * makeObservable call and subclasses add observables the same way.
9
+ */
10
+ import { Segment } from "../core/segment.js";
11
+ import type { RenderOptions } from "../core/protocol.js";
12
+ import type { InteractiveWidget, KeyEvent, WidgetMouseEvent, WidgetFocusEvent } from "./types.js";
13
+ import type { Unsubscribe } from "../core/subscription.js";
14
+ export declare abstract class WidgetBase implements InteractiveWidget {
15
+ abstract readonly id: string;
16
+ abstract readonly focusable: boolean;
17
+ accessor focused: boolean;
18
+ accessor hovered: boolean;
19
+ accessor active: boolean;
20
+ accessor disabled: boolean;
21
+ private readonly changeHandlers;
22
+ private readonly submitHandlers;
23
+ abstract handleKey(event: KeyEvent): void;
24
+ handleMouse(_event: WidgetMouseEvent): void;
25
+ handleFocus(event: WidgetFocusEvent): void;
26
+ focus(): void;
27
+ blur(): void;
28
+ setDisabled(value: boolean): void;
29
+ setHovered(value: boolean): void;
30
+ onChange(handler: (widget: InteractiveWidget) => void): Unsubscribe;
31
+ onSubmit(handler: (widget: InteractiveWidget) => void): Unsubscribe;
32
+ protected emitChange(): void;
33
+ protected emitSubmit(): void;
34
+ render(options: RenderOptions): Iterable<Segment>;
35
+ protected abstract draw(options: RenderOptions): Iterable<Segment>;
36
+ abstract measure(options: RenderOptions): {
37
+ minimum: number;
38
+ maximum: number;
39
+ };
40
+ }