@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
@@ -10,27 +10,32 @@
10
10
  * part of the drawing asks whether it is leaving. Telling it is a new value
11
11
  * (`leave`), as every `Timeline` operation is. [LAW:no-ambient-temporal-coupling]
12
12
  *
13
- * Each half is a `Transition` of a `Veil` on its own clock from 0, handed the time since
14
- * its moment, and its schedule carried onto the host's clock by
15
- * `Timeline.run` is that half's hold — every frame for a fade, once a hold
16
- * for one on a beat — so the transition's schedule, and `done` read off it,
17
- * stays the one rule for where a one-shot ends and when its frame changes,
18
- * and nothing here measures progress itself. The frames carry no word of
19
- * their own on when they change: placed, the lifecycle stamps them with its
20
- * hold at the frame's time (`placedRender`). The exit veils whatever the
21
- * arrival draws, so an element told to leave while it is still arriving leaves
22
- * from where it is, never shown whole first — though a colour exit is spread
23
- * over the cells each frame shows, so a cell the arrival adds can move one
24
- * already leaving back toward whole (`ownChange`, `./effects.ts`).
25
- * [LAW:dataflow-not-control-flow]
13
+ * Each half is a `Half`: handed the element, an `Animation<Renderable>` of
14
+ * it on the half's own clock from 0, in the shape of the library's other
15
+ * one-shot from renderables to an animation (`Change`, `./change.ts`). One
16
+ * value carries the half's frames and its holds, so neither is kept in
17
+ * agreement with the other by hand, and nothing here measures progress
18
+ * itself: a half's schedule carried onto the host's clock by `Timeline.run`
19
+ * is that half's hold — every frame for a fade, once a hold for one on a
20
+ * beat — and the moment it says it never changes again (`Animation.done`)
21
+ * is where the arrival ends and where the element is gone. The frames carry
22
+ * no word of their own on when they change: placed, the lifecycle stamps
23
+ * them with its hold at the frame's time (`placedRender`). The exit is
24
+ * drawn over whatever the arrival draws, so an element told to leave while
25
+ * it is still arriving leaves from where it is, never shown whole first —
26
+ * though a colour exit is spread over the cells each frame shows, so a cell
27
+ * the arrival adds can move one already leaving back toward whole
28
+ * (`ownChange`, `./effects.ts`). [LAW:dataflow-not-control-flow]
26
29
  *
