@promptctl/rich-js 0.25.2 → 0.27.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 (49) hide show
  1. package/dist/core/animation.d.ts +26 -8
  2. package/dist/core/animation.js +32 -11
  3. package/dist/core/color.d.ts +20 -11
  4. package/dist/core/color.js +26 -21
  5. package/dist/core/console.d.ts +8 -0
  6. package/dist/core/console.js +2 -1
  7. package/dist/core/easing.js +6 -1
  8. package/dist/core/oklch.d.ts +2 -0
  9. package/dist/core/oklch.js +3 -3
  10. package/dist/core/protocol.d.ts +26 -16
  11. package/dist/core/render.d.ts +1 -1
  12. package/dist/core/render.js +1 -0
  13. package/dist/core/view.d.ts +22 -8
  14. package/dist/core/view.js +32 -8
  15. package/dist/host/app.d.ts +4 -4
  16. package/dist/host/app.js +5 -5
  17. package/dist/index.d.ts +6 -5
  18. package/dist/renderables/effect.d.ts +44 -19
  19. package/dist/renderables/effect.js +20 -13
  20. package/dist/renderables/effects.d.ts +15 -9
  21. package/dist/renderables/effects.js +166 -34
  22. package/dist/renderables/lifecycle.d.ts +38 -28
  23. package/dist/renderables/lifecycle.js +85 -45
  24. package/dist/renderables/live.d.ts +3 -3
  25. package/dist/renderables/live.js +4 -4
  26. package/dist/renderables/progress.d.ts +11 -4
  27. package/dist/renderables/progress.js +29 -17
  28. package/dist/themes/buildPalette.d.ts +14 -39
  29. package/dist/themes/buildPalette.js +196 -67
  30. package/dist/themes/colorMath.d.ts +10 -4
  31. package/dist/themes/colorMath.js +33 -1
  32. package/dist/themes/data/types.d.ts +3 -1
  33. package/dist/themes/palette.d.ts +22 -6
  34. package/dist/themes/palette.js +13 -21
  35. package/dist/themes/registry.d.ts +2 -3
  36. package/dist/themes/registry.js +7 -26
  37. package/dist/themes/terminalThemes.d.ts +6 -5
  38. package/dist/themes/terminalThemes.js +6 -5
  39. package/dist/themes/transpose.d.ts +5 -10
  40. package/dist/themes/transpose.js +13 -24
  41. package/dist/widgets/event-router.js +2 -2
  42. package/dist/widgets/ink.d.ts +5 -4
  43. package/dist/widgets/ink.js +6 -9
  44. package/dist/widgets/text-input.d.ts +1 -1
  45. package/dist/widgets/text-input.js +41 -32
  46. package/dist/widgets/types.d.ts +2 -2
  47. package/dist/widgets/widget-app.d.ts +3 -3
  48. package/dist/widgets/widget-app.js +5 -5
  49. package/package.json +1 -1
@@ -17,10 +17,10 @@
17
17
  * `ThemeKey` to apply (data), not *whether* to apply one (branch).
18
18
  *
19
19
  * [LAW:one-source-of-truth] Anchor classification lives only in
20
- * `ANCHORED_ROOTS` below. The buildPalette derivation guarantees every
21
- * variant of a semantic root shares the root as its hyphen prefix
22
- * (`error`, `error-darken-1`, `error-lighten-2`, ...), so one set covers
23
- * the whole family.
20
+ * `ANCHORED_ROOTS` below. Every variant of a semantic root carries the root
21
+ * as its first hyphen segment (`error`, `error-darken-1`, `error-muted`, ...)
22
+ * or after a `text-` or `on-` that draws on it (`text-error`, `on-error`), so
23
+ * one set covers the whole family.
24
24
  *
25
25
  * [LAW:one-way-deps] Imports flow `core/oklch → themes/transpose`;
26
26
  * nothing in core/ depends back on this file.
@@ -54,12 +54,7 @@ export declare function isAnchored(varName: string): boolean;
54
54
  * lightness shifts, mirror-inversions, hue rotations that don't touch L,
