@promptctl/rich-js 0.24.1 → 0.25.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 (156) hide show
  1. package/README.md +7 -0
  2. package/dist/core/anchor.d.ts +5 -0
  3. package/dist/core/anchor.js +11 -4
  4. package/dist/core/animation.d.ts +175 -10
  5. package/dist/core/animation.js +283 -15
  6. package/dist/core/ansi.js +2 -2
  7. package/dist/core/box.d.ts +7 -7
  8. package/dist/core/cells.d.ts +23 -13
  9. package/dist/core/cells.js +58 -48
  10. package/dist/core/clock.d.ts +2 -2
  11. package/dist/core/clock.js +6 -4
  12. package/dist/core/color.d.ts +27 -10
  13. package/dist/core/color.js +21 -29
  14. package/dist/core/console.d.ts +28 -17
  15. package/dist/core/console.js +44 -39
  16. package/dist/core/easing.d.ts +30 -0
  17. package/dist/core/easing.js +65 -20
  18. package/dist/core/emoji.d.ts +1 -1
  19. package/dist/core/emoji.js +1 -2
  20. package/dist/core/export-html.js +2 -1
  21. package/dist/core/export-lines.js +3 -3
  22. package/dist/core/highlighter.d.ts +9 -8
  23. package/dist/core/highlighter.js +3 -2
  24. package/dist/core/json.js +2 -2
  25. package/dist/core/markup.js +16 -12
  26. package/dist/core/measure.js +2 -2
  27. package/dist/core/memo.d.ts +11 -2
  28. package/dist/core/memo.js +16 -0
  29. package/dist/core/oklch.js +17 -6
  30. package/dist/core/osc8.d.ts +1 -1
  31. package/dist/core/osc8.js +4 -5
  32. package/dist/core/paint.d.ts +2 -1
  33. package/dist/core/paint.js +4 -3
  34. package/dist/core/pattern.d.ts +21 -0
  35. package/dist/core/pattern.js +21 -0
  36. package/dist/core/pretty.js +9 -10
  37. package/dist/core/protocol.d.ts +100 -39
  38. package/dist/core/protocol.js +167 -21
  39. package/dist/core/render.d.ts +15 -2
  40. package/dist/core/render.js +25 -5
  41. package/dist/core/segment.d.ts +13 -5
  42. package/dist/core/segment.js +23 -9
  43. package/dist/core/spinnerData.d.ts +1 -1
  44. package/dist/core/state.d.ts +53 -0
  45. package/dist/core/state.js +82 -0
  46. package/dist/core/strip.d.ts +13 -16
  47. package/dist/core/strip.js +119 -110
  48. package/dist/core/style.d.ts +47 -14
  49. package/dist/core/style.js +57 -28
  50. package/dist/core/text.d.ts +5 -8
  51. package/dist/core/text.js +116 -124
  52. package/dist/core/timeline.d.ts +30 -5
  53. package/dist/core/timeline.js +43 -7
  54. package/dist/core/tween.d.ts +1 -1
  55. package/dist/core/tween.js +5 -3
  56. package/dist/core/view.d.ts +92 -0
  57. package/dist/core/view.js +83 -0
  58. package/dist/host/app.d.ts +49 -21
  59. package/dist/host/app.js +71 -37
  60. package/dist/host/input.js +3 -4
  61. package/dist/index.d.ts +24 -12
  62. package/dist/index.js +9 -4
  63. package/dist/node/traceback.js +4 -3
  64. package/dist/renderables/align.d.ts +2 -3
  65. package/dist/renderables/align.js +0 -3
  66. package/dist/renderables/change.d.ts +42 -0
  67. package/dist/renderables/change.js +112 -0
  68. package/dist/renderables/columns.d.ts +3 -4
  69. package/dist/renderables/columns.js +2 -7
  70. package/dist/renderables/constrain.d.ts +2 -3
  71. package/dist/renderables/constrain.js +0 -3
  72. package/dist/renderables/effect.d.ts +132 -54
  73. package/dist/renderables/effect.js +188 -115
  74. package/dist/renderables/effects.d.ts +122 -11
  75. package/dist/renderables/effects.js +238 -94
  76. package/dist/renderables/embed.d.ts +1 -1
  77. package/dist/renderables/embed.js +16 -8
  78. package/dist/renderables/flexStrip.d.ts +2 -3
  79. package/dist/renderables/flexStrip.js +0 -4
  80. package/dist/renderables/frames.d.ts +35 -54
  81. package/dist/renderables/frames.js +43 -58
  82. package/dist/renderables/glyph-effect.d.ts +158 -0
  83. package/dist/renderables/glyph-effect.js +237 -0
  84. package/dist/renderables/glyph-effects.d.ts +147 -0
  85. package/dist/renderables/glyph-effects.js +283 -0
  86. package/dist/renderables/group.d.ts +3 -4
  87. package/dist/renderables/group.js +0 -3
  88. package/dist/renderables/layout.d.ts +12 -12
  89. package/dist/renderables/layout.js +40 -46
  90. package/dist/renderables/lifecycle.d.ts +77 -0
  91. package/dist/renderables/lifecycle.js +132 -0
  92. package/dist/renderables/list-view.d.ts +4 -5
  93. package/dist/renderables/list-view.js +9 -13
  94. package/dist/renderables/live.d.ts +36 -21
  95. package/dist/renderables/live.js +88 -41
  96. package/dist/renderables/markdown-parse.d.ts +4 -4
  97. package/dist/renderables/markdown-parse.js +52 -46
  98. package/dist/renderables/markdown-url.js +11 -11
  99. package/dist/renderables/padding.d.ts +4 -5
  100. package/dist/renderables/padding.js +0 -3
  101. package/dist/renderables/panel.d.ts +14 -16
  102. package/dist/renderables/panel.js +6 -14
  103. package/dist/renderables/progress.d.ts +83 -34
  104. package/dist/renderables/progress.js +182 -95
  105. package/dist/renderables/progressBar.d.ts +22 -12
  106. package/dist/renderables/progressBar.js +32 -9
  107. package/dist/renderables/prompt.js +3 -2
  108. package/dist/renderables/python-space.js +2 -1
  109. package/dist/renderables/scroll.d.ts +50 -0
  110. package/dist/renderables/scroll.js +106 -0
  111. package/dist/renderables/spinner.d.ts +35 -14
  112. package/dist/renderables/spinner.js +65 -25
  113. package/dist/renderables/status.js +1 -1
  114. package/dist/renderables/syntax.d.ts +10 -9
  115. package/dist/renderables/syntax.js +3 -2
  116. package/dist/renderables/table.d.ts +34 -40
  117. package/dist/renderables/table.js +41 -63
  118. package/dist/renderables/traceback.d.ts +2 -2
  119. package/dist/renderables/traceback.js +6 -5
  120. package/dist/renderables/tree.d.ts +27 -25
  121. package/dist/renderables/tree.js +104 -58
  122. package/dist/renderables/viewport.d.ts +75 -44
  123. package/dist/renderables/viewport.js +145 -82
  124. package/dist/template-bindings/animation-funcs.d.ts +78 -9
  125. package/dist/template-bindings/animation-funcs.js +132 -14
  126. package/dist/template-bindings/index.d.ts +5 -5
  127. package/dist/template-bindings/index.js +5 -5
  128. package/dist/themes/ramp.d.ts +46 -3
  129. package/dist/themes/ramp.js +61 -4
  130. package/dist/themes/registry.js +5 -7
  131. package/dist/widgets/button.js +3 -2
  132. package/dist/widgets/checkbox.js +4 -3
  133. package/dist/widgets/dropdown.d.ts +3 -1
  134. package/dist/widgets/dropdown.js +20 -15
  135. package/dist/widgets/event-router.js +4 -2
  136. package/dist/widgets/focus-manager.d.ts +2 -0
  137. package/dist/widgets/focus-manager.js +6 -4
  138. package/dist/widgets/hit.d.ts +6 -0
  139. package/dist/widgets/hit.js +8 -1
  140. package/dist/widgets/observable.d.ts +23 -0
  141. package/dist/widgets/observable.js +43 -0
  142. package/dist/widgets/slider.d.ts +1 -1
  143. package/dist/widgets/slider.js +12 -3
  144. package/dist/widgets/static-item.d.ts +2 -3
  145. package/dist/widgets/static-item.js +0 -3
  146. package/dist/widgets/text-input.d.ts +28 -32
  147. package/dist/widgets/text-input.js +360 -346
  148. package/dist/widgets/themed-widget.js +2 -1
  149. package/dist/widgets/toggle.js +4 -3
  150. package/dist/widgets/types.d.ts +10 -4
  151. package/dist/widgets/types.js +8 -2
  152. package/dist/widgets/widget-app.d.ts +14 -4
  153. package/dist/widgets/widget-app.js +36 -7
  154. package/dist/widgets/widget-base.d.ts +2 -2
  155. package/dist/widgets/widget-base.js +29 -18
  156. package/package.json +8 -1
