@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
@@ -12,8 +12,17 @@ export declare const MEMO_MAX = 4096;
12
12
  * be a pure function of `key`, so a value recomputed after a clear is the one
13
13
  * that was dropped.
14
14
  */
15
- export declare class Memo<V extends NonNullable<unknown>> {
15
+ export declare class Memo<V extends NonNullable<unknown>, K extends string = string> {
16
16
  private readonly entries;
17
17
  get size(): number;
18
- get(key: string, compute: (key: string) => V): V;
18
+ get(key: K, compute: (key: K) => V): V;
19
+ }
20
+ /**
21
+ * `Memo`'s contract for a key that is an object rather than a string: a
22
+ * value computed once per key, for as long as the key lives. Held weakly, so
23
+ * it needs no bound — a key a host stops holding takes its entry with it.
24
+ */
25
+ export declare class WeakMemo<V extends NonNullable<unknown>, K extends object> {
26
+ private readonly entries;
27
+ get(key: K, compute: (key: K) => V): V;
19
28
  }
package/dist/core/memo.js CHANGED
@@ -32,3 +32,19 @@ export class Memo {
32
32
  return value;
33
33
  }
34
34
  }
35
+ /**
36
+ * `Memo`'s contract for a key that is an object rather than a string: a
37
+ * value computed once per key, for as long as the key lives. Held weakly, so
38
+ * it needs no bound — a key a host stops holding takes its entry with it.
39
+ */
40
+ export class WeakMemo {
41
+ entries = new WeakMap();
42
+ get(key, compute) {
43
+ const remembered = this.entries.get(key);
44
+ if (remembered !== undefined)
45
+ return remembered;
46
+ const value = compute(key);
47
+ this.entries.set(key, value);
48
+ return value;
49
+ }
50
+ }
@@ -96,6 +96,11 @@ function inGamut(r, g, b) {
96
96
  g >= -GAMUT_EPS && g <= 1 + GAMUT_EPS &&
97
97
  b >= -GAMUT_EPS && b <= 1 + GAMUT_EPS);
98
98
  }
99
+ /** Polar (C, h°) → OKLab (a, b): the one spelling `deltaE` measures by and `toRgba` draws by. */
100
+ function labOf(c, h) {
101
+ const hRad = h * (Math.PI / 180);
102
+ return [c * Math.cos(hRad), c * Math.sin(hRad)];
103
+ }
99
104
  // ---------------------------------------------------------------------------
100
105
  // Oklch — the value type.
101
106
  // ---------------------------------------------------------------------------
@@ -130,6 +135,7 @@ export class Oklch {
130
135
  if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
131
136
  throw new RangeError(`Oklch.alpha must be a finite number in [0,1]; got ${alpha}`);
132
137
  }
138
+ this.#lab = labOf(c, h);
133
139
  }
134
140
  /** sRGB → linear → OKLab → polar. Pure; no I/O, no allocation beyond the result. */