55
55
  * and combinations of all three. [LAW:types-are-the-program]
56
56
  *
57
- * The transposing path throws if the palette has no `background` var (the
58
- * `dark`-flag derivation has nothing to read). Failing loudly is preferred
59
- * over a silent fallback because the alternative — trusting the source
60
- * `palette.dark` after an arbitrary L-transform — produces flags that lie.
61
- * The identity fast-path is exempt: it preserves the source `dark` flag
62
- * verbatim (no derivation), so it needs no `background` and never throws.
57
+ * The identity fast-path preserves the source `dark` flag verbatim.
63
58
  *
64
59
  * @param name Optional override for the resulting palette name. Defaults
65
60
  * to the source palette's name. Callers building a family of transposed
@@ -17,15 +17,16 @@
17
17
  * `ThemeKey` to apply (data), not *whether* to apply one (branch).
18
18
  *
19
19
  * [LAW:one-source-of-truth] Anchor classification lives only in
20
- * `ANCHORED_ROOTS` below. The buildPalette derivation guarantees every
21
- * variant of a semantic root shares the root as its hyphen prefix
22
- * (`error`, `error-darken-1`, `error-lighten-2`, ...), so one set covers
23
- * the whole family.
20
+ * `ANCHORED_ROOTS` below. Every variant of a semantic root carries the root
21
+ * as its first hyphen segment (`error`, `error-darken-1`, `error-muted`, ...)
22
+ * or after a `text-` or `on-` that draws on it (`text-error`, `on-error`), so
23
+ * one set covers the whole family.
24
24
  *
25
25
  * [LAW:one-way-deps] Imports flow `core/oklch → themes/transpose`;
26
26
  * nothing in core/ depends back on this file.
27
27
  */
28
28
  import { Oklch, isIdentityKey } from "../core/oklch.js";
29
+ import { pattern } from "../core/pattern.js";
29
30
  import { Palette } from "./palette.js";
30
31
  /**
31
32
  * Semantic roots whose hue is locked under transposition. `error` must
@@ -43,9 +44,11 @@ export const ANCHORED_ROOTS = new Set([
43
44
  "success",
44
45
  "warning",
45
46
  ]);
47
+ const DRAWN_ON = pattern(/^(?:text|on)-/);
46
48
  function rootOf(varName) {
47
- const dash = varName.indexOf("-");
48
- return dash === -1 ? varName : varName.slice(0, dash);
49
+ const role = varName.replace(DRAWN_ON, "");
50
+ const dash = role.indexOf("-");
51
+ return dash === -1 ? role : role.slice(0, dash);
49
52
  }
50
53
  /**
51
54
  * Whether a palette var's hue is locked under transposition. The single
@@ -68,12 +71,7 @@ export function isAnchored(varName) {
68
71
  * lightness shifts, mirror-inversions, hue rotations that don't touch L,
69
72
  * and combinations of all three. [LAW:types-are-the-program]
70
73
  *
71
- * The transposing path throws if the palette has no `background` var (the
72
- * `dark`-flag derivation has nothing to read). Failing loudly is preferred
73
- * over a silent fallback because the alternative — trusting the source
74
- * `palette.dark` after an arbitrary L-transform — produces flags that lie.
75
- * The identity fast-path is exempt: it preserves the source `dark` flag
76
- * verbatim (no derivation), so it needs no `background` and never throws.
74
+ * The identity fast-path preserves the source `dark` flag verbatim.
77
75
  *
78
76
  * @param name Optional override for the resulting palette name. Defaults
79
77
  * to the source palette's name. Callers building a family of transposed
@@ -81,23 +79,14 @@ export function isAnchored(varName) {
81
79
  */