@@ -4,12 +4,11 @@
4
4
  */
5
5
  import type { Segment } from "../core/segment.js";
6
6
  import { type Measurement } from "../core/measure.js";
7
- import type { Composite, Measurable, Renderable, RenderOptions } from "../core/protocol.js";
8
- export declare class Group implements Renderable, Measurable, Composite {
9
- readonly renderables: Renderable[];
7
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
8
+ export declare class Group implements Renderable, Measurable {
9
+ readonly renderables: readonly Renderable[];
10
10
  constructor(...renderables: Renderable[]);
11
11
  render(options: RenderOptions): Iterable<Segment>;
12
12
  /** As wide as its widest member: the reference's `measure_renderables`. */
13
13
  measure(options: RenderOptions): Measurement;
14
- contents(): Iterable<Renderable>;
15
14
  }
@@ -19,7 +19,4 @@ export class Group {
19
19
  measure(options) {
20
20
  return measureRenderables(options, this.renderables);
21
21
  }
22
- contents() {
23
- return this.renderables;
24
- }
25
22
  }
@@ -2,7 +2,7 @@
2
2
  * Layout — divides the screen into rectangular regions.
3
3
  */
4
4
  import { Segment } from "../core/segment.js";
5
- import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
5
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
6
6
  import type { CellCol } from "../core/cells.js";
7
7
  import type { Weight } from "./ratio.js";
8
8
  export interface LayoutOptions {
@@ -12,16 +12,17 @@ export interface LayoutOptions {
12
12
  minimumSize?: number;
13
13
  visible?: boolean;
14
14
  }
15
- export declare class Layout implements Renderable, Measurable, Composite {
16
- name: string | undefined;
17
- visible: boolean;
18
- private _ratio;
19
- private _size;
20
- private _minimumSize;
21
- private _renderable;
22
- private _children;
23
- private _splitDirection;
15
+ export declare class Layout implements Renderable, Measurable {
16
+ readonly name: string | undefined;
17
+ private readonly _visible;
18
+ private readonly _ratio;
19
+ private readonly _size;
20
+ private readonly _minimumSize;
21
+ private readonly _renderable;
22
+ private readonly _split;
24
23
  constructor(renderable?: Renderable | string, options?: LayoutOptions);
24
+ get visible(): boolean;
25
+ set visible(value: boolean);
25
26
  /**
26
27
  * The three declared numbers, parsed on assignment rather than at the
27
28
  * constructor. All three are public and a caller reaches them long after
@@ -41,7 +42,7 @@ export declare class Layout implements Renderable, Measurable, Composite {
41
42
  set size(value: number | undefined);
42
43
  get minimumSize(): CellCol;
43
44
  set minimumSize(value: number);
44
- get children(): Layout[];
45
+ get children(): readonly Layout[];
45
46
  /**
46
47
  * Whether this layout draws `_renderable` or its children.
47
48
  *
@@ -57,7 +58,6 @@ export declare class Layout implements Renderable, Measurable, Composite {
57
58
  update(renderable: Renderable | string): void;
58
59
  getByName(name: string): Layout | undefined;
59
60
  render(rawOptions: RenderOptions): Iterable<Segment>;
60
- contents(): Iterable<Renderable>;
61
61
  private _renderColumn;
62
62
  private _renderRow;
63
63
  /**
@@ -8,6 +8,7 @@ import { render } from "../core/protocol.js";
8
8
  import { Measurement } from "../core/measure.js";
9
9
  import { cellCount } from "../core/cells.js";
10
10
  import { ratioBudget, ratioResolve, shareWeight } from "./ratio.js";
11
+ import { State } from "../core/state.js";
11
12
  /**
12
13
  * A pane rendered into a region of `rows` and held to exactly that many lines,
13
14
  * or — with `rows` undefined — rendered under the layout's own budget, as a
@@ -21,26 +22,31 @@ function paneLines(pane, options, rows) {
21
22
  const height = rows === undefined ? stackedHeight(options.height) : { rows, exact: true };
22
23
  return fitHeight(Segment.splitLines(render(pane, { ...options, height })), height);
23
24
  }
25
+ /** A declared size as a cell count; absence stays absence, a flex pane, which `cellCount` would read as zero. */
26
+ const sizeOf = (value) => (value === undefined ? undefined : cellCount(value));
24
27
  export class Layout {
25
28
  name;
26
- visible;
29
+ // Rich's `Layout` is split, updated, resized and hidden after it is built,
30
+ // so each of these is state, and a write to one while it is drawn throws.
31
+ _visible;
27
32
  _ratio;
28
33
  _size;
29
34
  _minimumSize;
30
35
  _renderable;
31
- _children;
32
- _splitDirection;
36
+ _split = new State({ direction: undefined, children: [] });
33
37
  constructor(renderable, options) {
34
- if (renderable !== undefined) {
35
- this._renderable = embed(renderable);
36
- }
38
+ this._renderable = new State(renderable === undefined ? undefined : embed(renderable));
37
39
  this.name = options?.name;
38
- this.ratio = options?.ratio ?? 1;
39
- this.size = options?.size;
40
- this.minimumSize = options?.minimumSize ?? 1;
41
- this.visible = options?.visible !== false;
42
- this._children = [];
43
- this._splitDirection = undefined;
40
+ this._ratio = new State(shareWeight(options?.ratio ?? 1));
41
+ this._size = new State(sizeOf(options?.size));
42
+ this._minimumSize = new State(cellCount(options?.minimumSize ?? 1));
43
+ this._visible = new State(options?.visible !== false);
44
+ }
45
+ get visible() {
46
+ return this._visible.get();
47
+ }
48
+ set visible(value) {
49
+ this._visible.set(value);
44
50
  }
45
51
  /**
46
52
  * The three declared numbers, parsed on assignment rather than at the
@@ -56,25 +62,25 @@ export class Layout {
56
62
  * selects a flex pane, and `cellCount` would read it as a declared zero.
57
63
  */
58
64
  get ratio() {
59
- return this._ratio;
65
+ return this._ratio.get();
60
66
  }
61
67
  set ratio(value) {
62
- this._ratio = shareWeight(value);
68
+ this._ratio.set(shareWeight(value));
63
69
  }
64
70
  get size() {
65
- return this._size;
71
+ return this._size.get();
66
72
  }
67
73
  set size(value) {
68
- this._size = value === undefined ? undefined : cellCount(value);
74
+ this._size.set(sizeOf(value));
69
75
  }
70
76
  get minimumSize() {
71
- return this._minimumSize;
77
+ return this._minimumSize.get();
72
78
  }
73
79
  set minimumSize(value) {
74
- this._minimumSize = cellCount(value);
80
+ this._minimumSize.set(cellCount(value));
75
81
  }
76
82
  get children() {
77
- return this._children;
83
+ return this._split.get().children;
78
84
  }
79
85
  /**
80
86
  * Whether this layout draws `_renderable` or its children.
@@ -86,23 +92,21 @@ export class Layout {
86
92
  * fit-mode `Panel` framed twelve cells of air.
87
93
  */
88
94
  get _isLeaf() {
89
- return this._children.length === 0;
95
+ return this._split.get().children.length === 0;
90
96
  }
91
97
  splitColumn(...layouts) {
92
- this._children = layouts;
93
- this._splitDirection = "column";
98
+ this._split.set({ direction: "column", children: layouts });
94
99
  }
95
100
  splitRow(...layouts) {
96
- this._children = layouts;
97
- this._splitDirection = "row";
101
+ this._split.set({ direction: "row", children: layouts });
98
102
  }
99
103
  update(renderable) {
100
- this._renderable = embed(renderable);
104
+ this._renderable.set(embed(renderable));
101
105
  }
102
106
  getByName(name) {
103
107
  if (this.name === name)
104
108
  return this;
105
- for (const child of this._children) {
109
+ for (const child of this._split.get().children) {
106
110
  const found = child.getByName(name);
107
111
  if (found)
108
112
  return found;
@@ -128,8 +132,9 @@ export class Layout {
128
132
  // ok\n")` is two lines, and a one-row region cuts the second. Every line
129
133
  // ended, as the split paths below end theirs: a leaf stacked in a
130
134
  // `Group` is still a region of lines.
131
- if (this._renderable) {
132
- const lines = fitHeight(Segment.splitLines(render(this._renderable, options)), options.height);
135
+ const renderable = this._renderable.get();
136
+ if (renderable) {
137
+ const lines = fitHeight(Segment.splitLines(render(renderable, options)), options.height);
133
138
  for (const line of lines) {
134
139
  yield* Segment.adjustLineLength(line, options.maxWidth);
135
140
  yield Segment.line();
@@ -137,28 +142,16 @@ export class Layout {
137
142
  }
138
143
  return;
139
144
  }
140
- const visibleChildren = this._children.filter((c) => c.visible);
145
+ const visibleChildren = this._split.get().children.filter((c) => c.visible);
141
146
  if (visibleChildren.length === 0)
142
147
  return;
143
- if (this._splitDirection === "row") {
148
+ if (this._split.get().direction === "row") {
144
149
  yield* this._renderRow(visibleChildren, options);
145
150
  }
146
151
  else {
147
152
  yield* this._renderColumn(visibleChildren, options);
148
153
  }
149
154
  }
150
- // What `render` draws, decided as it decides it: nothing when hidden, a
151
- // leaf's content, or the visible panes of a split.
152
- *contents() {
153
- if (!this.visible)
154
- return;
155
- if (this._isLeaf) {
156
- if (this._renderable)
157
- yield this._renderable;
158
- return;
159
- }
160
- yield* this._children.filter((c) => c.visible);
161
- }
162
155
  *_renderColumn(children, options) {
163
156
  // Each pane gets full width and its share of the region. With no region —
164
157
  // a ceiling, or no budget at all — a pane's share is its declared `size`,
@@ -200,15 +193,16 @@ export class Layout {
200
193
  if (!this.visible)
201
194
  return 0;
202
195
  if (this._isLeaf) {
203
- if (this._renderable === undefined)
196
+ const renderable = this._renderable.get();
197
+ if (renderable === undefined)
204
198
  return 0;
205
- return Measurement.get(options, this._renderable).maximum;
199
+ return Measurement.get(options, renderable).maximum;
206
200
  }
207
- const visible = this._children.filter((c) => c.visible);
201
+ const visible = this._split.get().children.filter((c) => c.visible);
208
202
  if (visible.length === 0)
209
203
  return 0;
210
204
  const widths = visible.map((c) => c._naturalWidth(options));
211
- if (this._splitDirection === "row")
205
+ if (this._split.get().direction === "row")
212
206
  return ratioBudget(visible, widths);
213
207
  // Accumulated, not spread: a column split holds as many children as a caller
214
208
  // made, and `Math.max(...widths)` passes one argument per child, so a
@@ -0,0 +1,77 @@
1
+ /**
2
+ * An element's lifecycle: it enters, stays, is told to leave, leaves, and is
3
+ * gone — a row added to a table, a line appended to a log, a panel closing.
4
+ * Drawn on the host's clock as an `Animation<Renderable>`, so it is a view a
5
+ * `Live` or an `App` draws as it stands, and a list of them is
6
+ * `Animation.all` of them mapped to whatever lays them out.
7
+ *
8
+ * The two times are handed in, never read: when the element entered, and when
9
+ * it was told to leave — `Infinity` until it is, a time like any other, so no
10
+ * part of the drawing asks whether it is leaving. Telling it is a new value
11
+ * (`leave`), as every `Timeline` operation is. [LAW:no-ambient-temporal-coupling]
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]
26
+ *
27
+ * 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
30
+ * 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
+ *
35
+ * Gone is a fact the host reads (`gone(t)`) to drop the element, and from then
36
+ * the lifecycle draws nothing and never changes, so a host still holding it
37
+ * wakes for nothing. Before it entered — asked by a timeline running
38
+ * backwards — it draws nothing too, but is not gone.
39
+ */
40
+ import { Animation } from "../core/animation.js";
41
+ import type { TerminalTheme } from "../core/color.js";
42
+ import type { Renderable } from "../core/protocol.js";
43
+ import { Transition } from "./effects.js";
44
+ import type { GlyphTransition } from "./glyph-effects.js";
45
+ /**
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.
50
+ */
51
+ export type Veil = (element: Renderable, t: number, key: string) => Renderable;
52
+ /** `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>;
54
+ /** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
55
+ export declare function throughGlyphs(transition: GlyphTransition): Transition<Veil>;
56
+ /** How an element arrives and leaves, and what it is named for their seeds. */
57
+ export interface LifecycleOptions {
58
+ /** The half it arrives through — `throughColors(fadeIn(curve, z), theme)`. */
59
+ readonly enter: Transition<Veil>;
60
+ /** The half it leaves through — `throughGlyphs(scrambleOut(timing, z))`. */
61
+ readonly exit: Transition<Veil>;
62
+ /** Names the element for both halves' seeds, as `Effected`'s and `GlyphEffected`'s key does. */
63
+ readonly key: string;
64
+ }
65
+ /** An element on the host's clock, from before it entered to after it is gone. */
66
+ export interface Lifecycle extends Animation<Renderable> {
67
+ /** The host time it entered. */
68
+ readonly entered: number;
69
+ /** The host time it was told to leave: `Infinity` until it is. */
70
+ readonly left: number;
71
+ /** Whether it has finished leaving by `t`, so a host drops it. */
72
+ gone(t: number): boolean;
73
+ /** This element told to leave at host time `at`. Throws if it was already told, or `at` is before it entered. */
74
+ leave(at: number): Lifecycle;
75
+ }
76
+ /** `element` entering at host time `entered`, through `options.enter`, and staying until it is told to `leave`. */
77
+ export declare function lifecycle(element: Renderable, options: LifecycleOptions, entered: number): Lifecycle;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * An element's lifecycle: it enters, stays, is told to leave, leaves, and is
3
+ * gone — a row added to a table, a line appended to a log, a panel closing.
4
+ * Drawn on the host's clock as an `Animation<Renderable>`, so it is a view a
5
+ * `Live` or an `App` draws as it stands, and a list of them is
6
+ * `Animation.all` of them mapped to whatever lays them out.
7
+ *
8
+ * The two times are handed in, never read: when the element entered, and when
9
+ * it was told to leave — `Infinity` until it is, a time like any other, so no
10
+ * part of the drawing asks whether it is leaving. Telling it is a new value
11
+ * (`leave`), as every `Timeline` operation is. [LAW:no-ambient-temporal-coupling]
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]
26
+ *
27
+ * 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
30
+ * 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
+ *
35
+ * Gone is a fact the host reads (`gone(t)`) to drop the element, and from then
36
+ * the lifecycle draws nothing and never changes, so a host still holding it
37
+ * wakes for nothing. Before it entered — asked by a timeline running
38
+ * backwards — it draws nothing too, but is not gone.
39
+ */
40
+ import { Animation } from "../core/animation.js";
41
+ import { firstReaching } from "../core/easing.js";
42
+ 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";
46
+ /** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
47
+ export function throughColors(transition, theme) {
48
+ return lift(transition, (element, t, key) => new Recolored(element, transition.effect, { t, key, theme }));
49
+ }
50
+ /** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
51
+ export function throughGlyphs(transition) {
52
+ return lift(transition, (element, t, key) => new Reglyphed(element, transition.effect, { t, key }));
53
+ }
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);
57
+ }
58
+ /** What an element draws before it entered and once it is gone: no cells, and no width. */
59
+ const NOTHING = { render: () => [], measure: () => ({ minimum: 0, maximum: 0 }) };
60
+ /** `element` entering at host time `entered`, through `options.enter`, and staying until it is told to `leave`. */
61
+ export function lifecycle(element, options, entered) {
62
+ if (!Number.isFinite(entered))
63
+ throw new RangeError(`lifecycle: entered must be a finite time, got ${entered}`);
64
+ return life(element, options, entered, Number.POSITIVE_INFINITY);
65
+ }
66
+ /**
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.
72
+ */
73
+ function doneAt(transition, start) {
74
+ return Number.isFinite(start) ? firstReaching(start, (x) => transition.done(x - start)) : start;
75
+ }
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);
82
+ // 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);
84
+ const held = (schedule, t) => ({ last: schedule.lastChange(t), next: schedule.nextChange(t) });
85
+ // [LAW:one-source-of-truth] Each phase's frame and its hold, side by side, so the two cannot disagree.
86
+ // The frames say nothing of when they change — the hold does, stamped where the lifecycle is placed.
87
+ const phase = (t) => {
88
+ if (t < entered)
89
+ return { frame: NOTHING, last: Number.NEGATIVE_INFINITY, next: entered };
90
+ if (t >= goneAt)
91
+ 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.
93
+ 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) };
96
+ }
97
+ if (t < arrived)
98
+ return { frame: shown(t), ...held(arriving, t) };
99
+ return { frame: element, last: arrived, next: left };
100
+ };
101
+ // [LAW:dataflow-not-control-flow] The spans `phase` draws, the transitions gone from them: told to
102
+ // leave, nothing shows, and the moment it is `gone` is still one a host is woken for, so it drops it on time.
103
+ const there = (t) => t < entered
104
+ ? { frame: NOTHING, last: Number.NEGATIVE_INFINITY, next: entered }
105
+ : t < left
106
+ ? { frame: element, last: entered, next: left }
107
+ : { frame: NOTHING, last: left, next: t < goneAt ? goneAt : Number.POSITIVE_INFINITY };
108
+ const present = Animation.of({
109
+ at: (t) => there(t).frame,
110
+ nextChange: (t) => there(t).next,
111
+ lastChange: (t) => there(t).last,
112
+ reduced: () => present,
113
+ });
114
+ return Animation.of({
115
+ entered,
116
+ left,
117
+ at: (t) => phase(t).frame,
118
+ nextChange: (t) => phase(t).next,
119
+ lastChange: (t) => phase(t).last,
120
+ // Reduced, the element is there from when it entered until it is told to leave: no arrival or exit to show, and the times kept.
121
+ reduced: () => present,
122
+ // [LAW:one-source-of-truth] The moment `phase` draws nothing from, so an element not yet told to leave is never asked its exit at -Infinity.
123
+ gone: (t) => t >= goneAt,
124
+ leave(at) {
125
+ if (Number.isFinite(left))
126
+ throw new RangeError(`lifecycle: already told to leave at ${left}, told again at ${at}`);
127
+ if (!(Number.isFinite(at) && at >= entered))
128
+ throw new RangeError(`lifecycle: leave must be a finite time from entered (${entered}) on, got ${at}`);
129
+ return life(element, options, entered, at);
130
+ },
131
+ });
132
+ }
@@ -5,8 +5,8 @@
5
5
  * An item can wrap, so the lines the selected one covers exist only at the
6
6
  * width it is drawn at, which a render is handed and whoever builds the frame
7
7
  * is not. So the items are drawn here, at the viewport's content width and
8
- * with no height — the lines the viewport scrolls — and the viewport is handed
9
- * those lines and the selected item's range.
8
+ * with no height — the lines the viewport scrolls — and shown through the
9
+ * viewport with the selected item's range followed.
10
10
  * [LAW:single-enforcer] Where the window sits is the viewport's call; this
11
11
  * only says which lines the selection is.
12
12
  *
@@ -16,7 +16,7 @@
16
16
  * it — so a consumer can take one without the others.
17
17
  */
18
18
  import { Segment } from "../core/segment.js";
19
- import type { Composite, Measurable, Renderable, RenderOptions } from "../core/protocol.js";
19
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
20
20
  import type { Viewport } from "./viewport.js";
21
21
  export interface ListViewOptions {
22
22
  /** Blank lines between one item and the next. Absent, none. */
@@ -27,7 +27,7 @@ export interface ListViewOptions {
27
27
  */
28
28
  context?: number;
29
29
  }
30
- export declare class ListView implements Renderable, Measurable, Composite {
30
+ export declare class ListView implements Renderable, Measurable {
31
31
  readonly viewport: Viewport;
32
32
  readonly items: readonly Renderable[];
33
33
  readonly selected: number;
@@ -35,7 +35,6 @@ export declare class ListView implements Renderable, Measurable, Composite {
35
35
  readonly context: number;
36
36
  constructor(viewport: Viewport, items: readonly Renderable[], selected: number, options?: ListViewOptions);
37
37
  render(rawOptions: RenderOptions): Iterable<Segment>;
38
- contents(): Iterable<Renderable>;
39
38
  measure(rawOptions: RenderOptions): {
40
39
  minimum: number;
41
40
  maximum: number;
@@ -5,8 +5,8 @@
5
5
  * An item can wrap, so the lines the selected one covers exist only at the
6
6
  * width it is drawn at, which a render is handed and whoever builds the frame
7
7
  * is not. So the items are drawn here, at the viewport's content width and
8
- * with no height — the lines the viewport scrolls — and the viewport is handed
9
- * those lines and the selected item's range.
8
+ * with no height — the lines the viewport scrolls — and shown through the
9
+ * viewport with the selected item's range followed.
10
10
  * [LAW:single-enforcer] Where the window sits is the viewport's call; this
11
11
  * only says which lines the selection is.
12
12
  *
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { Segment } from "../core/segment.js";
19
19
  import { Measurement, measureRenderables } from "../core/measure.js";
20
- import { drawsNothing, render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
20
+ import { render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
21
21
  function lineCount(name, value) {
22
22
  if (!Number.isInteger(value) || value < 0)
23
23
  throw new RangeError(`ListView: ${name} ${value} is not a whole number of lines`);
@@ -51,7 +51,7 @@ export class ListView {
51
51
  const start = drawn.slice(0, this.selected).reduce((total, item) => total + item.length + this.gap, 0);
52
52
  const end = start + (drawn[this.selected]?.length ?? 0);
53
53
  const lines = drawn.flatMap((item, i) => (i === 0 ? item : [...gap, ...item]));
54
- this.viewport.content = {
54
+ const content = {
55
55
  *render() {
56
56
  for (const line of lines) {
57
57
  yield* line;
@@ -62,15 +62,11 @@ export class ListView {
62
62
  // A range reaching past either end is clamped by the viewport, which holds
63
63
  // its offset to the lines that exist. Followed, not ensured: the wheel can
64
64
  // scroll away from the selection until the selection moves. Items handed
65
- // no column are held, not drawn, and cover no lines, so they name no range
66
- // and the one last followed stands; followed, it would read as a move.
67
- if (!drawsNothing(options))
68
- this.viewport.follow(start - this.context, end + this.context);
69
- yield* render(this.viewport, bounded);
70
- }
71
- *contents() {
72
- yield* this.items;
73
- yield this.viewport;
65
+ // no column are held, not drawn, and so is the viewport's content, which
66
+ // keeps nothing it is handed — the range last followed stands.
67
+ // [LAW:effects-at-boundaries] Shown through the viewport rather than
68
+ // handed to it: the viewport keeps the lines' range once the frame is drawn.
69
+ yield* render(this.viewport.showing(content, { start: start - this.context, end: end + this.context }), bounded);
74
70
  }
75
71
  measure(rawOptions) {
76
72
  // Stacked, the items are as wide as the widest of them, plus whatever
@@ -26,17 +26,9 @@
26
26
  */
27
27
  import { type Clock } from "../core/clock.js";
28
28
  import { Console } from "../core/console.js";
29
- import { type Renderable } from "../core/protocol.js";
30
- /**
31
- * What a Live draws: a renderable, or a view asked for one each frame with
32
- * the frame's time in seconds on the Live's clock, as an `App`'s view is
33
- * (`host/app.ts`). A renderable is the view that is the same at every `t` —
34
- * a table mutated in place between frames — and a function is what a frame
35
- * drawn for a time needs: `(t) => spinner.at(t)`.
36
- * [LAW:no-ambient-temporal-coupling] The Live reads its clock once a frame
37
- * and hands the time down as data, so nothing it draws reads one.
38
- */
39
- export type LiveView = Renderable | ((t: number) => Renderable);
29
+ import { type FrameHost } from "../core/protocol.js";
30
+ import { Timeline } from "../core/timeline.js";
31
+ import { type View } from "../core/view.js";
40
32
  export interface LiveOptions {
41
33
  /**
42
34
  * How often the auto-refresh draws on its own, in frames a second:
@@ -48,6 +40,13 @@ export interface LiveOptions {
48
40
  refreshPerSecond?: number;
49
41
  /** What the auto-refresh waits on. The platform's, unless given. */
50
42
  clock?: Clock;
43
+ /**
44
+ * The map from the clock to the time the view is drawn for (`Timeline`).
45
+ * Playing from 0 when the host is made, at the clock's pace, unless given
46
+ * — so a tween in the view starts when the host is made, not when the clock
47
+ * began; `live.timeline` reads and replaces it.
48
+ */
49
+ timeline?: Timeline;
51
50
  autoRefresh?: boolean;
52
51
  transient?: boolean;
53
52
  console?: Console;
@@ -57,26 +56,42 @@ export interface LiveOptions {
57
56
  * a full-screen layout fills it. */
58
57
  altScreen?: boolean;
59
58
  }
60
- export declare class Live {
61
- private _view;
62
- private _console;
59
+ export declare class Live implements FrameHost {
60
+ private readonly _view;
61
+ private readonly _console;
63
62
  private readonly _rate;
64
63
  private readonly _clock;
65
- private _autoRefresh;
66
- private _transient;
67
- private _verticalOverflow;
64
+ private readonly _epoch;
65
+ private readonly _timeline;
66
+ private readonly _autoRefresh;
67
+ private readonly _transient;
68
+ private readonly _verticalOverflow;
68
69
  private readonly _painter;
69
- private _waking;
70
- private _held;
71
- constructor(view?: LiveView, options?: LiveOptions);
70
+ private readonly _waking;
71
+ private readonly _held;
72
+ constructor(view?: View, options?: LiveOptions);
72
73
  /**
73
74
  * The console to print through while this Live runs: what it prints lands
74
75
  * above an inline frame. The alternate screen is all frame, and paints over it.
75
76
  */
76
77
  get console(): Console;
78
+ /** The clock the auto-refresh waits on: where the host time a `Timeline` operation takes is read. */
79
+ get clock(): Clock;
80
+ /** The map from the clock to the time the view is drawn for. */
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;
84
+ private _time;
85
+ /**
86
+ * Replace the timeline and draw the frame for the time it now maps the
87
+ * clock's now to, so a seek shows where it sought, once. Paused, the frame
88
+ * then holds — the auto-refresh waits on nothing until the timeline plays
89
+ * again — while `refresh()` and `update(…, { refresh: true })` still draw.
90
+ */
91
+ set timeline(timeline: Timeline);
77
92
  start(): void;
78
93
  stop(): void;
79
- update(view?: LiveView, options?: {
94
+ update(view?: View, options?: {
80
95
  refresh?: boolean;
81
96
  }): void;
82
97
  refresh(): void;