@promptctl/rich-js 0.26.0 → 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 (47) 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 +5 -4
  18. package/dist/renderables/effect.d.ts +7 -7
  19. package/dist/renderables/effects.js +8 -3
  20. package/dist/renderables/lifecycle.d.ts +38 -28
  21. package/dist/renderables/lifecycle.js +85 -45
  22. package/dist/renderables/live.d.ts +3 -3
  23. package/dist/renderables/live.js +4 -4
  24. package/dist/renderables/progress.d.ts +11 -4
  25. package/dist/renderables/progress.js +29 -17
  26. package/dist/themes/buildPalette.d.ts +14 -39
  27. package/dist/themes/buildPalette.js +196 -67
  28. package/dist/themes/colorMath.d.ts +10 -4
  29. package/dist/themes/colorMath.js +33 -1
  30. package/dist/themes/data/types.d.ts +3 -1
  31. package/dist/themes/palette.d.ts +22 -6
  32. package/dist/themes/palette.js +13 -21
  33. package/dist/themes/registry.d.ts +2 -3
  34. package/dist/themes/registry.js +7 -26
  35. package/dist/themes/terminalThemes.d.ts +6 -5
  36. package/dist/themes/terminalThemes.js +6 -5
  37. package/dist/themes/transpose.d.ts +5 -10
  38. package/dist/themes/transpose.js +13 -24
  39. package/dist/widgets/event-router.js +2 -2
  40. package/dist/widgets/ink.d.ts +5 -4
  41. package/dist/widgets/ink.js +6 -9
  42. package/dist/widgets/text-input.d.ts +1 -1
  43. package/dist/widgets/text-input.js +41 -32
  44. package/dist/widgets/types.d.ts +2 -2
  45. package/dist/widgets/widget-app.d.ts +3 -3
  46. package/dist/widgets/widget-app.js +5 -5
  47. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  export { cellLen, setCellSize, splitText, chopCells, cellFit, graphemes, asCellCol, asCodePoint, } from "./core/cells.js";
2
2
  export type { CellCol, CodeUnit, CodePoint } from "./core/cells.js";
3
- export { ColorRgba, ColorTable, ColorDepth, ColorSpec, ColorParseError, TerminalTheme, parseRgbHex, parseRgbaHex, parseHexColor, blendRgb, resolveColorSystem, detectColorSystem, resolveDestination, STANDARD_TABLE, EIGHT_BIT_TABLE, WINDOWS_TABLE, ANSI_COLOR_NAMES, } from "./core/color.js";
3
+ export { ColorRgba, ColorTable, ColorDepth, ColorSpec, ColorParseError, TerminalTheme, type TerminalColors, parseRgbHex, parseRgbaHex, parseHexColor, blendRgb, resolveColorSystem, detectColorSystem, resolveDestination, STANDARD_TABLE, EIGHT_BIT_TABLE, WINDOWS_TABLE, ANSI_COLOR_NAMES, } from "./core/color.js";
4
4
  export type { ColorSystemName, DetectColorOptions, Destination, DestinationOptions } from "./core/color.js";
5
5
  export { Oklch, IDENTITY, INVERT_LIGHTNESS, isIdentityKey, } from "./core/oklch.js";
6
6
  export type { ThemeKey, OklchWeights } from "./core/oklch.js";
7
7
  export { Palette } from "./themes/palette.js";
8
+ export type { AccentRole, BaseRole, PaletteRole, PaletteRoles, ShadedRole } from "./themes/palette.js";
8
9
  export { resolveColorRef, ColorRefError } from "./themes/colorRef.js";
9
10
  export { buildPalette } from "./themes/buildPalette.js";