135
141
  static fromRgba(color) {
@@ -265,16 +271,21 @@ export class Oklch {
265
271
  * Symmetric and pure.
266
272
  */
267
273
  deltaE(other) {
268
- const a = (this.h * Math.PI) / 180;
269
- const b = (other.h * Math.PI) / 180;
270
- return Math.hypot(this.l - other.l, this.c * Math.cos(a) - other.c * Math.cos(b), this.c * Math.sin(a) - other.c * Math.sin(b));
274
+ const [a, b] = this.#lab;
275
+ const [otherA, otherB] = other.#lab;
276
+ return Math.hypot(this.l - other.l, a - otherA, b - otherB);
271
277
  }
278
+ /**
279
+ * This colour's OKLab a and b, kept with it: a colour matched against a
280
+ * palette is measured against every entry, and each entry against every
281
+ * colour it is matched with, so the trigonometry is paid once a colour
282
+ * rather than once a comparison.
283
+ */
284
+ #lab;
272
285
  /** Linear-sRGB coordinates for an explicit (l, C, h). Pure; `toRgba` passes
273
286
  * already-normalized values so this never sees out-of-range inputs. */
274
287
  toLinearRgb(l, C, h) {
275
- const hRad = h * (Math.PI / 180);
276
- const aLab = C * Math.cos(hRad);
277
- const bLab = C * Math.sin(hRad);
288
+ const [aLab, bLab] = labOf(C, h);
278
289
  const lCube = l + 0.3963377774 * aLab + 0.2158037573 * bLab;
279
290
  const mCube = l - 0.1055613458 * aLab - 0.0638541728 * bLab;
280
291
  const sCube = l - 0.0894841775 * aLab - 1.2914855480 * bLab;
@@ -49,7 +49,7 @@ export declare const OSC8_CLOSE = "\u001B]8;;\u001B\\";
49
49
  * on a close. To read bytes back use `osc8Sequences`; this pattern is exported
50
50
  * for composing into a larger one (a width measure's zero-width alternation).
51
51
  */
52
- export declare const OSC8: RegExp;
52
+ export declare const OSC8: import("./pattern.js").Pattern;
53
53
  /** One OSC 8 sequence found in rendered bytes. A close has `uri === ""`. */
54
54
  export interface Osc8Sequence {
55
55
  /** Offset of the sequence in the scanned string. */
package/dist/core/osc8.js CHANGED
@@ -31,15 +31,15 @@
31
31
  * exact, not approximate.
32
32
  */
33
33
  import { fnv1a } from "./fnv1a.js";
34
+ import { pattern } from "./pattern.js";
34
35
  /**
35
36
  * The bytes that prematurely terminate an OSC 8 sequence: ESC (`\x1b`, which
36
37
  * begins ST `ESC \`), BEL (`\x07`), and the 8-bit ST (`\x9c`).
37
38
  */
38
39
  const TERMINATORS = "\\x1b\\x07\\x9c";
39
- const TERMINATOR_RE = new RegExp(`[${TERMINATORS}]`, "g");
40
40
  /** Remove every byte that could break a URL out of its OSC 8 wrap. */
41
41
  export function stripOscTerminators(url) {
42
- return url.replace(TERMINATOR_RE, "");
42
+ return url.replace(new RegExp(`[${TERMINATORS}]`, "g"), "");
43
43
  }
44
44
  /**
45
45
  * The bytes that open a hyperlink to `url`.
@@ -62,14 +62,13 @@ export const OSC8_CLOSE = "\x1b]8;;\x1b\\";
62
62
  * on a close. To read bytes back use `osc8Sequences`; this pattern is exported
63
63
  * for composing into a larger one (a width measure's zero-width alternation).
64
64
  */
65
- export const OSC8 = new RegExp(`\\x1b\\]8;([^;${TERMINATORS}]*);([^${TERMINATORS}]*)(?:\\x1b\\\\|\\x07|\\x9c)`);
66
- const OSC8_SCAN = new RegExp(OSC8.source, "g");
65
+ export const OSC8 = pattern(new RegExp(`\\x1b\\]8;([^;${TERMINATORS}]*);([^${TERMINATORS}]*)(?:\\x1b\\\\|\\x07|\\x9c)`));
67
66
  /**
68
67
  * Every OSC 8 sequence in `text`, in order — the typed way to read rendered
69
68
  * bytes back.
70
69
  */
71
70
  export function osc8Sequences(text) {
72
- return [...text.matchAll(OSC8_SCAN)].map((m) => ({
71
+ return [...text.matchAll(new RegExp(OSC8.source, "g"))].map((m) => ({
73
72
  index: m.index,
74
73
  length: m[0].length,
75
74
  params: m[1],
@@ -71,7 +71,8 @@ export declare class SurfacePainter implements Painter {
71
71
  * Paint `frame` over the last frame, leaving the cursor on its last row,
72
72
  * and return it as painted. No row is wider than the screen, since one that
73
73
  * soft-wrapped would push every row below it down a row the next paint does
74
- * not go back over.
74
+ * not go back over. The rows returned are what `destination` shows
75
+ * (`shownOn`): under reduced motion, nothing in them blinks.
75
76
  */
76
77
  paint(frame: FrameSource, screen: Screen, destination: Destination): Segment[][];
77
78
  /**
@@ -13,7 +13,7 @@
13
13
  * the last row, and the newline that ends it is written once, when the frame
14
14
  * is handed back.
15
15
  */
16
- import { segmentsToString } from "./render.js";
16
+ import { segmentsToString, shownOn } from "./render.js";
17
17
  import { Segment } from "./segment.js";
18
18
  const GEOMETRY = {
19
19
  alternate: {
@@ -84,10 +84,11 @@ export class SurfacePainter {
84
84
  * Paint `frame` over the last frame, leaving the cursor on its last row,
85
85
  * and return it as painted. No row is wider than the screen, since one that
86
86
  * soft-wrapped would push every row below it down a row the next paint does
87
- * not go back over.
87
+ * not go back over. The rows returned are what `destination` shows
88
+ * (`shownOn`): under reduced motion, nothing in them blinks.
88
89
  */
89
90
  paint(frame, screen, destination) {
90
- const rows = fitRows(frame(), screen);
91
+ const rows = fitRows(frame(), screen).map((row) => row.map((segment) => shownOn(segment, destination)));
91
92
  this.write(SYNC_START + this.over(rows, screen, destination) + SYNC_END);
92
93
  return rows;
93
94
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * pattern — `Pattern`: a regular expression held where every caller reaches
3
+ * it — module scope, a class static, a renderable's field — with no cursor
4
+ * for them to share.
5
+ *
6
+ * A `RegExp` carries `lastIndex`, and the `exec`, `test` and `replace` of a
7
+ * global or sticky one write it, so a global regex held at module scope is
8
+ * state that every caller moves. A regex with neither flag has a `lastIndex`
9
+ * that `exec`, `test`, `match`, `replace` and `split` never write. `pattern`
10
+ * is where that is checked: it refuses `g` and `y`, and hands back only
11
+ * those, with `source` and `flags` to compose it into another. A cursor — a
12
+ * sticky scan, a global replace — is made in the function that runs it, so
13
+ * each call owns its own.
14
+ */
15
+ declare const PATTERN: unique symbol;
16
+ /** A regex with no cursor: only `pattern` makes one. */
17
+ export type Pattern = Pick<RegExp, "exec" | "test" | "source" | "flags" | typeof Symbol.match | typeof Symbol.replace | typeof Symbol.split> & {
18
+ readonly [PATTERN]: true;
19
+ };
20
+ export declare function pattern(re: RegExp): Pattern;
21
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * pattern — `Pattern`: a regular expression held where every caller reaches
3
+ * it — module scope, a class static, a renderable's field — with no cursor
4
+ * for them to share.
5
+ *
6
+ * A `RegExp` carries `lastIndex`, and the `exec`, `test` and `replace` of a
7
+ * global or sticky one write it, so a global regex held at module scope is
8
+ * state that every caller moves. A regex with neither flag has a `lastIndex`
9
+ * that `exec`, `test`, `match`, `replace` and `split` never write. `pattern`
10
+ * is where that is checked: it refuses `g` and `y`, and hands back only
11
+ * those, with `source` and `flags` to compose it into another. A cursor — a
12
+ * sticky scan, a global replace — is made in the function that runs it, so
13
+ * each call owns its own.
14
+ */
15
+ export function pattern(re) {
16
+ // [LAW:no-silent-failure] A held cursor is refused where it is made, not found later as a match that began mid-string.
17
+ if (re.global || re.sticky) {
18
+ throw new TypeError(`/${re.source}/${re.flags} keeps a cursor in lastIndex; make it in the function that runs it`);
19
+ }
20
+ return re;
21
+ }
@@ -18,10 +18,12 @@
18
18
  */
19
19
  import { asCellCol, cellColToCodeUnitOffset, cellLen, graphemes, splitAtCells } from "./cells.js";
20
20
  import { divideLine } from "./wrap.js";
21
+ import { Memo } from "./memo.js";
21
22
  import { Segment } from "./segment.js";
22
23
  import { RichText, stripControlChars } from "./text.js";
23
24
  import { ReprHighlighter } from "./highlighter.js";
24
25
  import { drawable, render } from "./protocol.js";
26
+ import { pattern } from "./pattern.js";
25
27
  const reprHighlighter = new ReprHighlighter();
26
28
  /**
27
29
  * What a value is bounded by when whoever formats it has not seen it:
@@ -453,7 +455,7 @@ const elided = (dropped) => dropped > 0 ? [{ head: `... +${dropped}`, join: "",
453
455
  * An index is at most 15 digits: every such literal is a safe integer and
454
456
  * names itself, where `99999999999999999999` would name `1e20`'s key.
455
457
  */
456
- const BARE_KEY = /^(?![^]*\p{Default_Ignorable_Code_Point})(?:[\p{ID_Start}$_][\p{ID_Continue}$]*|0|[1-9]\d{0,14})$/u;
458
+ const BARE_KEY = pattern(/^(?![^]*\p{Default_Ignorable_Code_Point})(?:[\p{ID_Start}$_][\p{ID_Continue}$]*|0|[1-9]\d{0,14})$/u);
457
459
  /**
458
460
  * What Python's `str.isprintable` refuses \u2014 control, format, private-use,
459
461
  * unassigned, and every separator but the space \u2014 which Rich's repr escapes
@@ -461,9 +463,9 @@ const BARE_KEY = /^(?![^]*\p{Default_Ignorable_Code_Point})(?:[\p{ID_Start}$_][\
461
463
  * byte reaches the terminal as a control, and a format character hides or
462
464
  * reorders what is around it.
463
465
  */
464
- const UNPRINTABLE = /(?! )[\p{Cc}\p{Cf}\p{Co}\p{Cn}\p{Zl}\p{Zp}\p{Zs}]/gu;
466
+ const unprintable = () => /(?! )[\p{Cc}\p{Cf}\p{Co}\p{Cn}\p{Zl}\p{Zp}\p{Zs}]/gu;
465
467
  /** `value` in double quotes, every character that does not print escaped. */
466
- const quoted = (value) => JSON.stringify(value).replace(UNPRINTABLE, (c) => {
468
+ const quoted = (value) => JSON.stringify(value).replace(unprintable(), (c) => {
467
469
  const hex = c.codePointAt(0).toString(16).padStart(4, "0");
468
470
  return hex.length > 4 ? `\\u{${hex}}` : `\\u${hex}`;
469
471
  });
@@ -477,7 +479,7 @@ export class Pretty {
477
479
  indentGuides;
478
480
  highlighter;
479
481
  // [LAW:no-shared-mutable-globals] A private memo, written only by `_cutString`.
480
- cutStrings = new Map();
482
+ cutStrings = new Memo();
481
483
  constructor(data, options) {
482
484
  this.data = data;
483
485
  this.indent = options?.indent ?? 4;
@@ -595,15 +597,12 @@ export class Pretty {
595
597
  * memoised per value.
596
598
  */
597
599
  _cutString(value, max) {
598
- let cut = this.cutStrings.get(value);
599
- if (cut === undefined) {
600
+ return this.cutStrings.get(value, () => {
600
601
  const clusters = graphemes(value);
601
- cut = clusters.length <= max
602
+ return clusters.length <= max
602
603
  ? quoted(value)
603
604
  : quoted(clusters.slice(0, max).join("")) + `+${clusters.length - max}`;
604
- this.cutStrings.set(value, cut);
605
- }
606
- return cut;
605
+ });
607
606
  }
608
607
  /**
609
608
  * A container, or the text standing in for one that is empty or past the
@@ -3,7 +3,7 @@
3
3
  * [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
4
4
  */
5
5
  import type { Segment } from "./segment.js";
6
- import { Style, type StyleSyntaxError, type Theme } from "./style.js";
6
+ import { Style, type Hold, type StyleSyntaxError, type Theme } from "./style.js";
7
7
  import { ColorDepth } from "./color.js";
8
8
  import type { Highlighter } from "./highlighter.js";
9
9
  /**
@@ -17,6 +17,15 @@ import type { Highlighter } from "./highlighter.js";
17
17
  * wrapping and leaves the edge, so a line too wide is cut by the method here.
18
18
  */
19
19
  export type OverflowMethod = "fold" | "crop" | "ellipsis" | "ignore";
20
+ /**
21
+ * What draws frames — a `Live`, an `App` — as what draws in them sees it:
22
+ * the time a frame drawn now is drawn for, the host's clock read through its
23
+ * timeline for a view on it (`frameTime`, `./view.ts`). Not a `Clock`: under
24
+ * a paused timeline it holds, and under a reversed one it runs backwards.
25
+ */
26
+ export interface FrameHost {
27
+ readonly frameTime: number;
28
+ }
20
29
  export interface RenderOptions {
21
30
  /**
22
31
  * The cells this renderable may occupy. A count of cells, so a non-negative
@@ -68,8 +77,8 @@ export interface RenderOptions {
68
77
  highlighter?: Highlighter;
69
78
  /**
70
79
  * The depth the output will be encoded at, `null` when it carries no colour.
71
- * A renderable choosing between two ways of drawing something — the strip's
72
- * arrow or its divider — decides on the colours the terminal will draw at
80
+ * A renderable that measures what it draws — the strip's joiner, holding a
81
+ * seam's ink off its ground — measures the colours the terminal will draw at
73
82
  * this depth, which at 256 colours or fewer are not the colours it was
74
83
  * handed. Absent means truecolor.
75
84
  */
@@ -86,17 +95,52 @@ export interface RenderOptions {
86
95
  * the render strict: the error leaves `render`.
87
96
  */
88
97
  onStyleError?: StyleErrorHandler;
98
+ /**
99
+ * The reader asked for reduced motion — CSS's `prefers-reduced-motion`,
100
+ * which a terminal has no way to report, so a host is told. A renderable
101
+ * built for the time the host draws at draws every animation reduced, its
102
+ * motion taken out and its content kept on time (`shownAt`,
103
+ * `./view.ts`; `Animation.reduced`, `./animation.ts`). Absent,
104
+ * everything moves. Nothing here reads it from the environment.
105
+ */
106
+ reducedMotion?: boolean;
107
+ /**
108
+ * The frame being drawn: `time`, the moment it is drawn for, in seconds, on
109
+ * the clock its cells' `nextChange` stamps are read on, and `host`, the
110
+ * host drawing it, whose `frameTime` that moment was. It is how a
111
+ * renderable that lives across frames — a widget, a `Progress` — draws for
112
+ * the time without being rebuilt each frame with one, and without reading
113
+ * a clock. Absent, the frame is drawn for no moment — a `print` — and
114
+ * nothing drawn from it moves.
115
+ */
116
+ frame?: {
117
+ readonly time: number;
118
+ readonly host: FrameHost;
119
+ };
89
120
  /**
90
121
  * Told of every renderable `render` is asked for, as it is asked, so the
91
- * calls arrive in document order: a container before what it holds, its
92
- * contents in the order it renders them. `"drawn"` is a renderable drawn at
122
+ * calls arrive in document order: a container before what it holds, what
123
+ * it holds in the order it renders it. `"drawn"` is a renderable drawn at
93
124
  * the width it was handed; `"held"` is one handed no column, which is still
94
125
  * in the view but has nothing drawn for it (`render`). A renderable rendered
95
126
  * twice is told twice. A render made to measure is not drawing and is not
96
127
  * told (`measuring`).
97
128
  */
98
129
  onPresent?: (renderable: Renderable, presence: Presence) => void;
130
+ /**
131
+ * Told of a hold the frame is in that no cell of it may carry: an animation
132
+ * placed in the view reports its own, whether or not it drew a cell — one
133
+ * not yet entered draws none — and one a container crops away is still
134
+ * reported. It reaches whatever `frame` reaches, so a container that hands
135
+ * its child the frame's time hands back the child's hold with no word about
136
+ * either. [LAW:composability] Absent, nobody is listening. A measurement
137
+ * (`measuring`) and a held render (`holding`) tell too: what they find
138
+ * decides the frame's layout, so when it changes, the frame may.
139
+ */
140
+ onHold?: (hold: Hold) => void;
99
141
  }
142
+ /** The one field of the render options that says whether the reader wants motion. */
143
+ export type Motion = Pick<RenderOptions, "reducedMotion">;
100
144
  /**
101
145
  * Whether a renderable the view holds was drawn, or held: in the view, handed
102
146
  * no column, and so not drawn (`render`).
@@ -251,6 +295,39 @@ export declare function withCellWidth(options: RenderOptions): RenderOptions;
251
295
  export declare function drawsNothing(options: {
252
296
  readonly maxWidth: number;
253
297
  }): boolean;
298
+ /**
299
+ * One render pass: the outermost `render` or `measure` call — a host's frame,
300
+ * a print — and every `next()` of what it returned, at any depth. Opaque: it
301
+ * is only an identity, which a `State` compares to say whether this pass made
302
+ * it.
303
+ */
304
+ export interface Pass {
305
+ readonly __pass: unique symbol;
306
+ }
307
+ /**
308
+ * `handler` run as if between passes: a callback a render makes to user code to
309
+ * report something it met, such as `onStyleError`, is an event handler, and may
310
+ * write what one may — print to a console, set a `State` — as React lets an
311
+ * event handler set state.
312
+ */
313
+ export declare function betweenPasses<T>(handler: () => T): T;
314
+ /** The pass a render or measure in progress belongs to, or `undefined` between passes. */
315
+ export declare function activePass(): Pass | undefined;
316
+ /**
317
+ * `effect` run once the render pass in progress has drawn its last segment,
318
+ * between passes, where it may write what an event handler may: React's commit
319
+ * phase. A render that learns something only drawing can tell it — the lines
320
+ * content shown through a `Viewport` runs to at the width it was handed —
321
+ * draws from what it learned and hands the commit here, rather than writing it
322
+ * as it draws.
323
+ *
324
+ * Effects run in the order they were handed over, which is document order. A
325
+ * measurement commits nothing, and neither does a pass that throws or whose
326
+ * caller stops iterating it early: nobody saw what it drew. Outside a pass — a
327
+ * renderable's `render` called directly — there is nothing to wait for, and
328
+ * `effect` runs now.
329
+ */
330
+ export declare function afterPass(effect: () => void): void;
254
331
  /**
255
332
  * `renderable` drawn under `options`: Rich's `Console.render`, which every
256
333
  * renderable there is drawn through, so under one column nothing is.
@@ -262,25 +339,30 @@ export declare function drawsNothing(options: {
262
339
  * this, each container asked `drawsNothing` itself, and the ones that forgot
263
340
  * drew a child handed no column anyway.
264
341
  *
342
+ * It is where a render pass is entered (`Pass`), so it is the one place a
343
+ * write during render is refused at: a `State` set in a pass that did not make
344
+ * it throws (`./state.ts`).
345
+ *
265
346
  * It is also the one reporter of what the view holds (`onPresent`). Under one
266
- * column a renderable is held, not drawn: what it holds (`Composite`) is
267
- * walked and reported held in turn, and nothing renders, so no renderable
268
- * that keeps state while it renders — a `Viewport`'s offset, a `TextInput`'s
269
- * scroll window — ever commits it at a width that is not shown. The walk runs
270
- * whether or not anyone listens. Drawing the squeezed subtree at one column
271
- * instead, and dropping what it drew, did commit that state.
347
+ * column a renderable is held: in the view, with nothing of it on the frame.
348
+ * It is drawn all the same and what it drew dropped (`holding`), so what a held
349
+ * container is reported holding is what it draws, under the options it hands
350
+ * each child — one description of a container's traversal, not a second
351
+ * kept beside it.
272
352
  */
273
353
  export declare function render(renderable: Renderable, options: RenderOptions): Iterable<Segment>;
274
354
  /**
275
- * `options` as a measurement is asked with. A `measure` that renders to learn
276
- * its shape draws nothing on the frame, so it reports nothing to `onPresent` —
277
- * one that did would put a widget in the view at the moment a container
278
- * measured it, and keep one the frame then cropped away.
355
+ * What `measurable` reports under `options`: asked in a render pass, as
356
+ * `render` draws in one, with `measuring`'s options.
279
357
  *
280
- * [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
281
- * `withBoundedWidth` ask through it, and each `measure` passes it on.
358
+ * [LAW:single-enforcer] Every call of a `Measurable`'s `measure` in `src/`
359
+ * goes through here, as every `render` goes through `render`;
360
+ * `test/seam/render-calls.test.ts` holds both.
282
361
  */
283
- export declare function measuring(options: RenderOptions): RenderOptions;
362
+ export declare function measure(measurable: Measurable, options: RenderOptions): {
363
+ minimum: number;
364
+ maximum: number;
365
+ };
284
366
  /**
285
367
  * The options a renderable should *lay out against*: `withCellWidth`, with an
286
368
  * unbounded offer resolved to the renderable's own natural width.
@@ -326,26 +408,6 @@ export interface Measurable {
326
408
  maximum: number;
327
409
  };
328
410
  }
329
- /**
330
- * A container: a renderable that draws other renderables. `contents` is what
331
- * its `render` hands to `render`, in that order where the order is not a
332
- * width's, decided as the render decides it — and so it is how a container
333
- * handed no column still reports what it holds (`render`). It takes no
334
- * options: what a container holds may depend on its state (a hidden `Layout`
335
- * holds nothing) but never on a width, because under one column there is none.
336
- * It commits no state, and yields only renderables: text a container builds
337
- * from a string it holds holds nothing.
338
- *
339
- * [LAW:one-source-of-truth] A second description of each container's
340
- * traversal, so `test/renderables/contents.test.ts` holds every exported
341
- * renderable's `contents` to what its `render` reports.
342
- *
343
- * A consumer's container that does not implement it reports nothing it holds
344
- * while it is handed no column.
345
- */
346
- export interface Composite {
347
- contents(): Iterable<Renderable>;
348
- }
349
411
  /**
350
412
  * What the wheel scrolls: an owner of anchors on the frame (`./anchor.ts`)
351
413
  * that moves by lines. `canScrollBy` says whether `scrollBy` with the same
@@ -358,4 +420,3 @@ export interface Scrollable {
358
420
  export declare function isRenderable(obj: unknown): obj is Renderable;
359
421
  export declare function isMeasurable(obj: unknown): obj is Measurable;
360
422
  export declare function isScrollable(obj: object): obj is Scrollable;
361
- export declare function isComposite(obj: object): obj is Composite;