@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
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * The glyph effect catalogue: a typewriter and a scramble, which run once and
3
3
  * are done, each with its way out for an element leaving (`backspace`,
4
- * `scrambleOut`), and a wave of case changes, rain and stars, which loop for as
5
- * long as they are drawn. Each is a `GlyphEffect` for `GlyphEffected`
6
- * (`./glyph-effect.ts`), built as the colour catalogue's are
7
- * (`./effects.ts`): one function each over the one shape, made by name, one
8
- * way, at the defaults in `GLYPH_DEFAULTS` (`GLYPH_EFFECT_CATALOGUE`).
4
+ * `scrambleOut`), and a wave of case changes, rain, stars and marching ants,
5
+ * which loop for as long as they are drawn. Each is a `GlyphEffect` for
6
+ * `GlyphEffected` (`./glyph-effect.ts`), built as the color catalogue's are
7
+ * (`./effects.ts`): one function each over the one shape, at the defaults in
8
+ * `GLYPH_DEFAULTS`. `GLYPH_EFFECT_CATALOGUE` makes by name those a template's
9
+ * `glyphs` takes.
9
10
  *
10
- * Where a colour loop glides, a glyph cannot: a cell shows one glyph or
11
+ * Where a color loop glides, a glyph cannot: a cell shows one glyph or
11
12
  * another, so every loop here moves in steps, on a `beat`, and says when its
12
13
  * next step is. The transitions are `Transition`s by the one rule the
13
- * colour transitions follow (`transition`): they move every frame until
14
+ * color transitions follow (`transition`): they move every frame until
14
15
  * their seconds are up, and never after.
15
16
  *
16
17
  * [LAW:no-ambient-temporal-coupling] No effect reads a clock, and what a cell