82
80
  export function transposePalette(palette, key, name) {
83
81
  if (isIdentityKey(key)) {
84
- return new Palette(name ?? palette.name, palette.dark, palette.vars);
82
+ return new Palette(name ?? palette.name, palette.dark, palette.mapRoles((colour) => colour));
85
83
  }
86
84
  // Anchors share *everything except* hue with the user's key. They still
87
85
  // lightness-invert in a dark↔light flip; they still chroma-scale in a
88
86
  // saturation sweep — only their hue is held.
89
87
  const anchorKey = { ...key, hueShift: 0 };
90
- const next = new Map();
91
- for (const [varName, color] of palette.vars) {
92
- const effective = isAnchored(varName) ? anchorKey : key;
93
- next.set(varName, Oklch.fromRgba(color).applyKey(effective).toRgba());
94
- }
95
- const newBackground = next.get("background");
96
- if (newBackground === undefined) {
97
- throw new Error(`transposePalette: palette "${palette.name}" has no "background" ` +
98
- `var; cannot derive the dark flag without a background color.`);
99
- }
100
- const newDark = Oklch.fromRgba(newBackground).l < 0.5;
88
+ const next = palette.mapRoles((colour, role) => Oklch.fromRgba(colour).applyKey(isAnchored(role) ? anchorKey : key).toRgba());
89
+ const newDark = Oklch.fromRgba(next.background).l < 0.5;
101
90
  return new Palette(name ?? palette.name, newDark, next);
102
91
  }
103
92
  /**
@@ -78,7 +78,7 @@ export class EventRouter {
78
78
  }
79
79
  /** Route a key the app heard. */
80
80
  key(press) {
81
- this.dispatchKey(new KeyEvent(press, this.source.frameTime));
81
+ this.dispatchKey(new KeyEvent(press, this.source.frameNow().time));
82
82
  }
83
83
  /** Route a pointer event the app heard. */
84
84
  mouse(event) {
@@ -173,7 +173,7 @@ export class EventRouter {
173
173
  y: event.y - target.originY,
174
174
  over: drew(frame, target.widget, event.x, event.y),
175
175
  cell: cellOf(frame, target.widget, event.x, event.y),
176
- time: this.source.frameTime,
176
+ time: this.source.frameNow().time,
177
177
  });
178
178
  if (event.type === "mouse_down")
179
179
  this.captured = target;
@@ -11,12 +11,13 @@
11
11
  *
12
12
  * [LAW:types-are-the-program] There is no call that takes a foreground alone,
