@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
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Scroll — a slot of fixed size over content wider or taller than it, the
3
+ * content's offset in the slot moving with time: a long line panning as a
4
+ * marquee, a log tail scrolling, a panel sliding into its place.
5
+ *
6
+ * [LAW:one-type-per-behavior] A marquee, a scroll and a slide are one window
7
+ * and differ only in the motion handed in: the offset along each axis is an
8
+ * `Animation<number>`, so a marquee is a looping `Phase` scaled to the span
9
+ * it crosses and a slide-in is a `tween` to 0, and when the window next
10
+ * changes is `Animation.all`'s answer over the two. Nothing here knows which
11
+ * one it is drawing.
12
+ *
13
+ * The slot is what the window is. It measures as its width whatever the
14
+ * content, and draws exactly its rows of its width at every offset — cropped
15
+ * only to a narrower room it is given — so what sits beside it never moves.
16
+ * An offset before or past the content shows blank cells rather than stopping
17
+ * at an edge: that is what lets a marquee enter from the right and a slide-in
18
+ * arrive from outside the slot.
19
+ *
20
+ * The content is drawn at its own natural width and no height, its own
21
+ * length, because the content wider and taller than the slot is the thing
22
+ * being windowed. Content that should wrap to the slot instead, or that has
23
+ * no natural width because it fills whatever it is given, is
24
+ * `new Constrain(content, width)`, composed rather than asked for.
25
+ *
26
+ * [LAW:single-enforcer] A row is cut at the window's edges by
27
+ * `Segment.divide`, the one place a line is cut at a column: a wide glyph a
28
+ * cut falls in is blanked, never halved, and each cell's anchor still names
29
+ * its column in the content, so hit-testing finds the owner through a moving
30
+ * window.
31
+ *
32
+ * [LAW:no-ambient-temporal-coupling] Pure in `t`, nothing kept between
33
+ * frames; `at` hands over the frame alone, and placed, the scroll stamps
34
+ * the hold it is in at the frame's time (`placedRender`).
35
+ */
36
+ import { Animation } from "../core/animation.js";
37
+ import { Measurement } from "../core/measure.js";
38
+ import { render, withBoundedWidth } from "../core/protocol.js";
39
+ import { Segment } from "../core/segment.js";
40
+ const NO_OFFSET = Animation.constant(0);
41
+ /** `content` seen through a slot of `width` by `height` cells, moved in it by `col` and `row` at each moment. */
42
+ export function scroll(content, options) {
43
+ const { width, height, col = NO_OFFSET, row = NO_OFFSET } = options;
44
+ for (const [name, n] of [["width", width], ["height", height]]) {
45
+ if (!(Number.isInteger(n) && n > 0))
46
+ throw new RangeError(`scroll: ${name} must be a positive whole number of cells, got ${n}`);
47
+ }
48
+ return Animation.map(Animation.all([col, row]), ([c, r]) => new ScrollFrame(content, width, height, cells("col", c), cells("row", r)));
49
+ }
50
+ /** `offset` rounded to the nearest cell. [LAW:no-silent-failure] One that is not finite has no cell to move to, and says so rather than drawing a blank slot. */
51
+ function cells(name, offset) {
52
+ if (!Number.isFinite(offset))
53
+ throw new RangeError(`scroll: ${name} must be a finite number of cells, got ${offset}`);
54
+ return Math.round(offset);
55
+ }
56
+ /**
57
+ * The width `content` is drawn at: its own natural one. [LAW:no-silent-failure]
58
+ * Content that takes whatever width it is offered — a `Rule`, a renderable
59
+ * with no `measure` — has none, and is refused by name rather than drawn at
60
+ * an unbounded width it cannot fill.
61
+ */
62
+ function naturalWidth(content, options) {
63
+ const natural = Measurement.get({ ...options, maxWidth: Number.POSITIVE_INFINITY }, content).maximum;
64
+ if (!Number.isFinite(natural)) {
65
+ throw new RangeError("scroll: the content has no natural width — it fills whatever width it is given; give it one with new Constrain(content, width)");
66
+ }
67
+ return natural;
68
+ }
69
+ /** One frame of a scroll: `content`'s cells from column `col` and row `row`, `width` by `height` of them, blank where it has none. */
70
+ class ScrollFrame {
71
+ content;
72
+ width;
73
+ height;
74
+ col;
75
+ row;
76
+ constructor(content, width, height, col, row) {
77
+ this.content = content;
78
+ this.width = width;
79
+ this.height = height;
80
+ this.col = col;
81
+ this.row = row;
82
+ }
83
+ *render(rawOptions) {
84
+ const options = withBoundedWidth(rawOptions, this);
85
+ // The slot is cropped to the room it is given, as a `Panel` of a declared width is.
86
+ const width = Math.min(this.width, options.maxWidth);
87
+ const drawn = [...render(this.content, { ...options, maxWidth: naturalWidth(this.content, options), height: undefined })];
88
+ const lines = Segment.splitLines(drawn);
89
+ const blank = Math.min(width, Math.max(0, -this.col));
90
+ const start = Math.max(0, this.col);
91
+ // [LAW:one-source-of-truth] What the window shows is cut from the whole content, so it may change
92
+ // when anything in the content does — a row above the slot entering pushes every row down, and run
93
+ // backwards, leaving it pulls them up. The cut throws away the cells that hold rode on, so it is
94
+ // told to the frame (`RenderOptions.onHold`).
95
+ options.onHold?.({ lastChange: Segment.lastChange(drawn), nextChange: Segment.nextChange(drawn) });
96
+ for (let r = 0; r < this.height; r++) {
97
+ const line = lines[this.row + r] ?? [];
98
+ const shown = Segment.divide(line, [start, start + width - blank])[1] ?? [];
99
+ yield* Segment.adjustLineLength([new Segment(" ".repeat(blank)), ...shown], width);
100
+ yield Segment.line();
101
+ }
102
+ }
103
+ measure() {
104
+ return { minimum: this.width, maximum: this.width };
105
+ }
106
+ }
@@ -8,7 +8,7 @@
8
8
  * the interval; the beat, the width every frame draws at and which frame `t`