27
30
  * A half draws the element through a colour transition (`throughColors`, a
28
- * `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each lift binds
29
- * its transition to the one wrapper that draws it, and to what that wrapper
31
+ * `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each binds its
32
+ * transition to the one wrapper that draws it, and to what that wrapper
30
33
  * needs — a theme for colours, nothing for glyphs — so nothing here asks which
31
- * kind a half is, and a line can type itself in and dissolve out. An exit is
32
- * a transition that ends with nothing drawn — `dissolveOut`, `backspace`,
33
- * `scrambleOut` — so the element has already gone from sight when it is gone.
34
+ * kind a half is, and a line can type itself in and dissolve out. A half
35
+ * written by hand is any function of that shape — an arrival that slides the
36
+ * element in, which no effect draws. An exit is a half that ends with nothing
37
+ * drawn — `dissolveOut`, `backspace`, `scrambleOut` — so the element has
38
+ * already gone from sight when it is gone.
34
39
  *
35
40
  * Gone is a fact the host reads (`gone(t)`) to drop the element, and from then
36
41
  * the lifecycle draws nothing and never changes, so a host still holding it
@@ -40,25 +45,30 @@
40
45
  import { Animation } from "../core/animation.js";
41
46
  import type { TerminalTheme } from "../core/color.js";
42
47
  import type { Renderable } from "../core/protocol.js";
43
- import { Transition } from "./effects.js";
48
+ import type { Transition } from "./effects.js";
44
49
  import type { GlyphTransition } from "./glyph-effects.js";
45
50
  /**
46
- * An element drawn through one half of its lifecycle at `t`, the seconds since
47
- * that half began, its effect's seeds named by `key`, saying nothing of when
48
- * that changes — the half's schedule does, as a `Recolored`'s and a
49
- * `Reglyphed`'s frame say nothing.
51
+ * One half of an element's lifecycle: handed the element and `key`, which
52
+ * names it for the half's seeds, the element as the half draws it from
53
+ * `t = 0`, the moment the half began, on its own clock. Its holds are the
54
+ * half's own, whatever element it is handed, and it is done from the first
55
+ * moment it says it never changes again (`Animation.done`): an arrival is
56
+ * the element from then, and an exit is gone. A half that never ends is
57
+ * refused when the lifecycle is made: an `Effected` or `GlyphEffected`
58
+ * handed in bare is one, since its own word is "now" at every time;
59
+ * `throughColors` and `throughGlyphs` lend one the transition's schedule.
50
60
  */
51
- export type Veil = (element: Renderable, t: number, key: string) => Renderable;
61
+ export type Half = (element: Renderable, key: string) => Animation<Renderable>;
52
62
  /** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
53
- export declare function throughColors(transition: Transition, theme: TerminalTheme): Transition<Veil>;
63
+ export declare function throughColors(transition: Transition, theme: TerminalTheme): Half;
54
64
  /** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
55
- export declare function throughGlyphs(transition: GlyphTransition): Transition<Veil>;
65
+ export declare function throughGlyphs(transition: GlyphTransition): Half;
56
66
  /** How an element arrives and leaves, and what it is named for their seeds. */
57
67
  export interface LifecycleOptions {
58
68
  /** The half it arrives through — `throughColors(fadeIn(curve, z), theme)`. */
59
- readonly enter: Transition<Veil>;
69
+ readonly enter: Half;
60
70
  /** The half it leaves through — `throughGlyphs(scrambleOut(timing, z))`. */
61
- readonly exit: Transition<Veil>;
71
+ readonly exit: Half;
62
72
  /** Names the element for both halves' seeds, as `Effected`'s and `GlyphEffected`'s key does. */
63
73
  readonly key: string;
64
74
  }
@@ -10,27 +10,32 @@
10
10
  * part of the drawing asks whether it is leaving. Telling it is a new value
11
11
  * (`leave`), as every `Timeline` operation is. [LAW:no-ambient-temporal-coupling]
12
12
  *
13
- * Each half is a `Transition` of a `Veil` on its own clock from 0, handed the time since
14
- * its moment, and its schedule carried onto the host's clock by
15
- * `Timeline.run` is that half's hold — every frame for a fade, once a hold
16
- * for one on a beat — so the transition's schedule, and `done` read off it,
17
- * stays the one rule for where a one-shot ends and when its frame changes,
18
- * and nothing here measures progress itself. The frames carry no word of
19
- * their own on when they change: placed, the lifecycle stamps them with its
20
- * hold at the frame's time (`placedRender`). The exit veils whatever the
21
- * arrival draws, so an element told to leave while it is still arriving leaves
22
- * from where it is, never shown whole first — though a colour exit is spread
23
- * over the cells each frame shows, so a cell the arrival adds can move one
24
- * already leaving back toward whole (`ownChange`, `./effects.ts`).
25
- * [LAW:dataflow-not-control-flow]
13
+ * Each half is a `Half`: handed the element, an `Animation<Renderable>` of
14
+ * it on the half's own clock from 0, in the shape of the library's other
15
+ * one-shot from renderables to an animation (`Change`, `./change.ts`). One
16
+ * value carries the half's frames and its holds, so neither is kept in
17
+ * agreement with the other by hand, and nothing here measures progress
18
+ * itself: a half's schedule carried onto the host's clock by `Timeline.run`
19
+ * is that half's hold — every frame for a fade, once a hold for one on a
20
+ * beat — and the moment it says it never changes again (`Animation.done`)
21
+ * is where the arrival ends and where the element is gone. The frames carry
22
+ * no word of their own on when they change: placed, the lifecycle stamps
23
+ * them with its hold at the frame's time (`placedRender`). The exit is
24
+ * drawn over whatever the arrival draws, so an element told to leave while
25
+ * it is still arriving leaves from where it is, never shown whole first —
26
+ * though a colour exit is spread over the cells each frame shows, so a cell
27
+ * the arrival adds can move one already leaving back toward whole
28
+ * (`ownChange`, `./effects.ts`). [LAW:dataflow-not-control-flow]
26
29
  *
27
30
  * A half draws the element through a colour transition (`throughColors`, a
28
- * `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each lift binds
29
- * its transition to the one wrapper that draws it, and to what that wrapper
31
+ * `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each binds its
32
+ * transition to the one wrapper that draws it, and to what that wrapper
30
33
  * needs — a theme for colours, nothing for glyphs — so nothing here asks which
31
- * kind a half is, and a line can type itself in and dissolve out. An exit is
32
- * a transition that ends with nothing drawn — `dissolveOut`, `backspace`,
33
- * `scrambleOut` — so the element has already gone from sight when it is gone.
34
+ * kind a half is, and a line can type itself in and dissolve out. A half
35
+ * written by hand is any function of that shape — an arrival that slides the
36
+ * element in, which no effect draws. An exit is a half that ends with nothing
37
+ * drawn — `dissolveOut`, `backspace`, `scrambleOut` — so the element has
38
+ * already gone from sight when it is gone.
34
39
  *
35
40
  * Gone is a fact the host reads (`gone(t)`) to drop the element, and from then
36
41
  * the lifecycle draws nothing and never changes, so a host still holding it
@@ -40,20 +45,31 @@
40
45
  import { Animation } from "../core/animation.js";
41
46
  import { firstReaching } from "../core/easing.js";
42
47
  import { Timeline } from "../core/timeline.js";
43
- import { Recolored } from "./effect.js";
44
- import { Transition } from "./effects.js";
45
- import { Reglyphed } from "./glyph-effect.js";
48
+ import { Effected } from "./effect.js";
49
+ import { GlyphEffected } from "./glyph-effect.js";
46
50
  /** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
47
51
  export function throughColors(transition, theme) {
48
- return lift(transition, (element, t, key) => new Recolored(element, transition.effect, { t, key, theme }));
52
+ return (element, key) => scheduled(new Effected(element, transition.effect, { key, theme }), transition.schedule);
49
53
  }
50
54
  /** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
51
55
  export function throughGlyphs(transition) {
52
- return lift(transition, (element, t, key) => new Reglyphed(element, transition.effect, { t, key }));
56
+ return (element, key) => scheduled(new GlyphEffected(element, transition.effect, { key }), transition.schedule);
53
57
  }
54
- /** [LAW:one-source-of-truth] A lifted half keeps its transition's schedule, so `done` and every hold are the transition's own. */
55
- function lift(transition, veil) {
56
- return new Transition(veil, transition.schedule);
58
+ /**
59
+ * `placed` — an element under a transition's effect — changing when
60
+ * `schedule`, the transition's own, says: its frames with the one word on
61
+ * when they change that the transition has and the wrapper lacks, whose own
62
+ * answer is "now" until it is drawn. [LAW:one-source-of-truth] Reduced,
63
+ * both are reduced together, as `onBeat` steps both: a one-shot's effect
64
+ * and its schedule each reduce to a cut at 0 (`transition`, `./effects.ts`).
65
+ */
66
+ function scheduled(placed, schedule) {
67
+ return Animation.of({
68
+ at: (t) => placed.at(t),
69
+ nextChange: (t) => schedule.nextChange(t),
70
+ lastChange: (t) => schedule.lastChange(t),
71
+ reduced: () => scheduled(placed.reduced(), schedule.reduced()),
72
+ });
57
73
  }
58
74
  /** What an element draws before it entered and once it is gone: no cells, and no width. */
59
75
  const NOTHING = { render: () => [], measure: () => ({ minimum: 0, maximum: 0 }) };
@@ -61,26 +77,50 @@ const NOTHING = { render: () => [], measure: () => ({ minimum: 0, maximum: 0 })
61
77
  export function lifecycle(element, options, entered) {
62
78
  if (!Number.isFinite(entered))
63
79
  throw new RangeError(`lifecycle: entered must be a finite time, got ${entered}`);
64
- return life(element, options, entered, Number.POSITIVE_INFINITY);
80
+ const { enter, exit, key } = options;
81
+ const arrival = ending(enter(element, key), "enter", key);
82
+ const departure = ending(exit(element, key), "exit", key);
83
+ return life(element, options, { arrival, arrived: doneAt(arrival, entered), departure }, entered, Number.POSITIVE_INFINITY);
65
84
  }
66
85
  /**
67
- * The first host moment `transition`, begun at host time `start`, is done:
68
- * its own `done` read on host time, never a sum `start + seconds`, which can
69
- * round to either side of it, as `Animation.sequence` carries its switch.
70
- * [LAW:single-enforcer] Since `x - start` never falls as `x` rises, a host time
71
- * is at or past this moment exactly when `done` holds there.
86
+ * The first host moment `half`, begun at host time `start`, is done: its own
87
+ * answer read on host time, never a sum `start + seconds`, which can round
88
+ * to either side of it, as `Animation.sequence` carries its switch.
89
+ * [LAW:single-enforcer] Since `x - start` never falls as `x` rises, a host
90
+ * time is at or past this moment exactly when `done` holds there. Done is
91
+ * for good, so a half done as it begins — the element at once — is done at
92
+ * `start`, and from a start it is not done at the walk is upward only: no
93
+ * half is asked about a time before it began.
72
94
  */
73
- function doneAt(transition, start) {
74
- return Number.isFinite(start) ? firstReaching(start, (x) => transition.done(x - start)) : start;
95
+ function doneAt(half, start) {
96
+ if (!Number.isFinite(start) || Animation.done(half, 0))
97
+ return start;
98
+ return firstReaching(start, (x) => Animation.done(half, x - start));
75
99
  }
76
- function life(element, options, entered, left) {
77
- const { enter, exit, key } = options;
78
- const arrived = doneAt(enter, entered);
79
- const goneAt = doneAt(exit, left);
80
- // The arrival's schedule on the host's clock: when its frame next changes, and since when it has been what it is.
81
- const arriving = Timeline.run(Timeline.play(entered), enter.schedule);
100
+ /**
101
+ * `half` over the element, asked where it ends on its own clock, or refused
102
+ * by its `role` and the element's `key` when it never does. [LAW:no-silent-failure]
103
+ * Kept, it would never arrive, or never be gone, and wake the host every frame
104
+ * for good; asked here, the refusal lands where the options were written and
105
+ * not at the first `leave`. A half on a beat that never ends is refused by the
106
+ * beat instead, where the walk asks it past what its holds resolve.
107
+ */
108
+ function ending(half, role, key) {
109
+ if (doneAt(half, 0) === Number.POSITIVE_INFINITY) {
110
+ throw new RangeError(`lifecycle "${key}": the ${role} half never ends: its animation never says it has stopped changing (Animation.done). An Effected or GlyphEffected on its own does not; throughColors(transition, theme) and throughGlyphs(transition) lend one the transition's schedule`);
111
+ }
112
+ return half;
113
+ }
114
+ function life(element, options, halves, entered, left) {
115
+ const { exit, key } = options;
116
+ const { arrival, arrived, departure } = halves;
117
+ // Where the exit is gone, and the exit's hold, are read off the exit over the element; the frame while
118
+ // leaving is the exit over what is shown, whose holds are the same by `Half`'s contract.
119
+ const goneAt = doneAt(departure, left);
120
+ // The arrival on the host's clock: when its frame next changes, and since when it has been what it is.
121
+ const arriving = Timeline.run(Timeline.play(entered), arrival);
82
122
  // What the element draws by `t` if it is not leaving: arriving through `enter`, then itself.
83
- const shown = (t) => (t < arrived ? enter.effect(element, t - entered, key) : element);
123
+ const shown = (t) => (t < arrived ? arrival.at(t - entered) : element);
84
124
  const held = (schedule, t) => ({ last: schedule.lastChange(t), next: schedule.nextChange(t) });
85
125
  // [LAW:one-source-of-truth] Each phase's frame and its hold, side by side, so the two cannot disagree.
86
126
  // The frames say nothing of when they change — the hold does, stamped where the lifecycle is placed.
@@ -89,10 +129,10 @@ function life(element, options, entered, left) {
89
129
  return { frame: NOTHING, last: Number.NEGATIVE_INFINITY, next: entered };
90
130
  if (t >= goneAt)
91
131
  return { frame: NOTHING, last: goneAt, next: Number.POSITIVE_INFINITY };
92
- // Leaving veils whatever arriving draws, so both schedules count; once arrived, the arrival's never changes again.
132
+ // Leaving is drawn over whatever arriving draws, so both schedules count; once arrived, the arrival's never changes again.
93
133
  if (t >= left) {
94
- const leaving = Animation.all([arriving, Timeline.run(Timeline.play(left), exit.schedule)]);
95
- return { frame: exit.effect(shown(t), t - left, key), ...held(leaving, t) };
134
+ const leaving = Animation.all([arriving, Timeline.run(Timeline.play(left), departure)]);
135
+ return { frame: exit(shown(t), key).at(t - left), ...held(leaving, t) };
96
136
  }
97
137
  if (t < arrived)
98
138
  return { frame: shown(t), ...held(arriving, t) };
@@ -126,7 +166,7 @@ function life(element, options, entered, left) {
126
166
  throw new RangeError(`lifecycle: already told to leave at ${left}, told again at ${at}`);
127
167
  if (!(Number.isFinite(at) && at >= entered))
128
168
  throw new RangeError(`lifecycle: leave must be a finite time from entered (${entered}) on, got ${at}`);
129
- return life(element, options, entered, at);
169
+ return life(element, options, halves, entered, at);
130
170
  },
131
171
  });
132
172
  }
@@ -26,7 +26,7 @@
26
26
  */
27
27
  import { type Clock } from "../core/clock.js";
28
28
  import { Console } from "../core/console.js";
29
- import { type FrameHost } from "../core/protocol.js";
29
+ import { type HostFrame, type FrameHost } from "../core/protocol.js";
30
30
  import { Timeline } from "../core/timeline.js";
31
31
  import { type View } from "../core/view.js";
32
32
  export interface LiveOptions {
@@ -79,8 +79,8 @@ export declare class Live implements FrameHost {
79
79
  get clock(): Clock;
80
80
  /** The map from the clock to the time the view is drawn for. */
81
81
  get timeline(): Timeline;
82
- /** The time a frame drawn now is drawn for: the clock's now, through the timeline. */
83
- get frameTime(): number;
82
+ /** The frame this Live draws now (`FrameHost`): the clock's now, through the timeline, on a frame it is the host of. */
83
+ frameNow(): HostFrame;
84
84
  private _time;
85
85
  /**
86
86
  * Replace the timeline and draw the frame for the time it now maps the
@@ -30,7 +30,7 @@ import { Segment } from "../core/segment.js";
30
30
  import { FinalFramePainter, SurfacePainter } from "../core/paint.js";
31
31
  import { fitHeight, render } from "../core/protocol.js";
32
32
  import { Timeline } from "../core/timeline.js";
33
- import { drawView, frameTime, hostView, undrawn } from "../core/view.js";
33
+ import { drawView, frameNow, hostView, undrawn } from "../core/view.js";
34
34
  import { State } from "../core/state.js";
35
35
  // Nothing: what a Live made with no view draws until `update` gives it one.
36
36
  const NOTHING = { render: () => [] };
@@ -89,9 +89,9 @@ export class Live {
89
89
  get timeline() {
90
90
  return this._timeline.get();
91
91
  }
92
- /** The time a frame drawn now is drawn for: the clock's now, through the timeline. */
93
- get frameTime() {
94
- return frameTime(this._time());
92
+ /** The frame this Live draws now (`FrameHost`): the clock's now, through the timeline, on a frame it is the host of. */
93
+ frameNow() {
94
+ return frameNow(this, this._time());
95
95
  }
96
96
  _time() {
97
97
  return { now: this._clock.now(), epoch: this._epoch, timeline: this._timeline.get() };
@@ -7,7 +7,7 @@ import { Console } from "../core/console.js";
7
7
  import type { Style } from "../core/style.js";
8
8
  import type { Clock } from "../core/clock.js";
9
9
  import { type ColumnOptions } from "./table.js";
10
- import type { FrameHost, Renderable, RenderOptions } from "../core/protocol.js";
10
+ import type { HostFrame, FrameHost, Renderable, RenderOptions } from "../core/protocol.js";
11
11
  import { Animation } from "../core/animation.js";
12
12
  import type { Timing } from "../core/easing.js";
13
13
  export interface TaskOptions {
@@ -156,7 +156,7 @@ export declare class MofNCompleteColumn implements ProgressColumn {
156
156
  }
157
157
  /**
158
158
  * How a `Progress` is set up. It is drawn by one host, and every time it
159
- * keeps or draws at is that host's `frameTime`: by its own `Live`, which the
159
+ * keeps or draws at is that host's `frameNow()`: by its own `Live`, which the
160
160
  * options for that `Live` configure, or, given `drawnBy`, by another host —
161
161
  * which then does all the drawing, so none of them apply.
162
162
  */
@@ -185,7 +185,7 @@ interface OwnLiveOptions {
185
185
  interface DrawnByOptions {
186
186
  /**
187
187
  * The host — a `Live`, an `App` — whose frames this `Progress` is drawn in:
188
- * tasks are timed by its `frameTime`, so they hold while its timeline is
188
+ * tasks are timed by its `frameNow()`, so they hold while its timeline is
189
189
  * paused and run back while it is reversed. Drawn in any other host's
190
190
  * frame, the `Progress` throws.
191
191
  */
@@ -207,6 +207,13 @@ export declare class Progress implements Renderable {
207
207
  constructor(...columns: (ProgressColumn | ProgressOptions)[]);
208
208
  /** The console its own `Live` prints above the bars through. */
209
209
  get console(): Console;
210
+ /**
211
+ * The frame its host draws now (`FrameHost.frameNow`): what a print of
212
+ * this display is drawn in — `console.print(progress, { frame:
213
+ * progress.frameNow() })` — so a still of it is drawn for a moment on the
214
+ * clock its tasks are timed on, the one the program asked at.
215
+ */
216
+ frameNow(): HostFrame;
210
217
  /** Rich's `Progress.finished`: every task finished, and so true with none. */
211
218
  get finished(): boolean;
212
219
  static getDefaultColumns(): ProgressColumn[];
@@ -224,7 +231,7 @@ export declare class Progress implements Renderable {
224
231
  refresh(): void;
225
232
  private _ownLive;
226
233
  render(options: RenderOptions): Iterable<Segment>;
227
- private _framed;
234
+ private _ownFrame;
228
235
  private _table;
229
236
  }
230
237
  export declare function track<T>(iterable: Iterable<T>, options?: {
@@ -321,9 +321,20 @@ export class Progress {
321
321
  get console() {
322
322
  return this._ownLive("console").console;
323
323
  }
324
+ /**
325
+ * The frame its host draws now (`FrameHost.frameNow`): what a print of
326
+ * this display is drawn in — `console.print(progress, { frame:
327
+ * progress.frameNow() })` — so a still of it is drawn for a moment on the
328
+ * clock its tasks are timed on, the one the program asked at.
329
+ */
330
+ frameNow() {
331
+ return this._host.frameNow();
332
+ }
324
333
  /** Rich's `Progress.finished`: every task finished, and so true with none. */
325
334
  get finished() {
326
- return this._tasks.get().every((task) => finished(taskAt(task.get(), this._host.frameTime)));
335
+ // [LAW:one-source-of-truth] One moment, so every task is judged at it.
336
+ const now = this._host.frameNow().time;
337
+ return this._tasks.get().every((task) => finished(taskAt(task.get(), now)));
327
338
  }
328
339
  static getDefaultColumns() {
329
340
  return [
@@ -342,7 +353,7 @@ export class Progress {
342
353
  completed: 0,
343
354
  fill: Animation.constant(0),
344
355
  visible: options?.visible !== false,
345
- startTime: options?.start === false ? undefined : this._host.frameTime,
356
+ startTime: options?.start === false ? undefined : this._host.frameNow().time,
346
357
  finishedAt: undefined,
347
358
  samples: [],
348
359
  }));
@@ -364,7 +375,7 @@ export class Progress {
364
375
  const completed = options.completed === undefined
365
376
  ? finiteCount("completed + advance", task.completed + advance)
366
377
  : finiteCount("completed", options.completed);
367
- const now = this._host.frameTime;
378
+ const now = this._host.frameNow().time;
368
379
  const reached = task.startTime !== undefined && task.total !== undefined && completed >= task.total;
369
380
  held.set({
370
381
  ...task,
@@ -385,7 +396,7 @@ export class Progress {
385
396
  const held = this._task(taskId);
386
397
  // Rich's `start_task`: a task already running keeps the time it started.
387
398
  if (held)
388
- held.set({ ...held.get(), startTime: held.get().startTime ?? this._host.frameTime });
399
+ held.set({ ...held.get(), startTime: held.get().startTime ?? this._host.frameNow().time });
389
400
  }
390
401
  start() {
391
402
  this._ownLive("start").start();
@@ -410,23 +421,24 @@ export class Progress {
410
421
  return this._live;
411
422
  }
412
423
  *render(options) {
413
- const framed = this._framed(options);
414
- yield* render(this._table(framed.frame.time), framed);
424
+ yield* render(this._table(this._ownFrame(options).time), options);
415
425
  }
416
- // Inside a frame, the time its host read for it: a second read here would
417
- // land a moving bar's stamp a hair after the frame, a moment the host takes
418
- // for one still to come. A frame is only ever this display's host's,
419
- // because the tasks were stamped on that host's time and a frame of any
420
- // other is drawn for a moment on a different clock. A print is drawn for no
421
- // moment, so it is drawn in a frame for the one the program asks at, read
422
- // as `updateTask` reads it, and every bar and spinner placed in the table
423
- // draws at that moment too. [LAW:one-source-of-truth]
424
- _framed(options) {
426
+ // The frame this display is drawn in, which is only ever its host's: the
427
+ // tasks were stamped on that host's time, and a frame of any other host is
428
+ // drawn for a moment on a different clock. A render reads no clock, so a
429
+ // print names its frame (`frameNow`, `PrintOptions.frame`): drawn for no
430
+ // moment — 0, as everything else is — every task would read as not yet
431
+ // started, so that print is refused with the call that draws it right.
432
+ // [LAW:parse-dont-validate] [LAW:no-silent-failure]
433
+ _ownFrame(options) {
425
434
  const { frame } = options;
426
- if (frame !== undefined && frame.host !== this._host) {
435
+ if (frame === undefined) {
436
+ throw new Error("a Progress is drawn for a moment on its host's clock, and this print named none; print it in its frame: console.print(progress, { frame: progress.frameNow() })");
437
+ }
438
+ if (frame.host !== this._host) {
427
439
  throw new Error("a Progress was drawn in a frame of a host other than its own; give it that host as `drawnBy`");
428
440
  }
429
- return { ...options, frame: frame ?? { time: this._host.frameTime, host: this._host } };
441
+ return frame;
430
442
  }
431
443
  // Rich's `make_tasks_table`: each column laid out as it says, at `now` on
432
444
  // this display's clock.
@@ -1,5 +1,5 @@
1
1
  import { ColorRgba } from "../core/color.js";
2
- import { Palette } from "./palette.js";
2
+ import { Palette, type PaletteRole, type PaletteRoles } from "./palette.js";
3
3
  /**
4
4
  * Base colors required to build a full semantic palette.
5
5
  * All are ColorRgba — callers construct from whatever source (hex, HSL, etc.).
@@ -14,44 +14,19 @@ export interface BaseColors {
14
14
  background: ColorRgba;
15
15
  foreground: ColorRgba;
16
16
  }
17
- type AccentKey = "primary" | "secondary" | "accent" | "success" | "warning" | "error";
18
- export declare const ACCENT_KEYS: readonly AccentKey[];
17
+ /** The eight roles every palette is given, and every role it may also state. */
18
+ export type StatedRoles = BaseColors & Partial<PaletteRoles>;
19
+ /** Every role of every palette, read off the rules. */
20
+ export declare const PALETTE_ROLES: readonly PaletteRole[];
19
21
  /**
20
- * Build a full semantic palette from base colors.
22
+ * Build a full semantic palette from its base colors and any other role it
23
+ * states: a stated role keeps its colour, and everything derived from it
24
+ * follows it, but for `text-*`, which is held to read on its `*-muted`. An
25
+ * authored theme is this with most roles stated; see `RULES` for what a role
26
+ * not stated becomes.
21
27
  *
22
- * Derived entries follow Textual's formulas:
23
- * - `*-muted` = color blended 70% toward its opposite base role. Defined for
24
- * every accent AND for the two base roles themselves
25
- * (`foreground-muted` blends toward `background`,
26
- * `background-muted` blends toward `foreground`) — a caller
27
- * de-emphasizing body/structural text reaches for
28
- * `foreground-muted` the same way it reaches for
29
- * `primary-muted` to de-emphasize an accent.
30
- * - `text-*` = contrast text tinted 66% with the accent color, then held
31
- * to WCAG AA (4.5:1) on `*-muted` — the foreground for a
32
- * `*-muted` ground
33
- * - `on-*` = WCAG-correct contrast colour (black or white) for use as
34
- * foreground when the FULL accent is the background. Picked
35
- * by relative luminance — single source of truth so widgets
36
- * never need to invert / reverse fg/bg to get readable text.
37
- * - `surface` = background blended 5% toward foreground
38
- *
39
- * A translucent base colour is drawn onto `background` before anything is
40
- * derived from it, so `on-*` is chosen against the colour the accent is drawn
41
- * as and every derived entry is opaque.
42
- */
43
- export declare function buildPalette(name: string, dark: boolean, given: BaseColors): Palette;
44
- /**
45
- * A palette's `text-*`: its tint held to WCAG AA on `*-muted`, moved toward
46
- * the palette's own text side so it also reads on `background`, where widgets
47
- * draw it too. The muted floor is the promise: where no lightness on the text
48
- * side clears it, the colour that does is on the other side, and the
49
- * background pair is the one given up. Every bundled palette clears both
50
- * (test/themes/text-on-muted.test.ts).
51
- *
52
- * [LAW:single-enforcer] The one rule for the pair, for the palettes derived
53
- * here and the authored ones the registry hydrates: the tint is the theme's,
54
- * whether it reads is decided here.
28
+ * A translucent colour is drawn onto `background` before anything is derived
29
+ * from it, so `on-*` is chosen against the colour the accent is drawn as.
30
+ * `boost` alone stays a layer: it is what `panel` composites.
55
31
  */
56
- export declare function accentText(tint: ColorRgba, muted: ColorRgba, background: ColorRgba): ColorRgba;
57
- export {};
32
+ export declare function buildPalette(name: string, dark: boolean, given: StatedRoles): Palette;