@promptctl/rich-js 0.27.0 → 0.29.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 (185) hide show
  1. package/README.md +7 -6
  2. package/THIRD-PARTY-NOTICES +42 -0
  3. package/dist/core/animation.d.ts +65 -46
  4. package/dist/core/animation.js +91 -67
  5. package/dist/core/ansi.d.ts +19 -12
  6. package/dist/core/ansi.js +114 -36
  7. package/dist/core/bidi-data.d.ts +14 -0
  8. package/dist/core/bidi-data.js +385 -0
  9. package/dist/core/bidi.d.ts +89 -0
  10. package/dist/core/bidi.js +615 -0
  11. package/dist/core/box.d.ts +9 -1
  12. package/dist/core/box.js +11 -0
  13. package/dist/core/cell-widths.d.ts +3 -2
  14. package/dist/core/cell-widths.js +93 -93
  15. package/dist/core/cells.d.ts +20 -0
  16. package/dist/core/cells.js +94 -6
  17. package/dist/core/clock.d.ts +1 -1
  18. package/dist/core/color.d.ts +56 -57
  19. package/dist/core/color.js +79 -81
  20. package/dist/core/console.d.ts +24 -7
  21. package/dist/core/console.js +27 -8
  22. package/dist/core/control-codes.d.ts +61 -0
  23. package/dist/core/control-codes.js +157 -0
  24. package/dist/core/easing.d.ts +40 -40
  25. package/dist/core/easing.js +40 -40
  26. package/dist/core/env.d.ts +6 -0
  27. package/dist/core/env.js +9 -1
  28. package/dist/core/export-html.js +5 -4
  29. package/dist/core/export-lines.d.ts +13 -11
  30. package/dist/core/export-lines.js +19 -15
  31. package/dist/core/export-svg.d.ts +3 -3
  32. package/dist/core/export-svg.js +10 -9
  33. package/dist/core/json.js +9 -2
  34. package/dist/core/markup.js +5 -4
  35. package/dist/core/memo.d.ts +1 -1
  36. package/dist/core/memo.js +1 -1
  37. package/dist/core/oklch.d.ts +14 -14
  38. package/dist/core/oklch.js +10 -10
  39. package/dist/core/orient.d.ts +68 -0
  40. package/dist/core/orient.js +224 -0
  41. package/dist/core/osc8.d.ts +12 -5
  42. package/dist/core/osc8.js +14 -7
  43. package/dist/core/paint.d.ts +9 -9
  44. package/dist/core/paint.js +3 -3
  45. package/dist/core/pretty.d.ts +1 -1
  46. package/dist/core/pretty.js +23 -19
  47. package/dist/core/protocol.d.ts +47 -26
  48. package/dist/core/protocol.js +31 -23
  49. package/dist/core/render.d.ts +19 -14
  50. package/dist/core/render.js +30 -17
  51. package/dist/core/seal.d.ts +26 -0
  52. package/dist/core/seal.js +75 -0
  53. package/dist/core/segment.d.ts +12 -4
  54. package/dist/core/segment.js +16 -6
  55. package/dist/core/strip.d.ts +83 -83
  56. package/dist/core/strip.js +70 -70
  57. package/dist/core/style.d.ts +46 -18
  58. package/dist/core/style.js +92 -56
  59. package/dist/core/text-order.d.ts +51 -0
  60. package/dist/core/text-order.js +50 -0
  61. package/dist/core/text.d.ts +12 -2
  62. package/dist/core/text.js +136 -90
  63. package/dist/core/timeline.d.ts +5 -5
  64. package/dist/core/timeline.js +11 -11
  65. package/dist/core/tween.d.ts +6 -6
  66. package/dist/core/tween.js +10 -10
  67. package/dist/core/view.d.ts +5 -5
  68. package/dist/core/view.js +15 -14
  69. package/dist/core/wrap.d.ts +24 -6
  70. package/dist/core/wrap.js +82 -16
  71. package/dist/host/app.d.ts +50 -9
  72. package/dist/host/app.js +120 -15
  73. package/dist/host/host-environment.d.ts +2 -2
  74. package/dist/host/host-environment.js +2 -2
  75. package/dist/host/index.d.ts +3 -2
  76. package/dist/host/index.js +1 -0
  77. package/dist/host/input.d.ts +42 -9
  78. package/dist/host/input.js +91 -7
  79. package/dist/host/terminal-colors.d.ts +18 -0
  80. package/dist/host/terminal-colors.js +46 -0
  81. package/dist/host/terminal-host.d.ts +4 -3
  82. package/dist/host/terminal-host.js +1 -1
  83. package/dist/index.d.ts +17 -16
  84. package/dist/index.js +11 -11
  85. package/dist/node/prompt.d.ts +1 -1
  86. package/dist/node/prompt.js +1 -1
  87. package/dist/node/terminal-host.d.ts +1 -1
  88. package/dist/node/traceback.js +2 -2
  89. package/dist/renderables/change.d.ts +1 -3
  90. package/dist/renderables/change.js +6 -8
  91. package/dist/renderables/effect.d.ts +88 -87
  92. package/dist/renderables/effect.js +124 -114
  93. package/dist/renderables/effects.d.ts +105 -76
  94. package/dist/renderables/effects.js +309 -176
  95. package/dist/renderables/embed.d.ts +1 -1
  96. package/dist/renderables/embed.js +2 -2
  97. package/dist/renderables/flexStrip.d.ts +7 -7
  98. package/dist/renderables/flexStrip.js +15 -15
  99. package/dist/renderables/frames.d.ts +7 -16
  100. package/dist/renderables/frames.js +12 -27
  101. package/dist/renderables/glyph-effect.d.ts +22 -23
  102. package/dist/renderables/glyph-effect.js +43 -41
  103. package/dist/renderables/glyph-effects.d.ts +33 -14
  104. package/dist/renderables/glyph-effects.js +90 -22
  105. package/dist/renderables/html-entities.d.ts +1 -1
  106. package/dist/renderables/html-entities.js +1 -1
  107. package/dist/renderables/lifecycle.d.ts +8 -10
  108. package/dist/renderables/lifecycle.js +17 -18
  109. package/dist/renderables/live.d.ts +2 -2
  110. package/dist/renderables/live.js +7 -7
  111. package/dist/renderables/markdown-parse.js +3 -3
  112. package/dist/renderables/markdown.js +4 -2
  113. package/dist/renderables/panel.js +7 -5
  114. package/dist/renderables/progress.d.ts +1 -1
  115. package/dist/renderables/progress.js +1 -1
  116. package/dist/renderables/progressBar.d.ts +3 -3
  117. package/dist/renderables/progressBar.js +21 -18
  118. package/dist/renderables/prompt.d.ts +1 -1
  119. package/dist/renderables/scroll.d.ts +1 -1
  120. package/dist/renderables/scroll.js +3 -3
  121. package/dist/renderables/spinner.d.ts +4 -4
  122. package/dist/renderables/spinner.js +10 -10
  123. package/dist/renderables/syntax.d.ts +2 -2
  124. package/dist/renderables/syntax.js +9 -4
  125. package/dist/renderables/table.js +5 -4
  126. package/dist/renderables/traceback.d.ts +1 -1
  127. package/dist/renderables/traceback.js +1 -1
  128. package/dist/renderables/tree.js +3 -3
  129. package/dist/template-bindings/animation-funcs.d.ts +60 -29
  130. package/dist/template-bindings/animation-funcs.js +126 -43
  131. package/dist/template-bindings/color-funcs.d.ts +14 -14
  132. package/dist/template-bindings/color-funcs.js +25 -25
  133. package/dist/template-bindings/index.d.ts +16 -14
  134. package/dist/template-bindings/index.js +17 -14
  135. package/dist/template-bindings/palette-funcs.d.ts +2 -2
  136. package/dist/template-bindings/palette-funcs.js +4 -4
  137. package/dist/template-bindings/style-funcs.d.ts +5 -5
  138. package/dist/template-bindings/style-funcs.js +16 -16
  139. package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
  140. package/dist/themes/{transpose.js → adjust.js} +28 -28
  141. package/dist/themes/buildPalette.d.ts +6 -5
  142. package/dist/themes/buildPalette.js +22 -22
  143. package/dist/themes/colorMath.d.ts +16 -16
  144. package/dist/themes/colorMath.js +32 -32
  145. package/dist/themes/data/ansi-tables.d.ts +2 -2
  146. package/dist/themes/data/ansi-tables.js +2 -2
  147. package/dist/themes/data/index.js +1 -4
  148. package/dist/themes/data/textual-ansi.js +0 -143
  149. package/dist/themes/data/types.d.ts +4 -4
  150. package/dist/themes/data/types.js +2 -2
  151. package/dist/themes/palette.d.ts +12 -12
  152. package/dist/themes/palette.js +16 -16
  153. package/dist/themes/ramp.d.ts +17 -17
  154. package/dist/themes/ramp.js +12 -12
  155. package/dist/themes/registry.d.ts +6 -8
  156. package/dist/themes/registry.js +6 -8
  157. package/dist/themes/terminalThemes.d.ts +4 -11
  158. package/dist/themes/terminalThemes.js +31 -47
  159. package/dist/widgets/button.d.ts +3 -3
  160. package/dist/widgets/button.js +8 -8
  161. package/dist/widgets/checkbox.d.ts +2 -2
  162. package/dist/widgets/checkbox.js +4 -4
  163. package/dist/widgets/dropdown.d.ts +3 -3
  164. package/dist/widgets/dropdown.js +7 -5
  165. package/dist/widgets/event-router.d.ts +6 -6
  166. package/dist/widgets/event-router.js +2 -2
  167. package/dist/widgets/focus-manager.d.ts +3 -3
  168. package/dist/widgets/focus-manager.js +3 -3
  169. package/dist/widgets/index.d.ts +2 -2
  170. package/dist/widgets/index.js +1 -1
  171. package/dist/widgets/ink.d.ts +3 -3
  172. package/dist/widgets/ink.js +4 -4
  173. package/dist/widgets/slider.d.ts +2 -2
  174. package/dist/widgets/static-item.d.ts +2 -2
  175. package/dist/widgets/text-input.d.ts +2 -2
  176. package/dist/widgets/text-input.js +5 -5
  177. package/dist/widgets/themed-widget.js +4 -4
  178. package/dist/widgets/toggle.d.ts +4 -4
  179. package/dist/widgets/toggle.js +6 -6
  180. package/dist/widgets/types.d.ts +6 -6
  181. package/dist/widgets/types.js +2 -2
  182. package/dist/widgets/widget-app.d.ts +10 -10
  183. package/dist/widgets/widget-app.js +8 -8
  184. package/dist/widgets/widget-base.d.ts +2 -2
  185. package/package.json +5 -4