9
9
  * lands on are the frames primitive's, so a spinner is one more sequence on
10
10
  * it and not a second way of turning frames. Custom frames are a `FrameSequence`
11
- * drawn through `Frames` directly. [LAW:one-source-of-truth]
11
+ * placed directly. [LAW:one-source-of-truth]
12
12
  *
13
13
  * It is built once and asked for each frame, as Rich's `Spinner` is built
14
14
  * once and has `render(time)` called on it: the name is parsed and the
@@ -19,13 +19,12 @@
19
19
  * the beat, the width, each frame padded to it — is made there too, so a
20
20
  * drawn frame costs only finding which one `t` lands on.
21
21
  *
22
- * [LAW:no-ambient-temporal-coupling] A Spinner draws nothing on its own:
23
- * only `at(t)` is a renderable, so a spinner cannot be drawn without being
24
- * handed its time, and nothing here reads a clock. Whoever draws frames
25
- * hands each one its time (`core/clock.ts`) — `SpinnerColumn` the task's
26
- * `time`, a `Live` the frame's time on its clock — as Rich's `Spinner.render`
27
- * is handed `console.get_time()` or `task.get_time()`. `t` is the clock's
28
- * time, as `App` and `Task.time` hand it, where Rich's spinner counts from
22
+ * [LAW:no-ambient-temporal-coupling] Placed, a spinner draws at the time of
23
+ * the frame it is drawn in, and nothing here reads a clock. Whoever draws
24
+ * frames hands each one its time (`core/clock.ts`) — a `Progress` the
25
+ * moment its tasks are drawn at, a `Live` the frame's time on its clock — as
26
+ * Rich's `Spinner.render` is handed `console.get_time()` or `task.get_time()`. `t` is the clock's
27
+ * time, as a host writes it on the frame, where Rich's spinner counts from
29
28
  * its first draw: here nothing is kept between frames, so the same `t` draws
30
29
  * the same bytes at one redraw a second or sixty, two spinners on one clock
31
30
  * agree at every `t`, a `Progress` on a hand-moved clock turns its spinners
@@ -35,10 +34,11 @@
35
34
  * that rate, as Rich scales the time, `t × speed` into the beat: the frames