@@ -90,27 +91,41 @@ export interface Particles {
90
91
  * row a step, and one that holds still draws a fresh field each life.
91
92
  */
92
93
  export declare function particles(spec: Particles, z: number): GlyphEffect;
93
- /** The names of the glyph effects that loop. */
94
+ /** How ants march: a step, in seconds, for the gaps to move one cell, and the most cells a gap may be from the next. */
95
+ export interface MarchingAnts {
96
+ readonly step: number;
97
+ readonly every: number;
98
+ }
99
+ /**
100
+ * Marching ants: gaps blanked round each box the element draws, spaced as
101
+ * evenly as the ring allows and never more than `every` cells apart, walking
102
+ * one cell clockwise a `step`. The whole pattern turns, so a ring that `every`
103
+ * does not divide closes with no seam at its corner. The ring is whatever the box
104
+ * is (`Box.frame`), at any width and beside a title. A ring may draw blanks and
105
+ * they keep the count, but an element with no ink on any ring never changes.
106
+ */
107
+ export declare function marchingAnts({ step, every }: MarchingAnts): GlyphEffect;
108
+ /** The names a template's `glyphs` takes for an effect that loops. */
94
109
  export type GlyphLoopName = "wave" | "rain" | "stars";
95
- /** The names of the glyph effects that run once. */
110
+ /** The names a template's `glyphs` takes for an effect that runs once. */
96
111
  export type GlyphTransitionName = "typewriter" | "scramble" | "backspace" | "scrambleOut";
97
- /** Each catalogue glyph effect's defaults, as first drawn in `examples/glyph-effects`. */
112
+ /** Each glyph effect's defaults, as first drawn in `examples/glyph-effects`. */
98
113
  export declare const GLYPH_DEFAULTS: {
99
114
  readonly typewriter: {
100
115
  readonly seconds: 2;
101
- readonly ease: import("../core/easing.js").Ease;
116
+ readonly ease: import("../core/easing.js").Easing;
102
117
  };
103
118
  readonly scramble: {
104
119
  readonly seconds: 2.5;
105
- readonly ease: import("../core/easing.js").Ease;
120
+ readonly ease: import("../core/easing.js").Easing;
106
121
  };
107
122
  readonly backspace: {
108
123
  readonly seconds: 1.5;
109
- readonly ease: import("../core/easing.js").Ease;
124
+ readonly ease: import("../core/easing.js").Easing;
110
125
  };
111
126
  readonly scrambleOut: {
112
127
  readonly seconds: 2.5;
113
- readonly ease: import("../core/easing.js").Ease;
128
+ readonly ease: import("../core/easing.js").Easing;
114
129
  };
115
130
  readonly wave: {
116
131
  readonly step: 0.06;
@@ -132,6 +147,10 @@ export declare const GLYPH_DEFAULTS: {
132
147
  readonly drift: 0;
133
148
  readonly life: 1;
134
149
  };
150
+ readonly march: {
151
+ readonly step: 0.15;
152
+ readonly every: 3;
153
+ };
135
154
  };
136
155
  /**
137
156
  * Every glyph effect in the catalogue by name, each made the one way: for an
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * The glyph effect catalogue: a typewriter and a scramble, which run once and
3
3
  * are done, each with its way out for an element leaving (`backspace`,
4
- * `scrambleOut`), and a wave of case changes, rain and stars, which loop for as
5
- * long as they are drawn. Each is a `GlyphEffect` for `GlyphEffected`
6
- * (`./glyph-effect.ts`), built as the colour catalogue's are
7
- * (`./effects.ts`): one function each over the one shape, made by name, one
8
- * way, at the defaults in `GLYPH_DEFAULTS` (`GLYPH_EFFECT_CATALOGUE`).
4
+ * `scrambleOut`), and a wave of case changes, rain, stars and marching ants,
5
+ * which loop for as long as they are drawn. Each is a `GlyphEffect` for
6
+ * `GlyphEffected` (`./glyph-effect.ts`), built as the color catalogue's are
7
+ * (`./effects.ts`): one function each over the one shape, at the defaults in
8
+ * `GLYPH_DEFAULTS`. `GLYPH_EFFECT_CATALOGUE` makes by name those a template's
9
+ * `glyphs` takes.
9
10
  *
10
- * Where a colour loop glides, a glyph cannot: a cell shows one glyph or
11
+ * Where a color loop glides, a glyph cannot: a cell shows one glyph or
11
12
  * another, so every loop here moves in steps, on a `beat`, and says when its
12
13
  * next step is. The transitions are `Transition`s by the one rule the
13
- * colour transitions follow (`transition`): they move every frame until
14
+ * color transitions follow (`transition`): they move every frame until
14
15
  * their seconds are up, and never after.
15
16
  *
16
17
  * [LAW:no-ambient-temporal-coupling] No effect reads a clock, and what a cell
@@ -21,7 +22,7 @@
21
22
  */
22
23
  import { Animation } from "../core/animation.js";
23
24
  import { cellLen, graphemes } from "../core/cells.js";
24
- import { EASES, Phase, beat, positiveSeconds, settled } from "../core/easing.js";
25
+ import { EASES, WrapMode, stepSequence, positiveSeconds, withSettlingTime } from "../core/easing.js";
25
26
  import { hash } from "../core/noise.js";
26
27
  import { drawable, isAscii } from "../core/protocol.js";
27
28
  import { inked } from "./effect.js";
@@ -50,6 +51,10 @@ function text(cells) {
50
51
  }
51
52
  return { length: place.size, place };
52
53
  }
54
+ /** `n` wrapped into [0, `size`), for a count a timeline may run backwards past 0. */
55
+ function wrapped(n, size) {
56
+ return ((n % size) + size) % size;
57
+ }
53
58
  /** Of `n` cells, how many have arrived at eased progress `p`, an ease that overshoots held to the cells there are. */
54
59
  function arrived(n, p) {
55
60
  return Math.min(n, Math.max(0, Math.floor(n * p)));
@@ -62,11 +67,11 @@ function arrived(n, p) {
62
67
  * the change shows. The beat is made once, for every frame drawn on it.
63
68
  */
64
69
  function stepped(seconds) {
65
- const pace = beat([seconds]);
70
+ const pace = stepSequence([seconds]);
66
71
  return (frame, still) => Animation.of({
67
- at: (t) => frame(Math.round(pace.lastChange(t) / seconds)),
68
- nextChange: (t) => pace.nextChange(t),
69
- lastChange: (t) => pace.lastChange(t),
72
+ at: (t) => frame(Math.round(pace.validFrom(t) / seconds)),
73
+ validUntil: (t) => pace.validUntil(t),
74
+ validFrom: (t) => pace.validFrom(t),
70
75
  reduced: () => Animation.constant(still),
71
76
  });
72
77
  }
@@ -80,9 +85,9 @@ const AS_WRITTEN = (cell) => cell.glyph;
80
85
  * with are not text, so no second is spent on them.
81
86
  */
82
87
  export function typewriter(timing) {
83
- const phase = Phase.once(0, timing.seconds);
88
+ const phase = WrapMode.once(0, timing.seconds);
84
89
  // Settled, so the last cell is typed at the seconds whatever ease was handed.
85
- const ease = settled(timing.ease);
90
+ const ease = withSettlingTime(timing.ease);
86
91
  return transition(timing.seconds, ({ cells }) => {
87
92
  const { length, place } = text(cells);
88
93
  return Animation.continuous((t) => {
@@ -105,9 +110,9 @@ const FLICKER = 1 / 15;
105
110
  */
106
111
  export function scramble(timing, z) {
107
112
  requireZ("scramble", z);
108
- const phase = Phase.once(0, timing.seconds);
113
+ const phase = WrapMode.once(0, timing.seconds);
109
114
  // Settled, so the last cell resolves at the seconds whatever ease was handed.
110
- const ease = settled(timing.ease);
115
+ const ease = withSettlingTime(timing.ease);
111
116
  const flicker = stepped(FLICKER)((step) => step, 0);
112
117
  return transition(timing.seconds, ({ cells }) => {
113
118
  const { length, place } = text(cells);
@@ -129,9 +134,9 @@ function churned(cell, z, step) {
129
134
  * element ends blank and nothing is left to vanish when it goes.
130
135
  */
131
136
  export function backspace(timing) {
132
- const phase = Phase.once(0, timing.seconds);
137
+ const phase = WrapMode.once(0, timing.seconds);
133
138
  // Settled, so the first cell is erased at the seconds whatever ease was handed.
134
- const ease = settled(timing.ease);
139
+ const ease = withSettlingTime(timing.ease);
135
140
  return transition(timing.seconds, ({ cells }) => {
136
141
  const { length, place } = text(cells);
137
142
  return Animation.continuous((t) => {
@@ -151,9 +156,9 @@ const CHURN = 0.3;
151
156
  */
152
157
  export function scrambleOut(timing, z) {
153
158
  requireZ("scrambleOut", z);
154
- const phase = Phase.once(0, timing.seconds);
159
+ const phase = WrapMode.once(0, timing.seconds);
155
160
  // Settled, so the last cell is blank at the seconds whatever ease was handed.
156
- const ease = settled(timing.ease);
161
+ const ease = withSettlingTime(timing.ease);
157
162
  const flicker = stepped(FLICKER)((step) => step, 0);
158
163
  return transition(timing.seconds, ({ cells }) => {
159
164
  const { length, place } = text(cells);
@@ -200,7 +205,7 @@ export function caseWave({ step, width }, z) {
200
205
  const start = Math.floor(hash(z, 2.3) * span);
201
206
  return steps((s) => {
202
207
  // The column just past the band's right edge; it enters at the left.
203
- const lead = (((s + start) % span) + span) % span;
208
+ const lead = wrapped(s + start, span);
204
209
  // A wide glyph is in the band if any of its columns is.
205
210
  return (cell) => (cell.col < lead && cell.col + cellLen(cell.glyph) > lead - width ? swapCase(cell.glyph) : cell.glyph);
206
211
  }, AS_WRITTEN);
@@ -250,7 +255,69 @@ export function particles(spec, z) {
250
255
  }, AS_WRITTEN);
251
256
  };
252
257
  }
253
- /** Each catalogue glyph effect's defaults, as first drawn in `examples/glyph-effects`. */
258
+ /**
259
+ * Where each cell stands on the ring of the box frame that drew it, by
260
+ * `index`. A ring is the
261
+ * perimeter of the rectangle a frame's cells cover, read in the element's own
262
+ * rows and columns, so it is true wherever the box sits among them. A rule or
263
+ * divider inside that rectangle, as a table draws, is frame and no part of the
264
+ * ring, and a title set into the frame is no frame but keeps its places.
265
+ */
266
+ function ringPlaces(cells) {
267
+ const frames = new Map();
268
+ for (const cell of cells) {
269
+ if (cell.boxFrame === undefined)
270
+ continue;
271
+ const ring = frames.get(cell.boxFrame);
272
+ if (ring === undefined)
273
+ frames.set(cell.boxFrame, [cell]);
274
+ else
275
+ ring.push(cell);
276
+ }
277
+ const places = new Map();
278
+ for (const ring of frames.values()) {
279
+ // Cells come in reading order, so the rows are the ring's two ends.
280
+ const [top, down] = [ring[0].row, ring[ring.length - 1].row - ring[0].row];
281
+ const left = ring.reduce((most, { col }) => Math.min(most, col), Infinity);
282
+ const across = ring.reduce((most, { col }) => Math.max(most, col), -Infinity) - left;
283
+ const size = Math.max(1, 2 * (across + down));
284
+ for (const cell of ring) {
285
+ const [y, x] = [cell.row - top, cell.col - left];
286
+ const place = y === 0 ? x : x === across ? across + y : y === down ? 2 * across + down - x : x === 0 ? 2 * (across + down) - y : undefined;
287
+ if (place !== undefined)
288
+ places.set(cell.index, { place, cells: size });
289
+ }
290
+ }
291
+ return places;
292
+ }
293
+ /**
294
+ * Marching ants: gaps blanked round each box the element draws, spaced as
295
+ * evenly as the ring allows and never more than `every` cells apart, walking
296
+ * one cell clockwise a `step`. The whole pattern turns, so a ring that `every`
297
+ * does not divide closes with no seam at its corner. The ring is whatever the box
298
+ * is (`Box.frame`), at any width and beside a title. A ring may draw blanks and
299
+ * they keep the count, but an element with no ink on any ring never changes.
300
+ */
301
+ export function marchingAnts({ step, every }) {
302
+ positiveSeconds("marchingAnts step", step);
303
+ if (!(Number.isInteger(every) && every > 1))
304
+ throw new RangeError(`marchingAnts every must be a whole number of cells above 1, got ${every}`);
305
+ const steps = stepped(step);
306
+ return ({ cells }) => {
307
+ const places = ringPlaces(cells);
308
+ if (!cells.some((cell) => places.has(cell.index) && inked(cell.glyph)))
309
+ return Animation.constant(AS_WRITTEN);
310
+ return steps((s) => (cell) => {
311
+ const at = places.get(cell.index);
312
+ if (at === undefined)
313
+ return cell.glyph;
314
+ // Bresenham's line over the ring: of `cells` places, the `gaps` where a running count crosses a whole number.
315
+ const gaps = Math.ceil(at.cells / every);
316
+ return (wrapped(at.place - s, at.cells) * gaps) % at.cells < gaps ? blank(cell.glyph) : cell.glyph;
317
+ }, AS_WRITTEN);
318
+ };
319
+ }
320
+ /** Each glyph effect's defaults, as first drawn in `examples/glyph-effects`. */
254
321
  export const GLYPH_DEFAULTS = {
255
322
  typewriter: { seconds: 2, ease: EASES.linear },
256
323
  scramble: { seconds: 2.5, ease: EASES["ease-in"] },
@@ -259,6 +326,7 @@ export const GLYPH_DEFAULTS = {
259
326
  wave: { step: 0.06, width: 3 },
260
327
  rain: { glyphs: "│╎", ascii: "|:", density: 0.06, step: 0.08, drift: 1, life: Number.POSITIVE_INFINITY },
261
328
  stars: { glyphs: "·+*", ascii: ".+*", density: 0.04, step: 1.5, drift: 0, life: 1 },
329
+ march: { step: 0.15, every: 3 },
262
330
  };
263
331
  /**
264
332
  * Every glyph effect in the catalogue by name, each made the one way: for an
@@ -5,4 +5,4 @@
5
5
  * `html.entities.html5`; regenerate rather than edit.
6
6
  */
7
7
  /** The characters `name`, a reference's name without its `&` and `;`, stands for, if it names any. */
8
- export declare function namedReference(name: string): string | undefined;
8
+ export declare function namedCharacterReference(name: string): string | undefined;
@@ -380,7 +380,7 @@ const NAMED_REFERENCES = {
380
380
  zwnj: "\u200c",
381
381
  };
382
382
  /** The characters `name`, a reference's name without its `&` and `;`, stands for, if it names any. */
383
- export function namedReference(name) {
383
+ export function namedCharacterReference(name) {
384
384
  // An own key only: `constructor` names nothing here, whatever the prototype holds.
385
385
  return Object.hasOwn(NAMED_REFERENCES, name) ? NAMED_REFERENCES[name] : undefined;
386
386
  }
@@ -23,15 +23,14 @@
23
23
  * them with its hold at the frame's time (`placedRender`). The exit is
24
24
  * drawn over whatever the arrival draws, so an element told to leave while
25
25
  * it is still arriving leaves from where it is, never shown whole first —
26
- * though a colour exit is spread over the cells each frame shows, so a cell
26
+ * though a color exit is spread over the cells each frame shows, so a cell
27
27
  * the arrival adds can move one already leaving back toward whole
28
28
  * (`ownChange`, `./effects.ts`). [LAW:dataflow-not-control-flow]
29
29
  *
30
- * A half draws the element through a colour transition (`throughColors`, a
30
+ * A half draws the element through a color transition (`throughColors`, a
31
31
  * `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each binds its
32
- * transition to the one wrapper that draws it, and to what that wrapper
33
- * needs — a theme for colours, nothing for glyphs — so nothing here asks which
34
- * kind a half is, and a line can type itself in and dissolve out. A half
32
+ * transition to the one wrapper that draws it, so nothing here asks which kind
33
+ * a half is, and a line can type itself in and dissolve out. A half
35
34
  * written by hand is any function of that shape — an arrival that slides the
36
35
  * element in, which no effect draws. An exit is a half that ends with nothing
37
36
  * drawn — `dissolveOut`, `backspace`, `scrambleOut` — so the element has
@@ -43,7 +42,6 @@
43
42
  * backwards — it draws nothing too, but is not gone.
44
43
  */
45
44
  import { Animation } from "../core/animation.js";
46
- import type { TerminalTheme } from "../core/color.js";
47
45
  import type { Renderable } from "../core/protocol.js";
48
46
  import type { Transition } from "./effects.js";
49
47
  import type { GlyphTransition } from "./glyph-effects.js";
@@ -59,13 +57,13 @@ import type { GlyphTransition } from "./glyph-effects.js";
59
57
  * `throughColors` and `throughGlyphs` lend one the transition's schedule.
60
58
  */
61
59
  export type Half = (element: Renderable, key: string) => Animation<Renderable>;
62
- /** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
63
- export declare function throughColors(transition: Transition, theme: TerminalTheme): Half;
64
- /** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
60
+ /** `transition`'s colors drawn over an element, arriving from and leaving to the ground of the terminal the frame is drawn on. */
61
+ export declare function throughColors(transition: Transition): Half;
62
+ /** `transition`'s glyphs drawn over an element, at every depth, with no color too. */
65
63
  export declare function throughGlyphs(transition: GlyphTransition): Half;
66
64
  /** How an element arrives and leaves, and what it is named for their seeds. */
67
65
  export interface LifecycleOptions {
68
- /** The half it arrives through — `throughColors(fadeIn(curve, z), theme)`. */
66
+ /** The half it arrives through — `throughColors(fadeIn(curve, z))`. */
69
67
  readonly enter: Half;
70
68
  /** The half it leaves through — `throughGlyphs(scrambleOut(timing, z))`. */
71
69
  readonly exit: Half;
@@ -23,15 +23,14 @@
23
23
  * them with its hold at the frame's time (`placedRender`). The exit is
24
24
  * drawn over whatever the arrival draws, so an element told to leave while
25
25
  * it is still arriving leaves from where it is, never shown whole first —
26
- * though a colour exit is spread over the cells each frame shows, so a cell
26
+ * though a color exit is spread over the cells each frame shows, so a cell
27
27
  * the arrival adds can move one already leaving back toward whole
28
28
  * (`ownChange`, `./effects.ts`). [LAW:dataflow-not-control-flow]
29
29
  *
30
- * A half draws the element through a colour transition (`throughColors`, a
30
+ * A half draws the element through a color transition (`throughColors`, a
31
31
  * `fadeIn`) or a glyph one (`throughGlyphs`, a `typewriter`): each binds its
32
- * transition to the one wrapper that draws it, and to what that wrapper
33
- * needs — a theme for colours, nothing for glyphs — so nothing here asks which
34
- * kind a half is, and a line can type itself in and dissolve out. A half
32
+ * transition to the one wrapper that draws it, so nothing here asks which kind
33
+ * a half is, and a line can type itself in and dissolve out. A half
35
34
  * written by hand is any function of that shape — an arrival that slides the
36
35
  * element in, which no effect draws. An exit is a half that ends with nothing
37
36
  * drawn — `dissolveOut`, `backspace`, `scrambleOut` — so the element has
@@ -47,11 +46,11 @@ import { firstReaching } from "../core/easing.js";
47
46
  import { Timeline } from "../core/timeline.js";
48
47
  import { Effected } from "./effect.js";
49
48
  import { GlyphEffected } from "./glyph-effect.js";
50
- /** `transition`'s colours drawn over an element on `theme`, whose ground it arrives from and leaves to. */
51
- export function throughColors(transition, theme) {
52
- return (element, key) => scheduled(new Effected(element, transition.effect, { key, theme }), transition.schedule);
49
+ /** `transition`'s colors drawn over an element, arriving from and leaving to the ground of the terminal the frame is drawn on. */
50
+ export function throughColors(transition) {
51
+ return (element, key) => scheduled(new Effected(element, transition.effect, { key }), transition.schedule);
53
52
  }
54
- /** `transition`'s glyphs drawn over an element, at every depth, with no colour too. */
53
+ /** `transition`'s glyphs drawn over an element, at every depth, with no color too. */
55
54
  export function throughGlyphs(transition) {
56
55
  return (element, key) => scheduled(new GlyphEffected(element, transition.effect, { key }), transition.schedule);
57
56
  }
@@ -60,14 +59,14 @@ export function throughGlyphs(transition) {
60
59
  * `schedule`, the transition's own, says: its frames with the one word on
61
60
  * when they change that the transition has and the wrapper lacks, whose own
62
61
  * answer is "now" until it is drawn. [LAW:one-source-of-truth] Reduced,
63
- * both are reduced together, as `onBeat` steps both: a one-shot's effect
62
+ * both are reduced together, as `sampleAndHold` steps both: a one-shot's effect
64
63
  * and its schedule each reduce to a cut at 0 (`transition`, `./effects.ts`).
65
64
  */
66
65
  function scheduled(placed, schedule) {
67
66
  return Animation.of({
68
67
  at: (t) => placed.at(t),
69
- nextChange: (t) => schedule.nextChange(t),
70
- lastChange: (t) => schedule.lastChange(t),
68
+ validUntil: (t) => schedule.validUntil(t),
69
+ validFrom: (t) => schedule.validFrom(t),
71
70
  reduced: () => scheduled(placed.reduced(), schedule.reduced()),
72
71
  });
73
72
  }
@@ -107,7 +106,7 @@ function doneAt(half, start) {
107
106
  */
108
107
  function ending(half, role, key) {
109
108
  if (doneAt(half, 0) === Number.POSITIVE_INFINITY) {
110
- throw new RangeError(`lifecycle "${key}": the ${role} half never ends: its animation never says it has stopped changing (Animation.done). An Effected or GlyphEffected on its own does not; throughColors(transition, theme) and throughGlyphs(transition) lend one the transition's schedule`);
109
+ throw new RangeError(`lifecycle "${key}": the ${role} half never ends: its animation never says it has stopped changing (Animation.done). An Effected or GlyphEffected on its own does not; throughColors(transition) and throughGlyphs(transition) lend one the transition's schedule`);
111
110
  }
112
111
  return half;
113
112
  }
@@ -121,7 +120,7 @@ function life(element, options, halves, entered, left) {
121
120
  const arriving = Timeline.run(Timeline.play(entered), arrival);
122
121
  // What the element draws by `t` if it is not leaving: arriving through `enter`, then itself.
123
122
  const shown = (t) => (t < arrived ? arrival.at(t - entered) : element);
124
- const held = (schedule, t) => ({ last: schedule.lastChange(t), next: schedule.nextChange(t) });
123
+ const held = (schedule, t) => ({ last: schedule.validFrom(t), next: schedule.validUntil(t) });
125
124
  // [LAW:one-source-of-truth] Each phase's frame and its hold, side by side, so the two cannot disagree.
126
125
  // The frames say nothing of when they change — the hold does, stamped where the lifecycle is placed.
127
126
  const phase = (t) => {
@@ -147,16 +146,16 @@ function life(element, options, halves, entered, left) {
147
146
  : { frame: NOTHING, last: left, next: t < goneAt ? goneAt : Number.POSITIVE_INFINITY };
148
147
  const present = Animation.of({
149
148
  at: (t) => there(t).frame,
150
- nextChange: (t) => there(t).next,
151
- lastChange: (t) => there(t).last,
149
+ validUntil: (t) => there(t).next,
150
+ validFrom: (t) => there(t).last,
152
151
  reduced: () => present,
153
152
  });
154
153
  return Animation.of({
155
154
  entered,
156
155
  left,
157
156
  at: (t) => phase(t).frame,
158
- nextChange: (t) => phase(t).next,
159
- lastChange: (t) => phase(t).last,
157
+ validUntil: (t) => phase(t).next,
158
+ validFrom: (t) => phase(t).last,
160
159
  // Reduced, the element is there from when it entered until it is told to leave: no arrival or exit to show, and the times kept.
161
160
  reduced: () => present,
162
161
  // [LAW:one-source-of-truth] The moment `phase` draws nothing from, so an element not yet told to leave is never asked its exit at -Infinity.
@@ -14,7 +14,7 @@
14
14
  * when the display stops, as Rich's `Live` does.
15
15
  *
16
16
  * Live is not built on `App`, though both take a terminal, paint a frame over
17
- * the last and hand the terminal back. That much is the `Painter`'s, and both
17
+ * the last and hand the terminal back. That much is the `Renderer`'s, and both
18
18
  * hold one. The rest differs in who owns what. An `App` owns its terminal: it
19
19
  * starts and stops a `TerminalHost`, sets raw mode, reads keys and pointers,
20
20
  * suspends, and builds its own `Console` from the host. A `Live` owns none of
@@ -35,7 +35,7 @@ export interface LiveOptions {
35
35
  * positive and finite, fractional included. The next frame comes a frame
36
36
  * interval after the last one drawn, a `refresh()` of your own included,
37
37
  * or sooner when that frame's content says it changes sooner
38
- * (`Segment.nextChange`).
38
+ * (`Segment.validUntil`).
39
39
  */
40
40
  refreshPerSecond?: number;
41
41
  /** What the auto-refresh waits on. The platform's, unless given. */
@@ -14,7 +14,7 @@
14
14
  * when the display stops, as Rich's `Live` does.
15
15
  *
16
16
  * Live is not built on `App`, though both take a terminal, paint a frame over
17
- * the last and hand the terminal back. That much is the `Painter`'s, and both
17
+ * the last and hand the terminal back. That much is the `Renderer`'s, and both
18
18
  * hold one. The rest differs in who owns what. An `App` owns its terminal: it
19
19
  * starts and stops a `TerminalHost`, sets raw mode, reads keys and pointers,
20
20
  * suspends, and builds its own `Console` from the host. A `Live` owns none of
@@ -27,7 +27,7 @@
27
27
  import { frameRate, systemClock } from "../core/clock.js";
28
28
  import { Console } from "../core/console.js";
29
29
  import { Segment } from "../core/segment.js";
30
- import { FinalFramePainter, SurfacePainter } from "../core/paint.js";
30
+ import { FinalFrameRenderer, SurfaceRenderer } from "../core/paint.js";
31
31
  import { fitHeight, render } from "../core/protocol.js";
32
32
  import { Timeline } from "../core/timeline.js";
33
33
  import { drawView, frameNow, hostView, undrawn } from "../core/view.js";
@@ -71,8 +71,8 @@ export class Live {
71
71
  // The final frame is a print, as in Rich, so a recording or a capture
72
72
  // holds it with everything else the console wrote.
73
73
  this._painter = this._console.isInteractive
74
- ? new SurfacePainter(options?.altScreen ? "alternate" : "inline", (bytes) => void this._console.file.write(bytes))
75
- : new FinalFramePainter((frame) => this._console.print(linesOf(frame)));
74
+ ? new SurfaceRenderer(options?.altScreen ? "alternate" : "inline", (bytes) => void this._console.file.write(bytes))
75
+ : new FinalFrameRenderer((frame) => this._console.print(linesOf(frame)));
76
76
  }
77
77
  /**
78
78
  * The console to print through while this Live runs: what it prints lands
@@ -177,7 +177,7 @@ export class Live {
177
177
  this._wake(held, said ?? undrawn(this._clock.now()));
178
178
  }
179
179
  // Arm the one wait for the next auto-refresh frame while `held`, from what
180
- // the frame says: at its `nextChange` when it holds until a moment sooner
180
+ // the frame says: at its `validUntil` when it holds until a moment sooner
181
181
  // than the rate, and at the rate otherwise — every view is drawn from state
182
182
  // that may change without saying so, which the auto-refresh polls, as
183
183
  // Rich's does, a paused timeline's included. Every moment is measured from the
@@ -185,11 +185,11 @@ export class Live {
185
185
  // does not stretch by what a frame cost to draw, and a change that fell
186
186
  // due while it was drawn is drawn at once (`Clock.at` fires now for a time
187
187
  // already past).
188
- _wake(held, { t, nextChange }) {
188
+ _wake(held, { t, validUntil }) {
189
189
  this._waking.get()?.stop();
190
190
  this._waking.set(undefined);
191
191
  const rate = t + this._rate.interval;
192
- const at = nextChange <= t ? rate : Math.min(nextChange, rate);
192
+ const at = validUntil <= t ? rate : Math.min(validUntil, rate);
193
193
  if (!this._autoRefresh)
194
194
  return;
195
195
  // No caller is on the stack to run `stop()` for a frame that throws
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { linkHref, normalizeLinkText } from "./markdown-url.js";
15
15
  import { PY_WHITESPACE, pyStrip } from "./python-space.js";
16
- import { namedReference } from "./html-entities.js";
16
+ import { namedCharacterReference } from "./html-entities.js";
17
17
  import { pattern } from "../core/pattern.js";
18
18
  /** A line of the source as the top level holds it. */
19
19
  function sourceLine(text) {
@@ -807,7 +807,7 @@ function referenceAt(text, i) {
807
807
  const named = matchAt(NAMED_REFERENCE, text, i);
808
808
  if (!named)
809
809
  return undefined;
810
- const chars = namedReference(named[1]);
810
+ const chars = namedCharacterReference(named[1]);
811
811
  return chars === undefined ? undefined : { kind: "text", text: chars, end: i + named[0].length };
812
812
  }
813
813
  /**
@@ -821,7 +821,7 @@ function unescapeAll(text) {
821
821
  if (escaped !== undefined)
822
822
  return escaped;
823
823
  const numeric = DESTINATION_NUMERIC.exec(whole);
824
- const chars = numeric ? referencedCharacter(referencedCode(numeric[1])) : namedReference(whole.slice(1, -1));
824
+ const chars = numeric ? referencedCharacter(referencedCode(numeric[1])) : namedCharacterReference(whole.slice(1, -1));
825
825
  return chars ?? whole;
826
826
  });
827
827
  }
@@ -8,6 +8,7 @@
8
8
  * followed: HTML, a block of it or a tag inline, is drawn as Rich draws it,
9
9
  * not at all.
10
10
  */
11
+ import { withoutExplicit } from "../core/bidi.js";
11
12
  import { cellCount, cellLen } from "../core/cells.js";
12
13
  import { Segment } from "../core/segment.js";
13
14
  import { Style } from "../core/style.js";
@@ -107,7 +108,8 @@ function appendInline(result, inlines, settings, options) {
107
108
  };
108
109
  const write = (text, styles) => {
109
110
  const start = result.length;
110
- result.append(text);
111
+ // Each inline node is its own content, so what it holds open ends with it.
112
+ result.append(new RichText(text));
111
113
  paint(start, styles);
112
114
  };
113
115
  const enter = (styles) => {
@@ -156,7 +158,7 @@ function appendInline(result, inlines, settings, options) {
156
158
  write("\n");
157
159
  break;
158
160
  case "code":
159
- write(inline.code, [groundedStyle(options, resolveStyle(options, settings.inlineCodeStyle))]);
161
+ write(withoutExplicit(inline.code), [groundedStyle(options, resolveStyle(options, settings.inlineCodeStyle))]);
160
162
  break;
161
163
  case "html":
162
164
  kbd(inline.content);
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Panel — a bordered box that wraps content, with optional title and subtitle.
3
3
  */
4
+ import { labeled } from "../core/orient.js";
4
5
  import { cellLen, setCellSize, asCellCol, cellCount } from "../core/cells.js";
5
6
  import { Segment } from "../core/segment.js";
6
7
  import { NULL_STYLE } from "../core/style.js";
@@ -39,13 +40,14 @@ function borderTitle(label, options) {
39
40
  }
40
41
  /**
41
42
  * The style of text set into a border — title, subtitle and accessory alike:
42
- * its own laid over the border's, the one rule for "what colour is the title
43
+ * its own laid over the border's, the one rule for "what color is the title
43
44
  * text in". Laid over rather than in place of it, as Rich's `stylize_before`
44
45
  * does, because the border's style carries the panel's own: a title that
45
- * replaced it cut a hole in the panel's background on the top row.
46
+ * replaced it cut a hole in the panel's background on the top row. The text
47
+ * is set into the frame and is not of it.
46
48
  */
47
49
  function borderTextStyle(border, own) {
48
- return own.isNull ? border : (border ?? NULL_STYLE).add(own);
50
+ return border.add(own).withBoxFrame(undefined);
49
51
  }
50
52
  /**
51
53
  * A border row: the opening corner, the rule across `span` cells with `label`
@@ -129,7 +131,7 @@ export class Panel {
129
131
  // and the content is rendered on top of it. Applied to the padding alone,
130
132
  // "white on dark_blue" drew a blue ring with a hole where the text was.
131
133
  const borderStyle = getStyle(options, this.style).add(getStyle(options, this.borderStyle));
132
- const border = borderStyle.isNull ? undefined : borderStyle;
134
+ const border = box.frame(borderStyle);
133
135
  const geometry = layoutPanel(this._getPanelWidth(options));
134
136
  const bodyLines = this._renderBody(options, geometry.spanWidth);
135
137
  yield* this._renderTopBorder(options, box, geometry, border);
@@ -241,7 +243,7 @@ export class Panel {
241
243
  const corner = box.bottom.right.repeat(geometry.right);
242
244
  const [close, afterAccessory] = accessoryText === "" ? [corner, ""] : ["", corner];
243
245
  yield* borderRow(options, box.bottom, geometry, geometry.spanWidth - cellLen(accessoryText), this._subtitleLabel, subtitleStyle, border, close);
244
- yield new Segment(accessoryText, borderTextStyle(border, accessoryOwn));
246
+ yield* labeled("", accessoryText, "", borderTextStyle(border, accessoryOwn), options.textOrder);
245
247
  yield new Segment(afterAccessory, border);
246
248
  yield Segment.line();
247
249
  }
@@ -89,7 +89,7 @@ export declare class BarColumn implements ProgressColumn {
89
89
  * toward, pulses, and a pulse draws no fill. Otherwise the bar is the
90
90
  * task's `fill` drawn at the frame's time, every cell stamped with when the
91
91
  * fill next moves, so a host draws while it eases and the bar asks for
92
- * nothing once it lands. Its colour is the task's: an ease that overshoots
92
+ * nothing once it lands. Its color is the task's: an ease that overshoots
93
93
  * the total does not finish the bar, and one that dips back does not
94
94
  * unfinish it.
95
95
  */
@@ -113,7 +113,7 @@ export class BarColumn {
113
113
  * toward, pulses, and a pulse draws no fill. Otherwise the bar is the
114
114
  * task's `fill` drawn at the frame's time, every cell stamped with when the
115
115
  * fill next moves, so a host draws while it eases and the bar asks for
116
- * nothing once it lands. Its colour is the task's: an ease that overshoots
116
+ * nothing once it lands. Its color is the task's: an ease that overshoots
117
117
  * the total does not finish the bar, and one that dips back does not
118
118
  * unfinish it.
119
119
  */
@@ -44,9 +44,9 @@ export declare class ProgressBar implements Renderable, Measurable {
44
44
  set finished(value: boolean | undefined);
45
45
  render(options: RenderOptions): Iterable<Segment>;
46
46
  /**
47
- * The shimmer over this bar's back colour as `Effected` sees it drawn at
48
- * `depth`, toward `bar.pulse`. A bar has no words to keep legible, so its
49
- * back takes the whole of the light.
47
+ * The shimmer over this bar's back color as `Effected` sees it drawn,
48
+ * toward `bar.pulse`. A bar has no words to keep legible, so its back takes
49
+ * the whole of the light.
50
50
  */
51
51
  private pulseEffect;
52
52
  /** Rich 9d8f9a3 `__rich_console__`: the fill is counted in half cells, and a total of zero is a bar already full. */