@promptctl/rich-js 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +9 -7
  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 +98 -18
  9. package/dist/core/color.js +331 -144
  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/osc8.d.ts +6 -6
  33. package/dist/core/osc8.js +9 -21
  34. package/dist/core/paint.d.ts +107 -0
  35. package/dist/core/paint.js +150 -0
  36. package/dist/core/place.d.ts +1 -1
  37. package/dist/core/place.js +4 -6
  38. package/dist/core/pretty.d.ts +105 -2
  39. package/dist/core/pretty.js +527 -97
  40. package/dist/core/protocol.d.ts +41 -3
  41. package/dist/core/protocol.js +20 -0
  42. package/dist/core/render.d.ts +33 -30
  43. package/dist/core/render.js +43 -83
  44. package/dist/core/segment.d.ts +6 -5
  45. package/dist/core/segment.js +11 -15
  46. package/dist/core/strip.d.ts +13 -2
  47. package/dist/core/strip.js +44 -23
  48. package/dist/core/style.d.ts +27 -7
  49. package/dist/core/style.js +97 -34
  50. package/dist/core/text.d.ts +43 -16
  51. package/dist/core/text.js +180 -59
  52. package/dist/core/wrap.d.ts +5 -0
  53. package/dist/core/wrap.js +16 -3
  54. package/dist/host/app.d.ts +31 -19
  55. package/dist/host/app.js +90 -60
  56. package/dist/host/index.d.ts +2 -1
  57. package/dist/host/terminal-host.d.ts +3 -2
  58. package/dist/index.d.ts +17 -10
  59. package/dist/index.js +11 -4
  60. package/dist/node/prompt.d.ts +13 -0
  61. package/dist/node/prompt.js +30 -9
  62. package/dist/node/save.d.ts +4 -3
  63. package/dist/node/save.js +6 -3
  64. package/dist/node/terminal-host.d.ts +3 -2
  65. package/dist/node/traceback.d.ts +2 -1
  66. package/dist/node/traceback.js +3 -39
  67. package/dist/renderables/align.js +2 -9
  68. package/dist/renderables/columns.d.ts +45 -29
  69. package/dist/renderables/columns.js +142 -103
  70. package/dist/renderables/constrain.js +6 -12
  71. package/dist/renderables/effect.d.ts +98 -0
  72. package/dist/renderables/effect.js +160 -0
  73. package/dist/renderables/embed.d.ts +87 -30
  74. package/dist/renderables/embed.js +134 -45
  75. package/dist/renderables/group.d.ts +6 -3
  76. package/dist/renderables/group.js +5 -0
  77. package/dist/renderables/layout.d.ts +6 -23
  78. package/dist/renderables/layout.js +24 -113
  79. package/dist/renderables/live.d.ts +40 -16
  80. package/dist/renderables/live.js +96 -83
  81. package/dist/renderables/markdown.d.ts +16 -2
  82. package/dist/renderables/markdown.js +521 -118
  83. package/dist/renderables/padding.d.ts +22 -1
  84. package/dist/renderables/padding.js +30 -24
  85. package/dist/renderables/panel.d.ts +31 -33
  86. package/dist/renderables/panel.js +137 -141
  87. package/dist/renderables/progress.d.ts +104 -16
  88. package/dist/renderables/progress.js +205 -81
  89. package/dist/renderables/progressBar.d.ts +2 -2
  90. package/dist/renderables/progressBar.js +30 -19
  91. package/dist/renderables/prompt.d.ts +37 -15
  92. package/dist/renderables/prompt.js +125 -85
  93. package/dist/renderables/ratio.d.ts +123 -0
  94. package/dist/renderables/ratio.js +213 -0
  95. package/dist/renderables/rule.d.ts +1 -0
  96. package/dist/renderables/rule.js +44 -48
  97. package/dist/renderables/spinner.d.ts +28 -2
  98. package/dist/renderables/spinner.js +52 -19
  99. package/dist/renderables/status.d.ts +13 -7
  100. package/dist/renderables/status.js +15 -29
  101. package/dist/renderables/syntax.d.ts +37 -7
  102. package/dist/renderables/syntax.js +134 -83
  103. package/dist/renderables/table.d.ts +58 -20
  104. package/dist/renderables/table.js +370 -192
  105. package/dist/renderables/traceback.d.ts +5 -4
  106. package/dist/renderables/traceback.js +239 -68
  107. package/dist/renderables/tree.d.ts +10 -2
  108. package/dist/renderables/tree.js +61 -34
  109. package/dist/renderables/viewport.js +2 -4
  110. package/dist/template-bindings/color-funcs.d.ts +31 -9
  111. package/dist/template-bindings/color-funcs.js +40 -14
  112. package/dist/template-bindings/helpers.d.ts +4 -2
  113. package/dist/template-bindings/helpers.js +5 -20
  114. package/dist/template-bindings/index.d.ts +29 -17
  115. package/dist/template-bindings/index.js +36 -20
  116. package/dist/template-bindings/palette-funcs.d.ts +3 -1
  117. package/dist/template-bindings/palette-funcs.js +11 -7
  118. package/dist/template-bindings/style-funcs.js +4 -2
  119. package/dist/themes/buildPalette.d.ts +24 -3
  120. package/dist/themes/buildPalette.js +52 -8
  121. package/dist/themes/colorMath.d.ts +29 -17
  122. package/dist/themes/colorMath.js +74 -48
  123. package/dist/themes/colorRef.d.ts +2 -17
  124. package/dist/themes/colorRef.js +13 -31
  125. package/dist/themes/palette.d.ts +24 -0
  126. package/dist/themes/palette.js +46 -3
  127. package/dist/themes/ramp.d.ts +21 -34
  128. package/dist/themes/ramp.js +23 -42
  129. package/dist/themes/registry.js +39 -28
  130. package/dist/widgets/button.d.ts +2 -6
  131. package/dist/widgets/button.js +9 -40
  132. package/dist/widgets/checkbox.d.ts +2 -5
  133. package/dist/widgets/checkbox.js +9 -31
  134. package/dist/widgets/dropdown.d.ts +2 -5
  135. package/dist/widgets/dropdown.js +14 -48
  136. package/dist/widgets/ink.d.ts +22 -0
  137. package/dist/widgets/ink.js +31 -0
  138. package/dist/widgets/slider.d.ts +2 -5
  139. package/dist/widgets/slider.js +11 -34
  140. package/dist/widgets/text-input.d.ts +19 -10
  141. package/dist/widgets/text-input.js +156 -147
  142. package/dist/widgets/themed-widget.d.ts +20 -0
  143. package/dist/widgets/themed-widget.js +89 -0
  144. package/dist/widgets/toggle.d.ts +2 -5
  145. package/dist/widgets/toggle.js +8 -31
  146. package/dist/widgets/widget-app.d.ts +2 -0
  147. package/dist/widgets/widget-app.js +13 -6
  148. package/dist/widgets/widget-base.d.ts +2 -0
  149. package/dist/widgets/widget-base.js +9 -2
  150. package/package.json +11 -1