36
35
  * are held for the interval the data gives, and the spinner is asked for the
37
36
  * frame at the time the timeline maps the host's to. [LAW:one-source-of-truth]
38
- * When the frame next changes (`Style.nextChange`) is a time on the host's
37
+ * When the frame next changes (`Style.hold`) is a time on the host's
39
38
  * timeline, and the sequence answers on its own, so the timeline carries the
40
- * answer back and the drawn cells are stamped with that (`frameAt`), where
41
- * the host's time is known. Backwards, the frames count down from the last at `t = 0`,
39
+ * answer back. `at` hands over the frame alone, as every animation's does;
40
+ * placed, a spinner draws at the frame's time and stamps its cells with that
41
+ * answer for the frames the output draws. Backwards, the frames count down from the last at `t = 0`,
42
42
  * a negative time landing in the last frame of the pass before, where Rich's
43
43
  * `int()` truncation holds frame 0 for a first interval and then counts
44
44
  * down: a departure, made so a negative speed is the one timeline run
@@ -46,10 +46,11 @@
46
46
  * frame forever, which a paused timeline is and a rate is not, and the
47
47
  * timeline refuses it.
48
48
  */
49
- import type { Animation } from "../core/animation.js";
49
+ import { Animation } from "../core/animation.js";
50
+ import { Segment } from "../core/segment.js";
50
51
  import { Style } from "../core/style.js";
51
52
  import { RichText } from "../core/text.js";