10
- export type { BaseColors } from "./themes/buildPalette.js";
11
+ export type { BaseColors, StatedRoles } from "./themes/buildPalette.js";
11
12
  export { DEFAULT_TERMINAL_THEME, MONOKAI, SVG_EXPORT_THEME, NORD, GRUVBOX, DRACULA, TOKYO_NIGHT, FLEXOKI, CYBERPUNK, CATPPUCCIN_MOCHA, CATPPUCCIN_LATTE, CATPPUCCIN_FRAPPE, CATPPUCCIN_MACCHIATO, SOLARIZED_DARK, SOLARIZED_LIGHT, ROSE_PINE, ROSE_PINE_MOON, ROSE_PINE_DAWN, ATOM_ONE_DARK, ATOM_ONE_LIGHT, TEXTUAL_DARK, TEXTUAL_LIGHT, TEXTUAL_ANSI, } from "./themes/terminalThemes.js";
12
13
  export { getThemePalette, listThemePalettes } from "./themes/registry.js";
13
14
  export type { ThemeName } from "./themes/registry.js";
@@ -33,7 +34,7 @@ export { Box } from "./core/box.js";
33
34
  export type { EdgeChars, ContentChars, RowLevel, SubstituteOptions } from "./core/box.js";
34
35
  export { ASCII, ASCII2, ASCII_DOUBLE_HEAD, SQUARE, SQUARE_DOUBLE_HEAD, MINIMAL, MINIMAL_HEAVY_HEAD, MINIMAL_DOUBLE_HEAD, SIMPLE, SIMPLE_HEAD, SIMPLE_HEAVY, HORIZONTALS, ROUNDED, HEAVY, HEAVY_EDGE, HEAVY_HEAD, DOUBLE, DOUBLE_EDGE, MARKDOWN, } from "./core/box.js";
35
36
  export { render, isRenderable, isMeasurable, withCellWidth, withBoundedWidth, insetHeight, stackedHeight, regionRows, fitHeight, getStyle, } from "./core/protocol.js";
36
- export type { RenderOptions, OverflowMethod, Height, Renderable, Measurable, Presence, StyleErrorHandler, FrameHost, } from "./core/protocol.js";
37
+ export type { RenderOptions, OverflowMethod, Height, Renderable, Measurable, Presence, StyleErrorHandler, FrameHost, HostFrame, } from "./core/protocol.js";
37
38
  export type { Unsubscribe } from "./core/subscription.js";
38
39
  export { Measurement, measureRenderables } from "./core/measure.js";
39
40
  export { Span, RichText } from "./core/text.js";
