@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.
- package/README.md +7 -6
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +65 -46
- package/dist/core/animation.js +91 -67
- package/dist/core/ansi.d.ts +19 -12
- package/dist/core/ansi.js +114 -36
- package/dist/core/bidi-data.d.ts +14 -0
- package/dist/core/bidi-data.js +385 -0
- package/dist/core/bidi.d.ts +89 -0
- package/dist/core/bidi.js +615 -0
- package/dist/core/box.d.ts +9 -1
- package/dist/core/box.js +11 -0
- package/dist/core/cell-widths.d.ts +3 -2
- package/dist/core/cell-widths.js +93 -93
- package/dist/core/cells.d.ts +20 -0
- package/dist/core/cells.js +94 -6
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/color.d.ts +56 -57
- package/dist/core/color.js +79 -81
- package/dist/core/console.d.ts +24 -7
- package/dist/core/console.js +27 -8
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/easing.d.ts +40 -40
- package/dist/core/easing.js +40 -40
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +5 -4
- package/dist/core/export-lines.d.ts +13 -11
- package/dist/core/export-lines.js +19 -15
- package/dist/core/export-svg.d.ts +3 -3
- package/dist/core/export-svg.js +10 -9
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +5 -4
- package/dist/core/memo.d.ts +1 -1
- package/dist/core/memo.js +1 -1
- package/dist/core/oklch.d.ts +14 -14
- package/dist/core/oklch.js +10 -10
- package/dist/core/orient.d.ts +68 -0
- package/dist/core/orient.js +224 -0
- package/dist/core/osc8.d.ts +12 -5
- package/dist/core/osc8.js +14 -7
- package/dist/core/paint.d.ts +9 -9
- package/dist/core/paint.js +3 -3
- package/dist/core/pretty.d.ts +1 -1
- package/dist/core/pretty.js +23 -19
- package/dist/core/protocol.d.ts +47 -26
- package/dist/core/protocol.js +31 -23
- package/dist/core/render.d.ts +19 -14
- package/dist/core/render.js +30 -17
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +12 -4
- package/dist/core/segment.js +16 -6
- package/dist/core/strip.d.ts +83 -83
- package/dist/core/strip.js +70 -70
- package/dist/core/style.d.ts +46 -18
- package/dist/core/style.js +92 -56
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +12 -2
- package/dist/core/text.js +136 -90
- package/dist/core/timeline.d.ts +5 -5
- package/dist/core/timeline.js +11 -11
- package/dist/core/tween.d.ts +6 -6
- package/dist/core/tween.js +10 -10
- package/dist/core/view.d.ts +5 -5
- package/dist/core/view.js +15 -14
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +50 -9
- package/dist/host/app.js +120 -15
- package/dist/host/host-environment.d.ts +2 -2
- package/dist/host/host-environment.js +2 -2
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +42 -9
- package/dist/host/input.js +91 -7
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +4 -3
- package/dist/host/terminal-host.js +1 -1
- package/dist/index.d.ts +17 -16
- package/dist/index.js +11 -11
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.js +1 -1
- package/dist/node/terminal-host.d.ts +1 -1
- package/dist/node/traceback.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +6 -8
- package/dist/renderables/effect.d.ts +88 -87
- package/dist/renderables/effect.js +124 -114
- package/dist/renderables/effects.d.ts +105 -76
- package/dist/renderables/effects.js +309 -176
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +2 -2
- package/dist/renderables/flexStrip.d.ts +7 -7
- package/dist/renderables/flexStrip.js +15 -15
- package/dist/renderables/frames.d.ts +7 -16
- package/dist/renderables/frames.js +12 -27
- package/dist/renderables/glyph-effect.d.ts +22 -23
- package/dist/renderables/glyph-effect.js +43 -41
- package/dist/renderables/glyph-effects.d.ts +33 -14
- package/dist/renderables/glyph-effects.js +90 -22
- package/dist/renderables/html-entities.d.ts +1 -1
- package/dist/renderables/html-entities.js +1 -1
- package/dist/renderables/lifecycle.d.ts +8 -10
- package/dist/renderables/lifecycle.js +17 -18
- package/dist/renderables/live.d.ts +2 -2
- package/dist/renderables/live.js +7 -7
- package/dist/renderables/markdown-parse.js +3 -3
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +7 -5
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +1 -1
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +21 -18
- package/dist/renderables/prompt.d.ts +1 -1
- package/dist/renderables/scroll.d.ts +1 -1
- package/dist/renderables/scroll.js +3 -3
- package/dist/renderables/spinner.d.ts +4 -4
- package/dist/renderables/spinner.js +10 -10
- package/dist/renderables/syntax.d.ts +2 -2
- package/dist/renderables/syntax.js +9 -4
- package/dist/renderables/table.js +5 -4
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +1 -1
- package/dist/renderables/tree.js +3 -3
- package/dist/template-bindings/animation-funcs.d.ts +60 -29
- package/dist/template-bindings/animation-funcs.js +126 -43
- package/dist/template-bindings/color-funcs.d.ts +14 -14
- package/dist/template-bindings/color-funcs.js +25 -25
- package/dist/template-bindings/index.d.ts +16 -14
- package/dist/template-bindings/index.js +17 -14
- package/dist/template-bindings/palette-funcs.d.ts +2 -2
- package/dist/template-bindings/palette-funcs.js +4 -4
- package/dist/template-bindings/style-funcs.d.ts +5 -5
- package/dist/template-bindings/style-funcs.js +16 -16
- package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
- package/dist/themes/{transpose.js → adjust.js} +28 -28
- package/dist/themes/buildPalette.d.ts +6 -5
- package/dist/themes/buildPalette.js +22 -22
- package/dist/themes/colorMath.d.ts +16 -16
- package/dist/themes/colorMath.js +32 -32
- package/dist/themes/data/ansi-tables.d.ts +2 -2
- package/dist/themes/data/ansi-tables.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/data/types.d.ts +4 -4
- package/dist/themes/data/types.js +2 -2
- package/dist/themes/palette.d.ts +12 -12
- package/dist/themes/palette.js +16 -16
- package/dist/themes/ramp.d.ts +17 -17
- package/dist/themes/ramp.js +12 -12
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +6 -8
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +3 -3
- package/dist/widgets/button.js +8 -8
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +3 -3
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/event-router.d.ts +6 -6
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/focus-manager.d.ts +3 -3
- package/dist/widgets/focus-manager.js +3 -3
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/ink.d.ts +3 -3
- package/dist/widgets/ink.js +4 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/static-item.d.ts +2 -2
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +5 -5
- package/dist/widgets/themed-widget.js +4 -4
- package/dist/widgets/toggle.d.ts +4 -4
- package/dist/widgets/toggle.js +6 -6
- package/dist/widgets/types.d.ts +6 -6
- package/dist/widgets/types.js +2 -2
- package/dist/widgets/widget-app.d.ts +10 -10
- package/dist/widgets/widget-app.js +8 -8
- package/dist/widgets/widget-base.d.ts +2 -2
- package/package.json +5 -4
package/dist/core/text.js
CHANGED
|
@@ -2,74 +2,56 @@
|
|
|
2
2
|
* RichText — styled text with spans. The primary text type for the library.
|
|
3
3
|
*/
|
|
4
4
|
import { cellLen, cellCount, cellFit, cellFitEnd, asCellCol, expandTabs, parseTabSize } from "./cells.js";
|
|
5
|
+
import { closed, closing } from "./bidi.js";
|
|
6
|
+
import { controlClass, dispositionOf, pictureOf } from "./control-codes.js";
|
|
7
|
+
import { reorder, drawnTail, edges, hangingWhitespace, measured } from "./orient.js";
|
|
5
8
|
import { divideLine, wordWidths } from "./wrap.js";
|
|
6
9
|
import { Segment } from "./segment.js";
|
|
7
10
|
import { Style, NULL_STYLE, StyleSyntaxError } from "./style.js";
|
|
8
|
-
import {
|
|
11
|
+
import { stripUrlControls } from "./osc8.js";
|
|
9
12
|
import { betweenPasses, drawable, getStyle, withBoundedWidth } from "./protocol.js";
|
|
10
13
|
import { pattern } from "./pattern.js";
|
|
11
14
|
import { ListState, State } from "./state.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
// (U+2066–U+2069), and the deprecated shaping and swapping switches
|
|
21
|
-
// (U+206A–U+206F). An opener left unclosed in content reverses whatever is
|
|
22
|
-
// drawn after it on the line, and a closer whose opener is gone closes
|
|
23
|
-
// nothing of the text's own. The bidi marks (LRM, RLM, ALM) hold no state and
|
|
24
|
-
// pass, since right-to-left text needs them.
|
|
15
|
+
const STRIPPED = pattern(new RegExp(controlClass((disposition) => disposition === "strip"), "u"));
|
|
16
|
+
const STOOD_IN_FOR = pattern(new RegExp(controlClass((disposition) => disposition === "line-break" || disposition === "pictured"), "u"));
|
|
17
|
+
/** What content holds in place of a `line-break` or `pictured` code point. */
|
|
18
|
+
function standIn(character) {
|
|
19
|
+
const codePoint = character.codePointAt(0);
|
|
20
|
+
return dispositionOf(codePoint) === "pictured" ? pictureOf(codePoint) : "\n";
|
|
21
|
+
}
|
|
22
|
+
// `text` without its `strip` code points, all that `sanitized` deletes.
|
|
25
23
|
// [LAW:single-enforcer] Single place where control chars are sanitized: every
|
|
26
|
-
// string that becomes part of a Text's characters passes
|
|
24
|
+
// string that becomes part of a Text's characters passes through `sanitized`.
|
|
25
|
+
// Which ones, and why, is `CONTROL_CODES`. A code point at a time and never by its
|
|
26
|
+
// neighbours, because it runs again on every slice and fragment: what depends on
|
|
27
|
+
// context is `wireText`'s. `match` and `split` need no cursor, so `STRIPPED` can
|
|
28
|
+
// be held. `match` first because nearly every string is clean, and not `test`,
|
|
29
|
+
// which coerces: a template's unlifted scope value reaches here typed as a
|
|
30
|
+
// string, and has to throw rather than read as `[object Object]`.
|
|
27
31
|
export function stripControlChars(text) {
|
|
28
|
-
return text.
|
|
29
|
-
}
|
|
30
|
-
const TRAILING_WHITESPACE_RE = pattern(/\s+$/);
|
|
31
|
-
/**
|
|
32
|
-
* The plain text of one line of segments, in the coordinate system
|
|
33
|
-
* `divideLine` and `Segment.divide` share: cell offsets into the styled line
|
|
34
|
-
* are cell offsets into this string. [LAW:one-source-of-truth] for that
|
|
35
|
-
* correspondence — the offsets are found in this text and applied to the
|
|
36
|
-
* segments it came from.
|
|
37
|
-
*/
|
|
38
|
-
function plainOf(line) {
|
|
39
|
-
return line.map((segment) => segment.text).join("");
|
|
32
|
+
return text.match(STRIPPED) === null ? text : text.split(STRIPPED).join("");
|
|
40
33
|
}
|
|
41
34
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* author wrote. Measuring it is what lets the overflow method tell "this line
|
|
47
|
-
* was cut" from "this line ends in spaces" and stamp an ellipsis only on the
|
|
48
|
-
* first, and what keeps a centred line from drifting half a space off true.
|
|
49
|
-
*
|
|
50
|
-
* Asked of the line's text rather than walked back through its segments,
|
|
51
|
-
* because where a line ends is a fact about the characters and not about how
|
|
52
|
-
* they were split. Walking the segments made it a fact about both: it read a
|
|
53
|
-
* segment with no trailing whitespace as content and stopped there, which an
|
|
54
|
-
* empty segment also looks like — and a crop leaves one behind. A styled line
|
|
55
|
-
* whose last cells were cropped therefore reported no hanging whitespace at
|
|
56
|
-
* all and was centred as though it were content to the edge, so a span landing
|
|
57
|
-
* anywhere in a wrap's whitespace un-centred the line it closed.
|
|
35
|
+
* `text` as a Text's characters hold it: stripped, every line ending an LF, and
|
|
36
|
+
* every control its picture. One code unit for one, so an offset into stripped
|
|
37
|
+
* text holds, and every reader of a Text finds one line break without having to
|
|
38
|
+
* know the others.
|
|
58
39
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
40
|
+
export function sanitized(text) {
|
|
41
|
+
const stripped = stripControlChars(text);
|
|
42
|
+
return stripped.match(STOOD_IN_FOR) === null ? stripped : stripped.replace(new RegExp(STOOD_IN_FOR.source, "gu"), standIn);
|
|
61
43
|
}
|
|
62
44
|
// [LAW:single-enforcer] RichText is the data-model trust boundary for link
|
|
63
45
|
// URLs: a `Style` is sanitized as it enters (`admitStyle`), and the `Style` a
|
|
64
46
|
// stored string resolves to is sanitized as it leaves for a render
|
|
65
47
|
// (`resolveStyle`). Wire-byte safety is enforced separately by `osc8Open`
|
|
66
48
|
// (core/osc8.ts), which every link producer calls and which applies the same
|
|
67
|
-
// `
|
|
49
|
+
// `stripUrlControls`.
|
|
68
50
|
function sanitizeStyleLink(style) {
|
|
69
51
|
const link = style.link;
|
|
70
52
|
if (!link)
|
|
71
53
|
return style;
|
|
72
|
-
const cleaned =
|
|
54
|
+
const cleaned = stripUrlControls(link);
|
|
73
55
|
if (cleaned === link)
|
|
74
56
|
return style;
|
|
75
57
|
return style.withLink(cleaned);
|
|
@@ -85,7 +67,7 @@ function sanitizeStyleLink(style) {
|
|
|
85
67
|
* lays its text out anew, so a stamp naming where a cell sat in the output it
|
|
86
68
|
* came from would name the wrong cell once it wraps, slices or overlaps; the
|
|
87
69
|
* owner that renders the RichText is the one that stamps its cells. A
|
|
88
|
-
* hold stays — `
|
|
70
|
+
* hold stays — `validFrom` and `validUntil`: when a span's cells began and
|
|
89
71
|
* next change is true of them wherever the text lays them, so a frame
|
|
90
72
|
* appended into a line still says when it turns.
|
|
91
73
|
*/
|
|
@@ -173,14 +155,18 @@ export class Span {
|
|
|
173
155
|
export class RichText {
|
|
174
156
|
// Rich's `Text` is changed in place — appended to, stylized, padded — so its
|
|
175
157
|
// characters, spans and format are state, and a write to one while
|
|
176
|
-
// something is drawn throws.
|
|
158
|
+
// something is drawn throws. Four holders rather than one per field: a
|
|
177
159
|
// `RichText` is the most-made object in the library.
|
|
178
160
|
_text;
|
|
179
161
|
_spans = new ListState();
|
|
162
|
+
// What `append` has added to `text` since it last closed it, so closing reads
|
|
163
|
+
// no more than that. Any other write leaves `text` naming an older string, and
|
|
164
|
+
// the whole is read instead.
|
|
165
|
+
_closure = new State({ text: "", tail: "" });
|
|
180
166
|
_format;
|
|
181
167
|
_tabSize;
|
|
182
168
|
constructor(text, options) {
|
|
183
|
-
this._text = new State(text ?
|
|
169
|
+
this._text = new State(text ? sanitized(text) : "");
|
|
184
170
|
this._format = new State({
|
|
185
171
|
// [LAW:single-enforcer] `admitStyle` is the boundary that sanitizes
|
|
186
172
|
// any link URL crossing into a RichText; downstream trusts the invariant.
|
|
@@ -192,6 +178,12 @@ export class RichText {
|
|
|
192
178
|
});
|
|
193
179
|
this._tabSize = parseTabSize(options?.tabSize ?? 8);
|
|
194
180
|
}
|
|
181
|
+
/** The part of the text whose direction state nothing has closed. */
|
|
182
|
+
_unclosed() {
|
|
183
|
+
const text = this._text.get();
|
|
184
|
+
const closure = this._closure.get();
|
|
185
|
+
return closure.text === text ? closure.tail : text;
|
|
186
|
+
}
|
|
195
187
|
_reformat(change) {
|
|
196
188
|
this._format.set({ ...this._format.get(), ...change });
|
|
197
189
|
}
|
|
@@ -200,10 +192,10 @@ export class RichText {
|
|
|
200
192
|
return this._text.get();
|
|
201
193
|
}
|
|
202
194
|
set plain(value) {
|
|
203
|
-
const
|
|
204
|
-
this._text.set(
|
|
195
|
+
const clean = sanitized(value);
|
|
196
|
+
this._text.set(clean);
|
|
205
197
|
// Trim spans that extend beyond new length
|
|
206
|
-
const len =
|
|
198
|
+
const len = clean.length;
|
|
207
199
|
this._spans.set(this._spans.get()
|
|
208
200
|
.map((s) => (s.end > len ? new Span(s.start, Math.min(s.end, len), s.style) : s))
|
|
209
201
|
.filter((s) => s.start < len));
|
|
@@ -272,8 +264,8 @@ export class RichText {
|
|
|
272
264
|
* any spans covering the leftmost (side="left") or rightmost (side="right")
|
|
273
265
|
* character.
|
|
274
266
|
*
|
|
275
|
-
* [LAW:locality-or-seam] Used by `
|
|
276
|
-
* adjacent `Strip` items.
|
|
267
|
+
* [LAW:locality-or-seam] Used by `Separator`s to paint the transition between
|
|
268
|
+
* adjacent `Strip` items. Separators only ever need the column adjacent to
|
|
277
269
|
* them, so the cell type exposes that column rather than constraining its
|
|
278
270
|
* interior to be uniform. For RichText with uniform styling, both edges
|
|
279
271
|
* return the same style; for RichText with edge variation, each edge
|
|
@@ -291,7 +283,8 @@ export class RichText {
|
|
|
291
283
|
const base = this.resolvedStyle(options);
|
|
292
284
|
if (this._text.get().length === 0)
|
|
293
285
|
return base;
|
|
294
|
-
const
|
|
286
|
+
const shown = edges(this._text.get(), options.textOrder ?? "plain");
|
|
287
|
+
const pos = side === "left" ? shown.left : shown.right;
|
|
295
288
|
let result = base;
|
|
296
289
|
for (const span of this._spans.get()) {
|
|
297
290
|
if (span.start <= pos && pos < span.end) {
|
|
@@ -306,10 +299,15 @@ export class RichText {
|
|
|
306
299
|
if (style !== undefined) {
|
|
307
300
|
throw new Error("Style argument must not be provided when appending RichText");
|
|
308
301
|
}
|
|
309
|
-
|
|
302
|
+
// [LAW:single-enforcer] Reached only when separate Texts are set on one
|
|
303
|
+
// line, so here direction state ends with the content holding it. The
|
|
304
|
+
// string arm below builds one content, and an embedding spans it.
|
|
305
|
+
const own = this._text.get() + closing(this._unclosed());
|
|
306
|
+
const offset = own.length;
|
|
310
307
|
// Read before anything is appended, so a text appended to itself takes its spans as they were.
|
|
311
308
|
const moved = content._spans.get().map((span) => span.move(offset));
|
|
312
|
-
this._text.set(
|
|
309
|
+
this._text.set(own + closed(content._text.get()));
|
|
310
|
+
this._closure.set({ text: this._text.get(), tail: "" });
|
|
313
311
|
// Its base style becomes a span under its own spans, as the reference's
|
|
314
312
|
// `append` does, so appended text looks as it did alone.
|
|
315
313
|
this._addSpan(offset, this._text.get().length, content._format.get().style);
|
|
@@ -317,9 +315,11 @@ export class RichText {
|
|
|
317
315
|
this._spans.push(span);
|
|
318
316
|
return this;
|
|
319
317
|
}
|
|
320
|
-
const
|
|
318
|
+
const clean = sanitized(content);
|
|
321
319
|
const start = this._text.get().length;
|
|
322
|
-
this.
|
|
320
|
+
const tail = this._unclosed() + clean;
|
|
321
|
+
this._text.set(this._text.get() + clean);
|
|
322
|
+
this._closure.set({ text: this._text.get(), tail });
|
|
323
323
|
this._addSpan(start, this._text.get().length, style ?? "");
|
|
324
324
|
return this;
|
|
325
325
|
}
|
|
@@ -475,7 +475,7 @@ export class RichText {
|
|
|
475
475
|
return this;
|
|
476
476
|
}
|
|
477
477
|
pad(count, char) {
|
|
478
|
-
const padding =
|
|
478
|
+
const padding = sanitized(char ?? " ").repeat(count);
|
|
479
479
|
// Shift all spans right by what the left side gained.
|
|
480
480
|
this._spans.set(this._spans.get().map((s) => s.move(padding.length)));
|
|
481
481
|
this._text.set(padding + this._text.get() + padding);
|
|
@@ -493,13 +493,13 @@ export class RichText {
|
|
|
493
493
|
return this;
|
|
494
494
|
}
|
|
495
495
|
padLeft(count, char) {
|
|
496
|
-
const padding =
|
|
496
|
+
const padding = sanitized(char ?? " ").repeat(count);
|
|
497
497
|
this._spans.set(this._spans.get().map((s) => s.move(padding.length)));
|
|
498
498
|
this._text.set(padding + this._text.get());
|
|
499
499
|
return this;
|
|
500
500
|
}
|
|
501
501
|
padRight(count, char) {
|
|
502
|
-
this._text.set(this._text.get() +
|
|
502
|
+
this._text.set(this._text.get() + sanitized(char ?? " ").repeat(count));
|
|
503
503
|
return this;
|
|
504
504
|
}
|
|
505
505
|
setLength(length) {
|
|
@@ -558,7 +558,7 @@ export class RichText {
|
|
|
558
558
|
// value, so width 0 and a marker wider than the width run the same walk
|
|
559
559
|
// as every other call: what is drawn plus what is kept is `cells` wide.
|
|
560
560
|
const cells = cellCount(width);
|
|
561
|
-
const marker = cellFit(
|
|
561
|
+
const marker = cellFit(sanitized(options?.marker ?? "\u2026"), cells);
|
|
562
562
|
const budget = asCellCol(cells - cellLen(marker));
|
|
563
563
|
if (mode === "right") {
|
|
564
564
|
this._cropRightTo(budget);
|
|
@@ -662,25 +662,24 @@ export class RichText {
|
|
|
662
662
|
}
|
|
663
663
|
// --- Token Appending ---
|
|
664
664
|
appendTokens(tokens) {
|
|
665
|
+
// Each token is its own content, as `assemble`'s parts are.
|
|
665
666
|
for (const [text, style] of tokens) {
|
|
666
|
-
this.append(text, style);
|
|
667
|
+
this.append(new RichText(text, { style }));
|
|
667
668
|
}
|
|
668
669
|
return this;
|
|
669
670
|
}
|
|
670
671
|
// --- Static Factories ---
|
|
671
672
|
static assemble(parts, options) {
|
|
672
673
|
const result = new RichText("", { style: options?.style });
|
|
674
|
+
// Each part is its own content, a string's included, so each takes
|
|
675
|
+
// `append`'s RichText arm.
|
|
673
676
|
for (const part of parts) {
|
|
674
|
-
if (
|
|
675
|
-
result.append(part);
|
|
676
|
-
}
|
|
677
|
-
else if (part instanceof RichText) {
|
|
677
|
+
if (part instanceof RichText)
|
|
678
678
|
result.append(part);
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
result.append(
|
|
683
|
-
}
|
|
679
|
+
else if (typeof part === "string")
|
|
680
|
+
result.append(new RichText(part));
|
|
681
|
+
else
|
|
682
|
+
result.append(new RichText(part[0], { style: part[1] }));
|
|
684
683
|
}
|
|
685
684
|
return result;
|
|
686
685
|
}
|
|
@@ -754,19 +753,32 @@ export class RichText {
|
|
|
754
753
|
const justify = overflow === "ignore" ? undefined : this._format.get().justify ?? options.justify;
|
|
755
754
|
// One cell either way, so the marker's stand-in leaves the cut where it was.
|
|
756
755
|
const ellipsis = drawable(options, "\u2026", ".");
|
|
756
|
+
const order = options.textOrder ?? "plain";
|
|
757
757
|
for (let index = 0; index < logicalLines.length; index += 1) {
|
|
758
|
-
|
|
758
|
+
// [LAW:dataflow-not-control-flow] Every line runs the same steps; the
|
|
759
|
+
// order is the value `directed` and `orient` take.
|
|
760
|
+
const { line, oriented, orient } = reorder(logicalLines[index], order);
|
|
759
761
|
const free = spanFree[index];
|
|
760
762
|
const terminateLine = index < logicalLines.length - 1;
|
|
761
763
|
// Wrap first, overflow last — the reference's order, and the reason a
|
|
762
764
|
// long sentence grows a table row while an unbreakable word in the same
|
|
763
765
|
// column still ellipsizes.
|
|
764
|
-
const
|
|
765
|
-
const wrapped = Segment.divide(line,
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
766
|
+
const breaks = divideLine(Segment.plainOf(line), wrapAt, { fold: overflow === "fold" });
|
|
767
|
+
const wrapped = Segment.divide(hyphenated(line, breaks), drawnCuts(breaks));
|
|
768
|
+
let start = 0;
|
|
769
|
+
const placed = this._justifyLines(wrapped.map((piece, at) => {
|
|
770
|
+
const source = Segment.plainOf(piece);
|
|
771
|
+
const from = start;
|
|
772
|
+
start += source.length;
|
|
773
|
+
return {
|
|
774
|
+
segments: [...this._fitLine(piece, edge, overflow, ellipsis)],
|
|
775
|
+
// An oriented row's cells are not where `free` counted them, and a
|
|
776
|
+
// pad joined to one would land inside its isolate. A hyphen's cell
|
|
777
|
+
// was counted by nobody.
|
|
778
|
+
free: oriented || breaks[at]?.hyphen !== undefined ? NEVER_FREE : (cell) => free(at === 0 ? cell : breaks[at - 1].cell + cell),
|
|
779
|
+
orient: (segments) => orient(segments, from, source),
|
|
780
|
+
};
|
|
781
|
+
}), maxWidth, base, justify);
|
|
770
782
|
for (let piece = 0; piece < placed.length; piece += 1) {
|
|
771
783
|
yield* placed[piece];
|
|
772
784
|
if (piece < placed.length - 1 || terminateLine) {
|
|
@@ -785,7 +797,7 @@ export class RichText {
|
|
|
785
797
|
}
|
|
786
798
|
// --- Measurable ---
|
|
787
799
|
measure(options) {
|
|
788
|
-
const lines = expandTabs(this._text.get(), this._tabSize).text.split("\n");
|
|
800
|
+
const lines = measured(expandTabs(this._text.get(), this._tabSize).text, options.textOrder ?? "plain").split("\n");
|
|
789
801
|
const maxLineWidth = lines.reduce((widest, line) => Math.max(widest, cellLen(line)), 0);
|
|
790
802
|
// [LAW:one-source-of-truth] The minimum is the widest piece the wrapper
|
|
791
803
|
// never breaks, asked of the wrapper — so a line's indent, which wraps
|
|
@@ -882,7 +894,7 @@ export class RichText {
|
|
|
882
894
|
if (justify !== "full") {
|
|
883
895
|
return lines.map((line) => [...this._justifyLine(line, maxWidth, base, justify)]);
|
|
884
896
|
}
|
|
885
|
-
return lines.map(({ segments }, index) => index === lines.length - 1 ? segments : this._fillLine(segments, maxWidth, base));
|
|
897
|
+
return lines.map(({ segments, orient }, index) => orient(index === lines.length - 1 ? segments : this._fillLine(segments, maxWidth, base)));
|
|
886
898
|
}
|
|
887
899
|
/**
|
|
888
900
|
* One line placed in a canvas `maxWidth` wide.
|
|
@@ -901,7 +913,7 @@ export class RichText {
|
|
|
901
913
|
* The padding is drawn in the text's base style, as the reference pads
|
|
902
914
|
* inside the `Text` it justifies: a title "on red" fills its row in red.
|
|
903
915
|
*/
|
|
904
|
-
*_justifyLine({ segments: line, free }, maxWidth, base,
|
|
916
|
+
*_justifyLine({ segments: line, free, orient }, maxWidth, base,
|
|
905
917
|
// [LAW:types-are-the-program] `full` is absent rather than ignored: it
|
|
906
918
|
// needs the lines either side of this one, so the type refuses it here
|
|
907
919
|
// instead of a branch quietly rendering it as `left`, which is the bug
|
|
@@ -914,14 +926,16 @@ export class RichText {
|
|
|
914
926
|
const body = Segment.adjustLineLength(line, Segment.getLineLength(line) - hangingWhitespace(line), undefined, false);
|
|
915
927
|
const gap = Math.max(maxWidth - Segment.getLineLength(body), 0);
|
|
916
928
|
const leftPad = justify === "center" ? Math.floor(gap / 2) : gap;
|
|
917
|
-
yield* padded(body, free, leftPad, gap - leftPad, padStyle);
|
|
929
|
+
yield* padded(orient(body), free, leftPad, gap - leftPad, padStyle);
|
|
918
930
|
break;
|
|
919
931
|
}
|
|
920
|
-
case "left":
|
|
921
|
-
|
|
932
|
+
case "left": {
|
|
933
|
+
const row = orient(line);
|
|
934
|
+
yield* padded(row, free, 0, Math.max(maxWidth - Segment.getLineLength(row), 0), padStyle);
|
|
922
935
|
break;
|
|
936
|
+
}
|
|
923
937
|
default:
|
|
924
|
-
yield* line;
|
|
938
|
+
yield* orient(line);
|
|
925
939
|
break;
|
|
926
940
|
}
|
|
927
941
|
}
|
|
@@ -946,9 +960,9 @@ export class RichText {
|
|
|
946
960
|
* blocks respectively.
|
|
947
961
|
*/
|
|
948
962
|
_fillLine(line, maxWidth, base) {
|
|
949
|
-
const plain = plainOf(line);
|
|
963
|
+
const plain = Segment.plainOf(line);
|
|
950
964
|
const words = plain.split(" ");
|
|
951
|
-
if (plain.endsWith(" "))
|
|
965
|
+
if (drawnTail(plain).endsWith(" "))
|
|
952
966
|
words.pop();
|
|
953
967
|
const gaps = words.length - 1;
|
|
954
968
|
const spaces = new Array(gaps).fill(1);
|
|
@@ -1030,6 +1044,38 @@ export class RichText {
|
|
|
1030
1044
|
yield* Segment.adjustLineLength(line, maxWidth, undefined, false);
|
|
1031
1045
|
}
|
|
1032
1046
|
}
|
|
1047
|
+
const NEVER_FREE = () => false;
|
|
1048
|
+
/**
|
|
1049
|
+
* `line` with the soft hyphen each of `breaks` took turned into `-`, in its own
|
|
1050
|
+
* segment and so its own style. One code unit for one, so an offset into the line
|
|
1051
|
+
* still reads the logical line `orient` was built from.
|
|
1052
|
+
*/
|
|
1053
|
+
function hyphenated(line, breaks) {
|
|
1054
|
+
const taken = breaks.flatMap(({ hyphen }) => hyphen ?? []);
|
|
1055
|
+
let next = 0;
|
|
1056
|
+
let end = 0;
|
|
1057
|
+
return line.map((segment) => {
|
|
1058
|
+
const start = end;
|
|
1059
|
+
end += segment.text.length;
|
|
1060
|
+
let text = "";
|
|
1061
|
+
let from = 0;
|
|
1062
|
+
for (; next < taken.length && taken[next] < end; next += 1) {
|
|
1063
|
+
const at = taken[next] - start;
|
|
1064
|
+
text += segment.text.slice(from, at) + "-";
|
|
1065
|
+
from = at + 1;
|
|
1066
|
+
}
|
|
1067
|
+
text += segment.text.slice(from);
|
|
1068
|
+
return text === segment.text ? segment : new Segment(text, segment.style, segment.control);
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
/** Where `breaks` cut once `hyphenated` has given each hyphen its cell, which the line above it ends in. */
|
|
1072
|
+
function drawnCuts(breaks) {
|
|
1073
|
+
let hyphens = 0;
|
|
1074
|
+
return breaks.map(({ cell, hyphen }) => {
|
|
1075
|
+
hyphens += hyphen === undefined ? 0 : 1;
|
|
1076
|
+
return asCellCol(cell + hyphens);
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1033
1079
|
/**
|
|
1034
1080
|
* For each logical line of a render, whether the cell at an offset is covered
|
|
1035
1081
|
* by no span. `covered` is `_buildSegments`'s answer per piece; a piece split
|
package/dist/core/timeline.d.ts
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* one time it holds, so there is no origin for it to be wrong about.
|
|
16
16
|
* [LAW:types-are-the-program]
|
|
17
17
|
*
|
|
18
|
-
* `run` is an animation on host time. Its `
|
|
18
|
+
* `run` is an animation on host time. Its `validUntil` is the host moment
|
|
19
19
|
* the mapped time reaches the animation's own answer, which is what
|
|
20
20
|
* `Spinner`'s speed scaled its holds for before this existed: an answer a
|
|
21
21
|
* host reads is on the host's timeline, and a beat over scaled time answers
|
|
22
22
|
* on the scaled one. Run backwards, the host next sees a change when the
|
|
23
23
|
* mapped time drops under where the value began — the animation's
|
|
24
|
-
* `
|
|
24
|
+
* `validFrom` — so a reversed loop turns exactly at its hold boundaries,
|
|
25
25
|
* and a reversed one-shot changes at its switch and never before its start.
|
|
26
26
|
* Paused, nothing changes: never, and never did. The host moment is the
|
|
27
27
|
* first host float the map carries past the edge, searched out from the sum
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
* hands in, so the same host time maps to the same animation time at one
|
|
51
51
|
* sample a second or sixty.
|
|
52
52
|
*/
|
|
53
|
-
import { Animation, type
|
|
53
|
+
import { Animation, type Behavior } from "./animation.js";
|
|
54
54
|
/**
|
|
55
55
|
* A map from host time to animation time. Playing, it is `time` at `origin`
|
|
56
56
|
* moving `rate` animation seconds a host second; paused, it is `time` at
|
|
@@ -95,7 +95,7 @@ export declare const Timeline: {
|
|
|
95
95
|
* Reduced, it is the animation reduced on the same timeline: what shows
|
|
96
96
|
* still follows the timeline, and only the motion is gone.
|
|
97
97
|
*/
|
|
98
|
-
run<T>(timeline: Timeline, animation:
|
|
98
|
+
run<T>(timeline: Timeline, animation: Behavior<T>): Animation<T>;
|
|
99
99
|
/**
|
|
100
100
|
* Every one of `animations` at once, each on a timeline playing from its
|
|
101
101
|
* index times `delay` seconds after the earliest, their values in order, on
|
|
@@ -105,5 +105,5 @@ export declare const Timeline: {
|
|
|
105
105
|
* Reduced, the offsets are the cascade's choreography, which is motion: every
|
|
106
106
|
* child reduced at once, so from 0 everyone has arrived.
|
|
107
107
|
*/
|
|
108
|
-
stagger<T>(animations: readonly
|
|
108
|
+
stagger<T>(animations: readonly Behavior<T>[], delay: number): Animation<readonly T[]>;
|
|
109
109
|
};
|
package/dist/core/timeline.js
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* one time it holds, so there is no origin for it to be wrong about.
|
|
16
16
|
* [LAW:types-are-the-program]
|
|
17
17
|
*
|
|
18
|
-
* `run` is an animation on host time. Its `
|
|
18
|
+
* `run` is an animation on host time. Its `validUntil` is the host moment
|
|
19
19
|
* the mapped time reaches the animation's own answer, which is what
|
|
20
20
|
* `Spinner`'s speed scaled its holds for before this existed: an answer a
|
|
21
21
|
* host reads is on the host's timeline, and a beat over scaled time answers
|
|
22
22
|
* on the scaled one. Run backwards, the host next sees a change when the
|
|
23
23
|
* mapped time drops under where the value began — the animation's
|
|
24
|
-
* `
|
|
24
|
+
* `validFrom` — so a reversed loop turns exactly at its hold boundaries,
|
|
25
25
|
* and a reversed one-shot changes at its switch and never before its start.
|
|
26
26
|
* Paused, nothing changes: never, and never did. The host moment is the
|
|
27
27
|
* first host float the map carries past the edge, searched out from the sum
|
|
@@ -82,10 +82,10 @@ function played(animation, { origin, time, rate }) {
|
|
|
82
82
|
// Forwards, the host next sees the animation's own next change, and the
|
|
83
83
|
// value began when the animation says it did; backwards, it next sees a
|
|
84
84
|
// change where the value began, and the value began where the animation
|
|
85
|
-
// says it ends. A hold is `[
|
|
85
|
+
// says it ends. A hold is `[validFrom, validUntil)`, so the far side of
|
|
86
86
|
// an edge is at it going up and strictly under it coming down.
|
|
87
|
-
const ahead = forward ? (t) => animation.
|
|
88
|
-
const behind = forward ? (t) => animation.
|
|
87
|
+
const ahead = forward ? (t, moves) => animation.validUntil(t, moves) : (t, moves) => animation.validFrom(t, moves);
|
|
88
|
+
const behind = forward ? (t, moves) => animation.validFrom(t, moves) : (t, moves) => animation.validUntil(t, moves);
|
|
89
89
|
const past = forward ? (t, edge) => t >= edge : (t, edge) => t < edge;
|
|
90
90
|
// The first host moment the map carries past `edge`, an animation time.
|
|
91
91
|
// An edge at infinity, or one so far off at this rate that no host float
|
|
@@ -97,16 +97,16 @@ function played(animation, { origin, time, rate }) {
|
|
|
97
97
|
};
|
|
98
98
|
return Animation.of({
|
|
99
99
|
at: (host) => animation.at(mapped(hostTime(host))),
|
|
100
|
-
|
|
100
|
+
validUntil(host, moves) {
|
|
101
101
|
const h = hostTime(host);
|
|
102
102
|
const t = mapped(h);
|
|
103
|
-
const edge = ahead(t);
|
|
103
|
+
const edge = ahead(t, moves);
|
|
104
104
|
return edge === t ? h : Math.max(h, hostOf(edge));
|
|
105
105
|
},
|
|
106
|
-
|
|
106
|
+
validFrom(host, moves) {
|
|
107
107
|
const h = hostTime(host);
|
|
108
108
|
const t = mapped(h);
|
|
109
|
-
const edge = behind(t);
|
|
109
|
+
const edge = behind(t, moves);
|
|
110
110
|
return edge === t ? h : Math.min(h, hostOf(edge));
|
|
111
111
|
},
|
|
112
112
|
reduced: () => played(animation.reduced(), { playing: true, origin, time, rate }),
|
|
@@ -154,8 +154,8 @@ export const Timeline = {
|
|
|
154
154
|
return played(animation, timeline);
|
|
155
155
|
return Animation.of({
|
|
156
156
|
at: (host) => animation.at(Timeline.at(timeline, host)),
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
validUntil: (host) => (hostTime(host), Number.POSITIVE_INFINITY),
|
|
158
|
+
validFrom: (host) => (hostTime(host), Number.NEGATIVE_INFINITY),
|
|
159
159
|
reduced: () => Timeline.run(timeline, animation.reduced()),
|
|
160
160
|
});
|
|
161
161
|
},
|
package/dist/core/tween.d.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Tween — a value eased from one number to another over seconds, then held:
|
|
3
3
|
* the animation a progress bar's fill, a counter or a width is handed so it
|
|
4
4
|
* moves to its new value instead of jumping. The first `Animation` of a
|
|
5
|
-
* value that is never drawn as cells, so its `
|
|
5
|
+
* value that is never drawn as cells, so its `validUntil` is its only way of
|
|
6
6
|
* saying when to look again (`./animation.ts`).
|
|
7
7
|
*
|
|
8
8
|
* The value at `t` is the `Timing`'s ease of the way from `from` to `to`
|
|
9
|
-
* through `
|
|
9
|
+
* through `WrapMode.once(0, seconds)`: `from` at `t ≤ 0` whenever the ease
|
|
10
10
|
* starts at 0, as every built-in does but `step-start` and the steps that
|
|
11
11
|
* jump at the start of their interval, which show their first jump before
|
|
12
12
|
* the tween begins, as `ColorRamp.at` does; `to` from `seconds` on. It is
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
* tween(old.at(t₁), to, timing))` — and starts from where it is because
|
|
26
26
|
* that is where it was built from. [LAW:no-ambient-temporal-coupling]
|
|
27
27
|
*
|
|
28
|
-
* Over
|
|
29
|
-
* `over`): the ramp is already the one place two
|
|
30
|
-
* OKLCH, and a
|
|
28
|
+
* Over colors it is a `ColorRamp` read over time (`../themes/ramp.ts`,
|
|
29
|
+
* `over`): the ramp is already the one place two colors are blended, in
|
|
30
|
+
* OKLCH, and a color tween is a two-stop ramp over its seconds, not a
|
|
31
31
|
* second blender. [LAW:one-source-of-truth]
|
|
32
32
|
*
|
|
33
33
|
* Tier 6 of `src/core/`: `./easing.ts` and `./animation.ts`, nothing else.
|
|
@@ -41,7 +41,7 @@ import { type Timing } from "./easing.js";
|
|
|
41
41
|
* every ease that starts at 0), `to` after, and so
|
|
42
42
|
* seen at its end, never mid-way, by a host whose frames are further apart
|
|
43
43
|
* than it is long. `from` and `to` must be finite; the seconds are checked
|
|
44
|
-
* where every one-shot's are, in `
|
|
44
|
+
* where every one-shot's are, in `WrapMode.once`. A tween to where it already
|
|
45
45
|
* is is `Animation.constant(from)`, decided here once: the blend would land
|
|
46
46
|
* one ulp off `from` at some `p`, and a value that never moves is one that
|
|
47
47
|
* never changes, so a host is told not to look again. [LAW:parse-dont-validate]
|
package/dist/core/tween.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Tween — a value eased from one number to another over seconds, then held:
|
|
3
3
|
* the animation a progress bar's fill, a counter or a width is handed so it
|
|
4
4
|
* moves to its new value instead of jumping. The first `Animation` of a
|
|
5
|
-
* value that is never drawn as cells, so its `
|
|
5
|
+
* value that is never drawn as cells, so its `validUntil` is its only way of
|
|
6
6
|
* saying when to look again (`./animation.ts`).
|
|
7
7
|
*
|
|
8
8
|
* The value at `t` is the `Timing`'s ease of the way from `from` to `to`
|
|
9
|
-
* through `
|
|
9
|
+
* through `WrapMode.once(0, seconds)`: `from` at `t ≤ 0` whenever the ease
|
|
10
10
|
* starts at 0, as every built-in does but `step-start` and the steps that
|
|
11
11
|
* jump at the start of their interval, which show their first jump before
|
|
12
12
|
* the tween begins, as `ColorRamp.at` does; `to` from `seconds` on. It is
|
|
@@ -25,23 +25,23 @@
|
|
|
25
25
|
* tween(old.at(t₁), to, timing))` — and starts from where it is because
|
|
26
26
|
* that is where it was built from. [LAW:no-ambient-temporal-coupling]
|
|
27
27
|
*
|
|
28
|
-
* Over
|
|
29
|
-
* `over`): the ramp is already the one place two
|
|
30
|
-
* OKLCH, and a
|
|
28
|
+
* Over colors it is a `ColorRamp` read over time (`../themes/ramp.ts`,
|
|
29
|
+
* `over`): the ramp is already the one place two colors are blended, in
|
|
30
|
+
* OKLCH, and a color tween is a two-stop ramp over its seconds, not a
|
|
31
31
|
* second blender. [LAW:one-source-of-truth]
|
|
32
32
|
*
|
|
33
33
|
* Tier 6 of `src/core/`: `./easing.ts` and `./animation.ts`, nothing else.
|
|
34
34
|
* [LAW:one-way-deps]
|
|
35
35
|
*/
|
|
36
36
|
import { Animation } from "./animation.js";
|
|
37
|
-
import {
|
|
37
|
+
import { WrapMode } from "./easing.js";
|
|
38
38
|
/**
|
|
39
39
|
* `from` eased to `to` over `timing`, as an `Animation<number>`: moving from
|
|
40
40
|
* 0 to `timing.seconds` on its own clock, the ease of 0 before (`from`, for
|
|
41
41
|
* every ease that starts at 0), `to` after, and so
|
|
42
42
|
* seen at its end, never mid-way, by a host whose frames are further apart
|
|
43
43
|
* than it is long. `from` and `to` must be finite; the seconds are checked
|
|
44
|
-
* where every one-shot's are, in `
|
|
44
|
+
* where every one-shot's are, in `WrapMode.once`. A tween to where it already
|
|
45
45
|
* is is `Animation.constant(from)`, decided here once: the blend would land
|
|
46
46
|
* one ulp off `from` at some `p`, and a value that never moves is one that
|
|
47
47
|
* never changes, so a host is told not to look again. [LAW:parse-dont-validate]
|
|
@@ -52,7 +52,7 @@ export function tween(from, to, timing) {
|
|
|
52
52
|
throw new RangeError(`tween ${name} must be finite, got ${v}`);
|
|
53
53
|
}
|
|
54
54
|
const { ease, seconds } = timing;
|
|
55
|
-
const phase =
|
|
55
|
+
const phase = WrapMode.once(0, seconds);
|
|
56
56
|
if (from === to)
|
|
57
57
|
return Animation.constant(from);
|
|
58
58
|
return Animation.of({
|
|
@@ -63,8 +63,8 @@ export function tween(from, to, timing) {
|
|
|
63
63
|
// Before it begins the value is held until 0; while it moves, every
|
|
64
64
|
// frame; from `seconds` on, never again — and it became what it is at
|
|
65
65
|
// `seconds`, or always was before it began.
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
validUntil: (t) => (t < 0 ? 0 : t < seconds ? t : Number.POSITIVE_INFINITY),
|
|
67
|
+
validFrom: (t) => (t < 0 ? Number.NEGATIVE_INFINITY : t < seconds ? t : seconds),
|
|
68
68
|
// A one-shot, reduced, cuts from where it starts to where it arrives.
|
|
69
69
|
reduced: () => Animation.cut(from, to),
|
|
70
70
|
});
|