52
- import type { Renderable, Measurable } from "../core/protocol.js";
53
+ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
53
54
  export interface SpinnerOptions {
54
55
  /** Multiplies the spinner's rate; negative runs it backwards; zero is refused. Defaults to 1. */
55
56
  speed?: number;
@@ -66,10 +67,19 @@ export declare class Spinner implements Animation<Renderable & Measurable> {
66
67
  * whatever the console says — and a `RichText` changed in place draws as
67
68
  * changed, as the `Text` Rich keeps does. [LAW:single-enforcer]
68
69
  */
69
- text: string | RichText;
70
+ get text(): string | RichText;
71
+ set text(value: string | RichText);
72
+ private readonly _text;
70
73
  private readonly _data;
71
74
  private readonly _named;
72
75
  private readonly _ascii;
76
+ /**
77
+ * The spinner drawn at the frame's time, its cells saying the hold it is in
78
+ * on the host's clock (`stampFrame`) — exactly, for the frames the output
79
+ * draws, where `nextChange` must answer for both. Under reduced motion it
80
+ * is the spinner reduced, which never turns (`shownAt`).
81
+ */
82
+ private readonly _placed;
73
83
  constructor(name?: string, text?: string | RichText, options?: SpinnerOptions);
74
84
  get frames(): readonly string[];
75
85
  /** Milliseconds each frame is held, as the spinner data gives it. */
@@ -79,8 +89,19 @@ export declare class Spinner implements Animation<Renderable & Measurable> {
79
89
  * the line wrapped to the width and ended, as the reference's `Text` is —
80
90
  * printed, stacked in a `Group`, or laid out in a `Progress` cell alike.
81
91
  * The frames this output can draw are chosen where the output is known.
92
+ * The frame says nothing of when it turns — `t` may be a time a timeline
93
+ * mapped — so placed, the spinner itself stamps it.
82
94
  */
83
95
  at(t: number): Renderable & Measurable;
96
+ /** The spinner with its motion taken out: its first frame and the label, which never turn, so any time answers. */
97
+ reduced(): Animation<Renderable & Measurable>;
98
+ render(options: RenderOptions): Iterable<Segment>;
99
+ measure(options: RenderOptions): {
100
+ minimum: number;
101
+ maximum: number;
102
+ };
103
+ /** The frame `choose` picks from the frames the output can draw, and the label, saying the hold it is in if `choose` says. */
104
+ private _frame;
84
105
  /**
85
106
  * When the spinner drawn at `t` next turns, on the host's clock: the
86
107
  * sooner of its two sequences' answers, since which one is drawn is the
@@ -8,7 +8,7 @@
8
8
  * the interval; the beat, the width every frame draws at and which frame `t`
9
9
  * lands on are the frames primitive's, so a spinner is one more sequence on
10
10
  * it and not a second way of turning frames. Custom frames are a `FrameSequence`
11
- * drawn through `Frames` directly. [LAW:one-source-of-truth]
11
+ * placed directly. [LAW:one-source-of-truth]
12
12
  *
13
13
  * It is built once and asked for each frame, as Rich's `Spinner` is built
14
14
  * once and has `render(time)` called on it: the name is parsed and the
@@ -19,13 +19,12 @@
19
19
  * the beat, the width, each frame padded to it — is made there too, so a
20
20
  * drawn frame costs only finding which one `t` lands on.
21
21
  *
22
- * [LAW:no-ambient-temporal-coupling] A Spinner draws nothing on its own:
23
- * only `at(t)` is a renderable, so a spinner cannot be drawn without being
24
- * handed its time, and nothing here reads a clock. Whoever draws frames
25
- * hands each one its time (`core/clock.ts`) — `SpinnerColumn` the task's
26
- * `time`, a `Live` the frame's time on its clock — as Rich's `Spinner.render`
27
- * is handed `console.get_time()` or `task.get_time()`. `t` is the clock's
28
- * time, as `App` and `Task.time` hand it, where Rich's spinner counts from
22
+ * [LAW:no-ambient-temporal-coupling] Placed, a spinner draws at the time of
23
+ * the frame it is drawn in, and nothing here reads a clock. Whoever draws
24
+ * frames hands each one its time (`core/clock.ts`) — a `Progress` the
25
+ * moment its tasks are drawn at, a `Live` the frame's time on its clock — as
26
+ * Rich's `Spinner.render` is handed `console.get_time()` or `task.get_time()`. `t` is the clock's
27
+ * time, as a host writes it on the frame, where Rich's spinner counts from
29
28
  * its first draw: here nothing is kept between frames, so the same `t` draws
30
29
  * the same bytes at one redraw a second or sixty, two spinners on one clock
31
30
  * agree at every `t`, a `Progress` on a hand-moved clock turns its spinners
@@ -35,10 +34,11 @@
35
34
  * that rate, as Rich scales the time, `t × speed` into the beat: the frames
36
35
  * are held for the interval the data gives, and the spinner is asked for the
37
36
  * frame at the time the timeline maps the host's to. [LAW:one-source-of-truth]
38
- * When the frame next changes (`Style.nextChange`) is a time on the host's
37
+ * When the frame next changes (`Style.hold`) is a time on the host's
39
38
  * timeline, and the sequence answers on its own, so the timeline carries the
40
- * answer back and the drawn cells are stamped with that (`frameAt`), where
41
- * the host's time is known. Backwards, the frames count down from the last at `t = 0`,
39
+ * answer back. `at` hands over the frame alone, as every animation's does;
40
+ * placed, a spinner draws at the frame's time and stamps its cells with that
41
+ * answer for the frames the output draws. Backwards, the frames count down from the last at `t = 0`,
42
42
  * a negative time landing in the last frame of the pass before, where Rich's
43
43
  * `int()` truncation holds frame 0 for a first interval and then counts
44
44
  * down: a departure, made so a negative speed is the one timeline run
@@ -46,14 +46,17 @@
46
46
  * frame forever, which a paused timeline is and a rate is not, and the
47
47
  * timeline refuses it.
48
48
  */
49
+ import { Animation, frameTimeOf, holdOf, shownAt, stampFrame } from "../core/animation.js";
49
50
  import { Segment } from "../core/segment.js";
50
51
  import { NULL_STYLE } from "../core/style.js";
51
52
  import { RichText } from "../core/text.js";
52
53
  import { SPINNERS, DEFAULT_SPINNER, LINE_SPINNER } from "../core/spinnerData.js";
53
54
  import { Timeline } from "../core/timeline.js";
54
- import { drawable, render } from "../core/protocol.js";
55
+ import { drawable, measure, render } from "../core/protocol.js";
55
56
  import { EmbeddedText } from "./embed.js";
56
- import { FrameSequence, frameAt } from "./frames.js";
57
+ import { FrameSequence } from "./frames.js";
58
+ import { finiteTime } from "../core/easing.js";
59
+ import { State } from "../core/state.js";
57
60
  const sequence = (data, style, timeline) => {
58
61
  const frames = new FrameSequence(data.frames.map((frame) => ({ text: new RichText("", { end: "" }).append(frame, style), seconds: data.interval / 1000 })));
59
62
  return { frames, played: Timeline.run(timeline, frames), glyphs: data.frames.join("") };
@@ -70,12 +73,26 @@ export class Spinner {
70
73
  * whatever the console says — and a `RichText` changed in place draws as
71
74
  * changed, as the `Text` Rich keeps does. [LAW:single-enforcer]
72
75
  */
73
- text;
76
+ get text() {
77
+ return this._text.get();
78
+ }
79
+ set text(value) {
80
+ this._text.set(value);
81
+ }
82
+ // Rich's `Spinner.update` sets the text after it is built.
83
+ _text;
74
84
  _data;
75
85
  // The named data's frames, and the `line` spinner's that stand in for them
76
86
  // on an ASCII-only output: prepared once, which is drawn chosen at `at`.
77
87
  _named;
78
88
  _ascii;
89
+ /**
90
+ * The spinner drawn at the frame's time, its cells saying the hold it is in
91
+ * on the host's clock (`stampFrame`) — exactly, for the frames the output
92
+ * draws, where `nextChange` must answer for both. Under reduced motion it
93
+ * is the spinner reduced, which never turns (`shownAt`).
94
+ */
95
+ _placed;
79
96
  constructor(name, text = "", options) {
80
97
  const spinnerName = name ?? DEFAULT_SPINNER;
81
98
  const data = SPINNERS[spinnerName];
@@ -87,12 +104,17 @@ export class Spinner {
87
104
  // refuses one that is not where it is built.
88
105
  const timeline = Timeline.play(0, speed);
89
106
  this.name = spinnerName;
90
- this.text = text;
107
+ this._text = new State(text);
91
108
  this.speed = speed;
92
109
  this.style = options?.style ?? NULL_STYLE;
93
110
  this._data = data;
94
111
  this._named = sequence(data, this.style, timeline);
95
112
  this._ascii = sequence(LINE_SPINNER, this.style, timeline);
113
+ this._placed = this._frame((played, options) => {
114
+ const t = frameTimeOf(options);
115
+ const shown = shownAt(played, t, options);
116
+ return { value: shown.value, hold: holdOf(shown, t) };
117
+ });
96
118
  }
97
119
  get frames() {
98
120
  return this._data.frames;
@@ -106,21 +128,39 @@ export class Spinner {
106
128
  * the line wrapped to the width and ended, as the reference's `Text` is —
107
129
  * printed, stacked in a `Group`, or laid out in a `Progress` cell alike.
108
130
  * The frames this output can draw are chosen where the output is known.
131
+ * The frame says nothing of when it turns — `t` may be a time a timeline
132
+ * mapped — so placed, the spinner itself stamps it.
109
133
  */
110
134
  at(t) {
111
- if (!Number.isFinite(t)) {
112
- throw new RangeError(`Spinner.at needs a finite t, got ${t}`);
113
- }
135
+ finiteTime("Spinner.at", t);
136
+ return this._frame((played) => ({ value: played.at(t) }));
137
+ }
138
+ /** The spinner with its motion taken out: its first frame and the label, which never turn, so any time answers. */
139
+ reduced() {
140
+ return Animation.constant(this._frame((played) => ({ value: played.reduced().at(0) })));
141
+ }
142
+ render(options) {
143
+ return render(this._placed, options);
144
+ }
145
+ measure(options) {
146
+ return measure(this._placed, options);
147
+ }
148
+ /** The frame `choose` picks from the frames the output can draw, and the label, saying the hold it is in if `choose` says. */
149
+ _frame(choose) {
114
150
  const drawn = (options) => drawable(options, this._named, this._ascii, sequenceGlyphs);
115
151
  return {
116
- render: (options) => this._render(drawn(options), t, options),
152
+ render: (options) => {
153
+ const { value, hold } = choose(drawn(options).played, options);
154
+ const segments = this._render(value, options);
155
+ return hold === undefined ? segments : stampFrame(segments, hold);
156
+ },
117
157
  // Measured at the frames' one width, the widest frame's, so a column
118
158
  // holding it never narrows as it turns, and never offered less than
119
159
  // that frame: a frame is one picture, which the reference's `Text`
120
160
  // measure would let wrap apart at its spaces.
121
161
  measure: (options) => {
122
162
  const { frames, played } = drawn(options);
123
- const { minimum, maximum } = this._line(played, t, options).measure(options);
163
+ const { minimum, maximum } = measure(this._line(choose(played, options).value, options), options);
124
164
  return { minimum: Math.max(minimum, Math.min(frames.width, maximum)), maximum };
125
165
  },
126
166
  };
@@ -139,8 +179,8 @@ export class Spinner {
139
179
  lastChange(t) {
140
180
  return Math.max(this._named.played.lastChange(t), this._ascii.played.lastChange(t));
141
181
  }
142
- *_render({ played }, t, options) {
143
- yield* render(this._line(played, t, options), options);
182
+ *_render(frame, options) {
183
+ yield* render(this._line(frame, options), options);
144
184
  yield Segment.line();
145
185
  }
146
186
  /**
@@ -150,9 +190,9 @@ export class Spinner {
150
190
  * styles nothing — is no label, not a trailing space, as an empty `Text` is
151
191
  * falsy there.
152
192
  */
153
- _line(played, t, options) {
154
- // The frame's cells say when it turns on the host's clock, the label's keep their own word.
155
- const line = new RichText("", { end: "" }).append(frameAt(played, t));
193
+ _line(frame, options) {
194
+ // The frame alone, saying nothing of when it turns: placed, the spinner stamps it.
195
+ const line = new RichText("", { end: "" }).append(frame);
156
196
  // Unhighlighted: Rich's label is a `Text` from the moment it is handed over, and a `Text` is never highlighted.
157
197
  const label = new EmbeddedText(this.text).text({ ...options, highlight: false });
158
198
  if (label.plain !== "")
@@ -20,7 +20,7 @@ export class Status {
20
20
  speed: options?.speed,
21
21
  style: options?.spinnerStyle ?? "status.spinner",
22
22
  });
23
- this._live = new Live((t) => this._spinner.at(t), {
23
+ this._live = new Live(this._spinner, {
24
24
  console: this._console,
25
25
  clock: options?.clock,
26
26
  transient: true,
@@ -9,11 +9,12 @@
9
9
  */
10
10
  import { Segment } from "../core/segment.js";
11
11
  import { type Measurable, type Renderable, type RenderOptions } from "../core/protocol.js";
12
+ import { type Pattern } from "../core/pattern.js";
12
13
  export interface SyntaxOptions {
13
14
  lineNumbers?: boolean;
14
15
  startLine?: number;
15
- lineRange?: [number, number];
16
- highlightLines?: Set<number>;
16
+ lineRange?: readonly [number, number];
17
+ highlightLines?: ReadonlySet<number>;
17
18
  wordWrap?: boolean;
18
19
  tabSize?: number;
19
20
  }
@@ -24,11 +25,11 @@ export interface SyntaxOptions {
24
25
  * render; `"text"` is the grammar with no rules.
25
26
  */
26
27
  declare const GRAMMARS: {
27
- javascript: RegExp;
28
- typescript: RegExp;
29
- python: RegExp;
30
- bash: RegExp;
31
- text: RegExp;
28
+ readonly javascript: Pattern;
29
+ readonly typescript: Pattern;
30
+ readonly python: Pattern;
31
+ readonly bash: Pattern;
32
+ readonly text: Pattern;
32
33
  };
33
34
  export type SyntaxLanguage = keyof typeof GRAMMARS;
34
35
  export declare class Syntax implements Renderable, Measurable {
@@ -36,8 +37,8 @@ export declare class Syntax implements Renderable, Measurable {
36
37
  readonly language: SyntaxLanguage;
37
38
  readonly lineNumbers: boolean;
38
39
  readonly startLine: number;
39
- readonly lineRange: [number, number] | undefined;
40
- readonly highlightLines: Set<number>;
40
+ readonly lineRange: readonly [number, number] | undefined;
41
+ readonly highlightLines: ReadonlySet<number>;
41
42
  readonly wordWrap: boolean;
42
43
  readonly tabSize: number;
43
44
  /** The code with its tabs expanded, as lines — what both render and measure read. */
@@ -12,6 +12,7 @@ import { Segment } from "../core/segment.js";
12
12
  import { RichText } from "../core/text.js";
13
13
  import { drawable, groundedStyle, getStyle, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
14
14
  import { render } from "../core/protocol.js";
15
+ import { pattern } from "../core/pattern.js";
15
16
  /**
16
17
  * One regex per grammar, each token a named alternative. A single left-to-right
17
18
  * scan is what keeps a `//` inside a string from starting a comment, and a
@@ -20,7 +21,7 @@ import { render } from "../core/protocol.js";
20
21
  * `(?!)` never matches, so a grammar with no rules is still a regex.
21
22
  */
22
23
  function grammar(rules) {
23
- return new RegExp([...rules.map(([token, source]) => `(?<${token}>${source})`), "(?!)"].join("|"), "g");
24
+ return pattern(new RegExp([...rules.map(([token, source]) => `(?<${token}>${source})`), "(?!)"].join("|")));
24
25
  }
25
26
  // Not after a `.`: `map.get` and `node.type` are properties, not keywords.
26
27
  const words = (list) => `(?<!\\.)\\b(?:${list.split(" ").join("|")})\\b`;
@@ -120,7 +121,7 @@ export class Syntax {
120
121
  // Tokenized whole and then sliced, so a block comment or a triple-quoted
121
122
  // string that spans lines is one token even when the range starts inside it.
122
123
  const text = new RichText(this._lines.join("\n"), { end: "" });
123
- for (const match of text.plain.matchAll(GRAMMARS[this.language])) {
124
+ for (const match of text.plain.matchAll(new RegExp(GRAMMARS[this.language].source, "g"))) {
124
125
  const [token] = Object.entries(match.groups).find(([, value]) => value !== undefined);
125
126
  text.stylize(`syntax.${token}`, match.index, match.index + match[0].length);
126
127
  }
@@ -6,43 +6,42 @@ import { Style } from "../core/style.js";
6
6
  import { Box } from "../core/box.js";
7
7
  import { RichText } from "../core/text.js";
8
8
  import type { PaddingDimensions } from "./padding.js";
9
- import type { Composite, Renderable, Measurable, OverflowMethod, RenderOptions } from "../core/protocol.js";
9
+ import type { Renderable, Measurable, OverflowMethod, RenderOptions } from "../core/protocol.js";
10
10
  export interface ColumnOptions {
11
11
  /** Anything a cell can hold, as Rich's `RenderableType` header is. */
12
- header?: string | RichText | Renderable;
13
- footer?: string | RichText | Renderable;
14
- headerStyle?: string | Style;
15
- footerStyle?: string | Style;
16
- style?: string | Style;
17
- justify?: "left" | "center" | "right" | "full";
18
- width?: number;
19
- minWidth?: number;
20
- maxWidth?: number;
21
- ratio?: number;
22
- noWrap?: boolean;
23
- overflow?: OverflowMethod;
12
+ readonly header?: string | RichText | Renderable;
13
+ readonly footer?: string | RichText | Renderable;
14
+ readonly headerStyle?: string | Style;
15
+ readonly footerStyle?: string | Style;
16
+ readonly style?: string | Style;
17
+ readonly justify?: "left" | "center" | "right" | "full";
18
+ readonly width?: number;
19
+ readonly minWidth?: number;
20
+ readonly maxWidth?: number;
21
+ readonly ratio?: number;
22
+ readonly noWrap?: boolean;
23
+ readonly overflow?: OverflowMethod;
24
24
  /**
25
25
  * Whether the column's string cells — its header and footer among them — are
26
26
  * drawn with the console's highlighter, as Rich's `Column(highlight=)`.
27
27
  * `false` for a `Column` built alone; `addColumn` takes the table's.
28
28
  */
29
- highlight?: boolean;
29
+ readonly highlight?: boolean;
30
30
  }
31
31
  export declare class Column {
32
- private _header;
33
- private _footer;
34
- headerStyle: string | Style;
35
- footerStyle: string | Style;
36
- style: string | Style;
37
- justify: "left" | "center" | "right" | "full";
38
- width: number | undefined;
39
- minWidth: number | undefined;
40
- maxWidth: number | undefined;
41
- ratio: number | undefined;
42
- noWrap: boolean;
43
- overflow: OverflowMethod;
44
- highlight: boolean;
45
- private _cells;
32
+ private readonly _header;
33
+ private readonly _footer;
34
+ readonly headerStyle: string | Style;
35
+ readonly footerStyle: string | Style;
36
+ readonly style: string | Style;
37
+ readonly justify: "left" | "center" | "right" | "full";
38
+ readonly width: number | undefined;
39
+ readonly minWidth: number | undefined;
40
+ readonly maxWidth: number | undefined;
41
+ readonly ratio: number | undefined;
42
+ readonly noWrap: boolean;
43
+ readonly overflow: OverflowMethod;
44
+ readonly highlight: boolean;
46
45
  constructor(options?: ColumnOptions);
47
46
  /**
48
47
  * The two stamped cells, stamped on assignment rather than at the constructor.
@@ -66,10 +65,6 @@ export declare class Column {
66
65
  get footer(): Renderable & Partial<Measurable>;
67
66
  set footer(content: ColumnOptions["footer"]);
68
67
  get flexible(): boolean;
69
- /** @internal */
70
- addCell(cell: Renderable): void;
71
- /** @internal */
72
- getCells(): Renderable[];
73
68
  copy(): Column;
74
69
  }
75
70
  export interface TableOptions {
@@ -94,13 +89,13 @@ export interface TableOptions {
94
89
  captionJustify?: "left" | "center" | "right" | "full";
95
90
  width?: number;
96
91
  minWidth?: number;
97
- rowStyles?: string[];
92
+ rowStyles?: readonly string[];
98
93
  /** The `highlight` every column `addColumn` makes takes unless told otherwise (default: `false`). */
99
94
  highlight?: boolean;
100
95
  }
101
- export declare class Table implements Renderable, Measurable, Composite {
102
- private _columns;
103
- private _rows;
96
+ export declare class Table implements Renderable, Measurable {
97
+ private readonly _columns;
98
+ private readonly _rows;
104
99
  readonly box: Box | null;
105
100
  private readonly _title;
106
101
  private readonly _caption;
@@ -109,7 +104,7 @@ export declare class Table implements Renderable, Measurable, Composite {
109
104
  readonly showFooter: boolean;
110
105
  readonly showLines: boolean;
111
106
  readonly showEdge: boolean;
112
- readonly padding: [number, number, number, number];
107
+ readonly padding: readonly [number, number, number, number];
113
108
  readonly collapsePadding: boolean;
114
109
  readonly padEdge: boolean;
115
110
  readonly style: string | Style;
@@ -122,12 +117,12 @@ export declare class Table implements Renderable, Measurable, Composite {
122
117
  readonly captionJustify: "left" | "center" | "right" | "full";
123
118
  readonly tableWidth: number | undefined;
124
119
  readonly minWidth: number | undefined;
125
- readonly rowStyles: string[];
120
+ readonly rowStyles: readonly string[];
126
121
  readonly highlight: boolean;
127
122
  get title(): (Renderable & Measurable) | undefined;
128
123
  get caption(): (Renderable & Measurable) | undefined;
129
124
  constructor(options?: TableOptions);
130
- get columns(): Column[];
125
+ get columns(): readonly Column[];
131
126
  get rowCount(): number;
132
127
  addColumn(header?: ColumnOptions["header"], options?: ColumnOptions): this;
133
128
  addRow(...cells: unknown[]): this;
@@ -201,7 +196,6 @@ export declare class Table implements Renderable, Measurable, Composite {
201
196
  */
202
197
  private _columnPadding;
203
198
  private _rowPadding;
204
- contents(): Iterable<Renderable>;
205
199
  private _rowCells;
206
200
  /**
207
201
  * Every cell column `index` draws, in draw order.