@promptctl/rich-js 0.24.1 → 0.25.1
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 -0
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +175 -10
- package/dist/core/animation.js +283 -15
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cells.d.ts +23 -13
- package/dist/core/cells.js +58 -48
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +24 -33
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +44 -39
- package/dist/core/easing.d.ts +30 -0
- package/dist/core/easing.js +65 -20
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +2 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +2 -2
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/pretty.js +9 -10
- package/dist/core/protocol.d.ts +100 -39
- package/dist/core/protocol.js +167 -21
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +25 -5
- package/dist/core/segment.d.ts +13 -5
- package/dist/core/segment.js +23 -9
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +98 -29
- package/dist/core/strip.js +211 -150
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +57 -28
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +116 -124
- package/dist/core/timeline.d.ts +30 -5
- package/dist/core/timeline.js +43 -7
- package/dist/core/tween.d.ts +1 -1
- package/dist/core/tween.js +5 -3
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +71 -37
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +24 -12
- package/dist/index.js +9 -4
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/align.d.ts +2 -3
- package/dist/renderables/align.js +0 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +3 -4
- package/dist/renderables/columns.js +2 -7
- package/dist/renderables/constrain.d.ts +2 -3
- package/dist/renderables/constrain.js +0 -3
- package/dist/renderables/effect.d.ts +158 -63
- package/dist/renderables/effect.js +378 -155
- package/dist/renderables/effects.d.ts +122 -11
- package/dist/renderables/effects.js +238 -94
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +16 -8
- package/dist/renderables/flexStrip.d.ts +2 -3
- package/dist/renderables/flexStrip.js +0 -4
- package/dist/renderables/frames.d.ts +35 -54
- package/dist/renderables/frames.js +43 -58
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +3 -4
- package/dist/renderables/group.js +0 -3
- package/dist/renderables/layout.d.ts +12 -12
- package/dist/renderables/layout.js +40 -46
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +4 -5
- package/dist/renderables/list-view.js +9 -13
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +88 -41
- package/dist/renderables/markdown-parse.d.ts +4 -4
- package/dist/renderables/markdown-parse.js +52 -46
- package/dist/renderables/markdown-url.js +11 -11
- package/dist/renderables/padding.d.ts +4 -5
- package/dist/renderables/padding.js +0 -3
- package/dist/renderables/panel.d.ts +14 -16
- package/dist/renderables/panel.js +6 -14
- package/dist/renderables/progress.d.ts +83 -34
- package/dist/renderables/progress.js +182 -95
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +32 -9
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.js +2 -1
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +35 -14
- package/dist/renderables/spinner.js +65 -25
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +3 -2
- package/dist/renderables/table.d.ts +34 -40
- package/dist/renderables/table.js +41 -63
- package/dist/renderables/traceback.d.ts +2 -2
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +27 -25
- package/dist/renderables/tree.js +104 -58
- package/dist/renderables/viewport.d.ts +75 -44
- package/dist/renderables/viewport.js +145 -82
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +5 -5
- package/dist/themes/ramp.d.ts +46 -3
- package/dist/themes/ramp.js +61 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +2 -0
- package/dist/widgets/focus-manager.js +6 -4
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +2 -3
- package/dist/widgets/static-item.js +0 -3
- package/dist/widgets/text-input.d.ts +28 -32
- package/dist/widgets/text-input.js +360 -346
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +14 -4
- package/dist/widgets/widget-app.js +36 -7
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -18
- package/package.json +8 -1
package/dist/core/console.js
CHANGED
|
@@ -16,9 +16,10 @@ import { ReprHighlighter, NullHighlighter } from "./highlighter.js";
|
|
|
16
16
|
// orchestrator reaching down for `Rule`. `RuleOptions` rides the same edge
|
|
17
17
|
// rather than being restated here; see `rule()`.
|
|
18
18
|
import { Rule } from "../renderables/rule.js";
|
|
19
|
-
import { segmentsToString } from "./render.js";
|
|
19
|
+
import { segmentsToString, shownOn } from "./render.js";
|
|
20
20
|
import { placeBlock } from "./place.js";
|
|
21
|
-
import { isRenderable, render } from "./protocol.js";
|
|
21
|
+
import { activePass, isRenderable, measure, render } from "./protocol.js";
|
|
22
|
+
import { ListState, State, refuseRenderWrite } from "./state.js";
|
|
22
23
|
// [LAW:dataflow-not-control-flow] With no live region claimed, stepping around
|
|
23
24
|
// it is the text itself: every write takes the same path.
|
|
24
25
|
const NO_LIVE_REGION = { around: (text) => text };
|
|
@@ -71,7 +72,7 @@ class TextRun {
|
|
|
71
72
|
yield this.end;
|
|
72
73
|
}
|
|
73
74
|
measure(options) {
|
|
74
|
-
return this.text
|
|
75
|
+
return measure(this.text, options);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
// A host with an environment but no streams — what a browser looks like from
|
|
@@ -177,7 +178,7 @@ export class Console {
|
|
|
177
178
|
// compared with: the reference's `LogRender._last_time`, one per console.
|
|
178
179
|
// [LAW:no-ambient-temporal-coupling] "same as the previous row" is a fact
|
|
179
180
|
// about an earlier call, so it is state this console owns, not order luck.
|
|
180
|
-
_lastLogTime = null;
|
|
181
|
+
_lastLogTime = new State(null);
|
|
181
182
|
_style;
|
|
182
183
|
_isTerminal;
|
|
183
184
|
_interactive;
|
|
@@ -193,14 +194,15 @@ export class Console {
|
|
|
193
194
|
_onStyleError;
|
|
194
195
|
_asciiOnly;
|
|
195
196
|
_highlighter;
|
|
196
|
-
_recorded;
|
|
197
|
+
_recorded = new ListState();
|
|
197
198
|
// [LAW:types-are-the-program] The capture state is carried in the *type*
|
|
198
199
|
// of this field, not in a sentinel value. `null` = not capturing; any
|
|
199
200
|
// string (including "") = capturing, this is the buffer. A paired
|
|
200
201
|
// boolean+string would admit the impossible state "not capturing, buffer
|
|
201
202
|
// non-empty" — `string | null` makes that unrepresentable.
|
|
202
|
-
_capture;
|
|
203
|
-
_liveRegion;
|
|
203
|
+
_capture = new State(null);
|
|
204
|
+
_liveRegion = new State(NO_LIVE_REGION);
|
|
205
|
+
_madeIn = activePass();
|
|
204
206
|
constructor(options) {
|
|
205
207
|
const environment = options?.environment ?? ambientEnvironment();
|
|
206
208
|
const stream = boundStream(environment, options?.stderr ?? false);
|
|
@@ -209,14 +211,13 @@ export class Console {
|
|
|
209
211
|
// [LAW:dataflow-not-control-flow] `isTTY` and `env` are forwarded
|
|
210
212
|
// unconditionally, so detection stays inside the console's injected
|
|
211
213
|
// environment rather than the ambient process's.
|
|
212
|
-
const resolved = resolveDestination(options?.colorSystem === undefined ? "auto" : options.colorSystem, { isTTY: this._isTerminal, env: environment.env });
|
|
214
|
+
const resolved = resolveDestination(options?.colorSystem === undefined ? "auto" : options.colorSystem, { isTTY: this._isTerminal, env: environment.env, reducedMotion: options?.reducedMotion });
|
|
213
215
|
// [LAW:single-enforcer] The console's one override, an explicit
|
|
214
216
|
// `hyperlinks`, is applied here and nowhere else; every write encodes for
|
|
215
|
-
// this value whole.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
};
|
|
217
|
+
// this value whole. [LAW:one-source-of-truth] The reader's motion
|
|
218
|
+
// preference lives here too, and `options` reads it back from here, so
|
|
219
|
+
// what is drawn and how it is encoded take one preference.
|
|
220
|
+
this._destination = { ...resolved, hyperlinks: options?.hyperlinks ?? resolved.hyperlinks };
|
|
220
221
|
this._getSize = resolveGetSize(options, environment, stream);
|
|
221
222
|
this._getDatetime = options?.getDatetime ?? (() => new Date());
|
|
222
223
|
// [LAW:no-ambient-temporal-coupling] The theme is assigned before the
|
|
@@ -232,9 +233,6 @@ export class Console {
|
|
|
232
233
|
this._highlighter = options?.highlighter ?? new ReprHighlighter();
|
|
233
234
|
this._onStyleError = options?.onStyleError;
|
|
234
235
|
this._asciiOnly = options?.asciiOnly ?? false;
|
|
235
|
-
this._recorded = [];
|
|
236
|
-
this._capture = null;
|
|
237
|
-
this._liveRegion = NO_LIVE_REGION;
|
|
238
236
|
}
|
|
239
237
|
// --- Properties ---
|
|
240
238
|
get size() {
|
|
@@ -256,8 +254,9 @@ export class Console {
|
|
|
256
254
|
return this._destination.colorSystem;
|
|
257
255
|
}
|
|
258
256
|
/**
|
|
259
|
-
* Where this console writes: the colour depth it draws at
|
|
260
|
-
* emits OSC 8 hyperlinks,
|
|
257
|
+
* Where this console writes: the colour depth it draws at, whether it
|
|
258
|
+
* emits OSC 8 hyperlinks, and whether its reader asked for reduced motion,
|
|
259
|
+
* overrides applied — the value its output is
|
|
261
260
|
* encoded for, for anything that encodes on its behalf (`Live`).
|
|
262
261
|
*/
|
|
263
262
|
get destination() {
|
|
@@ -287,6 +286,7 @@ export class Console {
|
|
|
287
286
|
height: { rows: height, exact: false },
|
|
288
287
|
isTerminal: this.isTerminal,
|
|
289
288
|
asciiOnly: this._asciiOnly,
|
|
289
|
+
reducedMotion: this._destination.reducedMotion,
|
|
290
290
|
theme: this._theme,
|
|
291
291
|
onStyleError: this._onStyleError,
|
|
292
292
|
colorSystem: this._destination.colorSystem,
|
|
@@ -474,7 +474,7 @@ export class Console {
|
|
|
474
474
|
log(...args) {
|
|
475
475
|
const options = this.options;
|
|
476
476
|
const stamped = logTime(this._getDatetime());
|
|
477
|
-
const repeated = stamped === this._lastLogTime;
|
|
477
|
+
const repeated = stamped === this._lastLogTime.get();
|
|
478
478
|
const time = new RichText(stamped, { end: "" });
|
|
479
479
|
time.stylize("log.time");
|
|
480
480
|
const width = Math.min(time.cellLength, options.maxWidth - 1);
|
|
@@ -492,7 +492,7 @@ export class Console {
|
|
|
492
492
|
this._writeSegments(emit({ rows: grid, closed: true, cropWidth: cropWidth + width }));
|
|
493
493
|
// Remembered only once the row is written: a log that threw printed no
|
|
494
494
|
// stamp, so the next row's must show.
|
|
495
|
-
this._lastLogTime
|
|
495
|
+
this._lastLogTime.set(stamped);
|
|
496
496
|
}
|
|
497
497
|
// [LAW:one-source-of-truth] `RuleOptions` is `Rule`'s, not a restatement of
|
|
498
498
|
// it. `console.ts` used to declare its own copy and field-by-field forward
|
|
@@ -519,15 +519,15 @@ export class Console {
|
|
|
519
519
|
* painted over by the first, so claiming one while another is held throws.
|
|
520
520
|
*/
|
|
521
521
|
claimLiveRegion(region) {
|
|
522
|
-
if (this._liveRegion !== NO_LIVE_REGION) {
|
|
522
|
+
if (this._liveRegion.get() !== NO_LIVE_REGION) {
|
|
523
523
|
throw new Error("A live display is already running on this console; stop it before starting another.");
|
|
524
524
|
}
|
|
525
|
-
this._liveRegion
|
|
525
|
+
this._liveRegion.set(region);
|
|
526
526
|
// Letting go of a region no longer held — a second call, or after another
|
|
527
527
|
// was claimed — leaves the one that is.
|
|
528
528
|
return () => {
|
|
529
|
-
if (this._liveRegion === region)
|
|
530
|
-
this._liveRegion
|
|
529
|
+
if (this._liveRegion.get() === region)
|
|
530
|
+
this._liveRegion.set(NO_LIVE_REGION);
|
|
531
531
|
};
|
|
532
532
|
}
|
|
533
533
|
// --- Capture ---
|
|
@@ -537,60 +537,65 @@ export class Console {
|
|
|
537
537
|
// the full text that would otherwise have been written. This is not a
|
|
538
538
|
// tee — callers wanting both must compose explicitly.
|
|
539
539
|
beginCapture() {
|
|
540
|
-
this._capture
|
|
540
|
+
this._capture.set("");
|
|
541
541
|
}
|
|
542
542
|
endCapture() {
|
|
543
|
-
const result = this._capture ?? "";
|
|
544
|
-
this._capture
|
|
543
|
+
const result = this._capture.get() ?? "";
|
|
544
|
+
this._capture.set(null);
|
|
545
545
|
return result;
|
|
546
546
|
}
|
|
547
547
|
// --- Export (when record:true) ---
|
|
548
548
|
exportText({ clear = true } = {}) {
|
|
549
|
-
const text = this._recorded.map((s) => s.text).join("");
|
|
549
|
+
const text = this._recorded.get().map((s) => s.text).join("");
|
|
550
550
|
if (clear)
|
|
551
|
-
this._recorded
|
|
551
|
+
this._recorded.set([]);
|
|
552
552
|
return text;
|
|
553
553
|
}
|
|
554
554
|
// [LAW:single-enforcer] `encodeHtml` draws the picture `exportLines`
|
|
555
555
|
// resolves; nothing here interprets a `Style`.
|
|
556
556
|
exportHtml({ theme, clear = true } = {}) {
|
|
557
|
-
const html = encodeHtml(this._recorded, theme);
|
|
557
|
+
const html = encodeHtml(this._recorded.get(), theme);
|
|
558
558
|
if (clear)
|
|
559
|
-
this._recorded
|
|
559
|
+
this._recorded.set([]);
|
|
560
560
|
return html;
|
|
561
561
|
}
|
|
562
562
|
// [LAW:single-enforcer] As `exportHtml`: `encodeSvg` draws, this clears.
|
|
563
563
|
exportSvg({ theme, title = "Rich", clear = true } = {}) {
|
|
564
|
-
const svg = encodeSvg(this._recorded, { theme, title, width: this.width });
|
|
564
|
+
const svg = encodeSvg(this._recorded.get(), { theme, title, width: this.width });
|
|
565
565
|
if (clear)
|
|
566
|
-
this._recorded
|
|
566
|
+
this._recorded.set([]);
|
|
567
567
|
return svg;
|
|
568
568
|
}
|
|
569
569
|
// --- Internal ---
|
|
570
570
|
// [LAW:single-enforcer] One encode call per write batch routes through the
|
|
571
571
|
// same `segmentsToString` used by `renderToString`. Recording captures every
|
|
572
|
-
// non-control segment for replay.
|
|
572
|
+
// non-control segment for replay, as its destination shows it.
|
|
573
573
|
_writeSegments(segments) {
|
|
574
|
+
const shown = segments.map((segment) => shownOn(segment, this._destination));
|
|
574
575
|
if (this._record) {
|
|
575
|
-
for (const segment of
|
|
576
|
+
for (const segment of shown) {
|
|
576
577
|
if (!segment.isControl)
|
|
577
578
|
this._recorded.push(segment);
|
|
578
579
|
}
|
|
579
580
|
}
|
|
580
|
-
const encoded = segmentsToString(
|
|
581
|
+
const encoded = segmentsToString(shown, this._destination);
|
|
581
582
|
if (encoded.length > 0)
|
|
582
583
|
this._write(encoded);
|
|
583
584
|
}
|
|
584
585
|
_write(text) {
|
|
586
|
+
// [LAW:single-enforcer] Every write is refused during a render this console was not made in, whatever
|
|
587
|
+
// the console records or captures: output is an effect, and one made mid-frame lands inside the frame.
|
|
588
|
+
refuseRenderWrite(this._madeIn);
|
|
585
589
|
// [LAW:types-are-the-program] `_capture !== null` is a discriminator
|
|
586
590
|
// narrow on `string | null`, not a sentinel comparison — the type
|
|
587
591
|
// carries the active/inactive state. The early return is what makes
|
|
588
592
|
// this a redirect (not a tee); see beginCapture for semantics.
|
|
589
|
-
|
|
590
|
-
|
|
593
|
+
const captured = this._capture.get();
|
|
594
|
+
if (captured !== null) {
|
|
595
|
+
this._capture.set(captured + text);
|
|
591
596
|
return;
|
|
592
597
|
}
|
|
593
598
|
const target = this._file ?? defaultSink(this._stream);
|
|
594
|
-
target.write(this._liveRegion.around(text));
|
|
599
|
+
target.write(this._liveRegion.get().around(text));
|
|
595
600
|
}
|
|
596
601
|
}
|
package/dist/core/easing.d.ts
CHANGED
|
@@ -63,6 +63,18 @@ export type StepPosition = "jump-start" | "jump-end" | "jump-none" | "jump-both"
|
|
|
63
63
|
* the very end.
|
|
64
64
|
*/
|
|
65
65
|
export declare function steps(n: number, position?: StepPosition): Ease;
|
|
66
|
+
/**
|
|
67
|
+
* The curve `shape` over [0, 1) as an ease that has arrived: its argument
|
|
68
|
+
* held to [0, 1], and exactly 1 at 1. Every built-in ease is one, so the two
|
|
69
|
+
* halves of the `Ease` contract — an argument in [0, 1], the end landed on —
|
|
70
|
+
* are enforced here and nowhere else. [LAW:single-enforcer] The end matters
|
|
71
|
+
* because a curve is 1 at its end only in the limit or up to a rounding — a
|
|
72
|
+
* spring settles to within `SPRING_REST`, the elastic's ringing never quite
|
|
73
|
+
* stops, a back curve's cubic or a bezier's Newton step lands an ulp off —
|
|
74
|
+
* and a count or a width eased by it must land on its target, not a
|
|
75
|
+
* thousandth or an ulp short.
|
|
76
|
+
*/
|
|
77
|
+
export declare const settled: (shape: (p: number) => number) => Ease;
|
|
66
78
|
/**
|
|
67
79
|
* A spring's physics: the mass on it, the stiffness of its coil and the
|
|
68
80
|
* damping it moves against, in the units every animation library spells them
|
|
@@ -180,7 +192,25 @@ export interface Beat {
|
|
|
180
192
|
* (`./animation.ts`).
|
|
181
193
|
*/
|
|
182
194
|
nextChange(t: number): number;
|
|
195
|
+
/** The beat with its motion taken out: its first hold, held for good, as a loop holds its first value. */
|
|
196
|
+
reduced(): Beat;
|
|
183
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* `t`, unless it is not a finite number of seconds, which throws naming
|
|
200
|
+
* `what`. [LAW:single-enforcer] The one refusal of a moment to draw at, for
|
|
201
|
+
* every loop and beat that reads time and every renderable drawn at a time.
|
|
202
|
+
*/
|
|
203
|
+
export declare function finiteTime(what: string, t: number): number;
|
|
204
|
+
/**
|
|
205
|
+
* Which pass of a `period` `t` is in, as a whole number of either sign, and
|
|
206
|
+
* the seconds into it, read through the one wrap: what a `beat` and an
|
|
207
|
+
* `Animation.repeat` (`./animation.ts`) both ask, so the two agree on the
|
|
208
|
+
* pass of every float, a boundary included.
|
|
209
|
+
*/
|
|
210
|
+
export declare function passAt(what: string, t: number, period: number): {
|
|
211
|
+
readonly pass: number;
|
|
212
|
+
readonly into: number;
|
|
213
|
+
};
|
|
184
214
|
/**
|
|
185
215
|
* The first float from `sum` up at which `reached` holds: a time built as a
|
|
186
216
|
* sum to land on a boundary — where a hold begins, where an animation's next
|
package/dist/core/easing.js
CHANGED
|
@@ -128,7 +128,7 @@ export function steps(n, position = "jump-end") {
|
|
|
128
128
|
* and a count or a width eased by it must land on its target, not a
|
|
129
129
|
* thousandth or an ulp short.
|
|
130
130
|
*/
|
|
131
|
-
const settled = (shape) => (x) => {
|
|
131
|
+
export const settled = (shape) => (x) => {
|
|
132
132
|
const p = Math.min(1, Math.max(0, x));
|
|
133
133
|
return p === 1 ? 1 : shape(p);
|
|
134
134
|
};
|
|
@@ -330,6 +330,25 @@ function positive(what, v, unit = "") {
|
|
|
330
330
|
export function positiveSeconds(what, seconds) {
|
|
331
331
|
positive(what, seconds, " of seconds");
|
|
332
332
|
}
|
|
333
|
+
/** A beat of one hold that never ends: hold 0 at every time, never changing — what every beat is reduced to. */
|
|
334
|
+
const STILL = {
|
|
335
|
+
holds: [Number.POSITIVE_INFINITY],
|
|
336
|
+
period: Number.POSITIVE_INFINITY,
|
|
337
|
+
at: () => 0,
|
|
338
|
+
lastChange: () => Number.NEGATIVE_INFINITY,
|
|
339
|
+
nextChange: () => Number.POSITIVE_INFINITY,
|
|
340
|
+
reduced: () => STILL,
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* `t`, unless it is not a finite number of seconds, which throws naming
|
|
344
|
+
* `what`. [LAW:single-enforcer] The one refusal of a moment to draw at, for
|
|
345
|
+
* every loop and beat that reads time and every renderable drawn at a time.
|
|
346
|
+
*/
|
|
347
|
+
export function finiteTime(what, t) {
|
|
348
|
+
if (!Number.isFinite(t))
|
|
349
|
+
throw new RangeError(`${what} needs a finite t, got ${t}`);
|
|
350
|
+
return t;
|
|
351
|
+
}
|
|
333
352
|
/**
|
|
334
353
|
* The seconds into the current pass of a `period` at `t`: `t` taken modulo
|
|
335
354
|
* the period, into [0, period), before zero too. [LAW:single-enforcer] The
|
|
@@ -340,9 +359,7 @@ export function positiveSeconds(what, seconds) {
|
|
|
340
359
|
* on the pass that begins there, and the answer is always under the period.
|
|
341
360
|
*/
|
|
342
361
|
function wrapSeconds(what, t, period) {
|
|
343
|
-
|
|
344
|
-
throw new RangeError(`${what} needs a finite t, got ${t}`);
|
|
345
|
-
const into = t % period;
|
|
362
|
+
const into = finiteTime(what, t) % period;
|
|
346
363
|
if (into >= 0)
|
|
347
364
|
return into;
|
|
348
365
|
// A negative epsilon carried up rounds to the period itself; that is the
|
|
@@ -350,19 +367,44 @@ function wrapSeconds(what, t, period) {
|
|
|
350
367
|
const carried = into + period;
|
|
351
368
|
return carried >= period ? 0 : carried;
|
|
352
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Which pass of a `period` `t` is in, as a whole number of either sign, and
|
|
372
|
+
* the seconds into it, read through the one wrap: what a `beat` and an
|
|
373
|
+
* `Animation.repeat` (`./animation.ts`) both ask, so the two agree on the
|
|
374
|
+
* pass of every float, a boundary included.
|
|
375
|
+
*/
|
|
376
|
+
export function passAt(what, t, period) {
|
|
377
|
+
const into = wrapSeconds(what, t, period);
|
|
378
|
+
// `t - into` is a whole number of periods but for rounding, which `round`
|
|
379
|
+
// takes off; carrying a pass's start out of `t` itself would leave each
|
|
380
|
+
// sample its own rounding. `+ 0` turns the `-0` a small negative `t` rounds
|
|
381
|
+
// to into pass 0, so the pass is a plain integer whoever reads it.
|
|
382
|
+
return { pass: Math.round((t - into) / period) + 0, into };
|
|
383
|
+
}
|
|
384
|
+
/** The least normal double, 2^-1022: below it every double is a multiple of `Number.MIN_VALUE`. */
|
|
385
|
+
const MIN_NORMAL = 2 ** -1022;
|
|
353
386
|
/**
|
|
354
387
|
* The float after `x`: one ulp up, away from zero for a positive `x` and
|
|
355
|
-
* toward it for a negative one, read through the bits of the
|
|
388
|
+
* toward it for a negative one. Computed, not read through the bits of the
|
|
389
|
+
* double: a buffer per call costs an allocation on every boundary walked, and
|
|
390
|
+
* one at module scope is state every caller shares.
|
|
356
391
|
*/
|
|
357
392
|
function nextUp(x) {
|
|
358
393
|
if (x === 0)
|
|
359
394
|
return Number.MIN_VALUE;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
395
|
+
const a = Math.abs(x);
|
|
396
|
+
if (a < MIN_NORMAL)
|
|
397
|
+
return x + Number.MIN_VALUE;
|
|
398
|
+
// `log2` may round across a power of two; the two checks put `a` in [2^e, 2^(e+1)).
|
|
399
|
+
let e = Math.floor(Math.log2(a));
|
|
400
|
+
if (2 ** e > a)
|
|
401
|
+
e -= 1;
|
|
402
|
+
else if (2 ** (e + 1) <= a)
|
|
403
|
+
e += 1;
|
|
404
|
+
const ulp = 2 ** (e - 52);
|
|
405
|
+
// Toward zero from a power of two, the floats below it are half as far apart.
|
|
406
|
+
return x > 0 ? x + ulp : x + (a === 2 ** e && a > MIN_NORMAL ? ulp / 2 : ulp);
|
|
363
407
|
}
|
|
364
|
-
const DOUBLE = new Float64Array(1);
|
|
365
|
-
const BITS = new BigUint64Array(DOUBLE.buffer);
|
|
366
408
|
/** How many ulps the roundings in a sum naming a boundary can put it under that boundary; a walk past this is a boundary finer than time resolves there. */
|
|
367
409
|
const ULPS_OF_ROUNDING = 8;
|
|
368
410
|
/**
|
|
@@ -461,12 +503,14 @@ export function beat(holds) {
|
|
|
461
503
|
// are not exact in binary — a tenth of a second is not — and `at` would
|
|
462
504
|
// then read it as the hold before, so it is walked up to the first float
|
|
463
505
|
// `at` places in the hold. The walk is a function of the pass and the hold
|
|
464
|
-
// alone, so every `t` in the hold still answers one float.
|
|
465
|
-
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
|
|
506
|
+
// alone, so every `t` in the hold still answers one float. It is walked
|
|
507
|
+
// until `at` places it in this hold of this pass: with one hold, every
|
|
508
|
+
// float is in hold 0, and only the pass tells the boundary from the float
|
|
509
|
+
// under it.
|
|
510
|
+
const boundary = (what, p, i, t) => walkUp(p * period + starts[i], (began) => {
|
|
511
|
+
const { pass, into } = passAt(what, began, period);
|
|
512
|
+
return index(into) === i && pass === p;
|
|
513
|
+
}, () => `${what}: hold ${i} (${own[i]} s) is finer than time resolves near t = ${t}`);
|
|
470
514
|
return {
|
|
471
515
|
holds: own,
|
|
472
516
|
period,
|
|
@@ -474,16 +518,17 @@ export function beat(holds) {
|
|
|
474
518
|
return index(wrapSeconds("Beat.at", t, period));
|
|
475
519
|
},
|
|
476
520
|
lastChange(t) {
|
|
477
|
-
const into =
|
|
478
|
-
return boundary("Beat.lastChange", pass
|
|
521
|
+
const { pass, into } = passAt("Beat.lastChange", t, period);
|
|
522
|
+
return boundary("Beat.lastChange", pass, index(into), t);
|
|
479
523
|
},
|
|
480
524
|
// The next hold's start, in the next pass when this hold is the last.
|
|
481
525
|
nextChange(t) {
|
|
482
|
-
const into =
|
|
526
|
+
const { pass, into } = passAt("Beat.nextChange", t, period);
|
|
483
527
|
const i = index(into);
|
|
484
528
|
const last = i === own.length - 1;
|
|
485
|
-
return boundary("Beat.nextChange", pass
|
|
529
|
+
return boundary("Beat.nextChange", pass + (last ? 1 : 0), last ? 0 : i + 1, t);
|
|
486
530
|
},
|
|
531
|
+
reduced: () => STILL,
|
|
487
532
|
};
|
|
488
533
|
}
|
|
489
534
|
/** The three phase shapes. Each takes seconds and returns a `Phase`. */
|
package/dist/core/emoji.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Segment } from "./segment.js";
|
|
5
5
|
import type { Style } from "./style.js";
|
|
6
6
|
import type { Renderable, RenderOptions } from "./protocol.js";
|
|
7
|
-
export declare const EMOJI: Record<string, string
|
|
7
|
+
export declare const EMOJI: Readonly<Record<string, string>>;
|
|
8
8
|
/**
|
|
9
9
|
* Replaces :shortcode: patterns in text with emoji characters.
|
|
10
10
|
*/
|
package/dist/core/emoji.js
CHANGED
|
@@ -324,12 +324,11 @@ export const EMOJI = {
|
|
|
324
324
|
radioactive: "☢",
|
|
325
325
|
biohazard: "☣",
|
|
326
326
|
};
|
|
327
|
-
const EMOJI_PATTERN = /:([a-z0-9_]+(?:-(?:emoji|text))?):/g;
|
|
328
327
|
/**
|
|
329
328
|
* Replaces :shortcode: patterns in text with emoji characters.
|
|
330
329
|
*/
|
|
331
330
|
export function emojiReplace(text, defaultVariant) {
|
|
332
|
-
return text.replace(
|
|
331
|
+
return text.replace(/:([a-z0-9_]+(?:-(?:emoji|text))?):/g, (_match, code) => {
|
|
333
332
|
let variant = defaultVariant;
|
|
334
333
|
let name = code;
|
|
335
334
|
// Check for inline variant suffix
|
package/dist/core/export-html.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { cellLen, graphemes } from "./cells.js";
|
|
16
16
|
import { escapeAttribute, escapeText, exportCanvas, exportLines, } from "./export-lines.js";
|
|
17
|
+
import { pattern } from "./pattern.js";
|
|
17
18
|
const BLINK_KEYFRAMES = "rich-blink";
|
|
18
19
|
// [LAW:dataflow-not-control-flow] Each enum-valued field of a look is a table
|
|
19
20
|
// from its values to the declarations it adds, so every value is spelled once
|
|
@@ -68,7 +69,7 @@ function glyphCss(look) {
|
|
|
68
69
|
const ANCHOR_CSS = "all:unset;cursor:revert;outline:revert";
|
|
69
70
|
const span = (css, content) => `<span style="${escapeAttribute(css.join(";"))}">${content}</span>`;
|
|
70
71
|
/** Text of printable ASCII only: one cell a character, nothing to box. */
|
|
71
|
-
const ONE_CELL = /^[\x20-\x7e]
|
|
72
|
+
const ONE_CELL = pattern(/^[\x20-\x7e]*$/);
|
|
72
73
|
/**
|
|
73
74
|
* A box exactly `cells` of the monospace font wide (`ch` is one of its cells)
|
|
74
75
|
* for glyphs the browser would otherwise draw at a fallback font's width,
|
|
@@ -62,15 +62,15 @@ const ENTITIES = { "&": "&", "<": "<", ">": ">", '"': """ };
|
|
|
62
62
|
* surrogates. One of them leaves a whole SVG unparsable, not one glyph missing.
|
|
63
63
|
* Each takes no cell, so dropping it moves nothing on the grid.
|
|
64
64
|
*/
|
|
65
|
-
const
|
|
65
|
+
const withoutNotXml = (text) => text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]|\p{Cs}/gu, "");
|
|
66
66
|
/**
|
|
67
67
|
* Text as character data in HTML or SVG markup. Both exporters write markup,
|
|
68
68
|
* and [LAW:single-enforcer] one escaper is what keeps a title or a run that is
|
|
69
69
|
* safe in one format from being live markup, or no markup at all, in the other.
|
|
70
70
|
*/
|
|
71
|
-
export const escapeText = (text) => text
|
|
71
|
+
export const escapeText = (text) => withoutNotXml(text).replace(/[&<>]/g, (c) => ENTITIES[c]);
|
|
72
72
|
/** A value inside a double-quoted attribute in HTML or SVG markup. */
|
|
73
|
-
export const escapeAttribute = (value) => value
|
|
73
|
+
export const escapeAttribute = (value) => withoutNotXml(value).replace(/[&"<>]/g, (c) => ENTITIES[c]);
|
|
74
74
|
/**
|
|
75
75
|
* How far a dim glyph moves toward its background — Rich's own factor, so an
|
|
76
76
|
* export of the same program matches the library this ports.
|
|
@@ -10,24 +10,25 @@ export declare class NullHighlighter extends Highlighter {
|
|
|
10
10
|
highlight(_text: RichText): void;
|
|
11
11
|
}
|
|
12
12
|
export declare class RegexHighlighter extends Highlighter {
|
|
13
|
-
|
|
13
|
+
/** Each pattern's `source` and `flags`: a regex is compiled from them per call, so a held one keeps no cursor. */
|
|
14
|
+
static readonly highlights: readonly (string | Pick<RegExp, "source" | "flags">)[];
|
|
14
15
|
/** Style applied to matches. When ending with "." it acts as a namespace
|
|
15
16
|
* prefix: named groups become `baseStyle + groupName` (e.g. "repr." + "url"
|
|
16
17
|
* → "repr.url"). Otherwise it's used as a literal style for all matches
|
|
17
18
|
* (e.g. "bold red" applies "bold red" to every named group). */
|
|
18
|
-
static baseStyle: string;
|
|
19
|
+
static readonly baseStyle: string;
|
|
19
20
|
highlight(text: RichText): void;
|
|
20
21
|
}
|
|
21
22
|
export declare class ReprHighlighter extends RegexHighlighter {
|
|
22
|
-
static baseStyle
|
|
23
|
-
static highlights:
|
|
23
|
+
static readonly baseStyle = "repr.";
|
|
24
|
+
static readonly highlights: typeof RegexHighlighter.highlights;
|
|
24
25
|
}
|
|
25
26
|
export declare class JSONHighlighter extends RegexHighlighter {
|
|
26
|
-
static baseStyle
|
|
27
|
-
static highlights:
|
|
27
|
+
static readonly baseStyle = "json.";
|
|
28
|
+
static readonly highlights: typeof RegexHighlighter.highlights;
|
|
28
29
|
highlight(text: RichText): void;
|
|
29
30
|
}
|
|
30
31
|
export declare class ISO8601Highlighter extends RegexHighlighter {
|
|
31
|
-
static baseStyle
|
|
32
|
-
static highlights:
|
|
32
|
+
static readonly baseStyle = "iso8601.";
|
|
33
|
+
static readonly highlights: typeof RegexHighlighter.highlights;
|
|
33
34
|
}
|
package/dist/core/highlighter.js
CHANGED
|
@@ -18,6 +18,7 @@ export class NullHighlighter extends Highlighter {
|
|
|
18
18
|
}
|
|
19
19
|
// --- RegexHighlighter ---
|
|
20
20
|
export class RegexHighlighter extends Highlighter {
|
|
21
|
+
/** Each pattern's `source` and `flags`: a regex is compiled from them per call, so a held one keeps no cursor. */
|
|
21
22
|
static highlights = [];
|
|
22
23
|
/** Style applied to matches. When ending with "." it acts as a namespace
|
|
23
24
|
* prefix: named groups become `baseStyle + groupName` (e.g. "repr." + "url"
|
|
@@ -34,8 +35,8 @@ export class RegexHighlighter extends Highlighter {
|
|
|
34
35
|
// `d` reports where each group matched, as Python's `match.span(name)`
|
|
35
36
|
// does; searching the match for the group's text finds the first copy
|
|
36
37
|
// of it, which is not always the one the group captured.
|
|
37
|
-
const source = pattern
|
|
38
|
-
const flags = new Set([...(pattern
|
|
38
|
+
const source = typeof pattern === "string" ? pattern : pattern.source;
|
|
39
|
+
const flags = new Set([...(typeof pattern === "string" ? "" : pattern.flags), "g", "d"]);
|
|
39
40
|
for (const match of text.plain.matchAll(new RegExp(source, [...flags].join("")))) {
|
|
40
41
|
if (match.indices === undefined)
|
|
41
42
|
throw new Error("a match of a `d`-flag pattern carries indices");
|
package/dist/core/json.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { RichText } from "./text.js";
|
|
10
10
|
import { JSONHighlighter } from "./highlighter.js";
|
|
11
|
-
import { render } from "./protocol.js";
|
|
11
|
+
import { measure, render } from "./protocol.js";
|
|
12
12
|
const highlighter = new JSONHighlighter();
|
|
13
13
|
export class JSONRenderable {
|
|
14
14
|
text;
|
|
@@ -49,6 +49,6 @@ export class JSONRenderable {
|
|
|
49
49
|
yield* render(this.text, options);
|
|
50
50
|
}
|
|
51
51
|
measure(options) {
|
|
52
|
-
return this.text
|
|
52
|
+
return measure(this.text, options);
|
|
53
53
|
}
|
|
54
54
|
}
|
package/dist/core/markup.js
CHANGED
|
@@ -21,6 +21,8 @@ import { cellLen } from "./cells.js";
|
|
|
21
21
|
import { Style, StyleSyntaxError } from "./style.js";
|
|
22
22
|
import { RichText, stripControlChars } from "./text.js";
|
|
23
23
|
import { emojiReplace } from "./emoji.js";
|
|
24
|
+
import { pattern } from "./pattern.js";
|
|
25
|
+
import { State } from "./state.js";
|
|
24
26
|
// --- Tag ---
|
|
25
27
|
export class Tag {
|
|
26
28
|
name;
|
|
@@ -159,7 +161,7 @@ function excerpt(text, index) {
|
|
|
159
161
|
*/
|
|
160
162
|
export function escape(text) {
|
|
161
163
|
return text
|
|
162
|
-
.replace(ESCAPE_RE, (_, run, live) => live === undefined ? `${run}\\[` : `${run}${run}\\[`)
|
|
164
|
+
.replace(new RegExp(ESCAPE_RE.source, "g"), (_, run, live) => live === undefined ? `${run}\\[` : `${run}${run}\\[`)
|
|
163
165
|
.replace(TRAILING_RUN_RE, "$&$&");
|
|
164
166
|
}
|
|
165
167
|
// --- render ---
|
|
@@ -188,18 +190,18 @@ export function escape(text) {
|
|
|
188
190
|
// run, on exactly the untrusted text `escape` is for.
|
|
189
191
|
const TAG_BODY = String.raw `[a-z#/@][^[]*?\]`;
|
|
190
192
|
const RUN = String.raw `(?<!\\)(\\*)`;
|
|
191
|
-
const TAG_RE = new RegExp(String.raw `${RUN}(\[${TAG_BODY})
|
|
193
|
+
const TAG_RE = pattern(new RegExp(String.raw `${RUN}(\[${TAG_BODY})`));
|
|
192
194
|
/**
|
|
193
195
|
* Every bracket and the run in front of it; the empty second group is set when
|
|
194
196
|
* a tag starts there or could, once more text is joined after the end. It is an
|
|
195
197
|
* alternation rather than `(…)?` because a quantified group may not match empty.
|
|
196
198
|
*/
|
|
197
|
-
const ESCAPE_RE = new RegExp(String.raw `${RUN}\[(?:(?=${TAG_BODY}|(?:[a-z#/@][^[\]]*)?$)()|)
|
|
198
|
-
const TRAILING_RUN_RE = /(?<!\\)
|
|
199
|
+
const ESCAPE_RE = pattern(new RegExp(String.raw `${RUN}\[(?:(?=${TAG_BODY}|(?:[a-z#/@][^[\]]*)?$)()|)`));
|
|
200
|
+
const TRAILING_RUN_RE = pattern(/(?<!\\)\\+$/);
|
|
199
201
|
function tokenize(markup) {
|
|
200
202
|
const tokens = [];
|
|
201
203
|
let position = 0;
|
|
202
|
-
for (const match of markup.matchAll(TAG_RE)) {
|
|
204
|
+
for (const match of markup.matchAll(new RegExp(TAG_RE.source, "g"))) {
|
|
203
205
|
const run = match[1];
|
|
204
206
|
const captured = match[2];
|
|
205
207
|
let start = match.index;
|
|
@@ -292,9 +294,9 @@ function findLastIndex(items, match) {
|
|
|
292
294
|
// could never fire. This tracked `[A-Za-z]` while `TAG_RE` wrongly admitted
|
|
293
295
|
// uppercase, which is the same drift, one subset down (rich-markup-sec).
|
|
294
296
|
const PLUGIN_TAG_NAME_SRC = "[a-z][A-Za-z0-9_-]*";
|
|
295
|
-
const LEGAL_PLUGIN_TAG_NAME = new RegExp(`^${PLUGIN_TAG_NAME_SRC}$`);
|
|
297
|
+
const LEGAL_PLUGIN_TAG_NAME = pattern(new RegExp(`^${PLUGIN_TAG_NAME_SRC}$`));
|
|
296
298
|
export class MarkupRegistry {
|
|
297
|
-
_handlers = new Map();
|
|
299
|
+
_handlers = new State(new Map());
|
|
298
300
|
register(name, handler) {
|
|
299
301
|
// [LAW:no-silent-failure] Registration used to accept any string, including
|
|
300
302
|
// names markup can never address (`a.b` stops the tag scan at the dot), and
|
|
@@ -305,16 +307,18 @@ export class MarkupRegistry {
|
|
|
305
307
|
if (isReservedTagName(name)) {
|
|
306
308
|
throw new MarkupError(`Cannot register markup tag "${name}": name is reserved by a built-in style.`);
|
|
307
309
|
}
|
|
308
|
-
this._handlers.set(name, handler);
|
|
310
|
+
this._handlers.set(new Map(this._handlers.get()).set(name, handler));
|
|
309
311
|
}
|
|
310
312
|
unregister(name) {
|
|
311
|
-
this._handlers.
|
|
313
|
+
const handlers = new Map(this._handlers.get());
|
|
314
|
+
handlers.delete(name);
|
|
315
|
+
this._handlers.set(handlers);
|
|
312
316
|
}
|
|
313
317
|
has(name) {
|
|
314
|
-
return this._handlers.has(name);
|
|
318
|
+
return this._handlers.get().has(name);
|
|
315
319
|
}
|
|
316
320
|
get(name) {
|
|
317
|
-
return this._handlers.get(name);
|
|
321
|
+
return this._handlers.get().get(name);
|
|
318
322
|
}
|
|
319
323
|
}
|
|
320
324
|
// [LAW:one-source-of-truth] Reserved-name detection delegates to Style.parse —
|
|
@@ -356,7 +360,7 @@ function isSpace(c) {
|
|
|
356
360
|
// continues with the whitespace that introduces attributes. Anything else —
|
|
357
361
|
// `[table.header]`, `[table=x]` — is the built-in dialect's, and falls through
|
|
358
362
|
// exactly as an unregistered name would.
|
|
359
|
-
const PLUGIN_TAG_HEAD = new RegExp(`^(${PLUGIN_TAG_NAME_SRC})(?=$|[ \\t])`);
|
|
363
|
+
const PLUGIN_TAG_HEAD = pattern(new RegExp(`^(${PLUGIN_TAG_NAME_SRC})(?=$|[ \\t])`));
|
|
360
364
|
function parsePluginAttrs(after) {
|
|
361
365
|
// Hand-written tokenizer. Walks `key=value` pairs separated by whitespace;
|
|
362
366
|
// values may be bare, single-quoted, or double-quoted. Matches the syntax
|
package/dist/core/measure.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Measurement — min/max cell width calculation for renderables.
|
|
3
3
|
*/
|
|
4
|
-
import { drawsNothing, isMeasurable,
|
|
4
|
+
import { drawsNothing, isMeasurable, measure } from "./protocol.js";
|
|
5
5
|
export class Measurement {
|
|
6
6
|
minimum;
|
|
7
7
|
maximum;
|
|
@@ -49,7 +49,7 @@ export class Measurement {
|
|
|
49
49
|
return new Measurement(0, 0);
|
|
50
50
|
if (!isMeasurable(renderable))
|
|
51
51
|
return new Measurement(0, options.maxWidth);
|
|
52
|
-
const { minimum, maximum } =
|
|
52
|
+
const { minimum, maximum } = measure(renderable, options);
|
|
53
53
|
return new Measurement(minimum, Math.min(maximum, options.maxWidth)).normalize();
|
|
54
54
|
}
|
|
55
55
|
}
|