@@ -1,5 +1,6 @@
1
- import { ColorTable, parseRgbHex, parseRgbaHex } from "../core/color.js";
2
- import { Palette } from "./palette.js";
1
+ import { ColorParseError, ColorTable, parseHexColor } from "../core/color.js";
2
+ import { Palette, drawnOn } from "./palette.js";
3
+ import { ACCENT_KEYS, accentText } from "./buildPalette.js";
3
4
  import { THEMES } from "./data/index.js";
4
5
  import { ANSI_SLOTS } from "./data/types.js";
5
6
  const THEME_NAMES = Object.freeze(Object.keys(THEMES).sort());
@@ -31,17 +32,22 @@ function isThemeName(name) {
31
32
  */
32
33
  export function getThemeBaseColors(name) {
33
34
  const data = THEMES[name];
35
+ const bg = requireBaseVar(data, "background");
36
+ // The colours `getThemePalette(name)` hands out for the same names: drawn on
37
+ // the theme's own background, as a Palette draws every colour it holds.
38
+ const draw = drawnOn(data.name, bg);
39
+ const base = (key) => draw(requireBaseVar(data, key));
34
40
  return {
35
41
  name: data.name,
36
42
  dark: data.dark,
37
- bg: requireBaseVar(data, "background"),
38
- fg: requireBaseVar(data, "foreground"),
39
- primary: requireBaseVar(data, "primary"),
40
- secondary: requireBaseVar(data, "secondary"),
41
- accent: requireBaseVar(data, "accent"),
42
- success: requireBaseVar(data, "success"),
43
- warning: requireBaseVar(data, "warning"),
44
- error: requireBaseVar(data, "error"),
43
+ bg,
44
+ fg: base("foreground"),
45
+ primary: base("primary"),
46
+ secondary: base("secondary"),
47
+ accent: base("accent"),
48
+ success: base("success"),
49
+ warning: base("warning"),
50
+ error: base("error"),
45
51
  ansi: new ColorTable(ANSI_SLOTS.map((slot) => parseOpaqueHex(data.ansi[slot], data.name, `ansi.${slot}`))),
46
52
  };
47
53
  }
@@ -64,31 +70,36 @@ function requireBaseVar(data, key) {
64
70
  function hydrate(data) {
65
71
  const map = new Map();
66
72
  for (const [k, v] of Object.entries(data.vars)) {
67
- map.set(k, parseHex(v, data.name, k));
73
+ map.set(k, parseHex(v, data.name, `var ${k}`));
74
+ }
75
+ // The data is Textual's as authored; its `text-*` is held to the pair this
76
+ // library promises by the same rule `buildPalette` derives it with.
77
+ for (const key of ACCENT_KEYS) {
78
+ const text = requireBaseVar(data, `text-${key}`);
79
+ map.set(`text-${key}`, accentText(text, requireBaseVar(data, `${key}-muted`), requireBaseVar(data, "background")));
68
80
  }
69
81
  return new Palette(data.name, data.dark, map);
70
82
  }
71
- // [LAW:single-enforcer] This is the loud-failure boundary for theme hex
72
- // data. `parseRgbHex`/`parseRgbaHex` in core/color.ts trust their input
73
- // shape (they assume the caller validated); here we are the caller from
74
- // the untrusted side (authored data files) and the place that has to
75
- // reject `"0G"`, `""`, `"#GGGGGG"`, etc.
76
- const HEX_RE = /^[0-9a-fA-F]+$/;
83
+ // Theme data is authored, so a malformed colour names the theme and the key it
84
+ // sits under; the grammar itself is `parseHexColor`'s.
85
+ function parseHex(value, theme, key) {
86
+ try {
87
+ return parseHexColor(value);
88
+ }
89
+ catch (cause) {
90
+ if (!(cause instanceof ColorParseError))
91
+ throw cause;
92
+ throw new Error(`Theme ${theme}: ${key}: ${cause.message}`, { cause });
93
+ }
94
+ }
77
95
  /**
78
96
  * An ANSI slot is a colour a terminal paints, never a blend, so it takes no
79
97
  * alpha: `flattenAlpha` leaves a STANDARD spec alone and would pass one through.
80
98
  */
81
99
  function parseOpaqueHex(value, theme, key) {
82
- if (!/^#[0-9A-Fa-f]{6}$/.test(value)) {
83
- throw new Error(`Theme ${theme}: ${key} has invalid hex ${JSON.stringify(value)} (expected #RRGGBB)`);
84
- }
85
- return parseRgbHex(value.slice(1));
86
- }
87
- function parseHex(value, theme, key) {
88
- const hex = value.startsWith("#") ? value.slice(1) : value;
89
- if ((hex.length !== 6 && hex.length !== 8) || !HEX_RE.test(hex)) {
90
- throw new Error(`Theme ${theme}: var ${key} has invalid hex ${JSON.stringify(value)} ` +
91
- `(expected #RRGGBB or #RRGGBBAA)`);
100
+ const color = parseHex(value, theme, key);
101
+ if (color.alpha !== 1) {
102
+ throw new Error(`Theme ${theme}: ${key} has alpha in ${JSON.stringify(value)} (expected #RRGGBB)`);
92
103
  }
93
- return hex.length === 6 ? parseRgbHex(hex) : parseRgbaHex(hex);
104
+ return color;
94
105
  }
@@ -18,7 +18,7 @@
18
18
  import { Segment } from "../core/segment.js";
19
19
  import type { RenderOptions } from "../core/protocol.js";
20
20
  import type { TerminalTheme } from "../core/color.js";
21
- import { WidgetBase } from "./widget-base.js";
21
+ import { ThemedWidget } from "./themed-widget.js";
22
22
  import type { KeyEvent, WidgetMouseEvent } from "./types.js";
23
23
  export type ButtonVariant = "default" | "primary" | "success" | "warning" | "danger";
24
24
  export interface ButtonOptions {
@@ -28,14 +28,12 @@ export interface ButtonOptions {
28
28
  disabled?: boolean;
29
29
  theme?: TerminalTheme;
30
30
  }
31
- export declare class Button extends WidgetBase {
31
+ export declare class Button extends ThemedWidget {
32
32
  readonly id: string;
33
33
  readonly focusable = true;
34
34
  accessor label: string;
35
35
  accessor variant: ButtonVariant;
36
- private accessor _theme;
37
36
  constructor(options: ButtonOptions);
38
- setTheme(theme: TerminalTheme): void;
39
37
  handleKey(event: KeyEvent): void;
40
38
  handleMouse(event: WidgetMouseEvent): void;
41
39
  setActive(value: boolean): void;
@@ -44,6 +42,4 @@ export declare class Button extends WidgetBase {
44
42
  minimum: number;
45
43
  maximum: number;
46
44
  };
47
- private resolveColors;
48
- private resolvePalette;
49
45
  }
@@ -52,10 +52,9 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
52
52
  import { observable, action, observableRef } from "mobx";
53
53
  import { Segment } from "../core/segment.js";
54
54
  import { Style } from "../core/style.js";
55
- import { ColorSpec } from "../core/color.js";
56
55
  import { cellLen } from "../core/cells.js";
57
- import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
58
- import { WidgetBase } from "./widget-base.js";
56
+ import { ThemedWidget } from "./themed-widget.js";
57
+ import { ink } from "./ink.js";
59
58
  // Variant → palette key mapping. Per variant:
60
59
  // bg / fg — normal state. Muted accent bg + mostly-accent fg → readable.
61
60
  // hover — full accent bg; pair with hoverFg, NOT with `fg`. The
@@ -70,7 +69,7 @@ const VARIANT_KEYS = {
70
69
  danger: { bg: "error-muted", fg: "text-error", hover: "error", hoverFg: "on-error" },
71
70
  };
72
71
  let Button = (() => {
73
- let _classSuper = WidgetBase;
72
+ let _classSuper = ThemedWidget;
74
73
  let _instanceExtraInitializers = [];
75
74
  let _label_decorators;
76
75
  let _label_initializers = [];
@@ -78,10 +77,6 @@ let Button = (() => {
78
77
  let _variant_decorators;
79
78
  let _variant_initializers = [];
80
79
  let _variant_extraInitializers = [];
81
- let __theme_decorators;
82
- let __theme_initializers = [];
83
- let __theme_extraInitializers = [];
84
- let _setTheme_decorators;
85
80
  let _handleKey_decorators;
86
81
  let _handleMouse_decorators;
87
82
  let _setActive_decorators;
@@ -90,15 +85,11 @@ let Button = (() => {
90
85
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
91
86
  _label_decorators = [observable];
92
87
  _variant_decorators = [observableRef];
93
- __theme_decorators = [observableRef];
94
- _setTheme_decorators = [action];
95
88
  _handleKey_decorators = [action];
96
89
  _handleMouse_decorators = [action];
97
90
  _setActive_decorators = [action];
98
91
  __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);
99
92
  __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);
100
- __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);
101
- __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);
102
93
  __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);
103
94
  __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);
104
95
  __esDecorate(this, null, _setActive_decorators, { kind: "method", name: "setActive", static: false, private: false, access: { has: obj => "setActive" in obj, get: obj => obj.setActive }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -112,21 +103,14 @@ let Button = (() => {
112
103
  #variant_accessor_storage = (__runInitializers(this, _label_extraInitializers), __runInitializers(this, _variant_initializers, void 0));
113
104
  get variant() { return this.#variant_accessor_storage; }
114
105
  set variant(value) { this.#variant_accessor_storage = value; }
115
- #_theme_accessor_storage = (__runInitializers(this, _variant_extraInitializers), __runInitializers(this, __theme_initializers, void 0));
116
- // [LAW:types-are-the-program] @observableRef so setTheme() triggers a
117
- // re-render — see slider.ts.
118
- get _theme() { return this.#_theme_accessor_storage; }
119
- set _theme(value) { this.#_theme_accessor_storage = value; }
120
106
  constructor(options) {
121
- super();
122
- __runInitializers(this, __theme_extraInitializers);
107
+ super(options.theme);
108
+ __runInitializers(this, _variant_extraInitializers);
123
109
  this.id = options.id ?? `button-${options.label.toLowerCase().replace(/\s+/g, "-")}`;
124
110
  this.label = options.label;
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)
@@ -170,19 +154,17 @@ let Button = (() => {
170
154
  const right = focused ? "]" : " ";
171
155
  const text = `${left} ${this.label} ${right}`;
172
156
  if (this.disabled) {
173
- return [new Segment(text, new Style({ color: "#666666", bgcolor: "#333333", dim: true }))];
157
+ return [new Segment(text, new Style(this.disabledInk))];
174
158
  }
175
159
  // Active and hover share the same colour pair (full accent bg + on-accent fg).
176
160
  // Active is differentiated by bold, not by inverting fg/bg — inversion gives
177
161
  // mostly-accent text on mostly-bg-tinted background, which is unreadable for
178
162
  // accents whose contrast partner depends on luminance.
163
+ const keys = VARIANT_KEYS[this.variant];
179
164
  if (this.active || this.hovered) {
180
- const fg = this.resolvePalette(VARIANT_KEYS[this.variant].hoverFg);
181
- const bg = this.resolvePalette(VARIANT_KEYS[this.variant].hover);
182
- return [new Segment(text, new Style({ color: fg, bgcolor: bg, bold: this.active }))];
165
+ return [new Segment(text, new Style({ ...ink(this.theme, keys.hoverFg, keys.hover), bold: this.active }))];
183
166
  }
184
- const { fg, bg } = this.resolveColors("bg");
185
- return [new Segment(text, new Style({ color: fg, bgcolor: bg }))];
167
+ return [new Segment(text, new Style(ink(this.theme, keys.fg, keys.bg)))];
186
168
  }
187
169
  measure(_options) {
188
170
  // [LAW:one-source-of-truth] cellLen is the cell-width authority; .length
@@ -190,19 +172,6 @@ let Button = (() => {
190
172
  const width = cellLen(this.label) + 4;
191
173
  return { minimum: width, maximum: width };
192
174
  }
193
- // --- Palette resolution ---
194
- resolveColors(bgKey) {
195
- const keys = VARIANT_KEYS[this.variant];
196
- return {
197
- fg: this.resolvePalette(keys.fg),
198
- bg: this.resolvePalette(bgKey === "hover" ? keys.hover : keys.bg),
199
- };
200
- }
201
- resolvePalette(key) {
202
- const rgba = this._theme.palette.get(key);
203
- // [LAW:no-defensive-null-guards] palette is required and must contain all keys.
204
- return ColorSpec.fromRgba(rgba);
205
- }
206
175
  };
207
176
  })();
208
177
  export { Button };
@@ -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 interface CheckboxOptions {
19
19
  label: string;
@@ -22,14 +22,12 @@ export interface CheckboxOptions {
22
22
  disabled?: boolean;
23
23
  theme?: TerminalTheme;
24
24
  }
25
- export declare class Checkbox extends WidgetBase {
25
+ export declare class Checkbox extends ThemedWidget {
26
26
  readonly id: string;
27
27
  readonly focusable = true;
28
28
  accessor label: string;
29
29
  accessor checked: boolean;
30
- private accessor _theme;
31
30
  constructor(options: CheckboxOptions);
32
- setTheme(theme: TerminalTheme): void;
33
31
  handleKey(event: KeyEvent): void;
34
32
  handleMouse(event: WidgetMouseEvent): void;
35
33
  protected draw(options: RenderOptions): Iterable<Segment>;
@@ -37,5 +35,4 @@ export declare class Checkbox extends WidgetBase {
37
35
  minimum: number;
38
36
  maximum: number;
39
37
  };
40
- private resolvePalette;
41
38
  }
@@ -44,16 +44,15 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
44
44
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
45
45
  done = true;
46
46
  };
47
- import { observable, action, observableRef } from "mobx";
47
+ import { observable, action } 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
51
  import { drawable } from "../core/protocol.js";
54
- import { WidgetBase } from "./widget-base.js";
52
+ import { ThemedWidget } from "./themed-widget.js";
53
+ import { ink } from "./ink.js";
55
54
  let Checkbox = (() => {
56
- let _classSuper = WidgetBase;
55
+ let _classSuper = ThemedWidget;
57
56
  let _instanceExtraInitializers = [];
58
57
  let _label_decorators;
59
58
  let _label_initializers = [];
@@ -61,10 +60,6 @@ let Checkbox = (() => {
61
60
  let _checked_decorators;
62
61
  let _checked_initializers = [];
63
62
  let _checked_extraInitializers = [];
64
- let __theme_decorators;
65
- let __theme_initializers = [];
66
- let __theme_extraInitializers = [];
67
- let _setTheme_decorators;
68
63
  let _handleKey_decorators;
69
64
  let _handleMouse_decorators;
70
65
  return class Checkbox extends _classSuper {
@@ -72,14 +67,10 @@ let Checkbox = (() => {
72
67
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
73
68
  _label_decorators = [observable];
74
69
  _checked_decorators = [observable];
75
- __theme_decorators = [observableRef];
76
- _setTheme_decorators = [action];
77
70
  _handleKey_decorators = [action];
78
71
  _handleMouse_decorators = [action];
79
72
  __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);
80
73
  __esDecorate(this, null, _checked_decorators, { kind: "accessor", name: "checked", static: false, private: false, access: { has: obj => "checked" in obj, get: obj => obj.checked, set: (obj, value) => { obj.checked = value; } }, metadata: _metadata }, _checked_initializers, _checked_extraInitializers);
81
- __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);
82
- __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);
83
74
  __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);
84
75
  __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);
85
76
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -92,21 +83,14 @@ let Checkbox = (() => {
92
83
  #checked_accessor_storage = (__runInitializers(this, _label_extraInitializers), __runInitializers(this, _checked_initializers, void 0));
93
84
  get checked() { return this.#checked_accessor_storage; }
94
85
  set checked(value) { this.#checked_accessor_storage = value; }
95
- #_theme_accessor_storage = (__runInitializers(this, _checked_extraInitializers), __runInitializers(this, __theme_initializers, void 0));
96
- // [LAW:types-are-the-program] @observableRef so setTheme() triggers a
97
- // re-render — see slider.ts.
98
- get _theme() { return this.#_theme_accessor_storage; }
99
- set _theme(value) { this.#_theme_accessor_storage = value; }
100
86
  constructor(options) {
101
- super();
102
- __runInitializers(this, __theme_extraInitializers);
87
+ super(options.theme);
88
+ __runInitializers(this, _checked_extraInitializers);
103
89
  this.id = options.id ?? `checkbox-${options.label.toLowerCase().replace(/\s+/g, "-")}`;
104
90
  this.label = options.label;
105
91
  this.checked = options.checked ?? false;
106
92
  this.disabled = options.disabled ?? false;
107
- this._theme = options.theme ?? DEFAULT_TERMINAL_THEME;
108
93
  }
109
- setTheme(theme) { this._theme = theme; }
110
94
  // --- Event handlers ---
111
95
  handleKey(event) {
112
96
  if (this.disabled)
@@ -136,22 +120,16 @@ let Checkbox = (() => {
136
120
  const indicator = this.checked ? drawable(options, "✓", "x") : " ";
137
121
  const text = `[${indicator}] ${this.label}`;
138
122
  if (this.disabled) {
139
- return [new Segment(text, new Style({ color: "#666666", bgcolor: "#333333", dim: true }))];
123
+ return [new Segment(text, new Style(this.disabledInk))];
140
124
  }
141
- const fg = this.checked ? this.resolvePalette("primary") : this.resolvePalette("foreground");
142
- return [new Segment(text, new Style({ color: fg, underline: this.focused }))];
125
+ const colors = ink(this.theme, this.checked ? "text-primary" : "foreground", "background");
126
+ return [new Segment(text, new Style({ ...colors, underline: this.focused }))];
143
127
  }
144
128
  measure(_options) {
145
129
  // [LAW:one-source-of-truth] cellLen — see button.ts.
146
130
  const width = cellLen(this.label) + 4;
147
131
  return { minimum: width, maximum: width };
148
132
  }
149
- // --- Palette resolution ---
150
- resolvePalette(key) {
151
- const rgba = this._theme.palette.get(key);
152
- // [LAW:no-defensive-null-guards] palette must contain all keys; missing is a construction bug.
153
- return ColorSpec.fromRgba(rgba);
154
- }
155
133
  };
156
134
  })();
157
135
  export { Checkbox };
@@ -25,7 +25,7 @@
25
25
  import { Segment } from "../core/segment.js";
26
26
  import { type RenderOptions } from "../core/protocol.js";
27
27
  import type { TerminalTheme } from "../core/color.js";
28
- import { WidgetBase } from "./widget-base.js";
28
+ import { ThemedWidget } from "./themed-widget.js";
29
29
  import type { KeyEvent, WidgetMouseEvent, WidgetFocusEvent, OverlayRenderable } from "./types.js";
30
30
  export interface DropdownOptions {
31
31
  options: string[];
@@ -34,7 +34,7 @@ export interface DropdownOptions {
34
34
  disabled?: boolean;
35
35
  theme?: TerminalTheme;
36
36
  }
37
- export declare class Dropdown extends WidgetBase implements OverlayRenderable {
37
+ export declare class Dropdown extends ThemedWidget implements OverlayRenderable {
38
38
  readonly id: string;
39
39
  readonly focusable = true;
40
40
  accessor options: string[];
@@ -46,10 +46,8 @@ export declare class Dropdown extends WidgetBase implements OverlayRenderable {
46
46
  label: string;
47
47
  idx: number;
48
48
  }[];
49
- private accessor _theme;
50
49
  constructor(options: DropdownOptions);
51
50
  private clampIndex;
52
- setTheme(theme: TerminalTheme): void;
53
51
  handleKey(event: KeyEvent): void;
54
52
  handleMouse(event: WidgetMouseEvent): void;
55
53
  handleFocus(event: WidgetFocusEvent): void;
@@ -63,5 +61,4 @@ export declare class Dropdown extends WidgetBase implements OverlayRenderable {
63
61
  minimum: number;
64
62
  maximum: number;
65
63
  };
66
- private resolvePalette;
67
64
  }
@@ -56,16 +56,15 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
56
56
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
57
57
  done = true;
58
58
  };
59
- import { observable, action, observableRef, observableShallow } from "mobx";
59
+ import { observable, action, observableShallow } from "mobx";
60
60
  import { Segment } from "../core/segment.js";
61
61
  import { Style } from "../core/style.js";
62
- import { ColorSpec } from "../core/color.js";
63
62
  import { cellLen, setCellSize, splitText, asCellCol } from "../core/cells.js";
64
- import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
65
63
  import { drawable } from "../core/protocol.js";
66
- import { WidgetBase } from "./widget-base.js";
64
+ import { ThemedWidget } from "./themed-widget.js";
65
+ import { ink } from "./ink.js";
67
66
  let Dropdown = (() => {
68
- let _classSuper = WidgetBase;
67
+ let _classSuper = ThemedWidget;
69
68
  let _instanceExtraInitializers = [];
70
69
  let _options_decorators;
71
70
  let _options_initializers = [];
@@ -82,10 +81,6 @@ let Dropdown = (() => {
82
81
  let _filter_decorators;
83
82
  let _filter_initializers = [];
84
83
  let _filter_extraInitializers = [];
85
- let __theme_decorators;
86
- let __theme_initializers = [];
87
- let __theme_extraInitializers = [];
88
- let _setTheme_decorators;
89
84
  let _handleKey_decorators;
90
85
  let _handleMouse_decorators;
91
86
  let _handleFocus_decorators;
@@ -97,8 +92,6 @@ let Dropdown = (() => {
97
92
  _expanded_decorators = [observable];
98
93
  _highlightedIndex_decorators = [observable];
99
94
  _filter_decorators = [observable];
100
- __theme_decorators = [observableRef];
101
- _setTheme_decorators = [action];
102
95
  _handleKey_decorators = [action];
103
96
  _handleMouse_decorators = [action];
104
97
  _handleFocus_decorators = [action];
@@ -107,8 +100,6 @@ let Dropdown = (() => {
107
100
  __esDecorate(this, null, _expanded_decorators, { kind: "accessor", name: "expanded", static: false, private: false, access: { has: obj => "expanded" in obj, get: obj => obj.expanded, set: (obj, value) => { obj.expanded = value; } }, metadata: _metadata }, _expanded_initializers, _expanded_extraInitializers);
108
101
  __esDecorate(this, null, _highlightedIndex_decorators, { kind: "accessor", name: "highlightedIndex", static: false, private: false, access: { has: obj => "highlightedIndex" in obj, get: obj => obj.highlightedIndex, set: (obj, value) => { obj.highlightedIndex = value; } }, metadata: _metadata }, _highlightedIndex_initializers, _highlightedIndex_extraInitializers);
109
102
  __esDecorate(this, null, _filter_decorators, { kind: "accessor", name: "filter", static: false, private: false, access: { has: obj => "filter" in obj, get: obj => obj.filter, set: (obj, value) => { obj.filter = value; } }, metadata: _metadata }, _filter_initializers, _filter_extraInitializers);
110
- __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);
111
- __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);
112
103
  __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);
113
104
  __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);
114
105
  __esDecorate(this, null, _handleFocus_decorators, { kind: "method", name: "handleFocus", static: false, private: false, access: { has: obj => "handleFocus" in obj, get: obj => obj.handleFocus }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -144,15 +135,9 @@ let Dropdown = (() => {
144
135
  }
145
136
  return result;
146
137
  }
147
- #_theme_accessor_storage = (__runInitializers(this, _filter_extraInitializers), __runInitializers(this, __theme_initializers, void 0));
148
- // [LAW:types-are-the-program] @observableRef so setTheme() triggers a
149
- // re-render — render() and resolvePalette() read _theme.palette, so the
150
- // theme reference must participate in MobX reactivity.
151
- get _theme() { return this.#_theme_accessor_storage; }
152
- set _theme(value) { this.#_theme_accessor_storage = value; }
153
138
  constructor(options) {
154
- super();
155
- __runInitializers(this, __theme_extraInitializers);
139
+ super(options.theme);
140
+ __runInitializers(this, _filter_extraInitializers);
156
141
  this.id = options.id ?? `dropdown-${Math.random().toString(36).slice(2, 8)}`;
157
142
  this.options = [...options.options];
158
143
  // [LAW:types-are-the-program] selectedIndex must point to a valid
@@ -163,7 +148,6 @@ let Dropdown = (() => {
163
148
  this.selectedIndex = this.clampIndex(options.selectedIndex ?? 0);
164
149
  this.highlightedIndex = this.selectedIndex;
165
150
  this.disabled = options.disabled ?? false;
166
- this._theme = options.theme ?? DEFAULT_TERMINAL_THEME;
167
151
  }
168
152
  // [LAW:single-enforcer] One place owns "is this index valid for the
169
153
  // current options". selectedIndex and options are public observables —
@@ -176,7 +160,6 @@ let Dropdown = (() => {
176
160
  return 0;
177
161
  return Math.max(0, Math.min(n, this.options.length - 1));
178
162
  }
179
- setTheme(theme) { this._theme = theme; }
180
163
  // --- Event handlers ---
181
164
  handleKey(event) {
182
165
  if (this.disabled)
@@ -322,10 +305,9 @@ let Dropdown = (() => {
322
305
  const caret = this.focused ? drawable(options, "│", "|") : "";
323
306
  const maxLabelLen = this.maxLabelLen();
324
307
  const baseStyle = this.disabled
325
- ? new Style({ color: "#666666", bgcolor: "#333333", dim: true })
308
+ ? new Style(this.disabledInk)
326
309
  : new Style({
327
- color: this.resolvePalette("foreground"),
328
- bgcolor: this.resolvePalette("surface"),
310
+ ...ink(this.theme, "foreground", "surface"),
329
311
  underline: this.focused,
330
312
  });
331
313
  const headerLabel = this.headerText(maxLabelLen, caret);
@@ -381,10 +363,9 @@ let Dropdown = (() => {
381
363
  const text = setCellSize("(no matches)", asCellCol(maxLabelLen));
382
364
  const inner = ` ${text} `;
383
365
  const style = this.disabled
384
- ? new Style({ color: "#666666", bgcolor: "#333333", dim: true })
366
+ ? new Style(this.disabledInk)
385
367
  : new Style({
386
- color: this.resolvePalette("foreground"),
387
- bgcolor: this.resolvePalette("surface"),
368
+ ...ink(this.theme, "foreground", "surface"),
388
369
  dim: true,
389
370
  });
390
371
  return [
@@ -405,21 +386,12 @@ let Dropdown = (() => {
405
386
  // Highlighted: muted bg → text-primary (mostly-accent fg) is readable.
406
387
  // Selected: full primary bg → on-primary (WCAG contrast) for fg.
407
388
  const rowStyle = this.disabled
408
- ? new Style({ color: "#666666", bgcolor: "#333333", dim: true })
389
+ ? new Style(this.disabledInk)
409
390
  : isHighlighted
410
- ? new Style({
411
- color: this.resolvePalette("text-primary"),
412
- bgcolor: this.resolvePalette("primary-muted"),
413
- })
391
+ ? new Style(ink(this.theme, "text-primary", "primary-muted"))
414
392
  : isSelected
415
- ? new Style({
416
- color: this.resolvePalette("on-primary"),
417
- bgcolor: this.resolvePalette("primary"),
418
- })
419
- : new Style({
420
- color: this.resolvePalette("foreground"),
421
- bgcolor: this.resolvePalette("surface"),
422
- });
393
+ ? new Style(ink(this.theme, "on-primary", "primary"))
394
+ : new Style(ink(this.theme, "foreground", "surface"));
423
395
  return [
424
396
  new Segment(" ", rowStyle),
425
397
  new Segment(inner, rowStyle),
@@ -439,12 +411,6 @@ let Dropdown = (() => {
439
411
  const width = this.maxLabelLen() + 4;
440
412
  return { minimum: width, maximum: width };
441
413
  }
442
- // --- Palette resolution ---
443
- resolvePalette(key) {
444
- const rgba = this._theme.palette.get(key);
445
- // [LAW:no-defensive-null-guards] palette must contain all keys.
446
- return ColorSpec.fromRgba(rgba);
447
- }
448
414
  };
449
415
  })();
450
416
  export { Dropdown };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ink — the colour pair a widget cell is drawn in: a foreground and the ground
3
+ * it stands on, both from one theme's palette.
4
+ *
5
+ * A palette's colours are chosen against that palette's own background. A
6
+ * foreground drawn over whatever the terminal shows is readable only when the
7
+ * terminal happens to match the theme — the default theme's white text
8
+ * vanished into a light terminal. With both halves taken from the palette, a
9
+ * widget looks the same on every terminal, so it is readable on all of them
10
+ * exactly when its theme is readable against itself.
11
+ *
12
+ * [LAW:types-are-the-program] There is no call that takes a foreground alone,
13
+ * so a widget cannot draw half a pair.
14
+ * [LAW:single-enforcer] Every widget resolves palette keys here, and this is
15
+ * where a key the palette lacks, or one that draws no colour (a theme's
16
+ * fully transparent "no background"), is reported.
17
+ */
18
+ import { ColorSpec, type TerminalTheme } from "../core/color.js";
19
+ export declare function ink(theme: TerminalTheme, fg: string, bg: string): {
20
+ color: ColorSpec;
21
+ bgcolor: ColorSpec;
22
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ink — the colour pair a widget cell is drawn in: a foreground and the ground
3
+ * it stands on, both from one theme's palette.
4
+ *
5
+ * A palette's colours are chosen against that palette's own background. A
6
+ * foreground drawn over whatever the terminal shows is readable only when the
7
+ * terminal happens to match the theme — the default theme's white text
8
+ * vanished into a light terminal. With both halves taken from the palette, a
9
+ * widget looks the same on every terminal, so it is readable on all of them
10
+ * exactly when its theme is readable against itself.
11
+ *
12
+ * [LAW:types-are-the-program] There is no call that takes a foreground alone,
13
+ * so a widget cannot draw half a pair.
14
+ * [LAW:single-enforcer] Every widget resolves palette keys here, and this is
15
+ * where a key the palette lacks, or one that draws no colour (a theme's
16
+ * fully transparent "no background"), is reported.
17
+ */
18
+ import { ColorSpec } from "../core/color.js";
19
+ export function ink(theme, fg, bg) {
20
+ return { color: paletteColor(theme, fg), bgcolor: paletteColor(theme, bg) };
21
+ }
22
+ function paletteColor(theme, key) {
23
+ const rgba = theme.palette.get(key);
24
+ if (rgba === undefined) {
25
+ throw new RangeError(`palette ${JSON.stringify(theme.palette.name)} has no ${JSON.stringify(key)}`);
26
+ }
27
+ if (rgba.alpha === 0) {
28
+ throw new RangeError(`palette ${JSON.stringify(theme.palette.name)}: ${JSON.stringify(key)} is fully transparent, so it cannot be half of a widget's colour pair`);
29
+ }
30
+ return ColorSpec.fromRgba(rgba);
31
+ }
@@ -20,7 +20,7 @@
20
20
  import { Segment } from "../core/segment.js";
21
21
  import { type RenderOptions } from "../core/protocol.js";
22
22
  import type { TerminalTheme } from "../core/color.js";
23
- import { WidgetBase } from "./widget-base.js";
23
+ import { ThemedWidget } from "./themed-widget.js";
24
24
  import type { KeyEvent, WidgetMouseEvent } from "./types.js";
25
25
  export interface SliderOptions {
26
26
  value?: number;
@@ -32,7 +32,7 @@ export interface SliderOptions {
32
32
  disabled?: boolean;
33
33
  theme?: TerminalTheme;
34
34
  }
35
- export declare class Slider extends WidgetBase {
35
+ export declare class Slider extends ThemedWidget {
36
36
  readonly id: string;
37
37
  readonly focusable = true;
38
38
  accessor value: number;
@@ -40,10 +40,8 @@ export declare class Slider extends WidgetBase {
40
40
  accessor max: number;
41
41
  accessor step: number;
42
42
  accessor width: number;
43
- private accessor _theme;
44
43
  private _dragging;
45
44
  constructor(options?: SliderOptions);
46
- setTheme(theme: TerminalTheme): void;
47
45
  handleKey(event: KeyEvent): void;
48
46
  handleMouse(event: WidgetMouseEvent): void;
49
47
  private setValue;
@@ -53,5 +51,4 @@ export declare class Slider extends WidgetBase {
53
51
  minimum: number;
54
52
  maximum: number;
55
53
  };
56
- private resolvePalette;
57
54
  }