gesso-core 0.6.30 → 0.8.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/CHANGELOG.md +47 -0
- package/dist/{CanvasSurface-CspYfnND.js → CanvasSurface-Cy2UErXI.js} +20177 -18279
- package/dist/CanvasSurface-Cy2UErXI.js.map +1 -0
- package/dist/{index-CLCtv12G.d.ts → index-CQPJZwhG.d.ts} +2568 -1423
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2053 -224
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +46 -3
- package/dist/testing.js +99 -10
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/CanvasSurface-CspYfnND.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,81 +1,5 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as UiPlatformAdapter, $a as boxShadowArraysEqual, $i as UiKeyboardEvent, $n as isVideoSurface, $r as pathValuesEqual, $t as textRunDecorations, A as formatNumber, Aa as withDensity, Ai as propertyValuesEqual, An as isPressable, Ao as colorsEqual, Ar as resolveString, At as paragraphGeometryFrom, B as MAX_MEASURES_PER_CHILD, Ba as contrastRatio, Bi as writeOverrideProperty, Bn as CARET_BLINK_MS, Bo as easings, Br as UiProperties, Bt as isPrintable, C as LayoutEngine, Ca as bandOf, Ci as borderRadiusCorners, Cn as selectionRangeOf, Co as colorsEqualPalette, Cr as wordRangeAt, Ct as scrollbarThumbs, D as describeOverrides, Da as lightTheme, Di as normalizeBorderRadius, Dn as isNodeInert, Do as colorToHex, Dr as resolveNumber, Dt as hasDrawnText, E as describeLength, Ea as darkTheme, Ei as borderRadiusValuesEqual, En as isNodeHitTestable, Eo as UiBasicColors, Er as resolveBoolean, Et as UiInputDispatcher, F as LayoutRecord, Fa as hasThemeExtension, Fi as DirtyNodeSet, Fn as hasTextLinks, Fo as createEnvironmentKey, Fr as inheritedPropertyNames, Ft as comparePositions, G as sizeGridTracks, Ga as defaultSpacing, Gi as visualStatesEqual, Gn as editorOf, Gr as normalizeStates, Gt as drawText, H as FlexDirection, Ha as raiseContrast, Hi as defaultVisualState, Hn as EDITOR_PROP, Ho as steps, Hr as UI_SEMANTIC_STATES, Ht as applyCanvasTextStyle, I as Constraints, Ia as noThemeExtensions, Ii as clearOverrideProperty, In as linkHoverOf, Io as findEnvironmentKey, Ir as propertyEffects, It as edgeField, J as MainAxisAlignment, Ja as spacingSteps, Ji as UiCompositionEvent, Jn as isReadOnly, Jr as validateStates, Jt as runCanvasStyleInto, K as AlignContent, Ka as scaleSpacing, Ki as MOUSE_POINTER, Kn as isEditableNode, Kr as statesEqual, Kt as fillTextRects, L as clampSize, La as themeExtension, Li as overrideSources, Ln as linkOf, Lo as provided, Lr as propertyIsInherited, Lt as editingGroupOf, M as withOverrideSource, Ma as lengths, Mi as withPointer, Mn as pointerEventsOf, Mo as parseColor, Mr as findPropertyDefinition, Mt as wordRangeIn, N as accumulatedOffsetTo, Na as unitsEqual, Ni as UiEnvironment, Nn as resolveCursor, No as rgb8, Nr as getPropertyNames, Nt as textInputOf, O as formatConstraints, Oa as themesEqual, Oi as uniformBorderRadius, On as isNodeSelectable, Oo as colorToRgba, Or as resolveProperty, Ot as offsetAtPointIn, P as contentOffset, Pa as defineThemeExtension, Pi as UiNode, Pn as clearLinkHover, Po as rgba, Pr as inheritedPropertyFlags, Pt as adjacentField, Q as CanvasPlatformSurface, Qa as boxShadow, Qi as UiInputEvent, Qn as resolveColorValue, Qr as paintValuesEqual, Qt as textRunBackgrounds, R as constraintsEqual, Ra as themeExtensionsEqual, Ri as resetOverrideWarnings, Rn as linkRunAtPointIn, Ro as cubicBezier, Rr as propertyNarrowers, Rt as commandForKey, S as tracePathData, Sa as UiContainerSizeSource, Si as borderRadius, Sn as selectableTextOf, So as textStylesEqual, Sr as previousWordStart, St as scrollbarThumb, T as SCROLLBAR_LINGER_MS, Ta as unknownContainerSize, Ti as borderRadiusIsZero, Tn as isNodeFocusable, To as lightColors, Tr as DEFAULT_LINE_HEIGHT_FACTOR, Tt as scrollsFromRight, U as parseFlexDirection, Ua as relativeLuminance, Ui as hasVisualState, Un as caretVisibleAt, Ur as isUiRole, Ut as buildFontString, V as isLayoutProtocol, Va as highContrastColors, Vi as UiVisualState, Vn as CARET_WIDTH, Vo as linear, Vr as UI_ROLES, Vt as EditableLayout, W as placeGridItems, Wa as densityFactors, Wi as visualState, Wn as editorFor, Wr as isUiSemanticState, Wt as createCanvasTextStyle, X as parseCrossAxisAlignment, Xa as shadowsEqual, Xi as UiFocusEvent, Xn as nextCaretToggle, Xr as validateSubgrid, Xt as spanOf, Y as parseAlignContent, Ya as defaultShadows, Yi as UiEventType, Yn as maskText, Yr as UI_SUBGRID_AXES, Yt as runFontString, Z as parseMainAxisAlignment, Za as BOX_SHADOW_BLUR_REACH, Zi as UiGestureEvent, Zn as syncEditorValue, Zr as inputsEqual, Zt as textMeasureRequest, _ as paintsAfterChildren, _a as springIn, _i as minmax, _n as matchRangesOf, _o as spanAtOffset, _r as lineEndAt, _t as SCROLLBAR_MIN_THUMB, a as layerRegion, aa as UiWheelDeltaMode, ai as MAX_GRADIENT_STOPS, an as DEFAULT_FONT_SIZE, ao as shadowsOf, ar as layoutParagraph, at as UiEditingController, b as labelOrigin, ba as noInsets, bi as resolveLength, bn as clearSelectionRange, bo as textContentOf, br as nextWordEnd, bt as scrollLeaderOf, c as paintPictures, ca as wheelDeltaYOf, ci as linearGradient, cn as colorToCss, co as shapesEqual, cr as lineIndexAtY, ct as UiPinchRecognizer, d as PaintRecorder, da as UiBinding, di as auto, dn as isPaintVisible, do as typographyEqual, dr as offsetAtPoint, dt as UiKeyboardController, ea as UiPasteEvent, ei as UiTransforms, en as bumpFontStack, eo as boxShadowRadius, er as videoFrameSize, et as capturePointer, f as replayPaint, fa as UiEnvironmentKeys, fi as fr, fn as normalizeTextAlign, fo as defaultTextStyle, fr as offsetAtX, ft as UiPointerController, g as hasDecorationPhase, ga as easingIn, gi as isPercentLength, gn as clearMatchRanges, go as resolvedSpansOf, gr as graphemeBoundaries, gt as SCROLLBAR_INSET, h as decorationRect, ha as durationIn, hi as isMinMaxTrack, hn as TEXT_MATCHES_PROP, ho as hiddenRangesOf, hr as EditableTextModel, ht as SCROLLBAR_HOVER_ZONE, i as traceRoundedRect, ia as UiTextChangeEvent, ii as transformsEqual, in as DEFAULT_FONT_FAMILY, io as fittedShadowRadius, ir as ELLIPSIS, it as touchActionFor, j as labelNode, ja as defaultUnits, ji as TOUCH_TARGET, jn as isSubtreeInert, jo as normalizeColor, jr as closestPropertyName, jt as selectionRectsIn, k as formatExplanation, ka as withContrast, ki as defineProperty, kn as isNodeTabStop, ko as colorValuesEqual, kr as resolvePropertyByName, kt as paragraphGeometry, l as PaintTarget, la as UiNodeType, li as radialGradient, ln as computeObjectFitRect, lo as defaultTypography, lr as lineIndexForOffset, lt as UiWheelController, m as decorationColor, ma as defineMotion, mi as isFrLength, mn as resolvePaintState, mo as flattenTextSpans, mr as selectionRects, mt as UiHitTester, n as createCanvasSurface, na as UiPointerEvent, ni as transform, nn as fontStackFor, no as boxShadowValuesEqual, nr as ParagraphTextMeasurer, nt as pointerDeviceOf, o as opacityNeedsLayer, oa as UiWheelEvent, oi as gradientPaint, on as DEFAULT_FONT_WEIGHT, oo as defaultShapes, or as proportionalFontMetrics, ot as UiTouchScroller, p as ScaledImageCache, pa as defaultMotion, pi as isAutoLength, pn as normalizeVerticalAlign, po as editableSpansOf, pr as offsetForVerticalMove, pt as UiFocusManager, q as CrossAxisAlignment, qa as spacingEqual, qi as UiBeforeInputEvent, qn as isMultiline, qr as validateRole, qt as layoutTextLines, r as Canvas2DRenderer, ra as UiSelectionChangeEvent, ri as transformIsIdentity, rn as registerFontStack, ro as boxShadowsEqual, rr as spannedRunsFor, rt as prepareInputSurface, s as PaintPictureCache, sa as noKeyModifiers, si as gradientsEqual, sn as DEFAULT_TEXT_COLOR, so as isShapeName, sr as caretRectFor, st as UiGestureRecognizer, t as CanvasSurface, ta as UiPinchEvent, ti as parseTransform, tn as clearFontStacks, to as boxShadowReach, tr as CharacterCountTextMeasurer, tt as isImeKey, u as EMPTY_RECORDING, ua as DirtyFlags, ui as validateGradient, un as createPaintState, uo as isTypographyRole, ur as lineLimit, ut as isScrollContainer, v as LABEL_PADDING_X, va as UiInsetRegistry, vi as percent, vn as setMatchRanges, vo as spannedTextOf, vr as lineStartAt, vt as SCROLLBAR_THICKNESS, w as SCROLLBAR_FADE_MS, wa as containerBands, wi as borderRadiusEqual, wn as setSelectionRange, wo as darkColors, wr as UiGraph, wt as scrollbarZoneAt, x as snapScroll, xa as observeViewportInsets, xi as UiBorderRadiuses, xn as selectableTextNodes, xo as textSpansEqual, xr as previousGraphemeStart, xt as scrollRange, y as drawOverlayShapes, ya as insetsEqual, yi as repeat, yn as TEXT_SELECTION_PROP, yo as spansDifferOnlyInPaint, yr as nextGraphemeEnd, yt as scrollBounds, z as tightenConstraints, za as withThemeExtension, zi as writeDeclaredProperty, zn as setLinkHover, zo as easingCss, zr as propertyValuesEqualByName, zt as detectEditingPlatform } from "./CanvasSurface-Cy2UErXI.js";
|
|
2
2
|
import { BehaviorSubject, EMPTY, Subject, combineLatest, distinctUntilChanged, isObservable as isObservable$1, map, of } from "rxjs";
|
|
3
|
-
//#region src/animation/UiEasing.ts
|
|
4
|
-
/** Progress equals time. What a spinner and a sweep want. */
|
|
5
|
-
const linear = (t) => t;
|
|
6
|
-
/**
|
|
7
|
-
* A cubic Bézier easing, in the CSS `cubic-bezier(x1, y1, x2, y2)`
|
|
8
|
-
* sense: the curve runs from (0, 0) to (1, 1) through the two control
|
|
9
|
-
* points, and the easing is y at the t where x equals the input.
|
|
10
|
-
*
|
|
11
|
-
* Solved by Newton's method with a bisection fallback, which is what
|
|
12
|
-
* every browser engine does; eight iterations is well past the point
|
|
13
|
-
* where the result stops moving in a float.
|
|
14
|
-
*/
|
|
15
|
-
function cubicBezier(x1, y1, x2, y2) {
|
|
16
|
-
const ax = 3 * x1 - 3 * x2 + 1;
|
|
17
|
-
const bx = 3 * x2 - 6 * x1;
|
|
18
|
-
const cx = 3 * x1;
|
|
19
|
-
const ay = 3 * y1 - 3 * y2 + 1;
|
|
20
|
-
const by = 3 * y2 - 6 * y1;
|
|
21
|
-
const cy = 3 * y1;
|
|
22
|
-
const sampleX = (t) => ((ax * t + bx) * t + cx) * t;
|
|
23
|
-
const sampleY = (t) => ((ay * t + by) * t + cy) * t;
|
|
24
|
-
const slopeX = (t) => (3 * ax * t + 2 * bx) * t + cx;
|
|
25
|
-
return (input) => {
|
|
26
|
-
if (input <= 0) return 0;
|
|
27
|
-
if (input >= 1) return 1;
|
|
28
|
-
let t = input;
|
|
29
|
-
for (let i = 0; i < 8; i++) {
|
|
30
|
-
const error = sampleX(t) - input;
|
|
31
|
-
if (Math.abs(error) < 1e-6) return sampleY(t);
|
|
32
|
-
const slope = slopeX(t);
|
|
33
|
-
if (Math.abs(slope) < 1e-6) break;
|
|
34
|
-
t -= error / slope;
|
|
35
|
-
}
|
|
36
|
-
let low = 0;
|
|
37
|
-
let high = 1;
|
|
38
|
-
t = input;
|
|
39
|
-
for (let i = 0; i < 24; i++) {
|
|
40
|
-
const x = sampleX(t);
|
|
41
|
-
if (Math.abs(x - input) < 1e-6) break;
|
|
42
|
-
if (x > input) high = t;
|
|
43
|
-
else low = t;
|
|
44
|
-
t = (low + high) / 2;
|
|
45
|
-
}
|
|
46
|
-
return sampleY(t);
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* An easing that only takes `count` distinct values.
|
|
51
|
-
*
|
|
52
|
-
* The reason it exists is the `Spinner`: eight blades have eight
|
|
53
|
-
* positions promised the F4 version would keep a
|
|
54
|
-
* turn at eight writes a second rather than sixty. Paired with an
|
|
55
|
-
* animation's `stepMs`, this is how that promise is kept — the value
|
|
56
|
-
* is stepped and the frames are too.
|
|
57
|
-
*/
|
|
58
|
-
function steps(count) {
|
|
59
|
-
if (!(count >= 1)) throw new Error(`steps() needs at least one step, got ${count}.`);
|
|
60
|
-
return (t) => Math.min(count - 1, Math.floor(t * count)) / count;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* The named curves a motion vocabulary is built from.
|
|
64
|
-
*
|
|
65
|
-
* `standard` is the one to reach for: something entering and leaving
|
|
66
|
-
* at a natural pace. `decelerate` is for something arriving (it starts
|
|
67
|
-
* fast and settles), `accelerate` for something leaving. `emphasized`
|
|
68
|
-
* is `standard` with more of a lean, for the one movement on a screen
|
|
69
|
-
* that should be noticed.
|
|
70
|
-
*/
|
|
71
|
-
const easings = {
|
|
72
|
-
linear,
|
|
73
|
-
standard: cubicBezier(.2, 0, 0, 1),
|
|
74
|
-
decelerate: cubicBezier(0, 0, 0, 1),
|
|
75
|
-
accelerate: cubicBezier(.3, 0, 1, 1),
|
|
76
|
-
emphasized: cubicBezier(.05, .7, .1, 1)
|
|
77
|
-
};
|
|
78
|
-
//#endregion
|
|
79
3
|
//#region src/animation/Interpolate.ts
|
|
80
4
|
const interpolateNumber = (from, to, t) => from + (to - from) * t;
|
|
81
5
|
/**
|
|
@@ -283,7 +207,8 @@ var UiAnimation = class {
|
|
|
283
207
|
this.begin(previous.lastAdvancedAt);
|
|
284
208
|
}
|
|
285
209
|
onBegin() {}
|
|
286
|
-
|
|
210
|
+
/** `hold`: at its end, stay there, unfinished, to be sampled again at an earlier time (`UiClock.holding`). */
|
|
211
|
+
advance(now, hold = false) {
|
|
287
212
|
if (this.finished) return;
|
|
288
213
|
this.begin(now);
|
|
289
214
|
this.lastAdvancedAt = now;
|
|
@@ -296,7 +221,7 @@ var UiAnimation = class {
|
|
|
296
221
|
this.cell.value = value;
|
|
297
222
|
this.subject.next(value);
|
|
298
223
|
}
|
|
299
|
-
if (done) this.finish();
|
|
224
|
+
if (done && !hold) this.finish();
|
|
300
225
|
}
|
|
301
226
|
/**
|
|
302
227
|
* Moves the ideal timeline on by one step.
|
|
@@ -519,40 +444,6 @@ function createTween(cell, to, options) {
|
|
|
519
444
|
return new UiTween(cell, to, options, interpolate);
|
|
520
445
|
}
|
|
521
446
|
//#endregion
|
|
522
|
-
//#region src/environment/UiMotion.ts
|
|
523
|
-
const defaultMotion = {
|
|
524
|
-
durations: {
|
|
525
|
-
/** No motion. What `reducedMotion` turns everything else into. */
|
|
526
|
-
instant: 0,
|
|
527
|
-
/** A control acknowledging a press; too short to watch. */
|
|
528
|
-
fast: 120,
|
|
529
|
-
/** The default: something appearing, moving or changing colour. */
|
|
530
|
-
normal: 200,
|
|
531
|
-
/** A panel or a dialog, which is bigger and travels further. */
|
|
532
|
-
slow: 320,
|
|
533
|
-
/** A movement meant to be followed by the eye. Rare. */
|
|
534
|
-
deliberate: 500
|
|
535
|
-
},
|
|
536
|
-
easings,
|
|
537
|
-
springs: {
|
|
538
|
-
gentle: {
|
|
539
|
-
stiffness: 120,
|
|
540
|
-
damping: 20,
|
|
541
|
-
mass: 1
|
|
542
|
-
},
|
|
543
|
-
snappy: {
|
|
544
|
-
stiffness: 220,
|
|
545
|
-
damping: 24,
|
|
546
|
-
mass: 1
|
|
547
|
-
},
|
|
548
|
-
stiff: {
|
|
549
|
-
stiffness: 400,
|
|
550
|
-
damping: 32,
|
|
551
|
-
mass: 1
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
};
|
|
555
|
-
//#endregion
|
|
556
447
|
//#region src/animation/UiTransition.ts
|
|
557
448
|
/** A tween of `duration` milliseconds along `easing`. */
|
|
558
449
|
function tween(duration, options = {}) {
|
|
@@ -571,12 +462,12 @@ function tween(duration, options = {}) {
|
|
|
571
462
|
function spring(spec = "snappy", options = {}) {
|
|
572
463
|
return {
|
|
573
464
|
kind: "spring",
|
|
574
|
-
spring:
|
|
465
|
+
spring: spec,
|
|
575
466
|
...options
|
|
576
467
|
};
|
|
577
468
|
}
|
|
578
469
|
function normalizeTransition(value) {
|
|
579
|
-
return typeof value === "number" ? {
|
|
470
|
+
return typeof value === "number" || typeof value === "string" ? {
|
|
580
471
|
kind: "tween",
|
|
581
472
|
duration: value
|
|
582
473
|
} : value;
|
|
@@ -598,11 +489,18 @@ function assertTransitionMap(nodeId, value, isKnownProperty) {
|
|
|
598
489
|
});
|
|
599
490
|
continue;
|
|
600
491
|
}
|
|
492
|
+
if (typeof entry === "string" && entry !== "") {
|
|
493
|
+
specs.set(property, {
|
|
494
|
+
kind: "tween",
|
|
495
|
+
duration: entry
|
|
496
|
+
});
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
601
499
|
if (typeof entry === "object" && entry !== null && (entry.kind === "tween" || entry.kind === "spring")) {
|
|
602
500
|
specs.set(property, entry);
|
|
603
501
|
continue;
|
|
604
502
|
}
|
|
605
|
-
throw new Error(`The 'transition' for '${property}' on node '${nodeId}' must be a duration in milliseconds or a spec built by tween() or spring(), got ${describe$1(entry)}.`);
|
|
503
|
+
throw new Error(`The 'transition' for '${property}' on node '${nodeId}' must be a duration in milliseconds or by name, or a spec built by tween() or spring(), got ${describe$1(entry)}.`);
|
|
606
504
|
}
|
|
607
505
|
return specs;
|
|
608
506
|
}
|
|
@@ -632,8 +530,13 @@ function erase(animation) {
|
|
|
632
530
|
*/
|
|
633
531
|
var AnimationDriver = class {
|
|
634
532
|
running = /* @__PURE__ */ new Map();
|
|
533
|
+
/** The clock each running animation was started on, when it was not real time; see `UiClock`. */
|
|
534
|
+
clocks = /* @__PURE__ */ new Map();
|
|
535
|
+
/** Each clock's `moves` as last drawn: one moved since (a seek) is drawn at its new time at once. */
|
|
536
|
+
seenMoves = /* @__PURE__ */ new WeakMap();
|
|
635
537
|
reducedMotion = false;
|
|
636
538
|
hidden = false;
|
|
539
|
+
advancing = false;
|
|
637
540
|
wake = null;
|
|
638
541
|
/**
|
|
639
542
|
* Called when an animation enters the running set on an idle driver.
|
|
@@ -648,6 +551,14 @@ var AnimationDriver = class {
|
|
|
648
551
|
setWakeListener(listener) {
|
|
649
552
|
this.wake = listener;
|
|
650
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Whether a frame of the running animations is being written now: a
|
|
556
|
+
* value written meanwhile is a step of an animation, not a change for
|
|
557
|
+
* a transition to animate again.
|
|
558
|
+
*/
|
|
559
|
+
get isAdvancing() {
|
|
560
|
+
return this.advancing;
|
|
561
|
+
}
|
|
651
562
|
/** Whether anything is running, so the `ticks` phase can report 0. */
|
|
652
563
|
get isRunning() {
|
|
653
564
|
return this.running.size > 0;
|
|
@@ -669,8 +580,8 @@ var AnimationDriver = class {
|
|
|
669
580
|
* animation says its movement is the information rather than the
|
|
670
581
|
* decoration.
|
|
671
582
|
*/
|
|
672
|
-
lands(policy) {
|
|
673
|
-
return policy === "snap" && (this.reducedMotion || this.hidden);
|
|
583
|
+
lands(policy, clock) {
|
|
584
|
+
return clock?.settled === true || policy === "snap" && (this.reducedMotion || this.hidden);
|
|
674
585
|
}
|
|
675
586
|
/**
|
|
676
587
|
* Starts an animation, replacing whatever was driving its cell.
|
|
@@ -682,16 +593,18 @@ var AnimationDriver = class {
|
|
|
682
593
|
* So a reduced-motion app runs no animation frames, rather than
|
|
683
594
|
* running them and drawing the same thing sixty times.
|
|
684
595
|
*/
|
|
685
|
-
start(animation) {
|
|
596
|
+
start(animation, clock = null) {
|
|
686
597
|
const cell = animation.cell;
|
|
687
598
|
this.running.get(cell)?.cancel();
|
|
688
599
|
this.running.delete(cell);
|
|
689
|
-
|
|
600
|
+
this.clocks.delete(cell);
|
|
601
|
+
if (this.lands(animation.reducedMotionPolicy, clock ?? void 0)) {
|
|
690
602
|
animation.snap();
|
|
691
603
|
return animation.values;
|
|
692
604
|
}
|
|
693
605
|
const wasIdle = this.running.size === 0;
|
|
694
606
|
this.running.set(cell, erase(animation));
|
|
607
|
+
if (clock !== null) this.clocks.set(cell, clock);
|
|
695
608
|
if (wasIdle) this.wake?.();
|
|
696
609
|
return animation.values;
|
|
697
610
|
}
|
|
@@ -711,12 +624,14 @@ var AnimationDriver = class {
|
|
|
711
624
|
const animation = this.running.get(cell);
|
|
712
625
|
if (animation === void 0) return false;
|
|
713
626
|
this.running.delete(cell);
|
|
627
|
+
this.clocks.delete(cell);
|
|
714
628
|
animation.cancel();
|
|
715
629
|
return true;
|
|
716
630
|
}
|
|
717
631
|
stopAll() {
|
|
718
632
|
const animations = [...this.running.values()];
|
|
719
633
|
this.running.clear();
|
|
634
|
+
this.clocks.clear();
|
|
720
635
|
for (const animation of animations) animation.cancel();
|
|
721
636
|
}
|
|
722
637
|
/**
|
|
@@ -770,8 +685,9 @@ var AnimationDriver = class {
|
|
|
770
685
|
}
|
|
771
686
|
/** Puts everything that may be landed where it was going. */
|
|
772
687
|
landRunning() {
|
|
773
|
-
for (const [cell, animation] of this.running) if (this.lands(animation.reducedMotionPolicy)) {
|
|
688
|
+
for (const [cell, animation] of this.running) if (this.lands(animation.reducedMotionPolicy, this.clocks.get(cell))) {
|
|
774
689
|
this.running.delete(cell);
|
|
690
|
+
this.clocks.delete(cell);
|
|
775
691
|
animation.snap();
|
|
776
692
|
}
|
|
777
693
|
}
|
|
@@ -788,8 +704,14 @@ var AnimationDriver = class {
|
|
|
788
704
|
*/
|
|
789
705
|
nextTickAt(now) {
|
|
790
706
|
let earliest;
|
|
791
|
-
for (const animation of this.running
|
|
792
|
-
const
|
|
707
|
+
for (const [cell, animation] of this.running) {
|
|
708
|
+
const clock = this.clocks.get(cell);
|
|
709
|
+
if (clock?.settled === true) return now;
|
|
710
|
+
if (clock !== void 0 && clock.moves !== (this.seenMoves.get(clock) ?? 0)) return now;
|
|
711
|
+
const at = clock === void 0 ? now : clock.local(now);
|
|
712
|
+
const dueAt = animation.dueAt(at) - animation.dueSlackMs;
|
|
713
|
+
const due = clock === void 0 ? dueAt : clock.real(dueAt);
|
|
714
|
+
if (!Number.isFinite(due)) continue;
|
|
793
715
|
if (earliest === void 0 || due < earliest) earliest = due;
|
|
794
716
|
}
|
|
795
717
|
return earliest;
|
|
@@ -805,14 +727,144 @@ var AnimationDriver = class {
|
|
|
805
727
|
* behind.
|
|
806
728
|
*/
|
|
807
729
|
advance(now) {
|
|
730
|
+
this.advancing = true;
|
|
731
|
+
try {
|
|
732
|
+
this.advanceRunning(now);
|
|
733
|
+
} finally {
|
|
734
|
+
this.advancing = false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
advanceRunning(now) {
|
|
738
|
+
const moved = [...new Set(this.clocks.values())].filter((clock) => clock.moves !== (this.seenMoves.get(clock) ?? 0));
|
|
739
|
+
this.advanceEach(now);
|
|
740
|
+
for (const clock of moved) this.seenMoves.set(clock, clock.moves);
|
|
741
|
+
}
|
|
742
|
+
advanceEach(now) {
|
|
808
743
|
for (const [cell, animation] of this.running) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
744
|
+
const clock = this.clocks.get(cell);
|
|
745
|
+
if (clock?.settled === true) {
|
|
746
|
+
this.running.delete(cell);
|
|
747
|
+
this.clocks.delete(cell);
|
|
748
|
+
animation.snap();
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const at = clock === void 0 ? now : clock.local(now);
|
|
752
|
+
if (!(clock !== void 0 && clock.moves !== (this.seenMoves.get(clock) ?? 0)) && animation.dueAt(at) - animation.dueSlackMs > at) continue;
|
|
753
|
+
animation.advance(at, clock?.holding === true);
|
|
754
|
+
if (animation.isFinished && this.running.get(cell) === animation) {
|
|
755
|
+
this.running.delete(cell);
|
|
756
|
+
this.clocks.delete(cell);
|
|
757
|
+
}
|
|
812
758
|
}
|
|
813
759
|
}
|
|
814
760
|
};
|
|
815
761
|
//#endregion
|
|
762
|
+
//#region src/animation/UiClock.ts
|
|
763
|
+
/**
|
|
764
|
+
* How time passes for the animations under a node: at a speed of its
|
|
765
|
+
* own, or not at all.
|
|
766
|
+
*
|
|
767
|
+
* A node provides one to its subtree,
|
|
768
|
+
* `provide={[provided(UiEnvironmentKeys.clock, clock)]}`, and every
|
|
769
|
+
* animation started there runs on it: a transition, a `motion` state,
|
|
770
|
+
* hover and press, and `AnimationService.animate` given the node it
|
|
771
|
+
* animates `on`. Nothing provided is real time.
|
|
772
|
+
*
|
|
773
|
+
* What a design tool needs to show one screen two ways at once. The
|
|
774
|
+
* frames on its board are pictures: under a `settled` clock every
|
|
775
|
+
* animation lands where it is going the moment it starts, and a loop
|
|
776
|
+
* holds still. The screen it plays runs at the person's own speed, a
|
|
777
|
+
* quarter of it to watch a press closely, or paused. Reduced motion and
|
|
778
|
+
* a hidden page are the whole app's; this is one part of it.
|
|
779
|
+
*
|
|
780
|
+
* An animation takes all its time from the driver, which hands each
|
|
781
|
+
* the local time of its clock, so nothing inside a tween or a spring
|
|
782
|
+
* knows; and the driver asks for the next frame in real time, so a
|
|
783
|
+
* slowed clock asks for frames as often as it needs them and a paused
|
|
784
|
+
* one asks for none.
|
|
785
|
+
*/
|
|
786
|
+
var UiClock = class {
|
|
787
|
+
rate;
|
|
788
|
+
/** A real time and the local time it was, from the last change of speed: where local time is measured from. */
|
|
789
|
+
anchor = null;
|
|
790
|
+
/** The real time last asked about, where a change of speed takes effect. */
|
|
791
|
+
lastReal = null;
|
|
792
|
+
/** Every animation lands where it is going, and none runs. */
|
|
793
|
+
settled;
|
|
794
|
+
/**
|
|
795
|
+
* Animations that reach their end stay, held there, rather than
|
|
796
|
+
* finishing: so moving the time back (`seek`) plays them back. What a
|
|
797
|
+
* design tool scrubs a transition with; let go, they finish as they
|
|
798
|
+
* would have. A spring, which is worked out step by step rather than
|
|
799
|
+
* from the time, holds but does not run back.
|
|
800
|
+
*/
|
|
801
|
+
holding = false;
|
|
802
|
+
/** Bumped by `seek`, so a driver knows to draw again at the new time. */
|
|
803
|
+
moves = 0;
|
|
804
|
+
constructor(options = {}) {
|
|
805
|
+
this.rate = checkedSpeed(options.speed ?? 1);
|
|
806
|
+
this.settled = options.settled ?? false;
|
|
807
|
+
}
|
|
808
|
+
/** How fast time passes: 1 is real time, 0.25 a quarter of it, 0 paused. */
|
|
809
|
+
get speed() {
|
|
810
|
+
return this.rate;
|
|
811
|
+
}
|
|
812
|
+
set speed(next) {
|
|
813
|
+
const speed = checkedSpeed(next);
|
|
814
|
+
if (this.anchor !== null && this.lastReal !== null) this.anchor = {
|
|
815
|
+
real: this.lastReal,
|
|
816
|
+
local: this.local(this.lastReal)
|
|
817
|
+
};
|
|
818
|
+
this.rate = speed;
|
|
819
|
+
}
|
|
820
|
+
/** The local time now, as last asked: where a seek is measured from. */
|
|
821
|
+
get now() {
|
|
822
|
+
return this.lastReal === null ? 0 : this.local(this.lastReal);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Puts local time at `at`, from now on: forward, or back to replay
|
|
826
|
+
* what is held (`holding`). Paused, it stays there; running, it goes
|
|
827
|
+
* on from there.
|
|
828
|
+
*/
|
|
829
|
+
seek(at) {
|
|
830
|
+
const real = this.lastReal ?? 0;
|
|
831
|
+
this.anchor = {
|
|
832
|
+
real,
|
|
833
|
+
local: at
|
|
834
|
+
};
|
|
835
|
+
this.moves += 1;
|
|
836
|
+
}
|
|
837
|
+
/** The local time at real time `now`. The first time asked, the two agree. */
|
|
838
|
+
local(now) {
|
|
839
|
+
this.anchor ??= {
|
|
840
|
+
real: now,
|
|
841
|
+
local: now
|
|
842
|
+
};
|
|
843
|
+
this.lastReal = now;
|
|
844
|
+
return this.anchor.local + (now - this.anchor.real) * this.rate;
|
|
845
|
+
}
|
|
846
|
+
/** The real time at which local time `at` comes; Infinity while paused. */
|
|
847
|
+
real(at) {
|
|
848
|
+
if (this.anchor === null) return at;
|
|
849
|
+
if (this.rate === 0) return Infinity;
|
|
850
|
+
return this.anchor.real + (at - this.anchor.local) / this.rate;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
function checkedSpeed(speed) {
|
|
854
|
+
if (!(speed >= 0) || !Number.isFinite(speed)) throw new Error(`A clock's speed is a number of 0 or more (0 is paused, 1 real time), not ${speed}.`);
|
|
855
|
+
return speed;
|
|
856
|
+
}
|
|
857
|
+
/** The motion vocabulary names resolve to under `node`: the nearest provided above it, or Gesso's. */
|
|
858
|
+
function motionOf(node) {
|
|
859
|
+
for (let at = node ?? null; at !== null; at = at.parent) if (at.environment !== null) return at.environment.get(UiEnvironmentKeys.motion);
|
|
860
|
+
return UiEnvironmentKeys.motion.defaultValue;
|
|
861
|
+
}
|
|
862
|
+
/** The clock the animations under `node` run on: the nearest one provided above it, or null for real time. */
|
|
863
|
+
function clockOf(node) {
|
|
864
|
+
for (let at = node ?? null; at !== null; at = at.parent) if (at.environment !== null) return at.environment.get(UiEnvironmentKeys.clock);
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
//#endregion
|
|
816
868
|
//#region src/animation/UiSharedElements.ts
|
|
817
869
|
/**
|
|
818
870
|
* Who currently owns each shared-element name, and where they were.
|
|
@@ -1099,9 +1151,7 @@ var UiChildrenBinding = class {
|
|
|
1099
1151
|
handleValue(value) {
|
|
1100
1152
|
const definitions = Array.isArray(value) ? value : [value];
|
|
1101
1153
|
const fragment = this.graph.requireNode(this.fragmentId);
|
|
1102
|
-
this.builder.reconcileChildren(fragment, definitions);
|
|
1103
|
-
const parent = this.graph.requireNode(this.parentId);
|
|
1104
|
-
this.graph.markDirty(parent, 16);
|
|
1154
|
+
if (this.builder.reconcileChildren(fragment, definitions).changed) this.graph.markDirty(this.graph.requireNode(this.parentId), 16);
|
|
1105
1155
|
}
|
|
1106
1156
|
};
|
|
1107
1157
|
//#endregion
|
|
@@ -1211,6 +1261,19 @@ function isUiModifier(value) {
|
|
|
1211
1261
|
}
|
|
1212
1262
|
//#endregion
|
|
1213
1263
|
//#region src/modifiers/interaction.ts
|
|
1264
|
+
/**
|
|
1265
|
+
* Draws what is under a node as though it were hovered, pressed or
|
|
1266
|
+
* focused: every `interactive` modifier there shows its hover or press
|
|
1267
|
+
* look, and every `focusRing` its ring. What a design tool draws a
|
|
1268
|
+
* component's pointer states with, beside its other states, where no
|
|
1269
|
+
* pointer is over it. Read as each modifier attaches, so a node drawn
|
|
1270
|
+
* under it starts so and stays so; the pointer still adds its own.
|
|
1271
|
+
* Pressed is hovered too, as a press under a pointer is.
|
|
1272
|
+
*/
|
|
1273
|
+
const ShownAsKey = createEnvironmentKey({
|
|
1274
|
+
name: "gesso.shownAs",
|
|
1275
|
+
defaultValue: null
|
|
1276
|
+
});
|
|
1214
1277
|
const BOTH = Object.freeze({
|
|
1215
1278
|
hover: true,
|
|
1216
1279
|
press: true
|
|
@@ -1228,19 +1291,25 @@ const kind$5 = defineModifier({
|
|
|
1228
1291
|
attach(host, options) {
|
|
1229
1292
|
let hovered = false;
|
|
1230
1293
|
let pressed = false;
|
|
1294
|
+
const shown = host.environment(ShownAsKey);
|
|
1295
|
+
const shownPressed = shown === "pressed";
|
|
1296
|
+
const shownHovered = shownPressed || shown === "hovered";
|
|
1231
1297
|
const styled = /* @__PURE__ */ new Set([...Object.keys(options.hovered ?? {}), ...Object.keys(options.pressed ?? {})]);
|
|
1232
1298
|
const sync = () => {
|
|
1299
|
+
const isHovered = hovered || shownHovered;
|
|
1300
|
+
const isPressed = pressed || shownPressed;
|
|
1233
1301
|
const states = /* @__PURE__ */ new Set();
|
|
1234
|
-
if (
|
|
1235
|
-
if (
|
|
1302
|
+
if (isHovered) states.add("hovered");
|
|
1303
|
+
if (isPressed) states.add("pressed");
|
|
1236
1304
|
if (states.size === 0) states.add("normal");
|
|
1237
1305
|
if (!sameStates(host.get("visualState"), states)) host.set("visualState", states);
|
|
1238
1306
|
for (const property of styled) {
|
|
1239
|
-
const value = (
|
|
1307
|
+
const value = (isPressed ? options.pressed?.[property] : void 0) ?? (isHovered ? options.hovered?.[property] : void 0);
|
|
1240
1308
|
if (value === void 0) host.clear(property);
|
|
1241
1309
|
else host.set(property, value);
|
|
1242
1310
|
}
|
|
1243
1311
|
};
|
|
1312
|
+
if (shownHovered) sync();
|
|
1244
1313
|
if (options.hover) {
|
|
1245
1314
|
host.on("pointerenter", () => {
|
|
1246
1315
|
hovered = true;
|
|
@@ -1337,7 +1406,7 @@ const measureFlow = defineModifier({
|
|
|
1337
1406
|
target.next(box);
|
|
1338
1407
|
};
|
|
1339
1408
|
report();
|
|
1340
|
-
host.
|
|
1409
|
+
host.onFlowLayout(report);
|
|
1341
1410
|
}
|
|
1342
1411
|
});
|
|
1343
1412
|
//#endregion
|
|
@@ -1618,6 +1687,13 @@ var UiModifierSet = class {
|
|
|
1618
1687
|
get names() {
|
|
1619
1688
|
return this.attached.map((entry) => entry.kind.name);
|
|
1620
1689
|
}
|
|
1690
|
+
/** What is attached, each kind with its current arguments, in order: for a tool reading what a node does. */
|
|
1691
|
+
get modifiers() {
|
|
1692
|
+
return this.attached.map((entry) => ({
|
|
1693
|
+
kind: entry.kind,
|
|
1694
|
+
args: entry.args
|
|
1695
|
+
}));
|
|
1696
|
+
}
|
|
1621
1697
|
get size() {
|
|
1622
1698
|
return this.attached.length;
|
|
1623
1699
|
}
|
|
@@ -1815,6 +1891,17 @@ var Host = class {
|
|
|
1815
1891
|
}
|
|
1816
1892
|
this.own(layout.onLayout(this.node, listener));
|
|
1817
1893
|
}
|
|
1894
|
+
onFlowLayout(listener) {
|
|
1895
|
+
const layout = this.services.layout;
|
|
1896
|
+
if (layout === void 0) {
|
|
1897
|
+
warnMissingLayout(this.name);
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
this.own(layout.onFlowLayout === void 0 ? layout.onLayout(this.node, () => {
|
|
1901
|
+
const box = layout.flowBox(this.node);
|
|
1902
|
+
if (box !== null) listener(box);
|
|
1903
|
+
}) : layout.onFlowLayout(this.node, listener));
|
|
1904
|
+
}
|
|
1818
1905
|
environment(key, of = this.node) {
|
|
1819
1906
|
const environment = this.services.environment;
|
|
1820
1907
|
if (environment === void 0) return key.defaultValue;
|
|
@@ -1863,7 +1950,7 @@ var Host = class {
|
|
|
1863
1950
|
return EMPTY;
|
|
1864
1951
|
}
|
|
1865
1952
|
this.animated.add(cell);
|
|
1866
|
-
return driver.start(tween);
|
|
1953
|
+
return driver.start(tween, clockOf(this.node));
|
|
1867
1954
|
}
|
|
1868
1955
|
spring(cell, to, options) {
|
|
1869
1956
|
const driver = this.services.animations;
|
|
@@ -1878,7 +1965,7 @@ var Host = class {
|
|
|
1878
1965
|
return driver.start(new UiSpring(cell, to, {
|
|
1879
1966
|
...options,
|
|
1880
1967
|
velocity
|
|
1881
|
-
}));
|
|
1968
|
+
}), clockOf(this.node));
|
|
1882
1969
|
}
|
|
1883
1970
|
own(teardown) {
|
|
1884
1971
|
this.teardowns.push(teardown);
|
|
@@ -2063,6 +2150,10 @@ var NodeTransitions = class {
|
|
|
2063
2150
|
write(property, value, flags) {
|
|
2064
2151
|
const spec = this.specs.get(property);
|
|
2065
2152
|
if (spec === void 0) return false;
|
|
2153
|
+
if (this.driver.isAdvancing) {
|
|
2154
|
+
this.stopProperty(property);
|
|
2155
|
+
return false;
|
|
2156
|
+
}
|
|
2066
2157
|
if (!this.node.properties.has(property)) return false;
|
|
2067
2158
|
const current = this.node.properties.get(property);
|
|
2068
2159
|
if (Object.is(current, value)) return false;
|
|
@@ -2082,6 +2173,12 @@ var NodeTransitions = class {
|
|
|
2082
2173
|
this.targets.clear();
|
|
2083
2174
|
this.specs = /* @__PURE__ */ new Map();
|
|
2084
2175
|
}
|
|
2176
|
+
cancel(property) {
|
|
2177
|
+
this.stopProperty(property);
|
|
2178
|
+
}
|
|
2179
|
+
get declared() {
|
|
2180
|
+
return this.specs;
|
|
2181
|
+
}
|
|
2085
2182
|
stopProperty(property) {
|
|
2086
2183
|
const cell = this.cells.get(property);
|
|
2087
2184
|
if (cell !== void 0) {
|
|
@@ -2095,10 +2192,9 @@ var NodeTransitions = class {
|
|
|
2095
2192
|
* node: the driver keys its running set by cell identity, so a fresh
|
|
2096
2193
|
* adapter per write would leave the previous animation running.
|
|
2097
2194
|
*
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2101
|
-
* bound one.
|
|
2195
|
+
* It writes what is drawn (`applyAnimatedValue`): the target already
|
|
2196
|
+
* is the value the element and any modifier over it settled on, so a
|
|
2197
|
+
* frame of the way there goes onto the node as it is.
|
|
2102
2198
|
*/
|
|
2103
2199
|
cellFor(property, flags) {
|
|
2104
2200
|
let cell = this.cells.get(property);
|
|
@@ -2110,7 +2206,7 @@ var NodeTransitions = class {
|
|
|
2110
2206
|
return node.properties.get(property);
|
|
2111
2207
|
},
|
|
2112
2208
|
set value(next) {
|
|
2113
|
-
graph.
|
|
2209
|
+
graph.applyAnimatedValue(node, property, next, flags);
|
|
2114
2210
|
}
|
|
2115
2211
|
};
|
|
2116
2212
|
this.cells.set(property, cell);
|
|
@@ -2125,16 +2221,17 @@ var NodeTransitions = class {
|
|
|
2125
2221
|
const previous = this.driver.animationFor(cell);
|
|
2126
2222
|
const velocity = previous instanceof UiSpring ? previous.currentVelocity : 0;
|
|
2127
2223
|
this.driver.start(new UiSpring(cell, to, {
|
|
2128
|
-
spring: spec.spring,
|
|
2224
|
+
spring: springIn(motionOf(this.node), spec.spring),
|
|
2129
2225
|
velocity,
|
|
2130
2226
|
restDelta: spec.restDelta,
|
|
2131
2227
|
reducedMotion: spec.reducedMotion
|
|
2132
|
-
}));
|
|
2228
|
+
}), clockOf(this.node));
|
|
2133
2229
|
return true;
|
|
2134
2230
|
}
|
|
2231
|
+
const motion = motionOf(this.node);
|
|
2135
2232
|
const tween = createTween(cell, to, {
|
|
2136
|
-
duration: spec.duration,
|
|
2137
|
-
easing: spec.easing,
|
|
2233
|
+
duration: durationIn(motion, spec.duration),
|
|
2234
|
+
easing: easingIn(motion, spec.easing),
|
|
2138
2235
|
stepMs: spec.stepMs,
|
|
2139
2236
|
reducedMotion: spec.reducedMotion
|
|
2140
2237
|
});
|
|
@@ -2142,7 +2239,7 @@ var NodeTransitions = class {
|
|
|
2142
2239
|
warnNotInterpolable(property);
|
|
2143
2240
|
return false;
|
|
2144
2241
|
}
|
|
2145
|
-
this.driver.start(tween);
|
|
2242
|
+
this.driver.start(tween, clockOf(this.node));
|
|
2146
2243
|
return true;
|
|
2147
2244
|
}
|
|
2148
2245
|
};
|
|
@@ -2307,8 +2404,9 @@ var UiGraphBuilder = class {
|
|
|
2307
2404
|
* the fragment's children on each emission.
|
|
2308
2405
|
*/
|
|
2309
2406
|
reconcileChildren(parent, definitions) {
|
|
2407
|
+
const present = definitions.some(isNothing) ? definitions.filter((each) => !isNothing(each)) : definitions;
|
|
2310
2408
|
if (this.reconciling.has(parent)) {
|
|
2311
|
-
this.deferredChildren.set(parent,
|
|
2409
|
+
this.deferredChildren.set(parent, present);
|
|
2312
2410
|
return {
|
|
2313
2411
|
nodes: this.collectChildren(parent),
|
|
2314
2412
|
changed: false
|
|
@@ -2318,7 +2416,7 @@ var UiGraphBuilder = class {
|
|
|
2318
2416
|
this.reconcileDepth++;
|
|
2319
2417
|
let result;
|
|
2320
2418
|
try {
|
|
2321
|
-
result = this.reconcile(parent,
|
|
2419
|
+
result = this.reconcile(parent, present);
|
|
2322
2420
|
} finally {
|
|
2323
2421
|
this.reconcileDepth--;
|
|
2324
2422
|
this.reconciling.delete(parent);
|
|
@@ -2326,7 +2424,11 @@ var UiGraphBuilder = class {
|
|
|
2326
2424
|
const deferred = this.deferredChildren.get(parent);
|
|
2327
2425
|
if (deferred !== void 0) {
|
|
2328
2426
|
this.deferredChildren.delete(parent);
|
|
2329
|
-
|
|
2427
|
+
const again = this.reconcileChildren(parent, deferred);
|
|
2428
|
+
return {
|
|
2429
|
+
nodes: again.nodes,
|
|
2430
|
+
changed: result.changed || again.changed
|
|
2431
|
+
};
|
|
2330
2432
|
}
|
|
2331
2433
|
if (this.reconcileDepth === 0) this.components?.flushMounts();
|
|
2332
2434
|
return result;
|
|
@@ -2458,7 +2560,7 @@ var UiGraphBuilder = class {
|
|
|
2458
2560
|
matched.add(anchor);
|
|
2459
2561
|
if (this.reconciledFrom.get(anchor) === element) return anchor;
|
|
2460
2562
|
}
|
|
2461
|
-
this.reconcileChildren(anchor, [resolver.resolve(element, anchorId)]);
|
|
2563
|
+
this.reconcileChildren(anchor, [resolver.resolve(element, anchorId, anchor)]);
|
|
2462
2564
|
this.reconciledFrom.set(anchor, element);
|
|
2463
2565
|
return anchor;
|
|
2464
2566
|
}
|
|
@@ -2524,7 +2626,7 @@ var UiGraphBuilder = class {
|
|
|
2524
2626
|
matchNode(parent, definition, unkeyed, matched) {
|
|
2525
2627
|
const key = this.elementKey(definition);
|
|
2526
2628
|
if (key !== void 0) {
|
|
2527
|
-
const candidate = this.graph.getNode(`${parent.id}:${key}`);
|
|
2629
|
+
const candidate = this.graph.getNode(`${parent.id}:${idSegment(key)}`);
|
|
2528
2630
|
if (candidate === void 0 || candidate.parent !== parent || candidate.type !== definition.type) return;
|
|
2529
2631
|
if (matched.has(candidate)) throw new Error(`Duplicate key '${key}' in parent '${parent.id}'.`);
|
|
2530
2632
|
return candidate;
|
|
@@ -2552,8 +2654,13 @@ var UiGraphBuilder = class {
|
|
|
2552
2654
|
createNode(parent, definition, index) {
|
|
2553
2655
|
const id = this.createNodeId(parent, definition, index);
|
|
2554
2656
|
const node = this.graph.createNode(id, definition.type);
|
|
2555
|
-
|
|
2556
|
-
|
|
2657
|
+
try {
|
|
2658
|
+
this.reconcileProps(node, definition.props, parent);
|
|
2659
|
+
this.reconcileChildren(node, definition.children);
|
|
2660
|
+
} catch (error) {
|
|
2661
|
+
this.removeSubtree(node);
|
|
2662
|
+
throw error;
|
|
2663
|
+
}
|
|
2557
2664
|
return node;
|
|
2558
2665
|
}
|
|
2559
2666
|
/**
|
|
@@ -2583,7 +2690,7 @@ var UiGraphBuilder = class {
|
|
|
2583
2690
|
if (isObservable(definition) || isComponentLikeElement(definition)) continue;
|
|
2584
2691
|
const key = this.elementKey(definition);
|
|
2585
2692
|
if (key === void 0) continue;
|
|
2586
|
-
const candidate = this.graph.getNode(`${parent.id}:${key}`);
|
|
2693
|
+
const candidate = this.graph.getNode(`${parent.id}:${idSegment(key)}`);
|
|
2587
2694
|
const position = candidate === void 0 ? void 0 : at.get(candidate);
|
|
2588
2695
|
if (candidate === void 0 || position === void 0 || candidate.type !== definition.type) continue;
|
|
2589
2696
|
nodes.push(candidate);
|
|
@@ -2731,6 +2838,19 @@ var UiGraphBuilder = class {
|
|
|
2731
2838
|
return this.modifiers.get(node);
|
|
2732
2839
|
}
|
|
2733
2840
|
/**
|
|
2841
|
+
* Where the element a node was last reconciled from was written, for
|
|
2842
|
+
* the inspector and for tools that edit the source. For a component's
|
|
2843
|
+
* anchor it is where the component was used, so walking up from a
|
|
2844
|
+
* node gives the line that made it and then each place it was used.
|
|
2845
|
+
*
|
|
2846
|
+
* Read off the definition the builder already keeps for each node
|
|
2847
|
+
* rather than stored on the node, so it costs a tree nothing when no
|
|
2848
|
+
* element carries one, which is every element in production.
|
|
2849
|
+
*/
|
|
2850
|
+
sourceOf(node) {
|
|
2851
|
+
return this.reconciledFrom.get(node)?.source;
|
|
2852
|
+
}
|
|
2853
|
+
/**
|
|
2734
2854
|
* Rejects a prop nothing will ever read.
|
|
2735
2855
|
*
|
|
2736
2856
|
* Every property layout, paint, input or the environment consults is
|
|
@@ -2834,7 +2954,7 @@ var UiGraphBuilder = class {
|
|
|
2834
2954
|
*/
|
|
2835
2955
|
createNodeId(parent, definition, index) {
|
|
2836
2956
|
const key = this.elementKey(definition);
|
|
2837
|
-
if (key !== void 0) return `${parent.id}:${key}`;
|
|
2957
|
+
if (key !== void 0) return `${parent.id}:${idSegment(key)}`;
|
|
2838
2958
|
return `${parent.id}:${index}`;
|
|
2839
2959
|
}
|
|
2840
2960
|
/**
|
|
@@ -2853,7 +2973,7 @@ var UiGraphBuilder = class {
|
|
|
2853
2973
|
*/
|
|
2854
2974
|
createComponentAnchorId(parent, element, index) {
|
|
2855
2975
|
const key = element.key === void 0 || element.key === null ? index : element.key;
|
|
2856
|
-
return `${parent.id}:component:${String(key)}`;
|
|
2976
|
+
return `${parent.id}:component:${idSegment(String(key))}`;
|
|
2857
2977
|
}
|
|
2858
2978
|
};
|
|
2859
2979
|
const NO_NODES = /* @__PURE__ */ new Set();
|
|
@@ -2882,6 +3002,24 @@ function longestIncreasing(values) {
|
|
|
2882
3002
|
for (let index = tops.length > 0 ? tops[tops.length - 1] : -1; index !== -1; index = previous[index]) run.push(index);
|
|
2883
3003
|
return run.reverse();
|
|
2884
3004
|
}
|
|
3005
|
+
/**
|
|
3006
|
+
* A key as one segment of a node id, which joins segments with colons.
|
|
3007
|
+
*
|
|
3008
|
+
* A key holding a colon would otherwise read as more than one segment,
|
|
3009
|
+
* and could spell another node's id: a row keyed `a:0` beside a row
|
|
3010
|
+
* keyed `a` whose first child is `a:0` again, which threw "already used
|
|
3011
|
+
* outside parent" for a list keyed by ids (a layers panel's). Escaped,
|
|
3012
|
+
* a key is one segment whatever it holds, and a key with no colon in it
|
|
3013
|
+
* is unchanged.
|
|
3014
|
+
*/
|
|
3015
|
+
function idSegment(key) {
|
|
3016
|
+
const text = String(key);
|
|
3017
|
+
return text.includes(":") || text.includes("%") ? text.replace(/%/g, "%25").replace(/:/g, "%3A") : text;
|
|
3018
|
+
}
|
|
3019
|
+
/** A child that draws nothing: null, undefined or a boolean, from plain script. */
|
|
3020
|
+
function isNothing(child) {
|
|
3021
|
+
return child === null || child === void 0 || typeof child === "boolean";
|
|
3022
|
+
}
|
|
2885
3023
|
//#endregion
|
|
2886
3024
|
//#region src/composition/UiVirtualWindow.ts
|
|
2887
3025
|
/** Marks an item wrapper with its index so the host can measure it. */
|
|
@@ -3535,7 +3673,7 @@ const sizeContainer = defineModifier({
|
|
|
3535
3673
|
const vertical = inset("paddingTop", "paddingY") + inset("paddingBottom", "paddingY");
|
|
3536
3674
|
args.source.report(Math.max(0, box.width - horizontal), Math.max(0, box.height - vertical));
|
|
3537
3675
|
};
|
|
3538
|
-
host.
|
|
3676
|
+
host.onFlowLayout(report);
|
|
3539
3677
|
report();
|
|
3540
3678
|
}
|
|
3541
3679
|
});
|
|
@@ -3580,7 +3718,7 @@ const breakpoint = defineModifier({
|
|
|
3580
3718
|
}
|
|
3581
3719
|
for (const [name, value] of written) host.set(name, value);
|
|
3582
3720
|
};
|
|
3583
|
-
host.
|
|
3721
|
+
host.onFlowLayout(apply);
|
|
3584
3722
|
apply();
|
|
3585
3723
|
}
|
|
3586
3724
|
});
|
|
@@ -4828,14 +4966,17 @@ var UiShortcutRegistry = class {
|
|
|
4828
4966
|
* `preventDefault()` so the keyboard controller's own defaults (Tab
|
|
4829
4967
|
* navigation, Enter on a button, an editable's typing) do not also
|
|
4830
4968
|
* act on a key that has already been spent.
|
|
4969
|
+
*
|
|
4970
|
+
* `code` is the physical key, which a digit written with Shift
|
|
4971
|
+
* matches on (see `sameStep`).
|
|
4831
4972
|
*/
|
|
4832
|
-
handleKey(key, modifiers, focused) {
|
|
4973
|
+
handleKey(key, modifiers, focused, code) {
|
|
4833
4974
|
if (MODIFIER_KEYS.has(key)) return false;
|
|
4834
4975
|
if (focused !== null && isTypingInto(focused) && this.isEditingKey(key, modifiers)) {
|
|
4835
4976
|
this.pending = [];
|
|
4836
4977
|
return false;
|
|
4837
4978
|
}
|
|
4838
|
-
const step = stepFor(key, modifiers);
|
|
4979
|
+
const step = stepFor(key, modifiers, code);
|
|
4839
4980
|
const at = this.now();
|
|
4840
4981
|
if (this.pending.length > 0 && at - this.pendingAt > this.chordTimeout) this.pending = [];
|
|
4841
4982
|
const sequence = [...this.pending, step];
|
|
@@ -5009,7 +5150,7 @@ function matchesPrefix(steps, sequence) {
|
|
|
5009
5150
|
return sequence.every((step, index) => sameStep(steps[index], step));
|
|
5010
5151
|
}
|
|
5011
5152
|
function sameStep(want, got) {
|
|
5012
|
-
if (want.key !== got.key) return false;
|
|
5153
|
+
if (want.key !== got.key && !shiftedDigit(want, got)) return false;
|
|
5013
5154
|
if (isSymbol(want.key)) {
|
|
5014
5155
|
if (isBare(want) && got.alt && !got.meta) return true;
|
|
5015
5156
|
} else if (want.shift !== got.shift) return false;
|
|
@@ -5018,6 +5159,15 @@ function sameStep(want, got) {
|
|
|
5018
5159
|
return want.ctrl === got.ctrl && want.meta === got.meta;
|
|
5019
5160
|
}
|
|
5020
5161
|
/**
|
|
5162
|
+
* A digit written with Shift, pressed: `Shift+1`, as a design tool's
|
|
5163
|
+
* "zoom to fit" is written, is the 1 key with Shift held, whatever
|
|
5164
|
+
* character that types. On a US keyboard it types `!`, on a French one
|
|
5165
|
+
* `1` itself, so the character cannot say; the physical key can.
|
|
5166
|
+
*/
|
|
5167
|
+
function shiftedDigit(want, got) {
|
|
5168
|
+
return want.shift && got.shift && /^[0-9]$/.test(want.key) && got.code === `Digit${want.key}`;
|
|
5169
|
+
}
|
|
5170
|
+
/**
|
|
5021
5171
|
* Whether a key is a character that isn't a letter: punctuation, a digit
|
|
5022
5172
|
* or a symbol, which different layouts put under different keys and
|
|
5023
5173
|
* reach with or without Shift. A letter keeps its Shift, which is what
|
|
@@ -5027,14 +5177,15 @@ function isSymbol(key) {
|
|
|
5027
5177
|
return key.length === 1 && key !== " " && key.toLowerCase() === key.toUpperCase();
|
|
5028
5178
|
}
|
|
5029
5179
|
/** The press the person actually made. */
|
|
5030
|
-
function stepFor(key, modifiers) {
|
|
5180
|
+
function stepFor(key, modifiers, code) {
|
|
5031
5181
|
return {
|
|
5032
5182
|
key: normalizeKey(key),
|
|
5033
5183
|
mod: false,
|
|
5034
5184
|
ctrl: modifiers.ctrl,
|
|
5035
5185
|
meta: modifiers.meta,
|
|
5036
5186
|
shift: modifiers.shift,
|
|
5037
|
-
alt: modifiers.alt
|
|
5187
|
+
alt: modifiers.alt,
|
|
5188
|
+
...code === void 0 ? {} : { code }
|
|
5038
5189
|
};
|
|
5039
5190
|
}
|
|
5040
5191
|
/**
|
|
@@ -5407,6 +5558,12 @@ function dragSessionFor(node) {
|
|
|
5407
5558
|
var LayoutNotifier = class {
|
|
5408
5559
|
listeners = /* @__PURE__ */ new Map();
|
|
5409
5560
|
last = /* @__PURE__ */ new Map();
|
|
5561
|
+
/**
|
|
5562
|
+
* Listeners told about the node's place in the layout and its scroll
|
|
5563
|
+
* offset, and not about where it is drawn (`addFlow`).
|
|
5564
|
+
*/
|
|
5565
|
+
flowListeners = /* @__PURE__ */ new Map();
|
|
5566
|
+
flowLast = /* @__PURE__ */ new Map();
|
|
5410
5567
|
/** Registers a listener; the returned function removes it. */
|
|
5411
5568
|
add(node, listener) {
|
|
5412
5569
|
let forNode = this.listeners.get(node);
|
|
@@ -5425,36 +5582,73 @@ var LayoutNotifier = class {
|
|
|
5425
5582
|
}
|
|
5426
5583
|
};
|
|
5427
5584
|
}
|
|
5585
|
+
/**
|
|
5586
|
+
* Registers a listener told when the node's laid-out box or its own
|
|
5587
|
+
* scroll offset changes, and not when a scroll or a transform above it
|
|
5588
|
+
* moves it on screen; the returned function removes it.
|
|
5589
|
+
*
|
|
5590
|
+
* Most listeners are this kind: a size container, a breakpoint, a
|
|
5591
|
+
* layout animation, a scroll position read only the node's own box
|
|
5592
|
+
* and offset. Told of every move on screen, each of them was asked
|
|
5593
|
+
* on every step of a pan over a design tool's board, 2,600 of them,
|
|
5594
|
+
* to find nothing it reads had changed; and each answer was where
|
|
5595
|
+
* the node was drawn, worked out through every ancestor. Its place in
|
|
5596
|
+
* the layout is its record, read at once.
|
|
5597
|
+
*/
|
|
5598
|
+
addFlow(node, listener) {
|
|
5599
|
+
let forNode = this.flowListeners.get(node);
|
|
5600
|
+
if (forNode === void 0) {
|
|
5601
|
+
forNode = /* @__PURE__ */ new Set();
|
|
5602
|
+
this.flowListeners.set(node, forNode);
|
|
5603
|
+
}
|
|
5604
|
+
forNode.add(listener);
|
|
5605
|
+
return () => {
|
|
5606
|
+
const set = this.flowListeners.get(node);
|
|
5607
|
+
if (set === void 0) return;
|
|
5608
|
+
set.delete(listener);
|
|
5609
|
+
if (set.size === 0) {
|
|
5610
|
+
this.flowListeners.delete(node);
|
|
5611
|
+
this.flowLast.delete(node);
|
|
5612
|
+
}
|
|
5613
|
+
};
|
|
5614
|
+
}
|
|
5428
5615
|
get size() {
|
|
5429
|
-
return this.listeners.size;
|
|
5616
|
+
return this.listeners.size + this.flowListeners.size;
|
|
5430
5617
|
}
|
|
5431
5618
|
/** Whether anything is listening, so a frame can skip the walk. */
|
|
5432
5619
|
isEmpty() {
|
|
5433
|
-
return this.listeners.size === 0;
|
|
5620
|
+
return this.listeners.size === 0 && this.flowListeners.size === 0;
|
|
5434
5621
|
}
|
|
5435
5622
|
/**
|
|
5436
5623
|
* Calls the listeners of every watched node whose box or scroll
|
|
5437
5624
|
* offset differs from what it was last told about.
|
|
5438
5625
|
*/
|
|
5439
|
-
notify(read) {
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
const previous = this.last.get(node);
|
|
5443
|
-
if (previous !== void 0 && sameLayout(previous, current)) continue;
|
|
5444
|
-
this.last.set(node, {
|
|
5445
|
-
box: { ...current.box },
|
|
5446
|
-
scrollX: current.scrollX,
|
|
5447
|
-
scrollY: current.scrollY
|
|
5448
|
-
});
|
|
5449
|
-
for (const listener of listeners) listener(current.box);
|
|
5450
|
-
}
|
|
5626
|
+
notify(read, readFlow = read) {
|
|
5627
|
+
notifyEach(this.listeners, this.last, read);
|
|
5628
|
+
notifyEach(this.flowListeners, this.flowLast, readFlow);
|
|
5451
5629
|
}
|
|
5452
5630
|
/** Drops a removed node's listeners, so nothing keeps it alive. */
|
|
5453
5631
|
handleNodeRemoved(node) {
|
|
5454
5632
|
this.listeners.delete(node);
|
|
5455
5633
|
this.last.delete(node);
|
|
5634
|
+
this.flowListeners.delete(node);
|
|
5635
|
+
this.flowLast.delete(node);
|
|
5456
5636
|
}
|
|
5457
5637
|
};
|
|
5638
|
+
/** Calls the listeners of each node whose layout, as `read` gives it, differs from what they were last told. */
|
|
5639
|
+
function notifyEach(listeners, last, read) {
|
|
5640
|
+
for (const [node, forNode] of listeners) {
|
|
5641
|
+
const current = read(node);
|
|
5642
|
+
const previous = last.get(node);
|
|
5643
|
+
if (previous !== void 0 && sameLayout(previous, current)) continue;
|
|
5644
|
+
last.set(node, {
|
|
5645
|
+
box: { ...current.box },
|
|
5646
|
+
scrollX: current.scrollX,
|
|
5647
|
+
scrollY: current.scrollY
|
|
5648
|
+
});
|
|
5649
|
+
for (const listener of forNode) listener(current.box);
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5458
5652
|
function sameLayout(a, b) {
|
|
5459
5653
|
return a.scrollX === b.scrollX && a.scrollY === b.scrollY && sameBox(a.box, b.box);
|
|
5460
5654
|
}
|
|
@@ -5565,8 +5759,9 @@ const kind$4 = defineModifier({
|
|
|
5565
5759
|
name: "focusRing",
|
|
5566
5760
|
attach(host, options) {
|
|
5567
5761
|
const shapes = ringShapes(options);
|
|
5762
|
+
const shown = host.environment(ShownAsKey) === "focused";
|
|
5568
5763
|
const sync = () => {
|
|
5569
|
-
host.decorate(host.isFocused() && host.isFocusVisible() ? shapes : null);
|
|
5764
|
+
host.decorate(shown || host.isFocused() && host.isFocusVisible() ? shapes : null);
|
|
5570
5765
|
};
|
|
5571
5766
|
sync();
|
|
5572
5767
|
host.onFocusChange(sync);
|
|
@@ -5722,7 +5917,7 @@ const kind$3 = defineModifier({
|
|
|
5722
5917
|
name: "autoFocus",
|
|
5723
5918
|
attach(host, options) {
|
|
5724
5919
|
let done = false;
|
|
5725
|
-
host.
|
|
5920
|
+
host.onFlowLayout(() => {
|
|
5726
5921
|
if (done) return;
|
|
5727
5922
|
done = true;
|
|
5728
5923
|
host.focus(options);
|
|
@@ -6048,7 +6243,7 @@ var DropTarget = class {
|
|
|
6048
6243
|
}
|
|
6049
6244
|
attach() {
|
|
6050
6245
|
this.registration.sync();
|
|
6051
|
-
this.host.
|
|
6246
|
+
this.host.onFlowLayout(() => this.registration.sync());
|
|
6052
6247
|
this.host.own(() => {
|
|
6053
6248
|
this.stopScrolling();
|
|
6054
6249
|
this.registration.dispose();
|
|
@@ -6526,7 +6721,7 @@ var Pinchable = class {
|
|
|
6526
6721
|
if (box === null) return;
|
|
6527
6722
|
const clamped = Math.min(Math.max(scale, this.options.minScale ?? 1), this.options.maxScale ?? 8);
|
|
6528
6723
|
const [unrotatedX, unrotatedY] = unapply(anchorX - box.x - this.translateX, anchorY - box.y - this.translateY, this.scale, this.rotation);
|
|
6529
|
-
const [reappliedX, reappliedY] = apply(unrotatedX, unrotatedY, clamped, rotation);
|
|
6724
|
+
const [reappliedX, reappliedY] = apply$1(unrotatedX, unrotatedY, clamped, rotation);
|
|
6530
6725
|
this.scale = clamped;
|
|
6531
6726
|
this.rotation = rotation;
|
|
6532
6727
|
this.translateX = anchorX + panX - box.x - reappliedX;
|
|
@@ -6547,7 +6742,7 @@ var Pinchable = class {
|
|
|
6547
6742
|
}
|
|
6548
6743
|
};
|
|
6549
6744
|
/** Rotates and scales a node-local point, the way the renderers do. */
|
|
6550
|
-
function apply(x, y, scale, degrees) {
|
|
6745
|
+
function apply$1(x, y, scale, degrees) {
|
|
6551
6746
|
const radians = degrees * Math.PI / 180;
|
|
6552
6747
|
const cos = Math.cos(radians);
|
|
6553
6748
|
const sin = Math.sin(radians);
|
|
@@ -6596,7 +6791,7 @@ const shortcutsKind = defineModifier({
|
|
|
6596
6791
|
host.onRoot("keydown", (event) => {
|
|
6597
6792
|
if (event.defaultPrevented) return;
|
|
6598
6793
|
const key = event;
|
|
6599
|
-
if (registry.handleKey(key.key, key.modifiers, key.target)) {
|
|
6794
|
+
if (registry.handleKey(key.key, key.modifiers, key.target, key.code)) {
|
|
6600
6795
|
event.preventDefault();
|
|
6601
6796
|
event.stopPropagation();
|
|
6602
6797
|
}
|
|
@@ -6775,7 +6970,7 @@ var LayoutAnimation = class {
|
|
|
6775
6970
|
this.baseScaleY = numberOr(declared.scaleY, 1);
|
|
6776
6971
|
this.baseRotation = numberOr(declared.rotation, 0);
|
|
6777
6972
|
}
|
|
6778
|
-
this.host.
|
|
6973
|
+
this.host.onFlowLayout(() => this.handleLayout());
|
|
6779
6974
|
}
|
|
6780
6975
|
makeCell(read, write) {
|
|
6781
6976
|
return {
|
|
@@ -6852,7 +7047,7 @@ var LayoutAnimation = class {
|
|
|
6852
7047
|
}
|
|
6853
7048
|
const named = this.options.spring ?? "snappy";
|
|
6854
7049
|
this.host.spring(cell, 0, {
|
|
6855
|
-
spring:
|
|
7050
|
+
spring: springIn(motionOf(this.host.node), named),
|
|
6856
7051
|
restDelta: .5
|
|
6857
7052
|
});
|
|
6858
7053
|
}
|
|
@@ -6861,6 +7056,1340 @@ function numberOr(value, fallback) {
|
|
|
6861
7056
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
6862
7057
|
}
|
|
6863
7058
|
//#endregion
|
|
7059
|
+
//#region src/rendering/svg/SvgXml.ts
|
|
7060
|
+
/**
|
|
7061
|
+
* The document's outermost element, or null when there is none.
|
|
7062
|
+
*/
|
|
7063
|
+
function parseSvgXml(source) {
|
|
7064
|
+
const stack = [];
|
|
7065
|
+
let root = null;
|
|
7066
|
+
let index = 0;
|
|
7067
|
+
const close = () => {
|
|
7068
|
+
const element = stack.pop();
|
|
7069
|
+
const parent = stack.at(-1);
|
|
7070
|
+
if (parent === void 0) root ??= element;
|
|
7071
|
+
else parent.children.push(element);
|
|
7072
|
+
};
|
|
7073
|
+
while (index < source.length) {
|
|
7074
|
+
const open = source.indexOf("<", index);
|
|
7075
|
+
if (open === -1) break;
|
|
7076
|
+
if (open > index && stack.length > 0) stack.at(-1).text += decodeEntities(source.slice(index, open));
|
|
7077
|
+
if (source.startsWith("<!--", open)) {
|
|
7078
|
+
const end = source.indexOf("-->", open + 4);
|
|
7079
|
+
index = end === -1 ? source.length : end + 3;
|
|
7080
|
+
continue;
|
|
7081
|
+
}
|
|
7082
|
+
if (source.startsWith("<![CDATA[", open)) {
|
|
7083
|
+
const end = source.indexOf("]]>", open + 9);
|
|
7084
|
+
const text = source.slice(open + 9, end === -1 ? source.length : end);
|
|
7085
|
+
if (stack.length > 0) stack.at(-1).text += text;
|
|
7086
|
+
index = end === -1 ? source.length : end + 3;
|
|
7087
|
+
continue;
|
|
7088
|
+
}
|
|
7089
|
+
if (source[open + 1] === "?" || source[open + 1] === "!") {
|
|
7090
|
+
index = skipDeclaration(source, open);
|
|
7091
|
+
continue;
|
|
7092
|
+
}
|
|
7093
|
+
if (source[open + 1] === "/") {
|
|
7094
|
+
const end = source.indexOf(">", open);
|
|
7095
|
+
const name = source.slice(open + 2, end === -1 ? source.length : end).trim();
|
|
7096
|
+
const at = stack.map((element) => element.name).lastIndexOf(name);
|
|
7097
|
+
if (at !== -1) while (stack.length > at) close();
|
|
7098
|
+
index = end === -1 ? source.length : end + 1;
|
|
7099
|
+
continue;
|
|
7100
|
+
}
|
|
7101
|
+
const tag = readTag(source, open);
|
|
7102
|
+
stack.push({
|
|
7103
|
+
name: tag.name,
|
|
7104
|
+
attributes: tag.attributes,
|
|
7105
|
+
children: [],
|
|
7106
|
+
text: ""
|
|
7107
|
+
});
|
|
7108
|
+
if (tag.selfClosing) close();
|
|
7109
|
+
index = tag.end;
|
|
7110
|
+
}
|
|
7111
|
+
while (stack.length > 0) close();
|
|
7112
|
+
return root;
|
|
7113
|
+
}
|
|
7114
|
+
/** Past a `<?…?>` or a `<!…>`, minding a doctype's bracketed subset. */
|
|
7115
|
+
function skipDeclaration(source, open) {
|
|
7116
|
+
let depth = 0;
|
|
7117
|
+
for (let i = open + 1; i < source.length; i++) {
|
|
7118
|
+
const char = source[i];
|
|
7119
|
+
if (char === "[") depth++;
|
|
7120
|
+
else if (char === "]") depth--;
|
|
7121
|
+
else if (char === ">" && depth <= 0) return i + 1;
|
|
7122
|
+
}
|
|
7123
|
+
return source.length;
|
|
7124
|
+
}
|
|
7125
|
+
function readTag(source, open) {
|
|
7126
|
+
let i = open + 1;
|
|
7127
|
+
const nameStart = i;
|
|
7128
|
+
while (i < source.length && !/[\s/>]/.test(source[i])) i++;
|
|
7129
|
+
const name = source.slice(nameStart, i);
|
|
7130
|
+
const attributes = {};
|
|
7131
|
+
for (;;) {
|
|
7132
|
+
while (i < source.length && /\s/.test(source[i])) i++;
|
|
7133
|
+
if (i >= source.length) return {
|
|
7134
|
+
name,
|
|
7135
|
+
attributes,
|
|
7136
|
+
selfClosing: false,
|
|
7137
|
+
end: i
|
|
7138
|
+
};
|
|
7139
|
+
if (source[i] === ">") return {
|
|
7140
|
+
name,
|
|
7141
|
+
attributes,
|
|
7142
|
+
selfClosing: false,
|
|
7143
|
+
end: i + 1
|
|
7144
|
+
};
|
|
7145
|
+
if (source[i] === "/") {
|
|
7146
|
+
const end = source.indexOf(">", i);
|
|
7147
|
+
return {
|
|
7148
|
+
name,
|
|
7149
|
+
attributes,
|
|
7150
|
+
selfClosing: true,
|
|
7151
|
+
end: end === -1 ? source.length : end + 1
|
|
7152
|
+
};
|
|
7153
|
+
}
|
|
7154
|
+
const keyStart = i;
|
|
7155
|
+
while (i < source.length && !/[\s=/>]/.test(source[i])) i++;
|
|
7156
|
+
const key = source.slice(keyStart, i);
|
|
7157
|
+
while (i < source.length && /\s/.test(source[i])) i++;
|
|
7158
|
+
if (source[i] !== "=") {
|
|
7159
|
+
if (key !== "") attributes[key] = "";
|
|
7160
|
+
else i++;
|
|
7161
|
+
continue;
|
|
7162
|
+
}
|
|
7163
|
+
i++;
|
|
7164
|
+
while (i < source.length && /\s/.test(source[i])) i++;
|
|
7165
|
+
const quote = source[i];
|
|
7166
|
+
if (quote === "\"" || quote === "'") {
|
|
7167
|
+
const end = source.indexOf(quote, i + 1);
|
|
7168
|
+
attributes[key] = decodeEntities(source.slice(i + 1, end === -1 ? source.length : end));
|
|
7169
|
+
i = end === -1 ? source.length : end + 1;
|
|
7170
|
+
} else {
|
|
7171
|
+
const valueStart = i;
|
|
7172
|
+
while (i < source.length && !/[\s>]/.test(source[i])) i++;
|
|
7173
|
+
attributes[key] = decodeEntities(source.slice(valueStart, i));
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
const NAMED_ENTITIES = {
|
|
7178
|
+
amp: "&",
|
|
7179
|
+
lt: "<",
|
|
7180
|
+
gt: ">",
|
|
7181
|
+
quot: "\"",
|
|
7182
|
+
apos: "'"
|
|
7183
|
+
};
|
|
7184
|
+
function decodeEntities(text) {
|
|
7185
|
+
if (!text.includes("&")) return text;
|
|
7186
|
+
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, entity) => {
|
|
7187
|
+
if (entity[0] === "#") {
|
|
7188
|
+
const code = entity[1] === "x" || entity[1] === "X" ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
7189
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
|
|
7190
|
+
}
|
|
7191
|
+
return NAMED_ENTITIES[entity] ?? whole;
|
|
7192
|
+
});
|
|
7193
|
+
}
|
|
7194
|
+
//#endregion
|
|
7195
|
+
//#region src/rendering/svg/SvgDocument.ts
|
|
7196
|
+
/** Elements drawn as nothing, because nothing here draws them yet. */
|
|
7197
|
+
const UNSUPPORTED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
7198
|
+
"text",
|
|
7199
|
+
"image",
|
|
7200
|
+
"foreignObject",
|
|
7201
|
+
"pattern",
|
|
7202
|
+
"marker"
|
|
7203
|
+
]);
|
|
7204
|
+
/** Attributes that point at one of those. */
|
|
7205
|
+
const UNSUPPORTED_ATTRIBUTES = {
|
|
7206
|
+
"marker-start": "marker",
|
|
7207
|
+
"marker-mid": "marker",
|
|
7208
|
+
"marker-end": "marker"
|
|
7209
|
+
};
|
|
7210
|
+
/**
|
|
7211
|
+
* Reads an SVG file. Throws when it holds no `<svg>` element, since
|
|
7212
|
+
* there is then nothing to draw and the caller should fall back.
|
|
7213
|
+
*/
|
|
7214
|
+
function parseSvg(source) {
|
|
7215
|
+
const root = parseSvgXml(source);
|
|
7216
|
+
if (root === null || localName(root.name) !== "svg") throw new Error("This is not an SVG file: it has no <svg> element.");
|
|
7217
|
+
const ids = /* @__PURE__ */ new Map();
|
|
7218
|
+
const rules = [];
|
|
7219
|
+
const unsupported = /* @__PURE__ */ new Set();
|
|
7220
|
+
const visit = (element) => {
|
|
7221
|
+
const name = localName(element.name);
|
|
7222
|
+
const id = element.attributes.id;
|
|
7223
|
+
if (id !== void 0 && !ids.has(id)) ids.set(id, element);
|
|
7224
|
+
if (name === "style") rules.push(...parseStyleSheet(element.text));
|
|
7225
|
+
if (UNSUPPORTED_ELEMENTS.has(name)) unsupported.add(name);
|
|
7226
|
+
if (name === "filter" && dropShadowOf(element) === void 0) unsupported.add("filter");
|
|
7227
|
+
for (const [attribute, feature] of Object.entries(UNSUPPORTED_ATTRIBUTES)) if (element.attributes[attribute] !== void 0 && element.attributes[attribute] !== "none") unsupported.add(feature);
|
|
7228
|
+
for (const attribute of ["mask", "filter"]) {
|
|
7229
|
+
const value = element.attributes[attribute];
|
|
7230
|
+
if (value !== void 0 && value !== "none") references.push([attribute, value]);
|
|
7231
|
+
}
|
|
7232
|
+
element.children.forEach(visit);
|
|
7233
|
+
};
|
|
7234
|
+
const references = [];
|
|
7235
|
+
visit(root);
|
|
7236
|
+
for (const [attribute, value] of references) {
|
|
7237
|
+
const id = referencedId(value);
|
|
7238
|
+
const target = id === void 0 ? void 0 : ids.get(id);
|
|
7239
|
+
if (target === void 0 || localName(target.name) !== attribute) unsupported.add(attribute);
|
|
7240
|
+
}
|
|
7241
|
+
const viewBox = parseViewBox(root.attributes.viewBox);
|
|
7242
|
+
const width = absoluteLength(root.attributes.width) ?? viewBox?.width ?? 300;
|
|
7243
|
+
const height = absoluteLength(root.attributes.height) ?? viewBox?.height ?? 150;
|
|
7244
|
+
return {
|
|
7245
|
+
root,
|
|
7246
|
+
width,
|
|
7247
|
+
height,
|
|
7248
|
+
viewBox: viewBox ?? {
|
|
7249
|
+
x: 0,
|
|
7250
|
+
y: 0,
|
|
7251
|
+
width,
|
|
7252
|
+
height
|
|
7253
|
+
},
|
|
7254
|
+
ids,
|
|
7255
|
+
rules,
|
|
7256
|
+
unsupported: [...unsupported]
|
|
7257
|
+
};
|
|
7258
|
+
}
|
|
7259
|
+
/** The id a value of the form `url(#id)` names, or undefined for anything else. */
|
|
7260
|
+
function referencedId(value) {
|
|
7261
|
+
return /^\s*url\(\s*['"]?#([^'")]+)['"]?\s*\)\s*$/.exec(value)?.[1];
|
|
7262
|
+
}
|
|
7263
|
+
/**
|
|
7264
|
+
* The `<feDropShadow>` a `<filter>` is, when it is exactly one drop
|
|
7265
|
+
* shadow of the element itself; undefined for any other filter, which
|
|
7266
|
+
* is not drawn.
|
|
7267
|
+
*/
|
|
7268
|
+
function dropShadowOf(filter) {
|
|
7269
|
+
const primitives = filter.children.filter((child) => !NOT_PRIMITIVES.has(localName(child.name)));
|
|
7270
|
+
const [primitive] = primitives;
|
|
7271
|
+
if (primitives.length !== 1 || localName(primitive.name) !== "feDropShadow") return;
|
|
7272
|
+
const input = primitive.attributes.in;
|
|
7273
|
+
return input === void 0 || input === "SourceGraphic" ? primitive : void 0;
|
|
7274
|
+
}
|
|
7275
|
+
/** What a `<filter>` may hold that is not a step of it. */
|
|
7276
|
+
const NOT_PRIMITIVES = /* @__PURE__ */ new Set([
|
|
7277
|
+
"title",
|
|
7278
|
+
"desc",
|
|
7279
|
+
"metadata"
|
|
7280
|
+
]);
|
|
7281
|
+
/** An element's name without its namespace prefix: `svg:path` is `path`. */
|
|
7282
|
+
function localName(name) {
|
|
7283
|
+
const colon = name.indexOf(":");
|
|
7284
|
+
return colon === -1 ? name : name.slice(colon + 1);
|
|
7285
|
+
}
|
|
7286
|
+
/** A `viewBox`, or undefined when it is absent or describes nothing. */
|
|
7287
|
+
function parseViewBox(value) {
|
|
7288
|
+
if (value === void 0) return;
|
|
7289
|
+
const [x, y, width, height] = numbersIn(value);
|
|
7290
|
+
if (x === void 0 || y === void 0 || width === void 0 || height === void 0 || width <= 0 || height <= 0) return;
|
|
7291
|
+
return {
|
|
7292
|
+
x,
|
|
7293
|
+
y,
|
|
7294
|
+
width,
|
|
7295
|
+
height
|
|
7296
|
+
};
|
|
7297
|
+
}
|
|
7298
|
+
/** Every number in a list separated by commas and spaces. */
|
|
7299
|
+
function numbersIn(value) {
|
|
7300
|
+
return (value.match(/[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi) ?? []).map(Number);
|
|
7301
|
+
}
|
|
7302
|
+
/** A length in pixels, or undefined for a percentage or anything that is not one. */
|
|
7303
|
+
function absoluteLength(value) {
|
|
7304
|
+
if (value === void 0 || value.trim().endsWith("%")) return;
|
|
7305
|
+
const number = parseFloat(value);
|
|
7306
|
+
if (!Number.isFinite(number) || number <= 0) return;
|
|
7307
|
+
const unit = value.trim().replace(/^[-+\d.e]+/i, "");
|
|
7308
|
+
return number * (UNIT_PIXELS[unit] ?? 1);
|
|
7309
|
+
}
|
|
7310
|
+
/** CSS's absolute units in pixels, 96 to the inch. */
|
|
7311
|
+
const UNIT_PIXELS = {
|
|
7312
|
+
"": 1,
|
|
7313
|
+
px: 1,
|
|
7314
|
+
pt: 96 / 72,
|
|
7315
|
+
pc: 16,
|
|
7316
|
+
in: 96,
|
|
7317
|
+
cm: 96 / 2.54,
|
|
7318
|
+
mm: 96 / 25.4
|
|
7319
|
+
};
|
|
7320
|
+
/** The declarations of an inline `style` attribute or a rule's block. */
|
|
7321
|
+
function parseDeclarations(text) {
|
|
7322
|
+
const declarations = {};
|
|
7323
|
+
for (const part of text.split(";")) {
|
|
7324
|
+
const colon = part.indexOf(":");
|
|
7325
|
+
if (colon === -1) continue;
|
|
7326
|
+
const property = part.slice(0, colon).trim().toLowerCase();
|
|
7327
|
+
const value = part.slice(colon + 1).replace(/!important/i, "").trim();
|
|
7328
|
+
if (property !== "" && value !== "") declarations[property] = value;
|
|
7329
|
+
}
|
|
7330
|
+
return declarations;
|
|
7331
|
+
}
|
|
7332
|
+
/**
|
|
7333
|
+
* A `<style>`'s rules. Only compound selectors are understood, which is
|
|
7334
|
+
* what an exporter writes (`.st0{fill:#fff}`); a rule whose selector
|
|
7335
|
+
* has a combinator or a pseudo-class is skipped rather than misapplied.
|
|
7336
|
+
*/
|
|
7337
|
+
function parseStyleSheet(text) {
|
|
7338
|
+
const rules = [];
|
|
7339
|
+
const source = text.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
7340
|
+
for (const match of source.matchAll(/([^{}]+)\{([^}]*)\}/g)) {
|
|
7341
|
+
const declarations = parseDeclarations(match[2]);
|
|
7342
|
+
for (const raw of match[1].split(",")) {
|
|
7343
|
+
const selector = parseSelector(raw.trim());
|
|
7344
|
+
if (selector !== null) {
|
|
7345
|
+
const specificity = (selector.id === void 0 ? 0 : 100) + selector.classes.length * 10 + (selector.name === void 0 ? 0 : 1);
|
|
7346
|
+
rules.push({
|
|
7347
|
+
selector,
|
|
7348
|
+
declarations,
|
|
7349
|
+
specificity
|
|
7350
|
+
});
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
return rules;
|
|
7355
|
+
}
|
|
7356
|
+
function parseSelector(text) {
|
|
7357
|
+
if (text === "" || /[\s>+~:[\]*]/.test(text)) return null;
|
|
7358
|
+
const parts = text.match(/[.#]?[^.#]+/g) ?? [];
|
|
7359
|
+
let name;
|
|
7360
|
+
let id;
|
|
7361
|
+
const classes = [];
|
|
7362
|
+
for (const part of parts) if (part.startsWith("#")) id = part.slice(1);
|
|
7363
|
+
else if (part.startsWith(".")) classes.push(part.slice(1));
|
|
7364
|
+
else name = part;
|
|
7365
|
+
return {
|
|
7366
|
+
...name === void 0 ? {} : { name },
|
|
7367
|
+
...id === void 0 ? {} : { id },
|
|
7368
|
+
classes
|
|
7369
|
+
};
|
|
7370
|
+
}
|
|
7371
|
+
/** Whether a rule's selector matches an element. */
|
|
7372
|
+
function selectorMatches(selector, element) {
|
|
7373
|
+
if (selector.name !== void 0 && localName(element.name) !== selector.name) return false;
|
|
7374
|
+
if (selector.id !== void 0 && element.attributes.id !== selector.id) return false;
|
|
7375
|
+
if (selector.classes.length > 0) {
|
|
7376
|
+
const classes = (element.attributes.class ?? "").split(/\s+/);
|
|
7377
|
+
return selector.classes.every((name) => classes.includes(name));
|
|
7378
|
+
}
|
|
7379
|
+
return true;
|
|
7380
|
+
}
|
|
7381
|
+
//#endregion
|
|
7382
|
+
//#region src/rendering/svg/SvgDraw.ts
|
|
7383
|
+
/**
|
|
7384
|
+
* Draws a read SVG file into a box, through the surface every painted
|
|
7385
|
+
* node draws through, so it is vectors on either renderer and is drawn
|
|
7386
|
+
* again at whatever size the box is laid out at.
|
|
7387
|
+
*/
|
|
7388
|
+
function drawSvg(document, surface, box, options = {}) {
|
|
7389
|
+
const { viewBox } = document;
|
|
7390
|
+
const fit = options.fit ?? "contain";
|
|
7391
|
+
let scaleX = box.width / viewBox.width;
|
|
7392
|
+
let scaleY = box.height / viewBox.height;
|
|
7393
|
+
if (fit !== "fill") {
|
|
7394
|
+
const contain = Math.min(scaleX, scaleY);
|
|
7395
|
+
const natural = document.width / viewBox.width;
|
|
7396
|
+
const uniform = fit === "cover" ? Math.max(scaleX, scaleY) : fit === "none" ? natural : fit === "scale-down" ? Math.min(contain, natural) : contain;
|
|
7397
|
+
scaleX = uniform;
|
|
7398
|
+
scaleY = uniform;
|
|
7399
|
+
}
|
|
7400
|
+
surface.save();
|
|
7401
|
+
if (fit === "cover" || fit === "none") {
|
|
7402
|
+
surface.beginPath();
|
|
7403
|
+
surface.rect(0, 0, box.width, box.height);
|
|
7404
|
+
surface.clip();
|
|
7405
|
+
}
|
|
7406
|
+
surface.translate((box.width - viewBox.width * scaleX) / 2, (box.height - viewBox.height * scaleY) / 2);
|
|
7407
|
+
surface.scale(scaleX, scaleY);
|
|
7408
|
+
surface.translate(-viewBox.x, -viewBox.y);
|
|
7409
|
+
const context = {
|
|
7410
|
+
document,
|
|
7411
|
+
surface,
|
|
7412
|
+
currentColor: options.color ?? "currentColor",
|
|
7413
|
+
using: /* @__PURE__ */ new Set()
|
|
7414
|
+
};
|
|
7415
|
+
drawChildren(document.root, cascade(INITIAL_STYLE, declarationsOf(document.root, document), context), viewBox, context);
|
|
7416
|
+
surface.restore();
|
|
7417
|
+
}
|
|
7418
|
+
const paints = /* @__PURE__ */ new WeakMap();
|
|
7419
|
+
/**
|
|
7420
|
+
* A read SVG file as a node's `paint`: drawn to fill the node, and the
|
|
7421
|
+
* file's own size as the node's when the layout gives it none.
|
|
7422
|
+
*
|
|
7423
|
+
* The same object for the same file and options, so a node given it
|
|
7424
|
+
* again is not drawn again; see `paintValuesEqual`.
|
|
7425
|
+
*/
|
|
7426
|
+
function svgPaint(document, options = {}) {
|
|
7427
|
+
let byOptions = paints.get(document);
|
|
7428
|
+
if (byOptions === void 0) {
|
|
7429
|
+
byOptions = /* @__PURE__ */ new Map();
|
|
7430
|
+
paints.set(document, byOptions);
|
|
7431
|
+
}
|
|
7432
|
+
const key = JSON.stringify([options.fit ?? "contain", options.color ?? "currentColor"]);
|
|
7433
|
+
let paint = byOptions.get(key);
|
|
7434
|
+
if (paint === void 0) {
|
|
7435
|
+
paint = {
|
|
7436
|
+
draw: (surface, box) => drawSvg(document, surface, box, options),
|
|
7437
|
+
inputs: [document, key],
|
|
7438
|
+
intrinsicWidth: document.width,
|
|
7439
|
+
intrinsicHeight: document.height
|
|
7440
|
+
};
|
|
7441
|
+
byOptions.set(key, paint);
|
|
7442
|
+
}
|
|
7443
|
+
return paint;
|
|
7444
|
+
}
|
|
7445
|
+
const BLACK = {
|
|
7446
|
+
kind: "color",
|
|
7447
|
+
color: {
|
|
7448
|
+
r: 0,
|
|
7449
|
+
g: 0,
|
|
7450
|
+
b: 0,
|
|
7451
|
+
a: 1
|
|
7452
|
+
}
|
|
7453
|
+
};
|
|
7454
|
+
const NONE = { kind: "none" };
|
|
7455
|
+
const INITIAL_STYLE = {
|
|
7456
|
+
fill: BLACK,
|
|
7457
|
+
fillOpacity: 1,
|
|
7458
|
+
fillRule: "nonzero",
|
|
7459
|
+
stroke: NONE,
|
|
7460
|
+
strokeOpacity: 1,
|
|
7461
|
+
strokeWidth: 1,
|
|
7462
|
+
lineCap: "butt",
|
|
7463
|
+
lineJoin: "miter",
|
|
7464
|
+
miterLimit: 4,
|
|
7465
|
+
dash: [],
|
|
7466
|
+
dashOffset: 0,
|
|
7467
|
+
clipRule: "nonzero",
|
|
7468
|
+
color: null,
|
|
7469
|
+
visible: true
|
|
7470
|
+
};
|
|
7471
|
+
/** Drawn only when something points at them, or not at all. */
|
|
7472
|
+
const NOT_DRAWN = /* @__PURE__ */ new Set([
|
|
7473
|
+
"defs",
|
|
7474
|
+
"clipPath",
|
|
7475
|
+
"linearGradient",
|
|
7476
|
+
"radialGradient",
|
|
7477
|
+
"symbol",
|
|
7478
|
+
"style",
|
|
7479
|
+
"title",
|
|
7480
|
+
"desc",
|
|
7481
|
+
"metadata",
|
|
7482
|
+
"mask",
|
|
7483
|
+
"filter",
|
|
7484
|
+
"pattern",
|
|
7485
|
+
"marker",
|
|
7486
|
+
"text",
|
|
7487
|
+
"image",
|
|
7488
|
+
"foreignObject"
|
|
7489
|
+
]);
|
|
7490
|
+
function drawChildren(element, style, viewport, context) {
|
|
7491
|
+
for (const child of element.children) drawElement(child, style, viewport, context);
|
|
7492
|
+
}
|
|
7493
|
+
function drawElement(element, inherited, viewport, context) {
|
|
7494
|
+
const name = localName(element.name);
|
|
7495
|
+
if (NOT_DRAWN.has(name)) return;
|
|
7496
|
+
const declared = declarationsOf(element, context.document);
|
|
7497
|
+
if (declared.display === "none") return;
|
|
7498
|
+
const style = cascade(inherited, declared, context);
|
|
7499
|
+
const { surface } = context;
|
|
7500
|
+
surface.save();
|
|
7501
|
+
if (declared.transform !== void 0) applyTransform$1(surface, declared.transform);
|
|
7502
|
+
const opacity = unit(declared.opacity);
|
|
7503
|
+
if (opacity < 1) surface.alpha(opacity);
|
|
7504
|
+
if (declared["clip-path"] !== void 0) clipTo(declared["clip-path"], viewport, context);
|
|
7505
|
+
const content = (at) => {
|
|
7506
|
+
switch (name) {
|
|
7507
|
+
case "g":
|
|
7508
|
+
case "a":
|
|
7509
|
+
case "switch":
|
|
7510
|
+
drawChildren(element, style, viewport, at);
|
|
7511
|
+
break;
|
|
7512
|
+
case "svg":
|
|
7513
|
+
drawNested(element, style, viewport, at);
|
|
7514
|
+
break;
|
|
7515
|
+
case "use":
|
|
7516
|
+
drawUse(element, style, viewport, at);
|
|
7517
|
+
break;
|
|
7518
|
+
default: {
|
|
7519
|
+
const shape = shapeOf(element, name, viewport);
|
|
7520
|
+
if (shape !== null && style.visible) paintShape(shape, style, viewport, at);
|
|
7521
|
+
}
|
|
7522
|
+
}
|
|
7523
|
+
};
|
|
7524
|
+
const filtered = () => {
|
|
7525
|
+
const filter = declared.filter === void 0 ? void 0 : referencedElement(declared.filter, "filter", context);
|
|
7526
|
+
const shadow = filter === void 0 ? void 0 : dropShadowOf(filter);
|
|
7527
|
+
if (filter === void 0 || shadow === void 0) content(context);
|
|
7528
|
+
else drawWithShadow(element, filter, shadow, style, viewport, context, content);
|
|
7529
|
+
};
|
|
7530
|
+
const mask = declared.mask === void 0 ? void 0 : referencedElement(declared.mask, "mask", context);
|
|
7531
|
+
if (mask === void 0 || context.using.has(mask)) filtered();
|
|
7532
|
+
else drawMasked(element, mask, viewport, context, filtered);
|
|
7533
|
+
surface.restore();
|
|
7534
|
+
}
|
|
7535
|
+
/** The element of a kind that a `url(#id)` value names, if it is one. */
|
|
7536
|
+
function referencedElement(value, kind, context) {
|
|
7537
|
+
const id = referencedId(value);
|
|
7538
|
+
const target = id === void 0 ? void 0 : context.document.ids.get(id);
|
|
7539
|
+
return target !== void 0 && localName(target.name) === kind ? target : void 0;
|
|
7540
|
+
}
|
|
7541
|
+
/**
|
|
7542
|
+
* Draws an element through a `<mask>`: its drawing is a layer, and the
|
|
7543
|
+
* mask's children, drawn into the layer's mask, say how much of it
|
|
7544
|
+
* shows. By their luminance, or by their alpha for `mask-type: alpha`.
|
|
7545
|
+
*
|
|
7546
|
+
* The mask's region and its content are placed in the element's user
|
|
7547
|
+
* space or in fractions of its bounding box, as `maskUnits` and
|
|
7548
|
+
* `maskContentUnits` say; the region is cut to as the layer is.
|
|
7549
|
+
*/
|
|
7550
|
+
function drawMasked(element, mask, viewport, context, draw) {
|
|
7551
|
+
const { surface } = context;
|
|
7552
|
+
const attributes = mask.attributes;
|
|
7553
|
+
const declared = declarationsOf(mask, context.document);
|
|
7554
|
+
const bounds = attributes.maskUnits === "userSpaceOnUse" && attributes.maskContentUnits !== "objectBoundingBox" ? void 0 : elementBounds(element, viewport, context);
|
|
7555
|
+
const region = regionOf(attributes, attributes.maskUnits === "userSpaceOnUse", bounds, viewport);
|
|
7556
|
+
surface.beginMaskedLayer(region.x, region.y, region.width, region.height);
|
|
7557
|
+
draw();
|
|
7558
|
+
surface.layerMask(declared["mask-type"] === "alpha" ? "alpha" : "luminance");
|
|
7559
|
+
if (attributes.maskContentUnits === "objectBoundingBox") surface.transform(bounds.width, 0, 0, bounds.height, bounds.x, bounds.y);
|
|
7560
|
+
const root = cascade(INITIAL_STYLE, declarationsOf(context.document.root, context.document), context);
|
|
7561
|
+
context.using.add(mask);
|
|
7562
|
+
drawChildren(mask, cascade(root, declared, context), viewport, {
|
|
7563
|
+
...context,
|
|
7564
|
+
flood: void 0
|
|
7565
|
+
});
|
|
7566
|
+
context.using.delete(mask);
|
|
7567
|
+
surface.endMaskedLayer();
|
|
7568
|
+
}
|
|
7569
|
+
/**
|
|
7570
|
+
* Draws an element with the drop shadow its filter is, and then the
|
|
7571
|
+
* element over it, both cut to the filter's region as a filter's result
|
|
7572
|
+
* is.
|
|
7573
|
+
*
|
|
7574
|
+
* The shadow is the element again, every paint of it the flood colour,
|
|
7575
|
+
* moved by `dx` and `dy` and blurred by `stdDeviation`: its alpha in
|
|
7576
|
+
* the flood colour, which is what `<feDropShadow>` makes. It is drawn
|
|
7577
|
+
* as a layer composited at the flood opacity, so where two of a group's
|
|
7578
|
+
* shapes overlap their shadow is not darker; a host without layers
|
|
7579
|
+
* draws it shape by shape, which differs only there.
|
|
7580
|
+
*/
|
|
7581
|
+
function drawWithShadow(element, filter, shadow, style, viewport, context, draw) {
|
|
7582
|
+
const { surface } = context;
|
|
7583
|
+
const attributes = filter.attributes;
|
|
7584
|
+
const userSpace = attributes.filterUnits === "userSpaceOnUse";
|
|
7585
|
+
const primitivesInBox = attributes.primitiveUnits === "objectBoundingBox";
|
|
7586
|
+
const bounds = userSpace && !primitivesInBox ? void 0 : elementBounds(element, viewport, context);
|
|
7587
|
+
const region = regionOf(attributes, userSpace, bounds, viewport);
|
|
7588
|
+
if (!(region.width > 0) || !(region.height > 0)) return;
|
|
7589
|
+
const declared = declarationsOf(shadow, context.document);
|
|
7590
|
+
const number = (value, fallback) => {
|
|
7591
|
+
const parsed = value === void 0 ? NaN : parseFloat(value);
|
|
7592
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
7593
|
+
};
|
|
7594
|
+
let dx = number(declared.dx, 2);
|
|
7595
|
+
let dy = number(declared.dy, 2);
|
|
7596
|
+
const [sx, sy] = numbersIn(declared.stdDeviation ?? "2");
|
|
7597
|
+
let deviation = Math.max(0, sy === void 0 ? sx ?? 2 : ((sx ?? 0) + sy) / 2);
|
|
7598
|
+
if (primitivesInBox) {
|
|
7599
|
+
dx *= bounds.width;
|
|
7600
|
+
dy *= bounds.height;
|
|
7601
|
+
deviation *= Math.sqrt((bounds.width ** 2 + bounds.height ** 2) / 2);
|
|
7602
|
+
}
|
|
7603
|
+
const color = colorValue(declared["flood-color"] ?? "black", style, context) ?? BLACK_COLOR;
|
|
7604
|
+
const opacity = unit(declared["flood-opacity"]);
|
|
7605
|
+
surface.save();
|
|
7606
|
+
surface.beginPath();
|
|
7607
|
+
surface.rect(region.x, region.y, region.width, region.height);
|
|
7608
|
+
surface.clip();
|
|
7609
|
+
if (opacity > 0) {
|
|
7610
|
+
surface.save();
|
|
7611
|
+
if (opacity < 1) surface.alpha(opacity);
|
|
7612
|
+
surface.beginMaskedLayer(region.x, region.y, region.width, region.height);
|
|
7613
|
+
surface.translate(dx, dy);
|
|
7614
|
+
if (deviation > 0) surface.blur(deviation);
|
|
7615
|
+
draw({
|
|
7616
|
+
...context,
|
|
7617
|
+
flood: color
|
|
7618
|
+
});
|
|
7619
|
+
surface.endMaskedLayer();
|
|
7620
|
+
surface.restore();
|
|
7621
|
+
}
|
|
7622
|
+
draw(context);
|
|
7623
|
+
surface.restore();
|
|
7624
|
+
}
|
|
7625
|
+
/**
|
|
7626
|
+
* A mask's or a filter's region: its `x`, `y`, `width` and `height` in
|
|
7627
|
+
* user space, or in fractions of the element's bounding box, which is
|
|
7628
|
+
* where they are by default and where they default to a tenth beyond
|
|
7629
|
+
* it on every side.
|
|
7630
|
+
*/
|
|
7631
|
+
function regionOf(attributes, userSpace, bounds, viewport) {
|
|
7632
|
+
if (userSpace) return {
|
|
7633
|
+
x: length(attributes.x ?? "-10%", viewport.width),
|
|
7634
|
+
y: length(attributes.y ?? "-10%", viewport.height),
|
|
7635
|
+
width: length(attributes.width ?? "120%", viewport.width),
|
|
7636
|
+
height: length(attributes.height ?? "120%", viewport.height)
|
|
7637
|
+
};
|
|
7638
|
+
const box = bounds;
|
|
7639
|
+
return {
|
|
7640
|
+
x: box.x + fraction(attributes.x ?? "-10%") * box.width,
|
|
7641
|
+
y: box.y + fraction(attributes.y ?? "-10%") * box.height,
|
|
7642
|
+
width: fraction(attributes.width ?? "120%") * box.width,
|
|
7643
|
+
height: fraction(attributes.height ?? "120%") * box.height
|
|
7644
|
+
};
|
|
7645
|
+
}
|
|
7646
|
+
/** A fraction written as a number or a percentage: `0.5` and `50%` alike. */
|
|
7647
|
+
function fraction(value) {
|
|
7648
|
+
const trimmed = value.trim();
|
|
7649
|
+
const parsed = trimmed.endsWith("%") ? parseFloat(trimmed) / 100 : parseFloat(trimmed);
|
|
7650
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7651
|
+
}
|
|
7652
|
+
/**
|
|
7653
|
+
* An element's bounding box in its own user space, as `getBBox` gives
|
|
7654
|
+
* it: its geometry and its children's through their transforms, without
|
|
7655
|
+
* a stroke and without its own transform.
|
|
7656
|
+
*/
|
|
7657
|
+
function elementBounds(element, viewport, context) {
|
|
7658
|
+
const bounds = new BoundsSurface();
|
|
7659
|
+
addBounds(element, IDENTITY$1, viewport, context, bounds, /* @__PURE__ */ new Set());
|
|
7660
|
+
return bounds.bounds();
|
|
7661
|
+
}
|
|
7662
|
+
function addBounds(element, matrix, viewport, context, bounds, using) {
|
|
7663
|
+
const name = localName(element.name);
|
|
7664
|
+
const into = new TransformedSurface(bounds, matrix);
|
|
7665
|
+
/** A child, or a used element, under its own transform. */
|
|
7666
|
+
const add = (child, at) => {
|
|
7667
|
+
const declared = declarationsOf(child, context.document);
|
|
7668
|
+
if (declared.display === "none") return;
|
|
7669
|
+
addBounds(child, declared.transform === void 0 ? at : multiply(at, parseTransform$1(declared.transform)), viewport, context, bounds, using);
|
|
7670
|
+
};
|
|
7671
|
+
switch (name) {
|
|
7672
|
+
case "g":
|
|
7673
|
+
case "a":
|
|
7674
|
+
case "switch":
|
|
7675
|
+
for (const child of element.children) if (!NOT_DRAWN.has(localName(child.name))) add(child, matrix);
|
|
7676
|
+
return;
|
|
7677
|
+
case "use": {
|
|
7678
|
+
const target = referenced(element, context.document);
|
|
7679
|
+
if (target === void 0 || using.has(element)) return;
|
|
7680
|
+
using.add(element);
|
|
7681
|
+
add(target, multiply(matrix, [
|
|
7682
|
+
1,
|
|
7683
|
+
0,
|
|
7684
|
+
0,
|
|
7685
|
+
1,
|
|
7686
|
+
length(element.attributes.x, viewport.width),
|
|
7687
|
+
length(element.attributes.y, viewport.height)
|
|
7688
|
+
]));
|
|
7689
|
+
using.delete(element);
|
|
7690
|
+
return;
|
|
7691
|
+
}
|
|
7692
|
+
case "svg":
|
|
7693
|
+
case "symbol": {
|
|
7694
|
+
const a = element.attributes;
|
|
7695
|
+
into.rect(length(a.x, viewport.width), length(a.y, viewport.height), length(a.width ?? "100%", viewport.width), length(a.height ?? "100%", viewport.height));
|
|
7696
|
+
return;
|
|
7697
|
+
}
|
|
7698
|
+
default: shapeOf(element, name, viewport)?.trace(into);
|
|
7699
|
+
}
|
|
7700
|
+
}
|
|
7701
|
+
/** A nested `<svg>`: a viewport of its own, at its x and y. */
|
|
7702
|
+
function drawNested(element, style, viewport, context) {
|
|
7703
|
+
const { surface } = context;
|
|
7704
|
+
const x = length(element.attributes.x, viewport.width);
|
|
7705
|
+
const y = length(element.attributes.y, viewport.height);
|
|
7706
|
+
const width = length(element.attributes.width ?? "100%", viewport.width);
|
|
7707
|
+
const height = length(element.attributes.height ?? "100%", viewport.height);
|
|
7708
|
+
const viewBox = parseViewBox(element.attributes.viewBox);
|
|
7709
|
+
surface.translate(x, y);
|
|
7710
|
+
if (viewBox !== void 0) {
|
|
7711
|
+
const scale = Math.min(width / viewBox.width, height / viewBox.height);
|
|
7712
|
+
surface.translate((width - viewBox.width * scale) / 2, (height - viewBox.height * scale) / 2);
|
|
7713
|
+
surface.scale(scale, scale);
|
|
7714
|
+
surface.translate(-viewBox.x, -viewBox.y);
|
|
7715
|
+
}
|
|
7716
|
+
drawChildren(element, style, viewBox ?? {
|
|
7717
|
+
x: 0,
|
|
7718
|
+
y: 0,
|
|
7719
|
+
width,
|
|
7720
|
+
height
|
|
7721
|
+
}, context);
|
|
7722
|
+
}
|
|
7723
|
+
/** A `<use>`: the element it names, drawn at its x and y, inheriting from the `<use>`. */
|
|
7724
|
+
function drawUse(element, style, viewport, context) {
|
|
7725
|
+
const target = referenced(element, context.document);
|
|
7726
|
+
if (target === void 0 || context.using.has(element)) return;
|
|
7727
|
+
const { surface } = context;
|
|
7728
|
+
surface.translate(length(element.attributes.x, viewport.width), length(element.attributes.y, viewport.height));
|
|
7729
|
+
context.using.add(element);
|
|
7730
|
+
if (localName(target.name) === "symbol") drawNested(target, style, viewport, context);
|
|
7731
|
+
else {
|
|
7732
|
+
const name = localName(target.name);
|
|
7733
|
+
if (name === "g" || name === "svg" || name === "use" || !NOT_DRAWN.has(name)) drawElement(target, style, viewport, context);
|
|
7734
|
+
}
|
|
7735
|
+
context.using.delete(element);
|
|
7736
|
+
}
|
|
7737
|
+
function referenced(element, document) {
|
|
7738
|
+
const href = element.attributes.href ?? element.attributes["xlink:href"];
|
|
7739
|
+
return href?.startsWith("#") ? document.ids.get(href.slice(1)) : void 0;
|
|
7740
|
+
}
|
|
7741
|
+
function shapeOf(element, name, viewport) {
|
|
7742
|
+
const a = element.attributes;
|
|
7743
|
+
const w = viewport.width;
|
|
7744
|
+
const h = viewport.height;
|
|
7745
|
+
const diagonal = Math.sqrt((w * w + h * h) / 2);
|
|
7746
|
+
switch (name) {
|
|
7747
|
+
case "path": {
|
|
7748
|
+
const d = a.d;
|
|
7749
|
+
return d === void 0 || d.trim() === "" ? null : { trace: (surface) => surface.path(d) };
|
|
7750
|
+
}
|
|
7751
|
+
case "rect": {
|
|
7752
|
+
const x = length(a.x, w);
|
|
7753
|
+
const y = length(a.y, h);
|
|
7754
|
+
const width = length(a.width, w);
|
|
7755
|
+
const height = length(a.height, h);
|
|
7756
|
+
if (width <= 0 || height <= 0) return null;
|
|
7757
|
+
const rx = a.rx === void 0 ? void 0 : length(a.rx, w);
|
|
7758
|
+
const ry = a.ry === void 0 ? void 0 : length(a.ry, h);
|
|
7759
|
+
const radius = Math.min(rx ?? ry ?? 0, ry ?? rx ?? 0);
|
|
7760
|
+
return { trace: (surface) => radius > 0 ? surface.roundRect(x, y, width, height, radius) : surface.rect(x, y, width, height) };
|
|
7761
|
+
}
|
|
7762
|
+
case "circle": return ellipse(length(a.cx, w), length(a.cy, h), length(a.r, diagonal), length(a.r, diagonal));
|
|
7763
|
+
case "ellipse": return ellipse(length(a.cx, w), length(a.cy, h), length(a.rx, w), length(a.ry, h));
|
|
7764
|
+
case "line": {
|
|
7765
|
+
const [x1, y1, x2, y2] = [
|
|
7766
|
+
length(a.x1, w),
|
|
7767
|
+
length(a.y1, h),
|
|
7768
|
+
length(a.x2, w),
|
|
7769
|
+
length(a.y2, h)
|
|
7770
|
+
];
|
|
7771
|
+
return { trace: (surface) => {
|
|
7772
|
+
surface.moveTo(x1, y1);
|
|
7773
|
+
surface.lineTo(x2, y2);
|
|
7774
|
+
} };
|
|
7775
|
+
}
|
|
7776
|
+
case "polyline":
|
|
7777
|
+
case "polygon": {
|
|
7778
|
+
const points = numbersIn(a.points ?? "");
|
|
7779
|
+
if (points.length < 4) return null;
|
|
7780
|
+
return { trace: (surface) => {
|
|
7781
|
+
surface.moveTo(points[0], points[1]);
|
|
7782
|
+
for (let i = 2; i + 1 < points.length; i += 2) surface.lineTo(points[i], points[i + 1]);
|
|
7783
|
+
if (name === "polygon") surface.closePath();
|
|
7784
|
+
} };
|
|
7785
|
+
}
|
|
7786
|
+
default: return null;
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
/** An ellipse as two arcs of path data, which the surface turns into curves. */
|
|
7790
|
+
function ellipse(cx, cy, rx, ry) {
|
|
7791
|
+
if (rx <= 0 || ry <= 0) return null;
|
|
7792
|
+
const d = `M${cx - rx} ${cy}A${rx} ${ry} 0 1 0 ${cx + rx} ${cy}A${rx} ${ry} 0 1 0 ${cx - rx} ${cy}Z`;
|
|
7793
|
+
return { trace: (surface) => surface.path(d) };
|
|
7794
|
+
}
|
|
7795
|
+
function paintShape(shape, style, viewport, context) {
|
|
7796
|
+
const { surface } = context;
|
|
7797
|
+
surface.beginPath();
|
|
7798
|
+
shape.trace(surface);
|
|
7799
|
+
if (style.fill.kind !== "none" && style.fillOpacity > 0) {
|
|
7800
|
+
surface.save();
|
|
7801
|
+
if (style.fillOpacity < 1) surface.alpha(style.fillOpacity);
|
|
7802
|
+
if (setPaint(style.fill, "fill", shape, style, viewport, context)) surface.fill(style.fillRule);
|
|
7803
|
+
surface.restore();
|
|
7804
|
+
}
|
|
7805
|
+
if (style.stroke.kind !== "none" && style.strokeOpacity > 0 && style.strokeWidth > 0) {
|
|
7806
|
+
surface.save();
|
|
7807
|
+
if (style.strokeOpacity < 1) surface.alpha(style.strokeOpacity);
|
|
7808
|
+
if (setPaint(style.stroke, "stroke", shape, style, viewport, context)) {
|
|
7809
|
+
surface.lineWidth(style.strokeWidth);
|
|
7810
|
+
surface.lineCap(style.lineCap);
|
|
7811
|
+
surface.lineJoin(style.lineJoin);
|
|
7812
|
+
surface.miterLimit(style.miterLimit);
|
|
7813
|
+
if (style.dash.length > 0) surface.lineDash(style.dash, style.dashOffset);
|
|
7814
|
+
surface.stroke();
|
|
7815
|
+
}
|
|
7816
|
+
surface.restore();
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
/**
|
|
7820
|
+
* Sets a fill or a stroke on the surface. False when there is nothing
|
|
7821
|
+
* to draw with: a gradient with no stops, or a reference to nothing and
|
|
7822
|
+
* no fallback.
|
|
7823
|
+
*
|
|
7824
|
+
* A gradient stroke is drawn in its last stop's colour, because the
|
|
7825
|
+
* surface strokes only in a colour.
|
|
7826
|
+
*/
|
|
7827
|
+
function setPaint(paint, role, shape, style, viewport, context) {
|
|
7828
|
+
if (paint.kind === "none") return false;
|
|
7829
|
+
if (context.flood !== void 0) {
|
|
7830
|
+
if (role === "fill") context.surface.fillColor(context.flood);
|
|
7831
|
+
else context.surface.strokeColor(context.flood);
|
|
7832
|
+
return true;
|
|
7833
|
+
}
|
|
7834
|
+
if (paint.kind === "color") {
|
|
7835
|
+
if (role === "fill") context.surface.fillColor(paint.color);
|
|
7836
|
+
else context.surface.strokeColor(paint.color);
|
|
7837
|
+
return true;
|
|
7838
|
+
}
|
|
7839
|
+
const target = context.document.ids.get(paint.id);
|
|
7840
|
+
const gradient = target === void 0 ? void 0 : gradientOf(target, shape, style, viewport, context);
|
|
7841
|
+
if (gradient === void 0) return setPaint(paint.fallback, role, shape, style, viewport, context);
|
|
7842
|
+
if (gradient.stops.length === 0) return false;
|
|
7843
|
+
if (role === "stroke" || gradient.stops.length === 1) {
|
|
7844
|
+
const color = gradient.stops.at(-1).color;
|
|
7845
|
+
if (role === "fill") context.surface.fillColor(color);
|
|
7846
|
+
else context.surface.strokeColor(color);
|
|
7847
|
+
return true;
|
|
7848
|
+
}
|
|
7849
|
+
context.surface.fillGradient(gradient.paint, gradient.rect.x, gradient.rect.y, gradient.rect.width, gradient.rect.height);
|
|
7850
|
+
return true;
|
|
7851
|
+
}
|
|
7852
|
+
/**
|
|
7853
|
+
* An SVG gradient as Gesso's, and the rectangle to place it in.
|
|
7854
|
+
*
|
|
7855
|
+
* SVG gives a linear gradient as the two ends of its line and Gesso as
|
|
7856
|
+
* an angle across a rectangle, CSS's way, so the rectangle is chosen to
|
|
7857
|
+
* make the two agree: a square about the line's midpoint whose
|
|
7858
|
+
* gradient line, at that angle, is exactly as long as the SVG's. A
|
|
7859
|
+
* radial one is its centre and radius in pixels, placed at the origin.
|
|
7860
|
+
*/
|
|
7861
|
+
function gradientOf(element, shape, style, viewport, context) {
|
|
7862
|
+
const name = localName(element.name);
|
|
7863
|
+
if (name !== "linearGradient" && name !== "radialGradient") return;
|
|
7864
|
+
const attributes = gradientAttributes(element, context.document);
|
|
7865
|
+
const stops = gradientStops(element, style, context);
|
|
7866
|
+
const userSpace = attributes.gradientUnits === "userSpaceOnUse";
|
|
7867
|
+
const box = userSpace ? viewport : boundsOf(shape);
|
|
7868
|
+
const matrix = attributes.gradientTransform === void 0 ? IDENTITY$1 : parseTransform$1(attributes.gradientTransform);
|
|
7869
|
+
/** A point in the gradient's units, in the drawing's. */
|
|
7870
|
+
const place = (x, y, fallbackX, fallbackY) => {
|
|
7871
|
+
const u = gradientLength(x ?? fallbackX, userSpace, viewport.width);
|
|
7872
|
+
const v = gradientLength(y ?? fallbackY, userSpace, viewport.height);
|
|
7873
|
+
const [tx, ty] = apply(matrix, u, v);
|
|
7874
|
+
return userSpace ? [tx, ty] : [box.x + tx * box.width, box.y + ty * box.height];
|
|
7875
|
+
};
|
|
7876
|
+
if (name === "linearGradient") {
|
|
7877
|
+
const [x1, y1] = place(attributes.x1, attributes.y1, "0%", "0%");
|
|
7878
|
+
const [x2, y2] = place(attributes.x2, attributes.y2, "100%", "0%");
|
|
7879
|
+
const dx = x2 - x1;
|
|
7880
|
+
const dy = y2 - y1;
|
|
7881
|
+
const span = Math.hypot(dx, dy);
|
|
7882
|
+
if (span === 0) return {
|
|
7883
|
+
paint: {
|
|
7884
|
+
kind: "linear",
|
|
7885
|
+
angle: 0,
|
|
7886
|
+
stops: stops.slice(-1)
|
|
7887
|
+
},
|
|
7888
|
+
stops: stops.slice(-1),
|
|
7889
|
+
rect: box
|
|
7890
|
+
};
|
|
7891
|
+
const angle = Math.atan2(dx, -dy);
|
|
7892
|
+
const side = span / (Math.abs(Math.sin(angle)) + Math.abs(Math.cos(angle)));
|
|
7893
|
+
return {
|
|
7894
|
+
paint: {
|
|
7895
|
+
kind: "linear",
|
|
7896
|
+
angle,
|
|
7897
|
+
stops
|
|
7898
|
+
},
|
|
7899
|
+
stops,
|
|
7900
|
+
rect: {
|
|
7901
|
+
x: (x1 + x2) / 2 - side / 2,
|
|
7902
|
+
y: (y1 + y2) / 2 - side / 2,
|
|
7903
|
+
width: side,
|
|
7904
|
+
height: side
|
|
7905
|
+
}
|
|
7906
|
+
};
|
|
7907
|
+
}
|
|
7908
|
+
const [cx, cy] = place(attributes.cx, attributes.cy, "50%", "50%");
|
|
7909
|
+
const radius = gradientLength(attributes.r ?? "50%", userSpace, Math.sqrt((viewport.width ** 2 + viewport.height ** 2) / 2)) * Math.sqrt(Math.abs(matrix[0] * matrix[3] - matrix[1] * matrix[2])) * (userSpace ? 1 : (box.width + box.height) / 2);
|
|
7910
|
+
if (radius <= 0) return {
|
|
7911
|
+
paint: {
|
|
7912
|
+
kind: "radial",
|
|
7913
|
+
stops: stops.slice(-1)
|
|
7914
|
+
},
|
|
7915
|
+
stops: stops.slice(-1),
|
|
7916
|
+
rect: box
|
|
7917
|
+
};
|
|
7918
|
+
return {
|
|
7919
|
+
paint: {
|
|
7920
|
+
kind: "radial",
|
|
7921
|
+
centerX: cx,
|
|
7922
|
+
centerY: cy,
|
|
7923
|
+
radius,
|
|
7924
|
+
stops
|
|
7925
|
+
},
|
|
7926
|
+
stops,
|
|
7927
|
+
rect: {
|
|
7928
|
+
x: 0,
|
|
7929
|
+
y: 0,
|
|
7930
|
+
width: 1,
|
|
7931
|
+
height: 1
|
|
7932
|
+
}
|
|
7933
|
+
};
|
|
7934
|
+
}
|
|
7935
|
+
/** A gradient's attributes, with the ones it inherits through `href` from the gradient it names. */
|
|
7936
|
+
function gradientAttributes(element, document) {
|
|
7937
|
+
const chain = [];
|
|
7938
|
+
for (let at = element; at !== void 0 && !chain.includes(at); at = referenced(at, document)) chain.push(at);
|
|
7939
|
+
return Object.assign({}, ...chain.reverse().map((link) => link.attributes));
|
|
7940
|
+
}
|
|
7941
|
+
/** Its stops, or those of the gradient it names when it has none. */
|
|
7942
|
+
function gradientStops(element, style, context) {
|
|
7943
|
+
const seen = [];
|
|
7944
|
+
let at = element;
|
|
7945
|
+
while (at !== void 0 && !seen.includes(at)) {
|
|
7946
|
+
seen.push(at);
|
|
7947
|
+
const stops = at.children.filter((child) => localName(child.name) === "stop");
|
|
7948
|
+
if (stops.length > 0) {
|
|
7949
|
+
let last = 0;
|
|
7950
|
+
return stops.map((stop) => {
|
|
7951
|
+
const declared = declarationsOf(stop, context.document);
|
|
7952
|
+
const raw = declared.offset ?? "0";
|
|
7953
|
+
const offset = Math.max(last, Math.min(1, raw.trim().endsWith("%") ? parseFloat(raw) / 100 : parseFloat(raw) || 0));
|
|
7954
|
+
last = offset;
|
|
7955
|
+
const color = colorValue(declared["stop-color"] ?? "black", style, context) ?? BLACK_COLOR;
|
|
7956
|
+
return {
|
|
7957
|
+
offset: percent(offset * 100),
|
|
7958
|
+
color: withOpacity(color, unit(declared["stop-opacity"]))
|
|
7959
|
+
};
|
|
7960
|
+
});
|
|
7961
|
+
}
|
|
7962
|
+
at = referenced(at, context.document);
|
|
7963
|
+
}
|
|
7964
|
+
return [];
|
|
7965
|
+
}
|
|
7966
|
+
const BLACK_COLOR = {
|
|
7967
|
+
r: 0,
|
|
7968
|
+
g: 0,
|
|
7969
|
+
b: 0,
|
|
7970
|
+
a: 1
|
|
7971
|
+
};
|
|
7972
|
+
function withOpacity(color, opacity) {
|
|
7973
|
+
if (opacity >= 1 || typeof color === "string") return color;
|
|
7974
|
+
return {
|
|
7975
|
+
...color,
|
|
7976
|
+
a: color.a * opacity
|
|
7977
|
+
};
|
|
7978
|
+
}
|
|
7979
|
+
/** A gradient coordinate: a fraction of the box, or pixels in user space. */
|
|
7980
|
+
function gradientLength(value, userSpace, extent) {
|
|
7981
|
+
const trimmed = value.trim();
|
|
7982
|
+
if (trimmed.endsWith("%")) {
|
|
7983
|
+
const fraction = parseFloat(trimmed) / 100;
|
|
7984
|
+
return userSpace ? fraction * extent : fraction;
|
|
7985
|
+
}
|
|
7986
|
+
return parseFloat(trimmed) || 0;
|
|
7987
|
+
}
|
|
7988
|
+
/** The box a shape's outline and control points lie in, for a gradient sized to it. */
|
|
7989
|
+
function boundsOf(shape) {
|
|
7990
|
+
const bounds = new BoundsSurface();
|
|
7991
|
+
shape.trace(bounds);
|
|
7992
|
+
return bounds.bounds();
|
|
7993
|
+
}
|
|
7994
|
+
/** A surface that only notes where a path goes. */
|
|
7995
|
+
var BoundsSurface = class {
|
|
7996
|
+
minX = Infinity;
|
|
7997
|
+
minY = Infinity;
|
|
7998
|
+
maxX = -Infinity;
|
|
7999
|
+
maxY = -Infinity;
|
|
8000
|
+
note(x, y) {
|
|
8001
|
+
this.minX = Math.min(this.minX, x);
|
|
8002
|
+
this.minY = Math.min(this.minY, y);
|
|
8003
|
+
this.maxX = Math.max(this.maxX, x);
|
|
8004
|
+
this.maxY = Math.max(this.maxY, y);
|
|
8005
|
+
}
|
|
8006
|
+
bounds() {
|
|
8007
|
+
if (this.minX > this.maxX) return {
|
|
8008
|
+
x: 0,
|
|
8009
|
+
y: 0,
|
|
8010
|
+
width: 0,
|
|
8011
|
+
height: 0
|
|
8012
|
+
};
|
|
8013
|
+
return {
|
|
8014
|
+
x: this.minX,
|
|
8015
|
+
y: this.minY,
|
|
8016
|
+
width: this.maxX - this.minX,
|
|
8017
|
+
height: this.maxY - this.minY
|
|
8018
|
+
};
|
|
8019
|
+
}
|
|
8020
|
+
moveTo(x, y) {
|
|
8021
|
+
this.note(x, y);
|
|
8022
|
+
}
|
|
8023
|
+
lineTo(x, y) {
|
|
8024
|
+
this.note(x, y);
|
|
8025
|
+
}
|
|
8026
|
+
quadraticCurveTo(cx, cy, x, y) {
|
|
8027
|
+
this.note(cx, cy);
|
|
8028
|
+
this.note(x, y);
|
|
8029
|
+
}
|
|
8030
|
+
bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
|
|
8031
|
+
this.note(c1x, c1y);
|
|
8032
|
+
this.note(c2x, c2y);
|
|
8033
|
+
this.note(x, y);
|
|
8034
|
+
}
|
|
8035
|
+
arc(x, y, radius) {
|
|
8036
|
+
this.note(x - radius, y - radius);
|
|
8037
|
+
this.note(x + radius, y + radius);
|
|
8038
|
+
}
|
|
8039
|
+
rect(x, y, width, height) {
|
|
8040
|
+
this.note(x, y);
|
|
8041
|
+
this.note(x + width, y + height);
|
|
8042
|
+
}
|
|
8043
|
+
roundRect(x, y, width, height) {
|
|
8044
|
+
this.rect(x, y, width, height);
|
|
8045
|
+
}
|
|
8046
|
+
closePath() {}
|
|
8047
|
+
path(d) {
|
|
8048
|
+
tracePathData(this, d);
|
|
8049
|
+
}
|
|
8050
|
+
};
|
|
8051
|
+
/** Intersects the clip with a `<clipPath>`'s shapes, in the drawing's own coordinates. */
|
|
8052
|
+
function clipTo(value, viewport, context) {
|
|
8053
|
+
const id = /url\(\s*['"]?#([^'")]+)['"]?\s*\)/.exec(value)?.[1];
|
|
8054
|
+
const clip = id === void 0 ? void 0 : context.document.ids.get(id);
|
|
8055
|
+
if (clip === void 0 || localName(clip.name) !== "clipPath") return;
|
|
8056
|
+
const { surface } = context;
|
|
8057
|
+
surface.beginPath();
|
|
8058
|
+
let rule = "nonzero";
|
|
8059
|
+
const add = (element) => {
|
|
8060
|
+
const name = localName(element.name);
|
|
8061
|
+
const declared = declarationsOf(element, context.document);
|
|
8062
|
+
if (name === "use") {
|
|
8063
|
+
const target = referenced(element, context.document);
|
|
8064
|
+
if (target !== void 0 && localName(target.name) !== "use") add(target);
|
|
8065
|
+
return;
|
|
8066
|
+
}
|
|
8067
|
+
if (name === "g") {
|
|
8068
|
+
element.children.forEach(add);
|
|
8069
|
+
return;
|
|
8070
|
+
}
|
|
8071
|
+
const shape = shapeOf(element, name, viewport);
|
|
8072
|
+
if (shape === null) return;
|
|
8073
|
+
if (declared["clip-rule"] === "evenodd") rule = "evenodd";
|
|
8074
|
+
if (declared.transform !== void 0) {
|
|
8075
|
+
const matrix = parseTransform$1(declared.transform);
|
|
8076
|
+
shape.trace(new TransformedSurface(surface, matrix));
|
|
8077
|
+
} else shape.trace(surface);
|
|
8078
|
+
};
|
|
8079
|
+
clip.children.forEach(add);
|
|
8080
|
+
surface.clip(rule);
|
|
8081
|
+
}
|
|
8082
|
+
/** Forwards path calls with every point through a matrix. */
|
|
8083
|
+
var TransformedSurface = class {
|
|
8084
|
+
target;
|
|
8085
|
+
matrix;
|
|
8086
|
+
constructor(target, matrix) {
|
|
8087
|
+
this.target = target;
|
|
8088
|
+
this.matrix = matrix;
|
|
8089
|
+
}
|
|
8090
|
+
at(x, y) {
|
|
8091
|
+
return apply(this.matrix, x, y);
|
|
8092
|
+
}
|
|
8093
|
+
moveTo(x, y) {
|
|
8094
|
+
this.target.moveTo(...this.at(x, y));
|
|
8095
|
+
}
|
|
8096
|
+
lineTo(x, y) {
|
|
8097
|
+
this.target.lineTo(...this.at(x, y));
|
|
8098
|
+
}
|
|
8099
|
+
quadraticCurveTo(cx, cy, x, y) {
|
|
8100
|
+
this.target.quadraticCurveTo(...this.at(cx, cy), ...this.at(x, y));
|
|
8101
|
+
}
|
|
8102
|
+
bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
|
|
8103
|
+
this.target.bezierCurveTo(...this.at(c1x, c1y), ...this.at(c2x, c2y), ...this.at(x, y));
|
|
8104
|
+
}
|
|
8105
|
+
rect(x, y, width, height) {
|
|
8106
|
+
this.moveTo(x, y);
|
|
8107
|
+
this.lineTo(x + width, y);
|
|
8108
|
+
this.lineTo(x + width, y + height);
|
|
8109
|
+
this.lineTo(x, y + height);
|
|
8110
|
+
this.closePath();
|
|
8111
|
+
}
|
|
8112
|
+
roundRect(x, y, width, height) {
|
|
8113
|
+
this.rect(x, y, width, height);
|
|
8114
|
+
}
|
|
8115
|
+
closePath() {
|
|
8116
|
+
this.target.closePath();
|
|
8117
|
+
}
|
|
8118
|
+
path(d) {
|
|
8119
|
+
tracePathData(this, d);
|
|
8120
|
+
}
|
|
8121
|
+
};
|
|
8122
|
+
/**
|
|
8123
|
+
* What an element declares, in the cascade's order: its presentation
|
|
8124
|
+
* attributes, then the `<style>` rules that match it by specificity,
|
|
8125
|
+
* then its own `style` attribute.
|
|
8126
|
+
*/
|
|
8127
|
+
function declarationsOf(element, document) {
|
|
8128
|
+
const declared = { ...element.attributes };
|
|
8129
|
+
if (document.rules.length > 0) {
|
|
8130
|
+
const matching = document.rules.map((rule, order) => ({
|
|
8131
|
+
rule,
|
|
8132
|
+
order
|
|
8133
|
+
})).filter(({ rule }) => selectorMatches(rule.selector, element)).sort((a, b) => a.rule.specificity - b.rule.specificity || a.order - b.order);
|
|
8134
|
+
for (const { rule } of matching) Object.assign(declared, rule.declarations);
|
|
8135
|
+
}
|
|
8136
|
+
if (element.attributes.style !== void 0) Object.assign(declared, parseDeclarations(element.attributes.style));
|
|
8137
|
+
return declared;
|
|
8138
|
+
}
|
|
8139
|
+
/** The inherited style at an element, from its parent's and what it declares. */
|
|
8140
|
+
function cascade(parent, declared, context) {
|
|
8141
|
+
const color = declared.color === void 0 || declared.color === "inherit" ? parent.color : colorValue(declared.color, parent, context) ?? parent.color;
|
|
8142
|
+
const here = {
|
|
8143
|
+
...parent,
|
|
8144
|
+
color
|
|
8145
|
+
};
|
|
8146
|
+
const pick = (key, property, read) => {
|
|
8147
|
+
const value = declared[property];
|
|
8148
|
+
return value === void 0 || value === "inherit" ? here[key] : read(value) ?? here[key];
|
|
8149
|
+
};
|
|
8150
|
+
const number = (value) => {
|
|
8151
|
+
const parsed = parseFloat(value);
|
|
8152
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
8153
|
+
};
|
|
8154
|
+
return {
|
|
8155
|
+
...here,
|
|
8156
|
+
fill: pick("fill", "fill", (value) => paintValue(value, here, context)),
|
|
8157
|
+
fillOpacity: pick("fillOpacity", "fill-opacity", (value) => unit(value)),
|
|
8158
|
+
fillRule: pick("fillRule", "fill-rule", (value) => value === "evenodd" ? "evenodd" : "nonzero"),
|
|
8159
|
+
stroke: pick("stroke", "stroke", (value) => paintValue(value, here, context)),
|
|
8160
|
+
strokeOpacity: pick("strokeOpacity", "stroke-opacity", (value) => unit(value)),
|
|
8161
|
+
strokeWidth: pick("strokeWidth", "stroke-width", number),
|
|
8162
|
+
lineCap: pick("lineCap", "stroke-linecap", (value) => value === "round" || value === "square" || value === "butt" ? value : void 0),
|
|
8163
|
+
lineJoin: pick("lineJoin", "stroke-linejoin", (value) => value === "round" || value === "bevel" || value === "miter" ? value : value === "arcs" || value === "miter-clip" ? "miter" : void 0),
|
|
8164
|
+
miterLimit: pick("miterLimit", "stroke-miterlimit", number),
|
|
8165
|
+
dash: pick("dash", "stroke-dasharray", (value) => {
|
|
8166
|
+
if (value === "none") return [];
|
|
8167
|
+
const segments = numbersIn(value);
|
|
8168
|
+
if (segments.some((segment) => segment < 0) || segments.every((segment) => segment === 0)) return [];
|
|
8169
|
+
return segments.length % 2 === 1 ? [...segments, ...segments] : segments;
|
|
8170
|
+
}),
|
|
8171
|
+
dashOffset: pick("dashOffset", "stroke-dashoffset", number),
|
|
8172
|
+
clipRule: pick("clipRule", "clip-rule", (value) => value === "evenodd" ? "evenodd" : "nonzero"),
|
|
8173
|
+
visible: pick("visible", "visibility", (value) => value === "visible" || value === "inherit" ? true : value === "hidden" || value === "collapse" ? false : void 0)
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
function paintValue(value, style, context) {
|
|
8177
|
+
const trimmed = value.trim();
|
|
8178
|
+
if (trimmed === "none" || trimmed === "transparent") return NONE;
|
|
8179
|
+
const reference = /^url\(\s*['"]?#([^'")]+)['"]?\s*\)\s*(.*)$/.exec(trimmed);
|
|
8180
|
+
if (reference !== null) {
|
|
8181
|
+
const fallback = reference[2] === "" || reference[2] === void 0 ? NONE : paintValue(reference[2], style, context) ?? NONE;
|
|
8182
|
+
return {
|
|
8183
|
+
kind: "ref",
|
|
8184
|
+
id: reference[1],
|
|
8185
|
+
fallback
|
|
8186
|
+
};
|
|
8187
|
+
}
|
|
8188
|
+
const color = colorValue(trimmed, style, context);
|
|
8189
|
+
return color === void 0 ? void 0 : {
|
|
8190
|
+
kind: "color",
|
|
8191
|
+
color
|
|
8192
|
+
};
|
|
8193
|
+
}
|
|
8194
|
+
/** A colour, with `currentColor` resolved to whatever it is here. */
|
|
8195
|
+
function colorValue(value, style, context) {
|
|
8196
|
+
const trimmed = value.trim();
|
|
8197
|
+
if (trimmed.toLowerCase() === "currentcolor") return style.color ?? context.currentColor;
|
|
8198
|
+
return parseColor(trimmed);
|
|
8199
|
+
}
|
|
8200
|
+
/** An opacity: a number or a percentage, clamped to 0..1, and 1 when absent. */
|
|
8201
|
+
function unit(value) {
|
|
8202
|
+
if (value === void 0) return 1;
|
|
8203
|
+
const trimmed = value.trim();
|
|
8204
|
+
const parsed = trimmed.endsWith("%") ? parseFloat(trimmed) / 100 : parseFloat(trimmed);
|
|
8205
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : 1;
|
|
8206
|
+
}
|
|
8207
|
+
/** A length in the drawing's units, a percentage being of `extent`. */
|
|
8208
|
+
function length(value, extent) {
|
|
8209
|
+
if (value === void 0) return 0;
|
|
8210
|
+
const trimmed = value.trim();
|
|
8211
|
+
if (trimmed.endsWith("%")) return parseFloat(trimmed) / 100 * extent;
|
|
8212
|
+
const parsed = parseFloat(trimmed);
|
|
8213
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
8214
|
+
}
|
|
8215
|
+
const IDENTITY$1 = [
|
|
8216
|
+
1,
|
|
8217
|
+
0,
|
|
8218
|
+
0,
|
|
8219
|
+
1,
|
|
8220
|
+
0,
|
|
8221
|
+
0
|
|
8222
|
+
];
|
|
8223
|
+
function multiply(m, n) {
|
|
8224
|
+
return [
|
|
8225
|
+
m[0] * n[0] + m[2] * n[1],
|
|
8226
|
+
m[1] * n[0] + m[3] * n[1],
|
|
8227
|
+
m[0] * n[2] + m[2] * n[3],
|
|
8228
|
+
m[1] * n[2] + m[3] * n[3],
|
|
8229
|
+
m[0] * n[4] + m[2] * n[5] + m[4],
|
|
8230
|
+
m[1] * n[4] + m[3] * n[5] + m[5]
|
|
8231
|
+
];
|
|
8232
|
+
}
|
|
8233
|
+
function apply(m, x, y) {
|
|
8234
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
8235
|
+
}
|
|
8236
|
+
/** A `transform` list as one matrix, its functions applied left to right as SVG says. */
|
|
8237
|
+
function parseTransform$1(value) {
|
|
8238
|
+
let matrix = IDENTITY$1;
|
|
8239
|
+
for (const match of value.matchAll(/(matrix|translate|scale|rotate|skewX|skewY)\s*\(([^)]*)\)/g)) {
|
|
8240
|
+
const args = numbersIn(match[2]);
|
|
8241
|
+
let step = IDENTITY$1;
|
|
8242
|
+
switch (match[1]) {
|
|
8243
|
+
case "matrix":
|
|
8244
|
+
if (args.length >= 6) step = [
|
|
8245
|
+
args[0],
|
|
8246
|
+
args[1],
|
|
8247
|
+
args[2],
|
|
8248
|
+
args[3],
|
|
8249
|
+
args[4],
|
|
8250
|
+
args[5]
|
|
8251
|
+
];
|
|
8252
|
+
break;
|
|
8253
|
+
case "translate":
|
|
8254
|
+
step = [
|
|
8255
|
+
1,
|
|
8256
|
+
0,
|
|
8257
|
+
0,
|
|
8258
|
+
1,
|
|
8259
|
+
args[0] ?? 0,
|
|
8260
|
+
args[1] ?? 0
|
|
8261
|
+
];
|
|
8262
|
+
break;
|
|
8263
|
+
case "scale":
|
|
8264
|
+
step = [
|
|
8265
|
+
args[0] ?? 1,
|
|
8266
|
+
0,
|
|
8267
|
+
0,
|
|
8268
|
+
args[1] ?? args[0] ?? 1,
|
|
8269
|
+
0,
|
|
8270
|
+
0
|
|
8271
|
+
];
|
|
8272
|
+
break;
|
|
8273
|
+
case "rotate": {
|
|
8274
|
+
const angle = (args[0] ?? 0) * Math.PI / 180;
|
|
8275
|
+
const cos = Math.cos(angle);
|
|
8276
|
+
const sin = Math.sin(angle);
|
|
8277
|
+
const [cx, cy] = [args[1] ?? 0, args[2] ?? 0];
|
|
8278
|
+
step = multiply(multiply([
|
|
8279
|
+
1,
|
|
8280
|
+
0,
|
|
8281
|
+
0,
|
|
8282
|
+
1,
|
|
8283
|
+
cx,
|
|
8284
|
+
cy
|
|
8285
|
+
], [
|
|
8286
|
+
cos,
|
|
8287
|
+
sin,
|
|
8288
|
+
-sin,
|
|
8289
|
+
cos,
|
|
8290
|
+
0,
|
|
8291
|
+
0
|
|
8292
|
+
]), [
|
|
8293
|
+
1,
|
|
8294
|
+
0,
|
|
8295
|
+
0,
|
|
8296
|
+
1,
|
|
8297
|
+
-cx,
|
|
8298
|
+
-cy
|
|
8299
|
+
]);
|
|
8300
|
+
break;
|
|
8301
|
+
}
|
|
8302
|
+
case "skewX":
|
|
8303
|
+
step = [
|
|
8304
|
+
1,
|
|
8305
|
+
0,
|
|
8306
|
+
Math.tan((args[0] ?? 0) * Math.PI / 180),
|
|
8307
|
+
1,
|
|
8308
|
+
0,
|
|
8309
|
+
0
|
|
8310
|
+
];
|
|
8311
|
+
break;
|
|
8312
|
+
case "skewY": step = [
|
|
8313
|
+
1,
|
|
8314
|
+
Math.tan((args[0] ?? 0) * Math.PI / 180),
|
|
8315
|
+
0,
|
|
8316
|
+
1,
|
|
8317
|
+
0,
|
|
8318
|
+
0
|
|
8319
|
+
];
|
|
8320
|
+
}
|
|
8321
|
+
matrix = multiply(matrix, step);
|
|
8322
|
+
}
|
|
8323
|
+
return matrix;
|
|
8324
|
+
}
|
|
8325
|
+
function applyTransform$1(surface, value) {
|
|
8326
|
+
const [a, b, c, d, e, f] = parseTransform$1(value);
|
|
8327
|
+
surface.transform(a, b, c, d, e, f);
|
|
8328
|
+
}
|
|
8329
|
+
//#endregion
|
|
8330
|
+
//#region src/rendering/svg/SvgResolver.ts
|
|
8331
|
+
/**
|
|
8332
|
+
* Whether a source is an SVG file, from its name or its data URL's
|
|
8333
|
+
* type: what `Image` asks before it decodes, since the platform
|
|
8334
|
+
* decoder refuses SVG and this one draws it.
|
|
8335
|
+
*/
|
|
8336
|
+
function isSvgSource(source) {
|
|
8337
|
+
if (/^data:image\/svg\+xml[;,]/i.test(source)) return true;
|
|
8338
|
+
const path = source.split(/[?#]/)[0] ?? "";
|
|
8339
|
+
return /\.svgz?$/i.test(path);
|
|
8340
|
+
}
|
|
8341
|
+
const DEFAULT_CAPACITY$3 = 64;
|
|
8342
|
+
/**
|
|
8343
|
+
* Fetch, read, keep.
|
|
8344
|
+
*
|
|
8345
|
+
* Kept by recency and never closed, unlike a bitmap: a read file holds
|
|
8346
|
+
* no decoded pixels, so there is nothing to release promptly, and a
|
|
8347
|
+
* file a screen shows a hundred times (an icon in every row) is read
|
|
8348
|
+
* once. A failed fetch is not kept, so the next node tries again.
|
|
8349
|
+
*/
|
|
8350
|
+
var DefaultSvgResolver = class {
|
|
8351
|
+
entries = /* @__PURE__ */ new Map();
|
|
8352
|
+
settled = /* @__PURE__ */ new Map();
|
|
8353
|
+
capacity;
|
|
8354
|
+
fetchText;
|
|
8355
|
+
constructor(options = {}) {
|
|
8356
|
+
this.capacity = options.capacity ?? DEFAULT_CAPACITY$3;
|
|
8357
|
+
this.fetchText = options.fetch ?? defaultFetch$2;
|
|
8358
|
+
}
|
|
8359
|
+
resolve(source) {
|
|
8360
|
+
const existing = this.entries.get(source);
|
|
8361
|
+
if (existing !== void 0) {
|
|
8362
|
+
this.entries.delete(source);
|
|
8363
|
+
this.entries.set(source, existing);
|
|
8364
|
+
return existing;
|
|
8365
|
+
}
|
|
8366
|
+
const entry = this.fetchText(source).then((text) => {
|
|
8367
|
+
const document = parseSvg(text);
|
|
8368
|
+
if (this.entries.get(source) === entry) this.settled.set(source, document);
|
|
8369
|
+
return document;
|
|
8370
|
+
}).catch((error) => {
|
|
8371
|
+
if (this.entries.get(source) === entry) this.entries.delete(source);
|
|
8372
|
+
throw error;
|
|
8373
|
+
});
|
|
8374
|
+
entry.catch(() => {});
|
|
8375
|
+
this.entries.set(source, entry);
|
|
8376
|
+
while (this.entries.size > this.capacity) {
|
|
8377
|
+
const oldest = this.entries.keys().next().value;
|
|
8378
|
+
this.entries.delete(oldest);
|
|
8379
|
+
this.settled.delete(oldest);
|
|
8380
|
+
}
|
|
8381
|
+
return entry;
|
|
8382
|
+
}
|
|
8383
|
+
peek(source) {
|
|
8384
|
+
return this.settled.get(source) ?? null;
|
|
8385
|
+
}
|
|
8386
|
+
};
|
|
8387
|
+
async function defaultFetch$2(source) {
|
|
8388
|
+
const response = await fetch(source);
|
|
8389
|
+
if (!response.ok) throw new Error(`Fetching the SVG '${source}' failed with ${response.status} ${response.statusText}.`);
|
|
8390
|
+
return response.text();
|
|
8391
|
+
}
|
|
8392
|
+
//#endregion
|
|
6864
8393
|
//#region src/modifiers/media.ts
|
|
6865
8394
|
/**
|
|
6866
8395
|
* A value or an Observable of one, as one Observable. The media
|
|
@@ -6870,6 +8399,29 @@ function numberOr(value, fallback) {
|
|
|
6870
8399
|
function follow(value) {
|
|
6871
8400
|
return isObservable$1(value) ? value : of(value);
|
|
6872
8401
|
}
|
|
8402
|
+
/** Pictures this thread's media modifiers are fetching or decoding now. */
|
|
8403
|
+
let inFlight = 0;
|
|
8404
|
+
/**
|
|
8405
|
+
* How many pictures the media modifiers in this thread are still
|
|
8406
|
+
* fetching or decoding: an `Image`'s bitmap or its SVG.
|
|
8407
|
+
*
|
|
8408
|
+
* A tree waiting on a picture asks for no frames while it waits, so a
|
|
8409
|
+
* caller that judges a tree finished by its quiet (`renderFrame`, which
|
|
8410
|
+
* draws a mock once, off screen) would draw it without the picture.
|
|
8411
|
+
* This is the other half of finished: nothing still on its way.
|
|
8412
|
+
*/
|
|
8413
|
+
function mediaLoadsInFlight() {
|
|
8414
|
+
return inFlight;
|
|
8415
|
+
}
|
|
8416
|
+
/** Counts a load from its start until it settles, either way. */
|
|
8417
|
+
function counted(loading) {
|
|
8418
|
+
inFlight++;
|
|
8419
|
+
const done = () => {
|
|
8420
|
+
inFlight--;
|
|
8421
|
+
};
|
|
8422
|
+
loading.then(done, done);
|
|
8423
|
+
return loading;
|
|
8424
|
+
}
|
|
6873
8425
|
/**
|
|
6874
8426
|
* Resolves a source to a bitmap and writes it onto the node's `image`.
|
|
6875
8427
|
*
|
|
@@ -6905,6 +8457,8 @@ function load$1(host, args, sources) {
|
|
|
6905
8457
|
let held = null;
|
|
6906
8458
|
args.onState?.("loading");
|
|
6907
8459
|
host.clear("image");
|
|
8460
|
+
host.clear("paint");
|
|
8461
|
+
host.clear("src");
|
|
6908
8462
|
const attempt = (index, lastError) => {
|
|
6909
8463
|
if (!live) return;
|
|
6910
8464
|
const source = sources[index];
|
|
@@ -6912,16 +8466,22 @@ function load$1(host, args, sources) {
|
|
|
6912
8466
|
args.onState?.("failed", lastError ?? /* @__PURE__ */ new Error("No image source was given."));
|
|
6913
8467
|
return;
|
|
6914
8468
|
}
|
|
8469
|
+
if (args.vectors !== void 0 && isSvgSource(source)) {
|
|
8470
|
+
drawVector(host, args, args.vectors, source, () => live, (error) => attempt(index + 1, error));
|
|
8471
|
+
return;
|
|
8472
|
+
}
|
|
6915
8473
|
held = source;
|
|
6916
8474
|
const ready = args.resolver.peek?.(source) ?? null;
|
|
6917
8475
|
if (ready !== null) {
|
|
6918
8476
|
host.set("image", ready);
|
|
8477
|
+
host.set("src", source);
|
|
6919
8478
|
args.onState?.("loaded");
|
|
6920
8479
|
return;
|
|
6921
8480
|
}
|
|
6922
|
-
args.resolver.resolve(source).then((bitmap) => {
|
|
8481
|
+
counted(args.resolver.resolve(source)).then((bitmap) => {
|
|
6923
8482
|
if (!live) return;
|
|
6924
8483
|
host.set("image", bitmap);
|
|
8484
|
+
host.set("src", source);
|
|
6925
8485
|
args.onState?.("loaded");
|
|
6926
8486
|
}).catch((error) => {
|
|
6927
8487
|
if (held === source) held = null;
|
|
@@ -6937,6 +8497,36 @@ function load$1(host, args, sources) {
|
|
|
6937
8497
|
}
|
|
6938
8498
|
};
|
|
6939
8499
|
}
|
|
8500
|
+
/** SVG sources already warned about, so a file shown in every row says what it lacks once. */
|
|
8501
|
+
const warnedSvg = /* @__PURE__ */ new Set();
|
|
8502
|
+
/**
|
|
8503
|
+
* An SVG source, read and set as the node's `paint`: vectors, drawn at
|
|
8504
|
+
* whatever size the node is laid out at, with `currentColor` the
|
|
8505
|
+
* node's inherited text colour. Nothing is held, because a read file
|
|
8506
|
+
* holds no pixels to release.
|
|
8507
|
+
*/
|
|
8508
|
+
function drawVector(host, args, vectors, source, live, fail) {
|
|
8509
|
+
const show = (document) => {
|
|
8510
|
+
const fit = host.node.getProperty("objectFit") ?? "contain";
|
|
8511
|
+
host.set("paint", svgPaint(document, { fit }));
|
|
8512
|
+
host.set("src", source);
|
|
8513
|
+
if (document.unsupported.length > 0 && !warnedSvg.has(source)) {
|
|
8514
|
+
warnedSvg.add(source);
|
|
8515
|
+
console.warn(`[gesso] ${source.slice(0, 80)} uses ${document.unsupported.join(", ")}, which Gesso does not draw yet; the rest of it is drawn.`);
|
|
8516
|
+
}
|
|
8517
|
+
args.onState?.("loaded");
|
|
8518
|
+
};
|
|
8519
|
+
const ready = vectors.peek?.(source) ?? null;
|
|
8520
|
+
if (ready !== null) {
|
|
8521
|
+
show(ready);
|
|
8522
|
+
return;
|
|
8523
|
+
}
|
|
8524
|
+
counted(vectors.resolve(source)).then((document) => {
|
|
8525
|
+
if (live()) show(document);
|
|
8526
|
+
}, (error) => {
|
|
8527
|
+
if (live()) fail(error);
|
|
8528
|
+
});
|
|
8529
|
+
}
|
|
6940
8530
|
function sameSources(a, b) {
|
|
6941
8531
|
if (typeof a === "string" || typeof b === "string") return a === b;
|
|
6942
8532
|
return a.length === b.length && a.every((url, index) => url === b[index]);
|
|
@@ -7243,14 +8833,14 @@ var MotionLayer = class {
|
|
|
7243
8833
|
}
|
|
7244
8834
|
drive(cell, to, timing) {
|
|
7245
8835
|
if (timing.spring !== void 0) return this.host.spring(cell, to, {
|
|
7246
|
-
spring:
|
|
8836
|
+
spring: springIn(motionOf(this.host.node), timing.spring),
|
|
7247
8837
|
delay: timing.delay,
|
|
7248
8838
|
reducedMotion: timing.reducedMotion,
|
|
7249
8839
|
restDelta: .002
|
|
7250
8840
|
});
|
|
7251
8841
|
return this.host.animate(cell, to, {
|
|
7252
|
-
duration:
|
|
7253
|
-
easing:
|
|
8842
|
+
duration: durationIn(motionOf(this.host.node), timing.duration),
|
|
8843
|
+
easing: easingIn(motionOf(this.host.node), timing.easing),
|
|
7254
8844
|
delay: timing.delay,
|
|
7255
8845
|
reducedMotion: timing.reducedMotion
|
|
7256
8846
|
});
|
|
@@ -7266,17 +8856,6 @@ var MotionLayer = class {
|
|
|
7266
8856
|
function channelEpsilon(channel) {
|
|
7267
8857
|
return channel === "x" || channel === "y" ? .01 : 1e-4;
|
|
7268
8858
|
}
|
|
7269
|
-
function springSpec(spring) {
|
|
7270
|
-
return typeof spring === "string" ? defaultMotion.springs[spring] : spring;
|
|
7271
|
-
}
|
|
7272
|
-
function resolveDuration(duration) {
|
|
7273
|
-
if (duration === void 0) return defaultMotion.durations.normal;
|
|
7274
|
-
return typeof duration === "number" ? duration : defaultMotion.durations[duration];
|
|
7275
|
-
}
|
|
7276
|
-
function resolveEasing(easing) {
|
|
7277
|
-
if (easing === void 0) return easings.standard;
|
|
7278
|
-
return typeof easing === "function" ? easing : easings[easing] ?? easings.standard;
|
|
7279
|
-
}
|
|
7280
8859
|
/**
|
|
7281
8860
|
* Moves an element to a visual state, and optionally in from another.
|
|
7282
8861
|
*
|
|
@@ -7319,7 +8898,7 @@ var MotionController = class {
|
|
|
7319
8898
|
attach() {
|
|
7320
8899
|
this.layer.measure(this.host.flowBox());
|
|
7321
8900
|
if (this.args.initial !== void 0) this.layer.snapTo(resolveMotionState(this.args.initial));
|
|
7322
|
-
this.host.
|
|
8901
|
+
this.host.onFlowLayout(() => this.layer.measure(this.host.flowBox()));
|
|
7323
8902
|
this.aim();
|
|
7324
8903
|
}
|
|
7325
8904
|
update(args) {
|
|
@@ -7615,13 +9194,13 @@ var SharedElementController = class {
|
|
|
7615
9194
|
const cell = geometry.cell(field);
|
|
7616
9195
|
const to = targets[field];
|
|
7617
9196
|
(timing.spring !== void 0 ? this.host.spring(cell, to, {
|
|
7618
|
-
spring:
|
|
9197
|
+
spring: springIn(motionOf(this.host.node), timing.spring),
|
|
7619
9198
|
delay: timing.delay,
|
|
7620
9199
|
reducedMotion: timing.reducedMotion,
|
|
7621
9200
|
restDelta: .5
|
|
7622
9201
|
}) : this.host.animate(cell, to, {
|
|
7623
|
-
duration:
|
|
7624
|
-
easing:
|
|
9202
|
+
duration: durationIn(motionOf(this.host.node), timing.duration),
|
|
9203
|
+
easing: easingIn(motionOf(this.host.node), timing.easing),
|
|
7625
9204
|
delay: timing.delay,
|
|
7626
9205
|
reducedMotion: timing.reducedMotion
|
|
7627
9206
|
})).subscribe({ complete: done });
|
|
@@ -8143,7 +9722,7 @@ const scrollPosition = defineModifier({
|
|
|
8143
9722
|
if (settling === 1) args.onSettled?.(offset);
|
|
8144
9723
|
} });
|
|
8145
9724
|
};
|
|
8146
|
-
host.
|
|
9725
|
+
host.onFlowLayout(() => {
|
|
8147
9726
|
const offset = host.scrollOffset();
|
|
8148
9727
|
if (offset === null || last !== null && last.x === offset.x && last.y === offset.y) return;
|
|
8149
9728
|
last = offset;
|
|
@@ -8193,7 +9772,7 @@ const publishInset = defineModifier({
|
|
|
8193
9772
|
if (write === null) write = source.publish(insets);
|
|
8194
9773
|
else write(insets);
|
|
8195
9774
|
};
|
|
8196
|
-
host.
|
|
9775
|
+
host.onFlowLayout(publish);
|
|
8197
9776
|
publish();
|
|
8198
9777
|
host.own(() => {
|
|
8199
9778
|
write?.();
|
|
@@ -8630,11 +10209,9 @@ const DEFAULT_CAPACITY$2 = 32;
|
|
|
8630
10209
|
*
|
|
8631
10210
|
* **What it cannot decode.** `createImageBitmap` takes a `Blob`, and
|
|
8632
10211
|
* Chrome refuses an SVG one — "The source image could not be decoded".
|
|
8633
|
-
*
|
|
8634
|
-
*
|
|
8635
|
-
*
|
|
8636
|
-
* pointing the playground's Media card at an SVG data URL and getting
|
|
8637
|
-
* a placeholder.
|
|
10212
|
+
* Found by pointing the playground's Media card at an SVG data URL and
|
|
10213
|
+
* getting a placeholder. So an `Image` never sends one here: an SVG
|
|
10214
|
+
* source is read by `SvgResolver` and drawn as vectors (`svg/`).
|
|
8638
10215
|
*
|
|
8639
10216
|
* **Eviction.** Reference counting first, LRU second. A bitmap
|
|
8640
10217
|
* something on screen is holding is never evicted, however long the
|
|
@@ -9282,6 +10859,19 @@ fn fs(in: VertexOutput) -> @location(0) vec4f {
|
|
|
9282
10859
|
let color = textureSample(tTexture, tSampler, in.uv);
|
|
9283
10860
|
return vec4f(color.rgb, color.a * in.opacity * alpha);
|
|
9284
10861
|
}
|
|
10862
|
+
|
|
10863
|
+
// An opacity group's layer, blended onto what holds it. The layer was
|
|
10864
|
+
// drawn into with the same blend as the frame, so it holds premultiplied
|
|
10865
|
+
// colour, and is scaled whole — colour and alpha alike — by the group's
|
|
10866
|
+
// opacity and the clip's coverage.
|
|
10867
|
+
@fragment
|
|
10868
|
+
fn fsLayer(in: VertexOutput) -> @location(0) vec4f {
|
|
10869
|
+
let alpha = clipCoverage(in.position.xy, uView.z, in.clipIndex);
|
|
10870
|
+
if (alpha <= 0.0) {
|
|
10871
|
+
discard;
|
|
10872
|
+
}
|
|
10873
|
+
return textureSample(tTexture, tSampler, in.uv) * (in.opacity * alpha);
|
|
10874
|
+
}
|
|
9285
10875
|
`;
|
|
9286
10876
|
//#endregion
|
|
9287
10877
|
//#region src/rendering/webgpu/WebGPUGlyphAtlas.ts
|
|
@@ -10109,6 +11699,13 @@ let CommandKind = /* @__PURE__ */ function(CommandKind) {
|
|
|
10109
11699
|
CommandKind[CommandKind["Glyphs"] = 1] = "Glyphs";
|
|
10110
11700
|
/** One textured instance drawn with an image. */
|
|
10111
11701
|
CommandKind[CommandKind["Image"] = 2] = "Image";
|
|
11702
|
+
/**
|
|
11703
|
+
* Everything until the matching `EndLayer` is drawn into a cleared
|
|
11704
|
+
* layer of its own, the start of an opacity group (`GroupOpacity.ts`).
|
|
11705
|
+
*/
|
|
11706
|
+
CommandKind[CommandKind["BeginLayer"] = 3] = "BeginLayer";
|
|
11707
|
+
/** The layer is done, and is blended onto what holds it at the group's opacity. */
|
|
11708
|
+
CommandKind[CommandKind["EndLayer"] = 4] = "EndLayer";
|
|
10112
11709
|
return CommandKind;
|
|
10113
11710
|
}({});
|
|
10114
11711
|
const IDENTITY = [
|
|
@@ -10223,8 +11820,21 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
|
|
|
10223
11820
|
height: logicalHeight
|
|
10224
11821
|
}
|
|
10225
11822
|
};
|
|
10226
|
-
/**
|
|
11823
|
+
/**
|
|
11824
|
+
* The nodes asking for `lift`, met on the walk and drawn after it,
|
|
11825
|
+
* with their opacity as a product of everything above them: inside a
|
|
11826
|
+
* layer `state.opacity` counts from the layer, and a lifted node is
|
|
11827
|
+
* drawn outside the layers it was met in.
|
|
11828
|
+
*/
|
|
10227
11829
|
const liftedPass = [];
|
|
11830
|
+
/**
|
|
11831
|
+
* The opacity each open layer will be blended at, multiplied out from
|
|
11832
|
+
* the frame: the last entry is what `state.opacity` is relative to.
|
|
11833
|
+
* One entry, 1, while no layer is open.
|
|
11834
|
+
*/
|
|
11835
|
+
const layerOpacities = [1];
|
|
11836
|
+
const physicalWidth = toPhysicalPixels(logicalWidth, dpr);
|
|
11837
|
+
const physicalHeight = toPhysicalPixels(logicalHeight, dpr);
|
|
10228
11838
|
function closePrimitives() {
|
|
10229
11839
|
const end = instanceData.length / 24;
|
|
10230
11840
|
if (end > openStart) commands.push({
|
|
@@ -10344,7 +11954,7 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
|
|
|
10344
11954
|
if (rec.lifted && !lifted) {
|
|
10345
11955
|
liftedPass.push({
|
|
10346
11956
|
node,
|
|
10347
|
-
opacity: state.opacity,
|
|
11957
|
+
opacity: state.opacity * layerOpacities[layerOpacities.length - 1],
|
|
10348
11958
|
ctm: state.ctm
|
|
10349
11959
|
});
|
|
10350
11960
|
return;
|
|
@@ -10352,9 +11962,33 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
|
|
|
10352
11962
|
const sticky = rec.stickyOffsetX !== 0 || rec.stickyOffsetY !== 0;
|
|
10353
11963
|
if (state.cull !== null && !intersectsCull(state.cull, rec.extentMinX + rec.stickyOffsetX, rec.extentMinY + rec.stickyOffsetY, rec.extentMaxX - rec.extentMinX, rec.extentMaxY - rec.extentMinY)) return;
|
|
10354
11964
|
const paint = resolvePaintState(node, paintScratch);
|
|
10355
|
-
const
|
|
11965
|
+
const layered = paint.opacity < 1 && opacityNeedsLayer(node, rec, paint, layout);
|
|
11966
|
+
const opacity = paint.opacity;
|
|
11967
|
+
const outerOpacity = state.opacity;
|
|
11968
|
+
const outerClip = state.clip;
|
|
11969
|
+
const outerRounded = state.rounded;
|
|
11970
|
+
const effectiveOpacity = layered ? 1 : state.opacity * paint.opacity;
|
|
10356
11971
|
const baseCtm = sticky ? translateTransform(state.ctm, rec.stickyOffsetX, rec.stickyOffsetY) : state.ctm;
|
|
10357
11972
|
const nodeCtm = paint.hasTransform ? multiplyTransform(baseCtm, buildOwnTransform(rec, paint.transform)) : baseCtm;
|
|
11973
|
+
const region = layered ? layerRegion(node, rec, {
|
|
11974
|
+
a: nodeCtm[0] * dpr,
|
|
11975
|
+
b: nodeCtm[1] * dpr,
|
|
11976
|
+
c: nodeCtm[2] * dpr,
|
|
11977
|
+
d: nodeCtm[3] * dpr,
|
|
11978
|
+
e: nodeCtm[4] * dpr,
|
|
11979
|
+
f: nodeCtm[5] * dpr
|
|
11980
|
+
}, layout, physicalWidth, physicalHeight, dpr) : null;
|
|
11981
|
+
if (layered) {
|
|
11982
|
+
closePrimitives();
|
|
11983
|
+
commands.push({
|
|
11984
|
+
kind: 3,
|
|
11985
|
+
scissor: null
|
|
11986
|
+
});
|
|
11987
|
+
layerOpacities.push(layerOpacities[layerOpacities.length - 1] * outerOpacity * opacity);
|
|
11988
|
+
state.opacity = 1;
|
|
11989
|
+
state.clip = null;
|
|
11990
|
+
state.rounded = -1;
|
|
11991
|
+
}
|
|
10358
11992
|
const ownScissor = state.clip;
|
|
10359
11993
|
const ownRounded = state.rounded;
|
|
10360
11994
|
let nextClip = state.clip;
|
|
@@ -10624,6 +12258,24 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
|
|
|
10624
12258
|
beginPrimitives(ownScissor);
|
|
10625
12259
|
pushScrollbars(instanceData, rec, effectiveOpacity, nodeCtm, ownRounded, now);
|
|
10626
12260
|
}
|
|
12261
|
+
if (region !== null) {
|
|
12262
|
+
closePrimitives();
|
|
12263
|
+
layerOpacities.pop();
|
|
12264
|
+
state.opacity = outerOpacity;
|
|
12265
|
+
state.clip = outerClip;
|
|
12266
|
+
state.rounded = outerRounded;
|
|
12267
|
+
const instance = region.width > 0 && region.height > 0 ? pushTextured(texturedData, region.x / dpr, region.y / dpr, region.width / dpr, region.height / dpr, outerOpacity * opacity, IDENTITY, outerRounded, {
|
|
12268
|
+
u: region.x / physicalWidth,
|
|
12269
|
+
v: region.y / physicalHeight,
|
|
12270
|
+
uw: region.width / physicalWidth,
|
|
12271
|
+
vh: region.height / physicalHeight
|
|
12272
|
+
}) : null;
|
|
12273
|
+
commands.push({
|
|
12274
|
+
kind: 4,
|
|
12275
|
+
instance,
|
|
12276
|
+
scissor: outerClip
|
|
12277
|
+
});
|
|
12278
|
+
}
|
|
10627
12279
|
}
|
|
10628
12280
|
/**
|
|
10629
12281
|
* The top layer, drawn once the tree is done: no clip, no scissor,
|
|
@@ -10643,7 +12295,7 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
|
|
|
10643
12295
|
};
|
|
10644
12296
|
for (let i = from; i < liftedPass.length; i++) {
|
|
10645
12297
|
const entry = liftedPass[i];
|
|
10646
|
-
state.opacity = entry.opacity;
|
|
12298
|
+
state.opacity = entry.opacity / layerOpacities[layerOpacities.length - 1];
|
|
10647
12299
|
state.ctm = entry.ctm;
|
|
10648
12300
|
state.clip = clip;
|
|
10649
12301
|
state.rounded = rounded;
|
|
@@ -10809,7 +12461,7 @@ function pushInstance(out, x, y, width, height, color, radius, opacity, borderWi
|
|
|
10809
12461
|
*/
|
|
10810
12462
|
function pushShadows(out, rec, paint, opacity, transform, clip, inset) {
|
|
10811
12463
|
const shadows = paint.boxShadows;
|
|
10812
|
-
const radius = borderRadiusIsZero(paint.borderRadius) ? 0 : uniformBorderRadius(paint.borderRadius);
|
|
12464
|
+
const radius = borderRadiusIsZero(paint.borderRadius) ? 0 : fittedShadowRadius(uniformBorderRadius(paint.borderRadius), rec.width, rec.height);
|
|
10813
12465
|
for (let i = shadows.length - 1; i >= 0; i--) {
|
|
10814
12466
|
const shadow = shadows[i];
|
|
10815
12467
|
if (shadow.inset === true !== inset || shadow.color.a <= 0) continue;
|
|
@@ -11125,6 +12777,19 @@ const STRAIGHT_ALPHA_BLEND = {
|
|
|
11125
12777
|
operation: "add"
|
|
11126
12778
|
}
|
|
11127
12779
|
};
|
|
12780
|
+
/** Source-over for a premultiplied source: an opacity group's layer. */
|
|
12781
|
+
const PREMULTIPLIED_BLEND = {
|
|
12782
|
+
color: {
|
|
12783
|
+
srcFactor: "one",
|
|
12784
|
+
dstFactor: "one-minus-src-alpha",
|
|
12785
|
+
operation: "add"
|
|
12786
|
+
},
|
|
12787
|
+
alpha: {
|
|
12788
|
+
srcFactor: "one",
|
|
12789
|
+
dstFactor: "one-minus-src-alpha",
|
|
12790
|
+
operation: "add"
|
|
12791
|
+
}
|
|
12792
|
+
};
|
|
11128
12793
|
/**
|
|
11129
12794
|
* Creates the primitive pipeline: rounded fills and borders.
|
|
11130
12795
|
*
|
|
@@ -11204,8 +12869,9 @@ function createTexturedPipeline(device, format, frameLayout) {
|
|
|
11204
12869
|
texture: {}
|
|
11205
12870
|
}
|
|
11206
12871
|
] });
|
|
12872
|
+
const layout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout, frameLayout] });
|
|
11207
12873
|
const pipeline = device.createRenderPipeline({
|
|
11208
|
-
layout
|
|
12874
|
+
layout,
|
|
11209
12875
|
vertex: {
|
|
11210
12876
|
module,
|
|
11211
12877
|
entryPoint: "vs",
|
|
@@ -11224,7 +12890,27 @@ function createTexturedPipeline(device, format, frameLayout) {
|
|
|
11224
12890
|
cullMode: "none"
|
|
11225
12891
|
}
|
|
11226
12892
|
});
|
|
11227
|
-
|
|
12893
|
+
const layerPipeline = device.createRenderPipeline({
|
|
12894
|
+
layout,
|
|
12895
|
+
vertex: {
|
|
12896
|
+
module,
|
|
12897
|
+
entryPoint: "vs",
|
|
12898
|
+
buffers: [quadVertexLayout(), texturedInstanceLayout()]
|
|
12899
|
+
},
|
|
12900
|
+
fragment: {
|
|
12901
|
+
module,
|
|
12902
|
+
entryPoint: "fsLayer",
|
|
12903
|
+
targets: [{
|
|
12904
|
+
format,
|
|
12905
|
+
blend: PREMULTIPLIED_BLEND
|
|
12906
|
+
}]
|
|
12907
|
+
},
|
|
12908
|
+
primitive: {
|
|
12909
|
+
topology: "triangle-list",
|
|
12910
|
+
cullMode: "none"
|
|
12911
|
+
}
|
|
12912
|
+
});
|
|
12913
|
+
if (pipeline === void 0 || layerPipeline === void 0) throw new WebGPUError("Failed to create textured render pipeline.", "pipeline");
|
|
11228
12914
|
const { vertexBuffer, indexBuffer } = createQuad(device);
|
|
11229
12915
|
const uniformBuffer = device.createBuffer({
|
|
11230
12916
|
size: 16,
|
|
@@ -11244,7 +12930,8 @@ function createTexturedPipeline(device, format, frameLayout) {
|
|
|
11244
12930
|
imageSampler: device.createSampler({
|
|
11245
12931
|
magFilter: "linear",
|
|
11246
12932
|
minFilter: "linear"
|
|
11247
|
-
})
|
|
12933
|
+
}),
|
|
12934
|
+
layerPipeline
|
|
11248
12935
|
};
|
|
11249
12936
|
}
|
|
11250
12937
|
function createQuad(device) {
|
|
@@ -11673,6 +13360,7 @@ function createCanvas(width, height) {
|
|
|
11673
13360
|
const vertexBufferUsage = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage.VERTEX : 32;
|
|
11674
13361
|
const storageBufferUsage = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage.STORAGE : 128;
|
|
11675
13362
|
const copyDstBufferUsage = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage.COPY_DST : 8;
|
|
13363
|
+
const layerTextureUsage = (typeof GPUTextureUsage !== "undefined" ? GPUTextureUsage.RENDER_ATTACHMENT : 16) | (typeof GPUTextureUsage !== "undefined" ? GPUTextureUsage.TEXTURE_BINDING : 4);
|
|
11676
13364
|
const copyDstUsage = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage.COPY_DST : 8;
|
|
11677
13365
|
const mapReadUsage = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage.MAP_READ : 1;
|
|
11678
13366
|
const mapModeRead = typeof GPUMapMode !== "undefined" ? GPUMapMode.READ : 1;
|
|
@@ -11705,6 +13393,12 @@ var WebGPURenderer = class {
|
|
|
11705
13393
|
frameLayout = null;
|
|
11706
13394
|
frameBindGroup = null;
|
|
11707
13395
|
cachedInstanceData = null;
|
|
13396
|
+
/**
|
|
13397
|
+
* The textures opacity groups are drawn in, one per depth of nesting,
|
|
13398
|
+
* so a group inside a group never clears what holds it. Made the first
|
|
13399
|
+
* time a frame reaches that depth and again when the surface resizes.
|
|
13400
|
+
*/
|
|
13401
|
+
groupLayers = [];
|
|
11708
13402
|
/** Draw calls of the most recent frame. */
|
|
11709
13403
|
lastDraws = {
|
|
11710
13404
|
primitiveDraws: 0,
|
|
@@ -11847,8 +13541,9 @@ var WebGPURenderer = class {
|
|
|
11847
13541
|
const encodeStart = this.hooks.onEncodeStart !== void 0 || this.hooks.onEncodeEnd !== void 0 ? performance.now() : 0;
|
|
11848
13542
|
const commandEncoder = device.createCommandEncoder();
|
|
11849
13543
|
const texture = this.surface.getCurrentTexture();
|
|
11850
|
-
const
|
|
11851
|
-
|
|
13544
|
+
const targets = [texture.createView()];
|
|
13545
|
+
let pass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
13546
|
+
view: targets[0],
|
|
11852
13547
|
clearValue: {
|
|
11853
13548
|
r: 0,
|
|
11854
13549
|
g: 0,
|
|
@@ -11868,6 +13563,49 @@ var WebGPURenderer = class {
|
|
|
11868
13563
|
const commands = list.commands;
|
|
11869
13564
|
for (let i = 0; i < commands.length; i++) {
|
|
11870
13565
|
const command = commands[i];
|
|
13566
|
+
if (command.kind === 3 || command.kind === 4) {
|
|
13567
|
+
pass.end();
|
|
13568
|
+
const depth = targets.length - 1;
|
|
13569
|
+
let layer;
|
|
13570
|
+
if (command.kind === 3) {
|
|
13571
|
+
layer = this.groupLayer(device, depth, texture.width, texture.height);
|
|
13572
|
+
targets.push(layer.view);
|
|
13573
|
+
} else {
|
|
13574
|
+
layer = this.groupLayers[depth - 1];
|
|
13575
|
+
targets.pop();
|
|
13576
|
+
}
|
|
13577
|
+
pass = commandEncoder.beginRenderPass({ colorAttachments: [command.kind === 3 ? {
|
|
13578
|
+
view: layer.view,
|
|
13579
|
+
clearValue: {
|
|
13580
|
+
r: 0,
|
|
13581
|
+
g: 0,
|
|
13582
|
+
b: 0,
|
|
13583
|
+
a: 0
|
|
13584
|
+
},
|
|
13585
|
+
loadOp: "clear",
|
|
13586
|
+
storeOp: "store"
|
|
13587
|
+
} : {
|
|
13588
|
+
view: targets[targets.length - 1],
|
|
13589
|
+
loadOp: "load",
|
|
13590
|
+
storeOp: "store"
|
|
13591
|
+
}] });
|
|
13592
|
+
pass.setBindGroup(1, this.frameBindGroup);
|
|
13593
|
+
currentScissor = null;
|
|
13594
|
+
currentKind = null;
|
|
13595
|
+
if (command.kind === 3) continue;
|
|
13596
|
+
const blendScissor = command.scissor ?? viewport;
|
|
13597
|
+
if (command.instance === null || blendScissor.width <= 0 || blendScissor.height <= 0) continue;
|
|
13598
|
+
pass.setPipeline(textured.layerPipeline);
|
|
13599
|
+
pass.setVertexBuffer(0, textured.vertexBuffer);
|
|
13600
|
+
pass.setVertexBuffer(1, this.texturedBuffer);
|
|
13601
|
+
pass.setIndexBuffer(textured.indexBuffer, "uint16");
|
|
13602
|
+
pass.setBindGroup(0, layer.bindGroup);
|
|
13603
|
+
currentScissor = setScissor(pass, currentScissor, blendScissor);
|
|
13604
|
+
pass.drawIndexed(textured.indexCount, 1, 0, 0, command.instance);
|
|
13605
|
+
this.lastDraws.texturedDraws++;
|
|
13606
|
+
this.lastDraws.texturedInstances++;
|
|
13607
|
+
continue;
|
|
13608
|
+
}
|
|
11871
13609
|
const scissor = command.scissor ?? viewport;
|
|
11872
13610
|
if (scissor.width <= 0 || scissor.height <= 0) continue;
|
|
11873
13611
|
if (command.kind === 0) {
|
|
@@ -11909,6 +13647,24 @@ var WebGPURenderer = class {
|
|
|
11909
13647
|
capture?.();
|
|
11910
13648
|
if (this.hooks.onEncodeEnd !== void 0) this.hooks.onEncodeEnd(performance.now() - encodeStart);
|
|
11911
13649
|
}
|
|
13650
|
+
/** The layer for an opacity group at `depth`, made or remade at the surface's size. */
|
|
13651
|
+
groupLayer(device, depth, width, height) {
|
|
13652
|
+
const existing = this.groupLayers[depth];
|
|
13653
|
+
if (existing !== void 0 && existing.texture.width === width && existing.texture.height === height) return existing;
|
|
13654
|
+
existing?.texture.destroy();
|
|
13655
|
+
const texture = device.createTexture({
|
|
13656
|
+
size: [width, height],
|
|
13657
|
+
format: this.format,
|
|
13658
|
+
usage: layerTextureUsage
|
|
13659
|
+
});
|
|
13660
|
+
const layer = {
|
|
13661
|
+
texture,
|
|
13662
|
+
view: texture.createView(),
|
|
13663
|
+
bindGroup: createTexturedBindGroup(device, this.textured, texture, this.textured.textSampler)
|
|
13664
|
+
};
|
|
13665
|
+
this.groupLayers[depth] = layer;
|
|
13666
|
+
return layer;
|
|
13667
|
+
}
|
|
11912
13668
|
/**
|
|
11913
13669
|
* When a capture is pending, encodes a copy of the frame into a
|
|
11914
13670
|
* mappable buffer and returns the step that maps it after submit.
|
|
@@ -12034,6 +13790,8 @@ var WebGPURenderer = class {
|
|
|
12034
13790
|
this.clipBuffer = null;
|
|
12035
13791
|
this.gradientBuffer?.destroy();
|
|
12036
13792
|
this.gradientBuffer = null;
|
|
13793
|
+
for (const layer of this.groupLayers) layer.texture.destroy();
|
|
13794
|
+
this.groupLayers = [];
|
|
12037
13795
|
this.frameBindGroup = null;
|
|
12038
13796
|
this.frameLayout = null;
|
|
12039
13797
|
this.primitives = null;
|
|
@@ -14723,6 +16481,14 @@ const NEW_DISPLAY = .15;
|
|
|
14723
16481
|
const BEAT_PASSES = 3;
|
|
14724
16482
|
/** Refreshes apart the two an interval is read between must be, at least. */
|
|
14725
16483
|
const MIN_BEAT_SPAN = 6;
|
|
16484
|
+
/**
|
|
16485
|
+
* The most refreshes a frame waits for a tick that has not sat in this
|
|
16486
|
+
* thread's queue (see `tick`): a frame is drawn on a stale one after
|
|
16487
|
+
* that, so a thread that is always behind still draws.
|
|
16488
|
+
*/
|
|
16489
|
+
const MAX_QUEUED_REFRESHES = 8;
|
|
16490
|
+
/** How long, at least, between timing this thread's refreshes again once they were given up; see `retryLocal`. */
|
|
16491
|
+
const RETRY_LOCAL_MS = 2e3;
|
|
14726
16492
|
/** The most refreshes a frame is given: two, sixty at 120 Hz; slower than that is no smoother. */
|
|
14727
16493
|
const MAX_STRIDE = 2;
|
|
14728
16494
|
function localAnimationFrame() {
|
|
@@ -14776,6 +16542,8 @@ var UiHostFrameClock = class {
|
|
|
14776
16542
|
/** Gaps between this thread's own refreshes while frames ran back to back. */
|
|
14777
16543
|
localIntervals = [];
|
|
14778
16544
|
lastLocalTime = null;
|
|
16545
|
+
/** How long the frame drawn at `lastLocalTime` took, which says whether the gap after it was the refresh's or the frame's. */
|
|
16546
|
+
lastLocalCost = 0;
|
|
14779
16547
|
/** The display's interval and the host's time origin, once the host has said. */
|
|
14780
16548
|
display = null;
|
|
14781
16549
|
/**
|
|
@@ -14784,6 +16552,11 @@ var UiHostFrameClock = class {
|
|
|
14784
16552
|
* show (see \`displayInterval\`).
|
|
14785
16553
|
*/
|
|
14786
16554
|
locked = false;
|
|
16555
|
+
/** This thread's own `requestAnimationFrame` once given up, kept to see whether it keeps time after all. */
|
|
16556
|
+
givenUp = null;
|
|
16557
|
+
/** When this thread's refreshes were last timed again, and their times while they are being. */
|
|
16558
|
+
lastRetry = -Infinity;
|
|
16559
|
+
retrying = null;
|
|
14787
16560
|
/** The host's recent refresh times, which give the beat its phase. */
|
|
14788
16561
|
refreshes = [];
|
|
14789
16562
|
/** Host refreshes since the last frame drawn on the beat. */
|
|
@@ -14818,7 +16591,11 @@ var UiHostFrameClock = class {
|
|
|
14818
16591
|
if (this.localHandle === null) this.localHandle = this.local((time) => {
|
|
14819
16592
|
this.localHandle = null;
|
|
14820
16593
|
this.recordLocal(time);
|
|
14821
|
-
if (this.pending)
|
|
16594
|
+
if (this.pending) {
|
|
16595
|
+
const started = this.now();
|
|
16596
|
+
this.deliver(time);
|
|
16597
|
+
this.lastLocalCost = this.now() - started;
|
|
16598
|
+
} else this.lastLocalCost = 0;
|
|
14822
16599
|
});
|
|
14823
16600
|
return;
|
|
14824
16601
|
}
|
|
@@ -14873,7 +16650,7 @@ var UiHostFrameClock = class {
|
|
|
14873
16650
|
this.lastLocalTime = time;
|
|
14874
16651
|
if (previous !== null && this.pending) {
|
|
14875
16652
|
const interval = time - previous;
|
|
14876
|
-
if (interval > 0 && interval < MAX_CADENCE_MS) {
|
|
16653
|
+
if (interval > 0 && interval < MAX_CADENCE_MS && this.lastLocalCost < interval / 2) {
|
|
14877
16654
|
this.localIntervals.push(interval);
|
|
14878
16655
|
if (this.localIntervals.length > LOCAL_SAMPLES) this.localIntervals.shift();
|
|
14879
16656
|
}
|
|
@@ -14891,9 +16668,59 @@ var UiHostFrameClock = class {
|
|
|
14891
16668
|
if (this.local === null || this.display === null || this.localIntervals.length < LOCAL_SAMPLES) return;
|
|
14892
16669
|
const sorted = [...this.localIntervals].sort((a, b) => a - b);
|
|
14893
16670
|
if (sorted[Math.floor(sorted.length / 2)] / this.display.interval <= 1.06) return;
|
|
16671
|
+
this.givenUp = this.local;
|
|
14894
16672
|
this.local = null;
|
|
14895
16673
|
this.locked = true;
|
|
14896
16674
|
}
|
|
16675
|
+
/**
|
|
16676
|
+
* Times this thread's own refreshes again, now and then, once they were
|
|
16677
|
+
* given up, and takes them back if they keep the display's time.
|
|
16678
|
+
*
|
|
16679
|
+
* Given up for good, a worker's refreshes stayed given up whatever had
|
|
16680
|
+
* made them slow. A Chrome worker busy between its frames, opening a
|
|
16681
|
+
* large file or scrolling through lines it has never drawn, refreshes
|
|
16682
|
+
* late and unevenly, which no measure of the gaps alone told from
|
|
16683
|
+
* WebKit's timer: 5.5, 25.5, 10.9, 25 ms on a 165 Hz display one time,
|
|
16684
|
+
* 12.1, 6.1, 12.2, 24.2 the next. Its frames then went through the
|
|
16685
|
+
* host's ticks for as long as the page was open, a message and a hop
|
|
16686
|
+
* through the page's thread each. Timed again while frames are cheap,
|
|
16687
|
+
* they come a refresh apart and are taken back; WebKit's come 15 ms
|
|
16688
|
+
* apart on a 120 Hz display however idle the thread is, and stay given
|
|
16689
|
+
* up. Nine refreshes every two seconds or more, while frames come cheap.
|
|
16690
|
+
*/
|
|
16691
|
+
retryLocal(interval) {
|
|
16692
|
+
if (this.givenUp === null || this.hidden || this.costs.length < SLOW_SAMPLES) return;
|
|
16693
|
+
const now = this.now();
|
|
16694
|
+
if (now - this.lastRetry < RETRY_LOCAL_MS) return;
|
|
16695
|
+
const sorted = [...this.costs].sort((a, b) => a - b);
|
|
16696
|
+
if (sorted[Math.floor(sorted.length / 2)] > interval * FAST_FRAME) return;
|
|
16697
|
+
this.lastRetry = now;
|
|
16698
|
+
const local = this.givenUp;
|
|
16699
|
+
const times = [];
|
|
16700
|
+
this.retrying = times;
|
|
16701
|
+
const step = (time) => {
|
|
16702
|
+
if (this.retrying !== times) return;
|
|
16703
|
+
times.push(time);
|
|
16704
|
+
if (times.length <= LOCAL_SAMPLES) {
|
|
16705
|
+
local(step);
|
|
16706
|
+
return;
|
|
16707
|
+
}
|
|
16708
|
+
this.retrying = null;
|
|
16709
|
+
const gaps = times.slice(1).map((value, index) => value - times[index]).sort((a, b) => a - b);
|
|
16710
|
+
if (this.givenUp === null || this.display === null || gaps[Math.floor(gaps.length / 2)] / this.display.interval > 1.06) return;
|
|
16711
|
+
this.local = this.givenUp;
|
|
16712
|
+
this.givenUp = null;
|
|
16713
|
+
this.locked = false;
|
|
16714
|
+
this.localIntervals.length = 0;
|
|
16715
|
+
this.lastLocalTime = null;
|
|
16716
|
+
this.costs.length = 0;
|
|
16717
|
+
this.stride = 1;
|
|
16718
|
+
this.clearFallback();
|
|
16719
|
+
this.setActive(false);
|
|
16720
|
+
if (this.pending) this.requestFrame();
|
|
16721
|
+
};
|
|
16722
|
+
local(step);
|
|
16723
|
+
}
|
|
14897
16724
|
/** A time on the host's clock, on this thread's. */
|
|
14898
16725
|
toLocal(host) {
|
|
14899
16726
|
return this.display === null ? host : host - this.timeOrigin + this.display.origin;
|
|
@@ -14959,6 +16786,7 @@ var UiHostFrameClock = class {
|
|
|
14959
16786
|
const started = this.now();
|
|
14960
16787
|
this.deliver(this.toLocal(refresh));
|
|
14961
16788
|
this.recordCost(this.now() - started, beat.interval);
|
|
16789
|
+
this.retryLocal(beat.interval);
|
|
14962
16790
|
}
|
|
14963
16791
|
/**
|
|
14964
16792
|
* Keeps the stride to what frames cost: every refresh while the typical
|
|
@@ -15120,7 +16948,8 @@ var UiHostFrameClock = class {
|
|
|
15120
16948
|
if (this.pending) {
|
|
15121
16949
|
this.clearFallback();
|
|
15122
16950
|
this.idleTicks = 0;
|
|
15123
|
-
|
|
16951
|
+
const queued = this.now() - this.toLocal(time) > this.display.interval;
|
|
16952
|
+
if (this.refreshesSinceFrame >= this.stride && (!queued || this.refreshesSinceFrame >= MAX_QUEUED_REFRESHES)) this.deliverOnBeat(time);
|
|
15124
16953
|
return;
|
|
15125
16954
|
}
|
|
15126
16955
|
}
|
|
@@ -16114,6 +17943,6 @@ function recordsEqual(a, b) {
|
|
|
16114
17943
|
return true;
|
|
16115
17944
|
}
|
|
16116
17945
|
//#endregion
|
|
16117
|
-
export { AlignContent, AnimationDriver, BOX_SHADOW_BLUR_REACH, BUTTON_INTERACTION, Box, Button, CARET_BLINK_MS, CARET_WIDTH, CLIP_STRIDE_BYTES, CLIP_STRIDE_FLOATS, Canvas2DRenderer, CanvasPlatformSurface, CanvasSurface, CanvasTextMeasurer, CharacterCountTextMeasurer, Column, CommandKind, Constraints, CrossAxisAlignment, DEFAULT_BLOCK_SIZE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_FONT_WEIGHT, DEFAULT_LINE_HEIGHT_FACTOR, DEFAULT_TEXT_COLOR, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, DirtyNodeSet, EDITOR_PROP, ELLIPSIS, EMPTY_RECORDING, EXTERNAL_FILES, EditableLayout, EditableText, EditableTextModel, EnvironmentNotifier, FlexDirection, FocusNotifier, GLYPH_SUBPIXEL_PHASES, GlyphShaper, Grid, INSPECTOR_HEAT_MS, INSTANCE_STRIDE_BYTES, INSTANCE_STRIDE_FLOATS, IconRasterizer, LABEL_PADDING_X, LayoutEngine, LayoutInspector, LayoutNotifier, LayoutRecord, LazyColumn, LazyGrid, LazyRow, LazySheet, MARK_PREFIX, MAX_GRADIENT_STOPS, MAX_MEASURES_PER_CHILD, MOTION_CHANNELS, MOTION_REST, MOUSE_POINTER, MainAxisAlignment, Mp3Frames, NO_CLIP_INDEX, NodeTransitions, Paint, PaintPictureCache, PaintRecorder, PaintTarget, ParagraphTextMeasurer, PrimitiveKind, Responsive, Row, SCROLLBAR_FADE_MS, SCROLLBAR_HOVER_ZONE, SCROLLBAR_INSET, SCROLLBAR_LINGER_MS, SCROLLBAR_MIN_THUMB, SCROLLBAR_THICKNESS, ScrollView, Stack, TEXTURED_STRIDE_BYTES, TEXTURED_STRIDE_FLOATS, TEXT_MATCHES_PROP, TEXT_RUN_ID_SEPARATOR, TEXT_SELECTION_PROP, Text, UI_ROLES, UI_SEMANTIC_STATES, UI_SUBGRID_AXES, UiAnimation, UiAnimationFrameClock, UiBasicColors, UiBeforeInputEvent, UiBinding, UiBorderRadiuses, UiChildrenBinding, UiCompositionEvent, UiContainerSizeSource, UiDragSession, UiEditingController, UiEnvironment, UiEnvironmentKeys, UiEventBinding, UiEventType, UiFindController, UiFocusEvent, UiFocusManager, UiFrame, UiGestureEvent, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInputEvent, UiInsetRegistry, UiKeyboardController, UiKeyboardEvent, UiManualFrameClock, UiModifierSet, UiNode, UiNodeType, UiPasteEvent, UiPinchEvent, UiPinchRecognizer, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiProperties, UiScheduler, UiSelectionChangeEvent, UiSelectionController, UiSharedElements, UiShortcutRegistry, UiSpring, UiTextChangeEvent, UiTimerFrameClock, UiTouchScroller, UiTransforms, UiTween, UiUnknownModifierPropertyError, UiVirtualSheet, UiVirtualWindow, UiVisualState, UiWheelController, UiWheelDeltaMode, UiWheelEvent, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPUError, WebGPUGlyphAtlas, WebGPUGlyphPages, WebGPURenderer, WebGPUSurface, WebGPUTextureCache, accumulatedOffsetTo, adjacentField, animateLayout, applyCanvasTextStyle, assertModifierList, assertTransitionMap, auto, autoFocus, bandOf, borderRadius, borderRadiusCorners, borderRadiusEqual, borderRadiusIsZero, borderRadiusValuesEqual, borderShapes, borders, boxShadow, boxShadowArraysEqual, boxShadowRadius, boxShadowReach, boxShadowsEqual, breakpoint, bufferSource, buildFontString, buildRenderList, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, bundle, canDecodeVideo, capturePointer, caretRectFor, caretVisibleAt, clampSize, clearFontStacks, clearLinkHover, clearMatchRanges, clearOverrideProperty, clearSelectionRange, clickOutside, closestPropertyName, colorToCss, colorToHex, colorToRgba, colorValuesEqual, colorsEqual, colorsEqualPalette, commandForKey, comparePositions, computeObjectFitRect, constraintsEqual, containerBands, contentOffset, contextMenu, contrastRatio, createCanvasSurface, createCanvasTextStyle, createElement, createEnvironmentKey, createPaintState, createTextCache, createTween, createWebGPUSurface, cubicBezier, cueAt, darkColors, darkTheme, decorated, decorationColor, decorationRect, defaultMotion, defaultShadows, defaultShapes, defaultSpacing, defaultTextStyle, defaultTypography, defaultVisualState, defineModifier, defineProperty, defineThemeExtension, demuxMp4Audio, demuxMp4Video, densityFactors, describeLength, describeOverrides, describeShortcut, detectEditingPlatform, diffSemantics, dragSessionFor, dragSource, draggable, drawOverlayShapes, drawText, dropIndexShifts, dropTarget, easings, edgeField, editableSpansOf, editingGroupOf, editorFor, editorOf, fade, fillTextRects, findEnvironmentKey, findMatchesIn, findPropertyDefinition, flattenTextSpans, focusRing, fontStackFor, formatConstraints, formatExplanation, formatShortcut, fr, getPropertyNames, glyphCount, gradientPaint, gradientsEqual, graphemeBoundaries, hasDecorationPhase, hasDrawnText, hasTextLinks, hasThemeExtension, hasVisualState, hiddenRangesOf, highContrastColors, hoverable, iconKey, iconSource, imageSource, inheritedPropertyFlags, inheritedPropertyNames, initializeWebGPU, inputsEqual, insetPadding, insetsEqual, interactive, interpolateColor, interpolateNumber, interpolateTransform, interpolatorFor, isAutoLength, isComponentLikeElement, isEditableNode, isFrLength, isImeKey, isLayoutProtocol, isMinMaxTrack, isMotionRest, isMultiline, isNodeFocusable, isNodeHitTestable, isNodeInert, isNodeSelectable, isNodeTabStop, isObservable, isPaintVisible, isPercentLength, isPrintable, isReadOnly, isScrollContainer, isShapeName, isSubtreeInert, isTypographyRole, isUiChild, isUiElement, isUiModifier, isUiRole, isUiSemanticState, isVideoSurface, isWebGPUAvailable, labelNode, labelOrigin, layoutParagraph, layoutTextLines, lazySource, lightColors, lightTheme, lineEndAt, lineIndexAtY, lineIndexForOffset, lineLimit, lineStartAt, linear, linearGradient, linkHoverOf, linkOf, linkRunAtPointIn, markInstant, markNow, matchRangesOf, measure, measureFlow, measureSpan, minmax, motion, nextCaretToggle, nextGraphemeEnd, nextWordEnd, noInsets, noKeyModifiers, noModifiers, noThemeExtensions, normalizeBorderRadius, normalizeColor, normalizeStates, normalizeTextAlign, normalizeTransition, normalizeVerticalAlign, observeViewportInsets, offsetAtPoint, offsetAtPointIn, offsetAtX, offsetForVerticalMove, overrideSources, paintPictures, paintValuesEqual, paintsAfterChildren, paragraphGeometry, paragraphGeometryFrom, parseAlignContent, parseColor, parseCrossAxisAlignment, parseFlexDirection, parseMainAxisAlignment, parseShortcut, parseTransform, parseWebVtt, pathValuesEqual, percent, performanceMarksEnabled, pinchable, placeGridItems, pointerDeviceOf, pointerEventsOf, pop, prepareInputSurface, pressable, previousGraphemeStart, previousWordStart, propertyEffects, propertyIsInherited, propertyNarrowers, propertyValuesEqual, propertyValuesEqualByName, proportionalFontMetrics, provideEnvironment, publishInset, radialGradient, raiseContrast, rangeSource, readMp3Header, recordsEqual, registerFontStack, relativeLuminance, reorderable, repeat, replayPaint, resetOverrideWarnings, resetTransitionWarnings, resolveBoolean, resolveCursor, resolveLength, resolveMotionState, resolveNumber, resolvePaintState, resolveProperty, resolvePropertyByName, resolveString, resolvedSpansOf, rewalkSemantics, rgb8, rgba, rotateFrom, runCanvasStyleInto, runFontString, sameArgs, scaleFrom, scaleSpacing, scrollBounds, scrollLeaderOf, scrollPosition, scrollRange, scrollbarThumb, scrollbarThumbs, scrollbarZoneAt, scrollsFromRight, selectableTextNodes, selectableTextOf, selectionRangeOf, selectionRects, selectionRectsIn, semanticsInertAbove, semanticsMemory, setLinkHover, setMatchRanges, setPerformanceMarks, setSelectionRange, shadowsEqual, shapesEqual, sharedElement, sheetSource, shortcut, shortcutKeyCaps, shortcuts, sizeContainer, sizeGridTracks, slideDown, slideFrom, slideUp, spacingEqual, spacingSteps, spanAtOffset, spannedRunsFor, spannedTextOf, spansDifferOnlyInPaint, splitMp3Frames, spring, statesEqual, steps, syncEditorValue, textContentOf, textInputOf, textRunBackgrounds, textRunDecorations, textRunOfRecordId, textRuns, textSpansEqual, textStylesEqual, themeExtension, themeExtensionsEqual, themeTokens, themesEqual, tightenConstraints, touchActionFor, tracePathData, traceRoundedRect, transform, transformIsIdentity, transformsEqual, tween, typographyEqual, uniformBorderRadius, unknownContainerSize, validateGradient, validateRole, validateStates, validateSubgrid, videoFrameSize, videoSource, visualState, visualStatesEqual, wheelDeltaYOf, withContrast, withDensity, withOverrideSource, withThemeExtension, wordRangeAt, wordRangeIn, writeDeclaredProperty, writeOverrideProperty };
|
|
17946
|
+
export { AlignContent, AnimationDriver, BOX_SHADOW_BLUR_REACH, BUTTON_INTERACTION, Box, Button, CARET_BLINK_MS, CARET_WIDTH, CLIP_STRIDE_BYTES, CLIP_STRIDE_FLOATS, Canvas2DRenderer, CanvasPlatformSurface, CanvasSurface, CanvasTextMeasurer, CharacterCountTextMeasurer, Column, CommandKind, Constraints, CrossAxisAlignment, DEFAULT_BLOCK_SIZE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_FONT_WEIGHT, DEFAULT_LINE_HEIGHT_FACTOR, DEFAULT_TEXT_COLOR, DefaultImageResolver, DefaultSvgResolver, DefaultVideoResolver, DirtyFlags, DirtyNodeSet, EDITOR_PROP, ELLIPSIS, EMPTY_RECORDING, EXTERNAL_FILES, EditableLayout, EditableText, EditableTextModel, EnvironmentNotifier, FlexDirection, FocusNotifier, GLYPH_SUBPIXEL_PHASES, GlyphShaper, Grid, INSPECTOR_HEAT_MS, INSTANCE_STRIDE_BYTES, INSTANCE_STRIDE_FLOATS, IconRasterizer, LABEL_PADDING_X, LayoutEngine, LayoutInspector, LayoutNotifier, LayoutRecord, LazyColumn, LazyGrid, LazyRow, LazySheet, MARK_PREFIX, MAX_GRADIENT_STOPS, MAX_MEASURES_PER_CHILD, MOTION_CHANNELS, MOTION_REST, MOUSE_POINTER, MainAxisAlignment, Mp3Frames, NO_CLIP_INDEX, NodeTransitions, Paint, PaintPictureCache, PaintRecorder, PaintTarget, ParagraphTextMeasurer, PrimitiveKind, Responsive, Row, SCROLLBAR_FADE_MS, SCROLLBAR_HOVER_ZONE, SCROLLBAR_INSET, SCROLLBAR_LINGER_MS, SCROLLBAR_MIN_THUMB, SCROLLBAR_THICKNESS, ScrollView, ShownAsKey, Stack, TEXTURED_STRIDE_BYTES, TEXTURED_STRIDE_FLOATS, TEXT_MATCHES_PROP, TEXT_RUN_ID_SEPARATOR, TEXT_SELECTION_PROP, TOUCH_TARGET, Text, UI_ROLES, UI_SEMANTIC_STATES, UI_SUBGRID_AXES, UiAnimation, UiAnimationFrameClock, UiBasicColors, UiBeforeInputEvent, UiBinding, UiBorderRadiuses, UiChildrenBinding, UiClock, UiCompositionEvent, UiContainerSizeSource, UiDragSession, UiEditingController, UiEnvironment, UiEnvironmentKeys, UiEventBinding, UiEventType, UiFindController, UiFocusEvent, UiFocusManager, UiFrame, UiGestureEvent, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInputEvent, UiInsetRegistry, UiKeyboardController, UiKeyboardEvent, UiManualFrameClock, UiModifierSet, UiNode, UiNodeType, UiPasteEvent, UiPinchEvent, UiPinchRecognizer, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiProperties, UiScheduler, UiSelectionChangeEvent, UiSelectionController, UiSharedElements, UiShortcutRegistry, UiSpring, UiTextChangeEvent, UiTimerFrameClock, UiTouchScroller, UiTransforms, UiTween, UiUnknownModifierPropertyError, UiVirtualSheet, UiVirtualWindow, UiVisualState, UiWheelController, UiWheelDeltaMode, UiWheelEvent, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPUError, WebGPUGlyphAtlas, WebGPUGlyphPages, WebGPURenderer, WebGPUSurface, WebGPUTextureCache, accumulatedOffsetTo, adjacentField, animateLayout, applyCanvasTextStyle, assertModifierList, assertTransitionMap, auto, autoFocus, bandOf, borderRadius, borderRadiusCorners, borderRadiusEqual, borderRadiusIsZero, borderRadiusValuesEqual, borderShapes, borders, boxShadow, boxShadowArraysEqual, boxShadowRadius, boxShadowReach, boxShadowValuesEqual, boxShadowsEqual, breakpoint, bufferSource, buildFontString, buildRenderList, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, bundle, canDecodeVideo, capturePointer, caretRectFor, caretVisibleAt, clampSize, clearFontStacks, clearLinkHover, clearMatchRanges, clearOverrideProperty, clearSelectionRange, clickOutside, clockOf, closestPropertyName, colorToCss, colorToHex, colorToRgba, colorValuesEqual, colorsEqual, colorsEqualPalette, commandForKey, comparePositions, computeObjectFitRect, constraintsEqual, containerBands, contentOffset, contextMenu, contrastRatio, createCanvasSurface, createCanvasTextStyle, createElement, createEnvironmentKey, createPaintState, createTextCache, createTween, createWebGPUSurface, cubicBezier, cueAt, darkColors, darkTheme, decorated, decorationColor, decorationRect, defaultMotion, defaultShadows, defaultShapes, defaultSpacing, defaultTextStyle, defaultTypography, defaultUnits, defaultVisualState, defineModifier, defineMotion, defineProperty, defineThemeExtension, demuxMp4Audio, demuxMp4Video, densityFactors, describeLength, describeOverrides, describeShortcut, detectEditingPlatform, diffSemantics, dragSessionFor, dragSource, draggable, drawOverlayShapes, drawSvg, drawText, dropIndexShifts, dropTarget, durationIn, easingCss, easingIn, easings, edgeField, editableSpansOf, editingGroupOf, editorFor, editorOf, fade, fillTextRects, findEnvironmentKey, findMatchesIn, findPropertyDefinition, fittedShadowRadius, flattenTextSpans, focusRing, fontStackFor, formatConstraints, formatExplanation, formatShortcut, fr, getPropertyNames, glyphCount, gradientPaint, gradientsEqual, graphemeBoundaries, hasDecorationPhase, hasDrawnText, hasTextLinks, hasThemeExtension, hasVisualState, hiddenRangesOf, highContrastColors, hoverable, iconKey, iconSource, imageSource, inheritedPropertyFlags, inheritedPropertyNames, initializeWebGPU, inputsEqual, insetPadding, insetsEqual, interactive, interpolateColor, interpolateNumber, interpolateTransform, interpolatorFor, isAutoLength, isComponentLikeElement, isEditableNode, isFrLength, isImeKey, isLayoutProtocol, isMinMaxTrack, isMotionRest, isMultiline, isNodeFocusable, isNodeHitTestable, isNodeInert, isNodeSelectable, isNodeTabStop, isObservable, isPaintVisible, isPercentLength, isPrintable, isReadOnly, isScrollContainer, isShapeName, isSubtreeInert, isSvgSource, isTypographyRole, isUiChild, isUiElement, isUiModifier, isUiRole, isUiSemanticState, isVideoSurface, isWebGPUAvailable, labelNode, labelOrigin, layoutParagraph, layoutTextLines, lazySource, lengths, lightColors, lightTheme, lineEndAt, lineIndexAtY, lineIndexForOffset, lineLimit, lineStartAt, linear, linearGradient, linkHoverOf, linkOf, linkRunAtPointIn, markInstant, markNow, matchRangesOf, measure, measureFlow, measureSpan, mediaLoadsInFlight, minmax, motion, motionOf, nextCaretToggle, nextGraphemeEnd, nextWordEnd, noInsets, noKeyModifiers, noModifiers, noThemeExtensions, normalizeBorderRadius, normalizeColor, normalizeStates, normalizeTextAlign, normalizeTransition, normalizeVerticalAlign, observeViewportInsets, offsetAtPoint, offsetAtPointIn, offsetAtX, offsetForVerticalMove, overrideSources, paintPictures, paintValuesEqual, paintsAfterChildren, paragraphGeometry, paragraphGeometryFrom, parseAlignContent, parseColor, parseCrossAxisAlignment, parseFlexDirection, parseMainAxisAlignment, parseShortcut, parseSvg, parseTransform, parseWebVtt, pathValuesEqual, percent, performanceMarksEnabled, pinchable, placeGridItems, pointerDeviceOf, pointerEventsOf, pop, prepareInputSurface, pressable, previousGraphemeStart, previousWordStart, propertyEffects, propertyIsInherited, propertyNarrowers, propertyValuesEqual, propertyValuesEqualByName, proportionalFontMetrics, provideEnvironment, provided, publishInset, radialGradient, raiseContrast, rangeSource, readMp3Header, recordsEqual, registerFontStack, relativeLuminance, reorderable, repeat, replayPaint, resetOverrideWarnings, resetTransitionWarnings, resolveBoolean, resolveCursor, resolveLength, resolveMotionState, resolveNumber, resolvePaintState, resolveProperty, resolvePropertyByName, resolveString, resolvedSpansOf, rewalkSemantics, rgb8, rgba, rotateFrom, runCanvasStyleInto, runFontString, sameArgs, scaleFrom, scaleSpacing, scrollBounds, scrollLeaderOf, scrollPosition, scrollRange, scrollbarThumb, scrollbarThumbs, scrollbarZoneAt, scrollsFromRight, selectableTextNodes, selectableTextOf, selectionRangeOf, selectionRects, selectionRectsIn, semanticsInertAbove, semanticsMemory, setLinkHover, setMatchRanges, setPerformanceMarks, setSelectionRange, shadowsEqual, shadowsOf, shapesEqual, sharedElement, sheetSource, shortcut, shortcutKeyCaps, shortcuts, sizeContainer, sizeGridTracks, slideDown, slideFrom, slideUp, spacingEqual, spacingSteps, spanAtOffset, spannedRunsFor, spannedTextOf, spansDifferOnlyInPaint, splitMp3Frames, spring, springIn, statesEqual, steps, svgPaint, syncEditorValue, textContentOf, textInputOf, textRunBackgrounds, textRunDecorations, textRunOfRecordId, textRuns, textSpansEqual, textStylesEqual, themeExtension, themeExtensionsEqual, themeTokens, themesEqual, tightenConstraints, touchActionFor, tracePathData, traceRoundedRect, transform, transformIsIdentity, transformsEqual, tween, typographyEqual, uniformBorderRadius, unitsEqual, unknownContainerSize, validateGradient, validateRole, validateStates, validateSubgrid, videoFrameSize, videoSource, visualState, visualStatesEqual, wheelDeltaYOf, withContrast, withDensity, withOverrideSource, withPointer, withThemeExtension, wordRangeAt, wordRangeIn, writeDeclaredProperty, writeOverrideProperty };
|
|
16118
17947
|
|
|
16119
17948
|
//# sourceMappingURL=index.js.map
|