@@ -67,7 +68,7 @@ export type { AttributeLoopName, Blink, ColorStep, Curve, EffectTarget, Field, L
67
68
  export { crossfade, wipe } from "./renderables/change.js";
68
69
  export type { Between, Change } from "./renderables/change.js";
69
70
  export { lifecycle, throughColors, throughGlyphs } from "./renderables/lifecycle.js";
70
- export type { Lifecycle, LifecycleOptions, Veil } from "./renderables/lifecycle.js";
71
+ export type { Half, Lifecycle, LifecycleOptions } from "./renderables/lifecycle.js";
71
72
  export { scroll } from "./renderables/scroll.js";
72
73
  export type { ScrollOptions } from "./renderables/scroll.js";
73
74
  export { GlyphEffected } from "./renderables/glyph-effect.js";
@@ -71,7 +71,7 @@
71
71
  import { type Anchor } from "../core/anchor.js";
72
72
  import { Animation } from "../core/animation.js";
73
73
  import type { Hold } from "../core/style.js";
74
- import { ColorDepth, type ColorRgba, type TerminalTheme } from "../core/color.js";
74
+ import { ColorDepth, type ColorRgba, type TerminalColors } from "../core/color.js";
75
75
  import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
76
76
  import { Segment } from "../core/segment.js";
77
77
  import { type Attributes } from "../core/style.js";
@@ -194,7 +194,7 @@ export interface Drawn {
194
194
  /** The depth the frame is written at: below truecolor a moved colour lands on a palette colour. */
195
195
  readonly depth: ColorDepth;
196
196
  /** The terminal the frame is drawn on. */
197
- readonly theme: TerminalTheme;
197
+ readonly theme: TerminalColors;
198
198
  }
199
199
  /** What an effect does to the cells of one frame: each cell's look and where it is in, its look out. Return `look` to leave a cell be. */
200
200
  export type EffectFrame = (look: CellLook, cell: EffectCell) => CellLook;
@@ -224,7 +224,7 @@ export interface EffectedOptions {
224
224
  * drawn in, and the sixteen an ANSI colour names. The terminal's own palette
225
225
  * is never queried, so the caller says what it is.
226
226
  */
227
- readonly theme: TerminalTheme;
227
+ readonly theme: TerminalColors;
228
228
  }
229
229
  /** A frame an effect drew at a moment, and the hold the effect says it is in there. */
230
230
  export interface Moved {
@@ -243,7 +243,7 @@ export declare class Recolored implements Renderable, Measurable {
243
243
  readonly effect: Effect;
244
244
  readonly t: number;
245
245
  readonly key: string;
246
- readonly theme: TerminalTheme;
246
+ readonly theme: TerminalColors;
247
247
  constructor(renderable: Renderable, effect: Effect, options: EffectedOptions & {
248
248
  readonly t: number;
249
249
  });
@@ -287,7 +287,7 @@ export declare class Effected implements Animation<Renderable> {
287
287
  readonly renderable: Renderable;
288
288
  readonly effect: Effect;
289
289
  readonly key: string;
290
- readonly theme: TerminalTheme;
290
+ readonly theme: TerminalColors;
291
291
  constructor(renderable: Renderable, effect: Effect, options: EffectedOptions);
292
292
  /** The element through the effect at `t`, unstamped. */
293
293
  at(t: number): Recolored;
@@ -308,7 +308,7 @@ export declare class Effected implements Animation<Renderable> {
308
308
  * [LAW:one-source-of-truth] Read through `movedSpecs`, the one writer a
309
309
  * moved slot is written with.
310
310
  */
311
- static shows(pair: Pair, to: CellColors, depth: ColorDepth, theme: TerminalTheme): CellColors;
311
+ static shows(pair: Pair, to: CellColors, depth: ColorDepth, theme: TerminalColors): CellColors;
312
312
  }
313
313
  /**
314
314
  * A cell's seed: FNV-1a of the element's key and the cell's position, scaled
@@ -355,7 +355,7 @@ export declare function confined(effect: Effect, ...owners: readonly object[]):
355
355
  * as `Effected` measures its child for every frame. Measure an element's
356
356
  * output under an effect to read the contrast its words are left at.
357
357
  */
358
- export declare function drawnOf(segments: Iterable<Segment>, depth: ColorDepth, theme: TerminalTheme): Drawn;
358
+ export declare function drawnOf(segments: Iterable<Segment>, depth: ColorDepth, theme: TerminalColors): Drawn;
359
359
  /**
360
360
  * The attributes a look carries, read off anything that sets them — a
361
361
  * `Style`, or a look a frame returned with more on it than `CellAttributes`
@@ -675,7 +675,7 @@ export class Transition {
675
675
  this.schedule = schedule;
676
676
  }
677
677
  done(t) {
678
- return this.schedule.nextChange(t) === Number.POSITIVE_INFINITY;
678
+ return Animation.done(this.schedule, t);
679
679
  }
680
680
  }
681
681
  /**
@@ -685,8 +685,13 @@ export class Transition {
685
685
  * glyph effect (`./glyph-effects.ts`) alike.
686
686
  */
687
687
  export function transition(seconds, effect) {
688
- // Moving every frame until `seconds`, then never: the sequence carries the switch.
689
- const schedule = Animation.sequence(Animation.continuous(() => undefined), seconds, Animation.constant(undefined));
688
+ // Moving every frame until `seconds`, then never: the sequence carries the switch. [LAW:one-source-of-truth]
689
+ // Reduced, a one-shot cuts from its start to its end at 0, and its schedule says so too, the one word on
690
+ // when the reduced frames change, so a half that lends this schedule to its frames tells the same hold.
691
+ const schedule = Animation.of({
692
+ ...Animation.sequence(Animation.continuous(() => undefined), seconds, Animation.constant(undefined)),
693
+ reduced: () => Animation.cut(undefined, undefined),
694
+ });
690
695
  const ran = new Transition((drawn) => {
691
696
  const moving = effect(drawn);
692
697
  return Animation.of({
@@ -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.