13
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.
14
+ * [LAW:single-enforcer] Every widget resolves palette roles here, and this is
15
+ * where one that draws no colour (a theme's fully transparent "no background")
16
+ * is reported.
17
17
  */
18
18
  import { ColorSpec, type TerminalTheme } from "../core/color.js";
19
- export declare function ink(theme: TerminalTheme, fg: string, bg: string): {
19
+ import type { PaletteRole } from "../themes/palette.js";
20
+ export declare function ink(theme: TerminalTheme, fg: PaletteRole, bg: PaletteRole): {
20
21
  color: ColorSpec;
21
22
  bgcolor: ColorSpec;
22
23
  };
@@ -11,21 +11,18 @@
11
11
  *
12
12
  * [LAW:types-are-the-program] There is no call that takes a foreground alone,
13
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.
14
+ * [LAW:single-enforcer] Every widget resolves palette roles here, and this is
15
+ * where one that draws no colour (a theme's fully transparent "no background")
16
+ * is reported.
17
17
  */
18
18
  import { ColorSpec } from "../core/color.js";
19
19
  export function ink(theme, fg, bg) {
20
20
  return { color: paletteColor(theme, fg), bgcolor: paletteColor(theme, bg) };
21
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
- }
22
+ function paletteColor(theme, role) {
23
+ const rgba = theme.palette.get(role);
27
24
  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`);
25
+ throw new RangeError(`palette ${JSON.stringify(theme.palette.name)}: ${JSON.stringify(role)} is fully transparent, so it cannot be half of a widget's colour pair`);
29
26
  }
30
27
  return ColorSpec.fromRgba(rgba);
31
28
  }
@@ -198,7 +198,7 @@ export declare class TextInput extends ThemedWidget {
198
198
  readonly focusable = true;
199
199
  private accessor _value;
200
200
  private accessor _cursor;
201
- private accessor _blinkFrom;
201
+ private accessor _blink;
202
202
  get value(): string;
203
203
  set value(v: string);
204
204
  get cursorPosition(): CodePoint;
@@ -107,7 +107,7 @@ import { Viewport } from "../renderables/viewport.js";
107
107
  import { Animation } from "../core/animation.js";
108
108
  import { beat } from "../core/easing.js";
109
109
  import { Timeline } from "../core/timeline.js";
110
- import { holdOf, shownAt, stampFrame } from "../core/animation.js";
110
+ import { placed } from "../core/animation.js";
111
111
  import { levelOf } from "../core/anchor.js";
112
112
  import { ThemedWidget } from "./themed-widget.js";
113
113
  import { ink } from "./ink.js";
@@ -244,8 +244,24 @@ function wordSpan(clusters, skipped, taken) {
244
244
  * for the next, on a `beat`. A loop, so with its motion taken out it holds lit.
245
245
  */
246
246
  const CARET_BLINK = Animation.map(beat([0.5, 0.5]), (hold) => hold === 0);
247
- /** The caret of a frame drawn for no moment — a `print` — which nothing moves: lit, and never changing. */
248
- const CARET_UNTIMED = { lit: true, hold: { lastChange: Number.NEGATIVE_INFINITY, nextChange: Number.POSITIVE_INFINITY } };
247
+ /**
248
+ * The caret's blink restarted at `from`, as one animation of the frame's
249
+ * time: `CARET_BLINK` run forward from `from` at and after it, and backward
250
+ * from it before, so a timeline played backwards also finds the caret lit
251
+ * just after a key. Each answer is the run the time falls in, bounded at
252
+ * `from` where the runs meet — early where it must guess, never late. A
253
+ * function of `from` alone, so the frame hands it its time (`placed`).
254
+ */
255
+ function caretBlink(from) {
256
+ const forward = Timeline.run(Timeline.play(from, 1), CARET_BLINK);
257
+ const backward = Timeline.run(Timeline.play(from, -1), CARET_BLINK);
258
+ return Animation.of({
259
+ at: (t) => (t >= from ? forward : backward).at(t),
260
+ nextChange: (t) => (t >= from ? forward.nextChange(t) : Math.min(backward.nextChange(t), from)),
261
+ lastChange: (t) => (t >= from ? Math.max(forward.lastChange(t), from) : backward.lastChange(t)),
262
+ reduced: () => CARET_BLINK.reduced(),
263
+ });
264
+ }
249
265
  let TextInput = (() => {
250
266
  let _classSuper = ThemedWidget;
251
267
  let _instanceExtraInitializers = [];
@@ -255,9 +271,9 @@ let TextInput = (() => {
255
271
  let __cursor_decorators;
256
272
  let __cursor_initializers = [];
257
273
  let __cursor_extraInitializers = [];
258
- let __blinkFrom_decorators;
259
- let __blinkFrom_initializers = [];
260
- let __blinkFrom_extraInitializers = [];
274
+ let __blink_decorators;
275
+ let __blink_initializers = [];
276
+ let __blink_extraInitializers = [];
261
277
  let _placeholder_decorators;
262
278
  let _placeholder_initializers = [];
263
279
  let _placeholder_extraInitializers = [];
@@ -298,7 +314,7 @@ let TextInput = (() => {
298
314
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
299
315
  __value_decorators = [observableRef];
300
316
  __cursor_decorators = [observableRef];
301
- __blinkFrom_decorators = [observableRef];
317
+ __blink_decorators = [observableRef];
302
318
  _placeholder_decorators = [observableRef];
303
319
  _indicatorStyleOverride_decorators = [observableRef];
304
320
  _cursorStyleOverride_decorators = [observableRef];
@@ -328,7 +344,7 @@ let TextInput = (() => {
328
344
  __insertText_decorators = [action];
329
345
  __esDecorate(this, null, __value_decorators, { kind: "accessor", name: "_value", static: false, private: false, access: { has: obj => "_value" in obj, get: obj => obj._value, set: (obj, value) => { obj._value = value; } }, metadata: _metadata }, __value_initializers, __value_extraInitializers);
330
346
  __esDecorate(this, null, __cursor_decorators, { kind: "accessor", name: "_cursor", static: false, private: false, access: { has: obj => "_cursor" in obj, get: obj => obj._cursor, set: (obj, value) => { obj._cursor = value; } }, metadata: _metadata }, __cursor_initializers, __cursor_extraInitializers);
331
- __esDecorate(this, null, __blinkFrom_decorators, { kind: "accessor", name: "_blinkFrom", static: false, private: false, access: { has: obj => "_blinkFrom" in obj, get: obj => obj._blinkFrom, set: (obj, value) => { obj._blinkFrom = value; } }, metadata: _metadata }, __blinkFrom_initializers, __blinkFrom_extraInitializers);
347
+ __esDecorate(this, null, __blink_decorators, { kind: "accessor", name: "_blink", static: false, private: false, access: { has: obj => "_blink" in obj, get: obj => obj._blink, set: (obj, value) => { obj._blink = value; } }, metadata: _metadata }, __blink_initializers, __blink_extraInitializers);
332
348
  __esDecorate(this, null, _placeholder_decorators, { kind: "accessor", name: "placeholder", static: false, private: false, access: { has: obj => "placeholder" in obj, get: obj => obj.placeholder, set: (obj, value) => { obj.placeholder = value; } }, metadata: _metadata }, _placeholder_initializers, _placeholder_extraInitializers);
333
349
  __esDecorate(this, null, _indicatorStyleOverride_decorators, { kind: "accessor", name: "indicatorStyleOverride", static: false, private: false, access: { has: obj => "indicatorStyleOverride" in obj, get: obj => obj.indicatorStyleOverride, set: (obj, value) => { obj.indicatorStyleOverride = value; } }, metadata: _metadata }, _indicatorStyleOverride_initializers, _indicatorStyleOverride_extraInitializers);
334
350
  __esDecorate(this, null, _cursorStyleOverride_decorators, { kind: "accessor", name: "cursorStyleOverride", static: false, private: false, access: { has: obj => "cursorStyleOverride" in obj, get: obj => obj.cursorStyleOverride, set: (obj, value) => { obj.cursorStyleOverride = value; } }, metadata: _metadata }, _cursorStyleOverride_initializers, _cursorStyleOverride_extraInitializers);
@@ -366,13 +382,12 @@ let TextInput = (() => {
366
382
  #_cursor_accessor_storage = (__runInitializers(this, __value_extraInitializers), __runInitializers(this, __cursor_initializers, void 0));
367
383
  get _cursor() { return this.#_cursor_accessor_storage; }
368
384
  set _cursor(value) { this.#_cursor_accessor_storage = value; }
369
- #_blinkFrom_accessor_storage = (__runInitializers(this, __cursor_extraInitializers), __runInitializers(this, __blinkFrom_initializers, 0));
370
- // When the field last took focus, a click, or a key it handled, on the
371
- // frame's clock (each event's `time`): where the caret's blink restarts.
372
- // The blink itself is a function of this and the time a frame is drawn
373
- // for, so nothing about it is kept.
374
- get _blinkFrom() { return this.#_blinkFrom_accessor_storage; }
375
- set _blinkFrom(value) { this.#_blinkFrom_accessor_storage = value; }
385
+ #_blink_accessor_storage = (__runInitializers(this, __cursor_extraInitializers), __runInitializers(this, __blink_initializers, caretBlink(0)));
386
+ // The caret's blink, restarted (`caretBlink`) when the field last took
387
+ // focus, a click, or a key it handled, at that event's `time` on the
388
+ // frame's clock. Built at the event, so a frame only reads it.
389
+ get _blink() { return this.#_blink_accessor_storage; }
390
+ set _blink(value) { this.#_blink_accessor_storage = value; }
376
391
  // [LAW:single-enforcer] Every write to either half of the pair passes
377
392
  // `graphemeBoundary`, so the cursor is a cluster boundary inside `value`
378
393
  // whoever set it last — a caller replacing `value`, a click, an insert that
@@ -388,7 +403,7 @@ let TextInput = (() => {
388
403
  }
389
404
  get cursorPosition() { return this._cursor; }
390
405
  set cursorPosition(p) { this._cursor = graphemeBoundary(this._value, p); }
391
- #placeholder_accessor_storage = (__runInitializers(this, __blinkFrom_extraInitializers), __runInitializers(this, _placeholder_initializers, void 0));
406
+ #placeholder_accessor_storage = (__runInitializers(this, __blink_extraInitializers), __runInitializers(this, _placeholder_initializers, void 0));
392
407
  get placeholder() { return this.#placeholder_accessor_storage; }
393
408
  set placeholder(value) { this.#placeholder_accessor_storage = value; }
394
409
  _maxLength = __runInitializers(this, _placeholder_extraInitializers);
@@ -468,7 +483,7 @@ let TextInput = (() => {
468
483
  this._edit(event);
469
484
  if (!event.stopped)
470
485
  return;
471
- this._blinkFrom = event.time;
486
+ this._blink = caretBlink(event.time);
472
487
  // An edit on the cursor's row moves no followed range, so the row is
473
488
  // brought into view here too: typing after the wheel scrolled away scrolls
474
489
  // back. The rows are the edited value's, at the width last drawn.
@@ -479,7 +494,7 @@ let TextInput = (() => {
479
494
  }
480
495
  handleFocus(event) {
481
496
  super.handleFocus(event);
482
- this._blinkFrom = event.time;
497
+ this._blink = caretBlink(event.time);
483
498
  }
484
499
  _edit(event) {
485
500
  if (this.disabled)
@@ -653,7 +668,7 @@ let TextInput = (() => {
653
668
  return;
654
669
  if (event.type !== "mouse_down")
655
670
  return;
656
- this._blinkFrom = event.time;
671
+ this._blink = caretBlink(event.time);
657
672
  // [LAW:no-silent-failure] Every cell the field draws carries its frame's
658
673
  // layout, so a press without one did not come off a frame it drew.
659
674
  const layout = layoutOn(event.cell);
@@ -1091,20 +1106,14 @@ let TextInput = (() => {
1091
1106
  return { lines: withArrows, layout: { drawn, kind: "rows", rows: visualRows, offset } };
1092
1107
  }
1093
1108
  // The caret's cell for the frame `options` draws: the blink restarted at the
1094
- // last touch, sampled at the frame's time (`shownAt`, so under reduced
1095
- // motion it holds lit). It runs away from the restart in either direction
1096
- // of time, so a timeline played backwards also finds it lit just after a
1097
- // key. Lit, the cell is the cursor's; dark, it is drawn as the
1098
- // text around it, since the cursor is a colour pair and no attribute hides
1099
- // it. The cell says when it next flips, which is when a host draws again.
1109
+ // last touch, placed as every animation is (`placed`), so it draws at the
1110
+ // frame's time, holds lit under reduced motion, and the cell says when it
1111
+ // next flips, which is when a host draws again. A print, drawn for no
1112
+ // moment, draws it at 0 like any placed animation. Lit, the cell is the
1113
+ // cursor's; dark, it is drawn as the text around it, since the cursor is a
1114
+ // colour pair and no attribute hides it.
1100
1115
  _caret(at, cursorStyle, contentStyle, options) {
1101
- const { frame } = options;
1102
- const blink = (time) => {
1103
- const shown = shownAt(Timeline.run(Timeline.play(this._blinkFrom, time < this._blinkFrom ? -1 : 1), CARET_BLINK), time, options);
1104
- return { lit: shown.value, hold: holdOf(shown, time) };
1105
- };
1106
- const { lit, hold } = frame === undefined ? CARET_UNTIMED : blink(frame.time);
1107
- return stampFrame([new Segment(at, lit ? cursorStyle : contentStyle)], hold);
1116
+ return placed(this._blink, options, (lit) => [new Segment(at, lit ? cursorStyle : contentStyle)]);
1108
1117
  }
1109
1118
  // Whether `rows` visual rows draw a scroll arrow in the last cell of a row.
1110
1119
  _drawsArrows(rows) {
@@ -5,7 +5,7 @@
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
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
8
+ import type { HostFrame, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
9
9
  import type { Segment } from "../core/segment.js";
10
10
  import type { Anchor } from "../core/anchor.js";
11
11
  import type { Unsubscribe } from "../core/subscription.js";
@@ -71,6 +71,6 @@ export interface FocusManager {
71
71
  export interface FrameSource {
72
72
  readonly focusManager: FocusManager;
73
73
  readonly frame: readonly (readonly Segment[])[];
74
- readonly frameTime: number;
74
+ frameNow(): HostFrame;
75
75
  refresh(): void;
76
76
  }
@@ -26,7 +26,7 @@
26
26
  * hear the input.
27
27
  */
28
28
  import { Segment } from "../core/segment.js";
29
- import type { FrameHost, Presence } from "../core/protocol.js";
29
+ import type { HostFrame, FrameHost, Presence } from "../core/protocol.js";
30
30
  import type { Unsubscribe } from "../core/subscription.js";
31
31
  import type { Clock, FrameRate } from "../core/clock.js";
32
32
  import type { Timeline } from "../core/timeline.js";
@@ -72,8 +72,8 @@ export declare class WidgetApp implements FrameSource, FrameHost {
72
72
  private get widgets();
73
73
  /** The rows on screen now, as painted: what a pointer event lands on. */
74
74
  get frame(): readonly (readonly Segment[])[];
75
- /** The time a frame drawn now is drawn for, as `App.frameTime` reads it. */
76
- get frameTime(): number;
75
+ /** The frame this app draws now (`FrameHost`): the App's time, on a frame this app is the host of, as its frames say. */
76
+ frameNow(): HostFrame;
77
77
  /**
78
78
  * Take the terminal, paint the first frame, and route input to its widgets
79
79
  * until the app stops — `App.run`, with the input.
@@ -50,7 +50,7 @@ export class WidgetApp {
50
50
  // The view is drawn inside `compose`, where what it reads is tracked —
51
51
  // a function view asked there too — and when it changes is its own to
52
52
  // say, as it would be to the App.
53
- // Its frames are its own, drawn for its `frameTime`: what draws in them
53
+ // Its frames are its own, drawn for its `frameNow()`: what draws in them
54
54
  // is told this app is the host, not the App it paints through, so
55
55
  // `drawnBy: widgetApp` names the host its frames carry.
56
56
  this.app = new App({
@@ -65,7 +65,7 @@ export class WidgetApp {
65
65
  const owners = ownersOn(this.app.frame);
66
66
  return this.widgets.filter((widget) => owners.has(widget));
67
67
  },
68
- frameTime: () => this.frameTime,
68
+ frameTime: () => this.frameNow().time,
69
69
  });
70
70
  this.focusManager = this.focus;
71
71
  this.router = new EventRouter({ source: this, focusOnClick });
@@ -118,9 +118,9 @@ export class WidgetApp {
118
118
  get frame() {
119
119
  return this.app.frame;
120
120
  }
121
- /** The time a frame drawn now is drawn for, as `App.frameTime` reads it. */
122
- get frameTime() {
123
- return this.app.frameTime;
121
+ /** The frame this app draws now (`FrameHost`): the App's time, on a frame this app is the host of, as its frames say. */
122
+ frameNow() {
123
+ return { time: this.app.frameNow().time, host: this };
124
124
  }
125
125
  /**
126
126
  * Take the terminal, paint the first frame, and route input to its widgets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptctl/rich-js",
3
- "version": "0.25.2",
3
+ "version": "0.27.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",