@@ -107,7 +107,7 @@ export declare function cutLabel(text: RichText, width: number, marker: string):
107
107
  * `Text` are not, and markup that styles nothing is — it reads as empty text,
108
108
  * but the string that holds it is not empty.
109
109
  */
110
- export declare function present(content: string | RichText | undefined): content is string | RichText;
110
+ export declare function mounted(content: string | RichText | undefined): content is string | RichText;
111
111
  /**
112
112
  * A label's text as one line of segments at its natural width, its own styles
113
113
  * laid over `base`. The caller has already cut it to its room, so it leaves as
@@ -159,7 +159,7 @@ export function cutLabel(text, width, marker) {
159
159
  * `Text` are not, and markup that styles nothing is — it reads as empty text,
160
160
  * but the string that holds it is not empty.
161
161
  */
162
- export function present(content) {
162
+ export function mounted(content) {
163
163
  // Asked of what the content is rather than of what it is not, so a `null`
164
164
  // from untyped JS is no content, as Rich's `None` is.
165
165
  return typeof content === "string" ? content !== "" : content instanceof RichText && content.plain !== "";
@@ -180,5 +180,5 @@ export function drawLabel(text, options, base) {
180
180
  * label, which still opens a gap in the rule, as Rich's does.
181
181
  */
182
182
  export function inlineLabel(content) {
183
- return present(content) ? new InlineLabel(content) : undefined;
183
+ return mounted(content) ? new InlineLabel(content) : undefined;
184
184
  }
@@ -2,10 +2,10 @@
2
2
  * FlexStrip — wrap-to-width horizontal layout for `StyledRenderable` items.
3
3
  *
4
4
  * Pack as many items as fit on a line, then break and continue. Each line is
5
- * an independent sub-strip: optional `Joiner` end-caps fire at every line
5
+ * an independent sub-strip: optional `Separator` end-caps fire at every line
6
6
  * boundary (not just the strip's first/last position), so a line break looks
7
- * the same as an endpoint to the joiner. Composes with the Strip + Joiner
8
- * primitive in `core/strip` — same `Joiner` protocol, no new join semantics.
7
+ * the same as an endpoint to the joiner. Composes with the Strip + Separator
8
+ * primitive in `core/strip` — same `Separator` protocol, no new join semantics.
9
9
  *
10
10
  * [LAW:dataflow-not-control-flow] The pack walk is the same shape every
11
11
  * render: measure each item, render every joiner form (start-cap, end-cap,
@@ -17,11 +17,11 @@
17
17
  */
18
18
  import { Segment } from "../core/segment.js";
19
19
  import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
20
- import { type Joiner, type StyledRenderable } from "../core/strip.js";
20
+ import { type Separator, type StyledRenderable } from "../core/strip.js";
21
21
  export type FlexAlign = "left" | "center" | "right" | "justify";
22
22
  export interface FlexStripOptions<T extends StyledRenderable> {
23
- /** Joiner used between items within a line, and at line endpoints. */
24
- joiner?: Joiner<T>;
23
+ /** Separator used between items within a line, and at line endpoints. */
24
+ separator?: Separator<T>;
25
25
  /** Cells of horizontal space inserted on each side of an inter-item joiner (default 0). */
26
26
  gap?: number;
27
27
  /** Line alignment when packed items don't fill the available width (default "left"). */
@@ -29,7 +29,7 @@ export interface FlexStripOptions<T extends StyledRenderable> {
29
29
  }
30
30
  export declare class FlexStrip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable {
31
31
  readonly items: readonly T[];
32
- readonly joiner: Joiner<T> | undefined;
32
+ readonly separator: Separator<T> | undefined;
33
33
  readonly gap: number;
34
34
  readonly align: FlexAlign;
35
35
  constructor(items: readonly T[], options?: FlexStripOptions<T>);
@@ -2,10 +2,10 @@
2
2
  * FlexStrip — wrap-to-width horizontal layout for `StyledRenderable` items.
3
3
  *
4
4
  * Pack as many items as fit on a line, then break and continue. Each line is
5
- * an independent sub-strip: optional `Joiner` end-caps fire at every line
5
+ * an independent sub-strip: optional `Separator` end-caps fire at every line
6
6
  * boundary (not just the strip's first/last position), so a line break looks
7
- * the same as an endpoint to the joiner. Composes with the Strip + Joiner
8
- * primitive in `core/strip` — same `Joiner` protocol, no new join semantics.
7
+ * the same as an endpoint to the joiner. Composes with the Strip + Separator
8
+ * primitive in `core/strip` — same `Separator` protocol, no new join semantics.
9
9
  *
10
10
  * [LAW:dataflow-not-control-flow] The pack walk is the same shape every
11
11
  * render: measure each item, render every joiner form (start-cap, end-cap,
@@ -47,12 +47,12 @@ function computeSlotFills(align, isFinalLine, spare, itemCount) {
47
47
  }
48
48
  export class FlexStrip {
49
49
  items;
50
- joiner;
50
+ separator;
51
51
  gap;
52
52
  align;
53
53
  constructor(items, options) {
54
54
  this.items = items;
55
- this.joiner = options?.joiner;
55
+ this.separator = options?.separator;
56
56
  this.gap = options?.gap ?? 0;
57
57
  this.align = options?.align ?? "left";
58
58
  }
@@ -62,7 +62,7 @@ export class FlexStrip {
62
62
  return;
63
63
  const maxWidth = Math.max(1, options.maxWidth);
64
64
  const gap = this.gap;
65
- const joiner = this.joiner;
65
+ const joiner = this.separator;
66
66
  // Pre-render every item and every joiner form we might need. Doing this
67
67
  // up-front turns packing into a pure data computation over widths.
68
68
  const itemBlocks = items.map((it) => renderToBlock(drawItem(it), options));
@@ -178,24 +178,24 @@ export class FlexStrip {
178
178
  let maximum = 0;
179
179
  for (let i = 0; i < items.length; i++) {
180
180
  const itemWidth = renderToBlock(items[i], options).width;
181
- const sc = this.joiner
182
- ? renderToBlock(drawJoin(this.joiner, null, items[i], { index: i }), options).width
181
+ const sc = this.separator
182
+ ? renderToBlock(drawJoin(this.separator, null, items[i], { index: i }), options).width
183
183
  : 0;
184
- const ec = this.joiner
185
- ? renderToBlock(drawJoin(this.joiner, items[i], null, { index: i + 1 }), options).width
184
+ const ec = this.separator
185
+ ? renderToBlock(drawJoin(this.separator, items[i], null, { index: i + 1 }), options).width
186
186
  : 0;
187
187
  minimum = Math.max(minimum, itemWidth + sc + ec);
188
188
  maximum += itemWidth;
189
189
  if (i > 0) {
190
190
  maximum += this.gap * 2;
191
- if (this.joiner) {
192
- maximum += renderToBlock(drawJoin(this.joiner, items[i - 1], items[i], { index: i }), options).width;
191
+ if (this.separator) {
192
+ maximum += renderToBlock(drawJoin(this.separator, items[i - 1], items[i], { index: i }), options).width;
193
193
  }
194
194
  }
195
195
  }
196
- if (this.joiner) {
197
- maximum += renderToBlock(drawJoin(this.joiner, null, items[0], { index: 0 }), options).width;
198
- maximum += renderToBlock(drawJoin(this.joiner, items[items.length - 1], null, { index: items.length }), options).width;
196
+ if (this.separator) {
197
+ maximum += renderToBlock(drawJoin(this.separator, null, items[0], { index: 0 }), options).width;
198
+ maximum += renderToBlock(drawJoin(this.separator, items[items.length - 1], null, { index: items.length }), options).width;
199
199
  }
200
200
  return { minimum, maximum };
201
201
  }
@@ -3,7 +3,7 @@
3
3
  * for its own seconds and looping, drawn as the one in progress at a time
4
4
  * the host hands in.
5
5
  *
6
- * An effect moves a cell's colours and may never change a glyph
6
+ * An effect moves a cell's colors and may never change a glyph
7
7
  * (`./effect.ts`: "It changes how cells look, never which cells exist or who
8
8
  * drew them"). A frame is the other kind of motion — what the element shows
9
9
  * changes — so it is its own primitive and not a wider `Effect`, and the two
@@ -30,7 +30,7 @@
30
30
  * placed sequence stamps them with its hold (`Style.hold`,
31
31
  * `placedRender` in `core/animation.ts`), which rides up
32
32
  * through every container to whoever holds the composed frame
33
- * (`Segment.nextChange`), so a host drawing at `t` knows when to draw again,
33
+ * (`Segment.validUntil`), so a host drawing at `t` knows when to draw again,
34
34
  * and one drawing slower than the frames turn can see that it is skipping
35
35
  * them. Nothing an animation's `at` returns carries a stamp of its own: the
36
36
  * stamp is put on where the frame's time is known.
@@ -45,8 +45,8 @@
45
45
  * children do.
46
46
  */
47
47
  import { Animation } from "../core/animation.js";
48
- import { type Beat } from "../core/easing.js";
49
- import type { Motion, RenderOptions } from "../core/protocol.js";
48
+ import { type StepSequence } from "../core/easing.js";
49
+ import type { RenderOptions } from "../core/protocol.js";
50
50
  import type { Segment } from "../core/segment.js";
51
51
  import { RichText } from "../core/text.js";
52
52
  /** One frame: what it shows, and how long it is held, in seconds. */
@@ -57,7 +57,7 @@ export interface Frame {
57
57
  /** A sequence of frames prepared to be drawn: built once, asked for the frame at a time. */
58
58
  export declare class FrameSequence implements Animation<RichText> {
59
59
  /** Which frame is in progress when: the holds are the frames' seconds, in order. */
60
- readonly beat: Beat;
60
+ readonly stepSequence: StepSequence;
61
61
  /** The width every frame draws at: the widest frame's, in cells. */
62
62
  readonly width: number;
63
63
  private readonly _shown;
@@ -70,9 +70,9 @@ export declare class FrameSequence implements Animation<RichText> {
70
70
  */
71
71
  at(t: number): RichText;
72
72
  /** When the frame in progress at `t` gives way to the next: the beat's answer, its holds being the frames'. */
73
- nextChange(t: number): number;
73
+ validUntil(t: number): number;
74
74
  /** When the frame in progress at `t` began: the beat's answer. */
75
- lastChange(t: number): number;
75
+ validFrom(t: number): number;
76
76
  /** The frames with their motion taken out: the beat's first frame, held, a copy as `at`'s is. */
77
77
  reduced(): Animation<RichText>;
78
78
  render(options: RenderOptions): Iterable<Segment>;
@@ -86,12 +86,3 @@ export declare class FrameSequence implements Animation<RichText> {
86
86
  maximum: number;
87
87
  };
88
88
  }
89
- /**
90
- * The text frame `animation` shows at host time `t` under `motion`
91
- * (`shownAt`), copied and stamped with the hold it is in there: what the
92
- * template `frames` and `glyphs` functions, which know the reader's
93
- * preference before any render, splice into a line. The stamp is on the
94
- * base style, which an empty text still draws as its one piece of no width,
95
- * and on a copy, since an animation may hand back the one `RichText` it holds.
96
- */
97
- export declare function textFrameAt(animation: Animation<RichText>, t: number, motion: Motion): RichText;
@@ -3,7 +3,7 @@
3
3
  * for its own seconds and looping, drawn as the one in progress at a time
4
4
  * the host hands in.
5
5
  *
6
- * An effect moves a cell's colours and may never change a glyph
6
+ * An effect moves a cell's colors and may never change a glyph
7
7
  * (`./effect.ts`: "It changes how cells look, never which cells exist or who
8
8
  * drew them"). A frame is the other kind of motion — what the element shows
9
9
  * changes — so it is its own primitive and not a wider `Effect`, and the two
@@ -30,7 +30,7 @@
30
30
  * placed sequence stamps them with its hold (`Style.hold`,
31
31
  * `placedRender` in `core/animation.ts`), which rides up
32
32
  * through every container to whoever holds the composed frame
33
- * (`Segment.nextChange`), so a host drawing at `t` knows when to draw again,
33
+ * (`Segment.validUntil`), so a host drawing at `t` knows when to draw again,
34
34
  * and one drawing slower than the frames turn can see that it is skipping
35
35
  * them. Nothing an animation's `at` returns carries a stamp of its own: the
36
36
  * stamp is put on where the frame's time is known.
@@ -44,20 +44,19 @@
44
44
  * binding has no console for, so the frame arrives read, as a `Group`'s
45
45
  * children do.
46
46
  */
47
- import { Animation, holdOf, placedRender, shownAt, stamp } from "../core/animation.js";
48
- import { beat } from "../core/easing.js";
49
- import { layerStyle } from "../core/style.js";
47
+ import { Animation, placedRender } from "../core/animation.js";
48
+ import { stepSequence } from "../core/easing.js";
50
49
  import { setIntoLine } from "./embed.js";
51
50
  /** A sequence of frames prepared to be drawn: built once, asked for the frame at a time. */
52
51
  export class FrameSequence {
53
52
  /** Which frame is in progress when: the holds are the frames' seconds, in order. */
54
- beat;
53
+ stepSequence;
55
54
  /** The width every frame draws at: the widest frame's, in cells. */
56
55
  width;
57
56
  // Each frame as it draws: a line fragment padded to `width`, made once.
58
57
  _shown;
59
58
  constructor(frames) {
60
- this.beat = beat(frames.map((frame) => frame.seconds));
59
+ this.stepSequence = stepSequence(frames.map((frame) => frame.seconds));
61
60
  const texts = frames.map((frame) => setIntoLine(frame.text.copy()));
62
61
  const widths = texts.map((text) => text.cellLength);
63
62
  this.width = Math.max(...widths);
@@ -76,19 +75,19 @@ export class FrameSequence {
76
75
  * refuses a `t` that is not finite.
77
76
  */
78
77
  at(t) {
79
- return this._shown[this.beat.at(t)].copy();
78
+ return this._shown[this.stepSequence.at(t)].copy();
80
79
  }
81
80
  /** When the frame in progress at `t` gives way to the next: the beat's answer, its holds being the frames'. */
82
- nextChange(t) {
83
- return this.beat.nextChange(t);
81
+ validUntil(t) {
82
+ return this.stepSequence.validUntil(t);
84
83
  }
85
84
  /** When the frame in progress at `t` began: the beat's answer. */
86
- lastChange(t) {
87
- return this.beat.lastChange(t);
85
+ validFrom(t) {
86
+ return this.stepSequence.validFrom(t);
88
87
  }
89
88
  /** The frames with their motion taken out: the beat's first frame, held, a copy as `at`'s is. */
90
89
  reduced() {
91
- return Animation.map(this.beat.reduced(), (i) => this._shown[i].copy());
90
+ return Animation.map(this.stepSequence.reduced(), (i) => this._shown[i].copy());
92
91
  }
93
92
  render(options) {
94
93
  return placedRender(this, options);
@@ -102,17 +101,3 @@ export class FrameSequence {
102
101
  return { minimum: this.width, maximum: this.width };
103
102
  }
104
103
  }
105
- /**
106
- * The text frame `animation` shows at host time `t` under `motion`
107
- * (`shownAt`), copied and stamped with the hold it is in there: what the
108
- * template `frames` and `glyphs` functions, which know the reader's
109
- * preference before any render, splice into a line. The stamp is on the
110
- * base style, which an empty text still draws as its one piece of no width,
111
- * and on a copy, since an animation may hand back the one `RichText` it holds.
112
- */
113
- export function textFrameAt(animation, t, motion) {
114
- const shown = shownAt(animation, t, motion);
115
- const stamped = shown.value.copy();
116
- stamped.style = layerStyle(stamped.style, stamp(holdOf(shown, t)));
117
- return stamped;
118
- }
@@ -3,8 +3,8 @@
3
3
  * glyph effect at a moment `t`: the glyph half of `Effected` (`./effect.ts`),
4
4
  * built to its contract.
5
5
  *
6
- * A glyph effect is the colour effect's two-stage shape with a glyph where
7
- * the colours were: for the cells an element draws in a frame, an
6
+ * A glyph effect is the color effect's two-stage shape with a glyph where
7
+ * the colors were: for the cells an element draws in a frame, an
8
8
  * `Animation` (`../core/animation.ts`) of what is done to them. The first
9
9
  * stage is handed every cell once a frame (`GlyphCell`), blanks included —
10
10
  * rain and stars live there — and whether the output draws ASCII alone
@@ -14,15 +14,15 @@
14
14
  * scramble, a wave of case changes and particles on blank cells are all this
15
15
  * one shape (`./glyph-effects.ts`) [LAW:one-type-per-behavior].
16
16
  *
17
- * Colour and glyph are not two kinds of animation: each is an `Animation` of
17
+ * Color and glyph are not two kinds of animation: each is an `Animation` of
18
18
  * a per-cell map, and they differ only in what a cell is mapped to. The
19
- * wrappers are two because the stages are handed different facts. A colour
20
- * effect is settled against the depth, the terminal and the colours of the
21
- * words (`Drawn`), and with no colour output there is nothing for it to
19
+ * wrappers are two because the stages are handed different facts. A color
20
+ * effect is settled against the depth, the terminal and the colors of the
21
+ * words (`Drawn`), and with no color output there is nothing for it to
22
22
  * change. A glyph effect needs none of that, and is written at every depth,
23
- * with no colour too. Over one element the two compose by nesting:
24
- * `new Effected(new GlyphEffected(child, glyphs, …), colours, …)` moves the
25
- * colours of the glyphs the glyph effect left, since each wrapper measures
23
+ * with no color too. Over one element the two compose by nesting:
24
+ * `new Effected(new GlyphEffected(child, glyphs, …), colors, …)` moves the
25
+ * colors of the glyphs the glyph effect left, since each wrapper measures
26
26
  * what its child drew.
27
27
  *
28
28
  * A `GlyphEffected` is the element under its glyph effect over time, an
@@ -51,11 +51,11 @@
51
51
  * Positions are the child's own: row and column count from the top-left of
52
52
  * what the child drew.
53
53
  */
54
- import { Animation } from "../core/animation.js";
54
+ import { Animation, type Reads } from "../core/animation.js";
55
55
  import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
56
56
  import { Segment } from "../core/segment.js";
57
57
  import { type RichText } from "../core/text.js";
58
- import { type Moved, type Placed } from "./effect.js";
58
+ import { type Placed } from "./effect.js";
59
59
  /**
60
60
  * A cell of the element a glyph effect wraps: the glyph it shows, where it
61
61
  * sits and who drew it, its place in reading order among the cells handed
@@ -85,7 +85,7 @@ export interface DrawnGlyphs {
85
85
  }
86
86
  /**
87
87
  * For an element drawing `drawn`, the animation of what is done to its
88
- * glyphs. Its `nextChange` is when the frame next changes, as an `Effect`'s is.
88
+ * glyphs. Its `validUntil` is when the frame next changes, as an `Effect`'s is.
89
89
  */
90
90
  export type GlyphEffect = (drawn: DrawnGlyphs) => Animation<GlyphFrame>;
91
91
  export interface GlyphEffectedOptions {
@@ -95,20 +95,18 @@ export interface GlyphEffectedOptions {
95
95
  /**
96
96
  * A `GlyphEffected` at `t`, or an element through a glyph effect at a time an
97
97
  * animation's `at` was handed: `renderable` with its cells' glyphs passed
98
- * through `effect` at `t`, saying nothing of when that changes, as a
99
- * `Recolored` is (`./effect.ts`). [LAW:one-type-per-behavior]
98
+ * through `effect` at `t`, telling the hold it drew in as a `Recolored` does
99
+ * (`RenderOptions.onReads`, `./effect.ts`). [LAW:one-type-per-behavior]
100
100
  */
101
101
  export declare class Reglyphed implements Renderable, Measurable {
102
102
  readonly renderable: Renderable;
103
103
  readonly effect: GlyphEffect;
104
- readonly t: number;
104
+ private readonly _t;
105
105
  readonly key: string;
106
106
  constructor(renderable: Renderable, effect: GlyphEffect, options: GlyphEffectedOptions & {
107
107
  readonly t: number;
108
108
  });
109
109
  render(options: RenderOptions): Iterable<Segment>;
110
- /** The frame the effect draws at `t`, and the hold it says that is in. */
111
- moved(options: RenderOptions): Moved;
112
110
  measure(options: RenderOptions): {
113
111
  minimum: number;
114
112
  maximum: number;
@@ -116,9 +114,10 @@ export declare class Reglyphed implements Renderable, Measurable {
116
114
  }
117
115
  /**
118
116
  * `renderable` under a glyph effect over time, an `Animation<Renderable>`
119
- * as an `Effected` is (`./effect.ts`): its own answer is `t`, and placed,
120
- * its cells carry the glyph effect's exact hold at the frame's time and the
121
- * output's `asciiOnly`, under whatever the element already said.
117
+ * as an `Effected` is (`./effect.ts`): with nothing drawn its answer is `t`,
118
+ * and placed, on its own or as a part of any combinator, it says the glyph
119
+ * effect's exact hold at that time and the output's `asciiOnly`, which the
120
+ * frame's drawing told (`Reads`).
122
121
  */
123
122
  export declare class GlyphEffected implements Animation<Renderable> {
124
123
  readonly renderable: Renderable;
@@ -127,8 +126,8 @@ export declare class GlyphEffected implements Animation<Renderable> {
127
126
  constructor(renderable: Renderable, effect: GlyphEffect, options: GlyphEffectedOptions);
128
127
  /** The element through the glyph effect at `t`, unstamped. */
129
128
  at(t: number): Reglyphed;
130
- nextChange(t: number): number;
131
- lastChange(t: number): number;
129
+ validUntil(t: number, moves?: Reads): number;
130
+ validFrom(t: number, moves?: Reads): number;
132
131
  /** The element under the glyph effect with its motion taken out. */
133
132
  reduced(): GlyphEffected;
134
133
  /** Drawn at the frame's time, its cells stamped with the glyph effect's exact hold there. */
@@ -150,7 +149,7 @@ export declare class GlyphEffected implements Animation<Renderable> {
150
149
  * its every style and span, each moved to where its glyphs now sit, and a
151
150
  * frame that changes nothing hands the text back as it was. What it says of
152
151
  * when it changes is the effect's, unstamped: whoever draws it at a time
153
- * stamps it (`textFrameAt`).
152
+ * stamps it (`placed`), or marks it for the engine to (the template `glyphs`).
154
153
  */
155
154
  export declare function reglyphText(text: RichText, effect: GlyphEffect, options: {
156
155
  readonly key: string;
@@ -3,8 +3,8 @@
3
3
  * glyph effect at a moment `t`: the glyph half of `Effected` (`./effect.ts`),
4
4
  * built to its contract.
5
5
  *
6
- * A glyph effect is the colour effect's two-stage shape with a glyph where
7
- * the colours were: for the cells an element draws in a frame, an
6
+ * A glyph effect is the color effect's two-stage shape with a glyph where
7
+ * the colors were: for the cells an element draws in a frame, an
8
8
  * `Animation` (`../core/animation.ts`) of what is done to them. The first
9
9
  * stage is handed every cell once a frame (`GlyphCell`), blanks included —
10
10
  * rain and stars live there — and whether the output draws ASCII alone
@@ -14,15 +14,15 @@
14
14
  * scramble, a wave of case changes and particles on blank cells are all this
15
15
  * one shape (`./glyph-effects.ts`) [LAW:one-type-per-behavior].
16
16
  *
17
- * Colour and glyph are not two kinds of animation: each is an `Animation` of
17
+ * Color and glyph are not two kinds of animation: each is an `Animation` of
18
18
  * a per-cell map, and they differ only in what a cell is mapped to. The
19
- * wrappers are two because the stages are handed different facts. A colour
20
- * effect is settled against the depth, the terminal and the colours of the
21
- * words (`Drawn`), and with no colour output there is nothing for it to
19
+ * wrappers are two because the stages are handed different facts. A color
20
+ * effect is settled against the depth, the terminal and the colors of the
21
+ * words (`Drawn`), and with no color output there is nothing for it to
22
22
  * change. A glyph effect needs none of that, and is written at every depth,
23
- * with no colour too. Over one element the two compose by nesting:
24
- * `new Effected(new GlyphEffected(child, glyphs, …), colours, …)` moves the
25
- * colours of the glyphs the glyph effect left, since each wrapper measures
23
+ * with no color too. Over one element the two compose by nesting:
24
+ * `new Effected(new GlyphEffected(child, glyphs, …), colors, …)` moves the
25
+ * colors of the glyphs the glyph effect left, since each wrapper measures
26
26
  * what its child drew.
27
27
  *
28
28
  * A `GlyphEffected` is the element under its glyph effect over time, an
@@ -51,44 +51,40 @@
51
51
  * Positions are the child's own: row and column count from the top-left of
52
52
  * what the child drew.
53
53
  */
54
- import { Animation, frameTimeOf, holdOf, shownAt, stampFrame } from "../core/animation.js";
54
+ import { Animation, placedRender, shownAt } from "../core/animation.js";
55
55
  import { cellLen, graphemes } from "../core/cells.js";
56
56
  import { Measurement } from "../core/measure.js";
57
57
  import { render } from "../core/protocol.js";
58
58
  import { Segment } from "../core/segment.js";
59
59
  import { finiteTime } from "../core/easing.js";
60
- import { stripControlChars } from "../core/text.js";
61
- import { cellSeed, walkGlyphs } from "./effect.js";
60
+ import { sanitized } from "../core/text.js";
61
+ import { cellSeed, validityOver, walkGlyphs } from "./effect.js";
62
62
  import { pattern } from "../core/pattern.js";
63
63
  /**
64
64
  * A `GlyphEffected` at `t`, or an element through a glyph effect at a time an
65
65
  * animation's `at` was handed: `renderable` with its cells' glyphs passed
66
- * through `effect` at `t`, saying nothing of when that changes, as a
67
- * `Recolored` is (`./effect.ts`). [LAW:one-type-per-behavior]
66
+ * through `effect` at `t`, telling the hold it drew in as a `Recolored` does
67
+ * (`RenderOptions.onReads`, `./effect.ts`). [LAW:one-type-per-behavior]
68
68
  */
69
69
  export class Reglyphed {
70
70
  renderable;
71
71
  effect;
72
- t;
72
+ _t;
73
73
  key;
74
74
  constructor(renderable, effect, options) {
75
75
  this.renderable = renderable;
76
76
  this.effect = effect;
77
- this.t = finiteTime("GlyphEffected", options.t);
77
+ this._t = finiteTime("GlyphEffected", options.t);
78
78
  this.key = options.key;
79
79
  }
80
80
  render(options) {
81
- return this.moved(options).segments;
82
- }
83
- /** The frame the effect draws at `t`, and the hold it says that is in. */
84
- moved(options) {
85
81
  const segments = [...render(this.renderable, options)];
86
82
  const { placed, cells } = placeGlyphs(segments, this.key);
87
83
  const animation = this.effect({ cells, asciiOnly: options.asciiOnly === true });
88
- const frame = shownAt(animation, this.t, options);
84
+ const frame = shownAt(animation, this._t, options);
89
85
  const shown = showGlyphs(placed, frame.value);
90
- const changed = segments.map((segment, i) => shown[i].every((text, j) => text === placed[i][j].glyph) ? segment : new Segment(shown[i].join(""), segment.style));
91
- return { segments: changed, hold: holdOf(frame, this.t) };
86
+ options.onReads?.(this._t, this.key, validityOver(cells.length > 0, frame, this._t));
87
+ return segments.map((segment, i) => (shown[i].every((text, j) => text === placed[i][j].glyph) ? segment : new Segment(shown[i].join(""), segment.style)));
92
88
  }
93
89
  measure(options) {
94
90
  return Measurement.get(options, this.renderable);
@@ -96,9 +92,10 @@ export class Reglyphed {
96
92
  }
97
93
  /**
98
94
  * `renderable` under a glyph effect over time, an `Animation<Renderable>`
99
- * as an `Effected` is (`./effect.ts`): its own answer is `t`, and placed,
100
- * its cells carry the glyph effect's exact hold at the frame's time and the
101
- * output's `asciiOnly`, under whatever the element already said.
95
+ * as an `Effected` is (`./effect.ts`): with nothing drawn its answer is `t`,
96
+ * and placed, on its own or as a part of any combinator, it says the glyph
97
+ * effect's exact hold at that time and the output's `asciiOnly`, which the
98
+ * frame's drawing told (`Reads`).
102
99
  */
103
100
  export class GlyphEffected {
104
101
  renderable;
@@ -113,11 +110,11 @@ export class GlyphEffected {
113
110
  at(t) {
114
111
  return new Reglyphed(this.renderable, this.effect, { t, key: this.key });
115
112
  }
116
- nextChange(t) {
117
- return t;
113
+ validUntil(t, moves) {
114
+ return moves?.(t, this.key)?.validUntil ?? t;
118
115
  }
119
- lastChange(t) {
120
- return t;
116
+ validFrom(t, moves) {
117
+ return moves?.(t, this.key)?.validFrom ?? t;
121
118
  }
122
119
  /** The element under the glyph effect with its motion taken out. */
123
120
  reduced() {
@@ -126,8 +123,7 @@ export class GlyphEffected {
126
123
  }
127
124
  /** Drawn at the frame's time, its cells stamped with the glyph effect's exact hold there. */
128
125
  render(options) {
129
- const { segments, hold } = this.at(frameTimeOf(options)).moved(options);
130
- return stampFrame(segments, hold);
126
+ return placedRender(this, options);
131
127
  }
132
128
  measure(options) {
133
129
  return Measurement.get(options, this.renderable);
@@ -161,7 +157,7 @@ function showGlyphs(placed, frame) {
161
157
  * its every style and span, each moved to where its glyphs now sit, and a
162
158
  * frame that changes nothing hands the text back as it was. What it says of
163
159
  * when it changes is the effect's, unstamped: whoever draws it at a time
164
- * stamps it (`textFrameAt`).
160
+ * stamps it (`placed`), or marks it for the engine to (the template `glyphs`).
165
161
  */
166
162
  export function reglyphText(text, effect, options) {
167
163
  const { placed, cells } = placeGlyphs([new Segment(text.plain)], options.key);
@@ -193,7 +189,7 @@ function rewritten(text, glyphs, shown) {
193
189
  result.stylize(span.style, moved(span.start), moved(span.end));
194
190
  return result;
195
191
  }
196
- /** Line breaks and tabs, which `stripControlChars` leaves for a text's own lines and which no cell may hold. */
192
+ /** Line breaks and tabs, which `sanitized` leaves for a text's own lines and which no cell may hold. */
197
193
  const BREAKS = pattern(/[\n\t]/);
198
194
  /**
199
195
  * What `frame` shows in `cell`, held to the contract: as many cells wide as
@@ -205,7 +201,7 @@ function shows(frame, cell) {
205
201
  const text = frame(cell);
206
202
  if (text === cell.glyph)
207
203
  return text;
208
- if (cellLen(text) !== cellLen(cell.glyph) || BREAKS.test(text) || stripControlChars(text) !== text) {
204
+ if (cellLen(text) !== cellLen(cell.glyph) || BREAKS.test(text) || sanitized(text) !== text) {
209
205
  throw new RangeError(`a glyph effect turned ${JSON.stringify(cell.glyph)} at row ${cell.row}, column ${cell.col} into ${JSON.stringify(text)}: ` +
210
206
  `it must be ${cellLen(cell.glyph)} cell(s) wide, with no line break or control character`);
211
207
  }
@@ -218,18 +214,24 @@ function shows(frame, cell) {
218
214
  * wide as its glyph is `shows`'s check; this is the half no one cell can
219
215
  * see — a trailing zero-width joiner that fuses with the next emoji, or a
220
216
  * leading combining mark that draws on the glyph before it, is right alone
221
- * and wrong beside its neighbour. Only a changed text is asked, so a child
222
- * that splits a cluster across its own segments is drawn as it drew it.
217
+ * and wrong beside its neighbour. Only a join the effect made is asked, seam
218
+ * by seam, so a child that splits a cluster across its own segments is drawn
219
+ * as it drew it, whatever becomes of the glyph its mark sits on.
223
220
  */
224
221
  function standsApart(placed, shown) {
225
- // A line break is no cell, and nothing joins across it.
226
- const beside = (j) => (placed[j]?.cell === undefined ? "" : shown[j]);
222
+ const fuse = (left, right) => graphemes(left + right).length < graphemes(left).length + graphemes(right).length;
223
+ // Whether the text at `i` fuses across its left seam and across its right.
224
+ const seams = (texts, i) => {
225
+ // A line break is no cell, and nothing joins across it.
226
+ const beside = (j) => (placed[j]?.cell === undefined ? "" : texts(j));
227
+ const [before, text, after] = [beside(i - 1), texts(i), beside(i + 1)];
228
+ return [fuse(before, text + after), fuse(before + text, after)];
229
+ };
227
230
  shown.forEach((text, i) => {
228
231
  const { glyph, cell } = placed[i];
229
232
  if (cell === undefined || text === glyph)
230
233
  return;
231
- const [before, after] = [beside(i - 1), beside(i + 1)];
232
- if (graphemes(before + text + after).length !== graphemes(before).length + graphemes(text).length + graphemes(after).length) {
234
+ if (seams((j) => shown[j], i).some((fused, seam) => fused && !seams((j) => placed[j].glyph, i)[seam])) {
233
235
  throw new RangeError(`a glyph effect turned ${JSON.stringify(glyph)} at row ${cell.row}, column ${cell.col} into ${JSON.stringify(text)}, ` +
234
236
  `which joins the cell beside it into one character`);
235
237
  }