gesso-core 0.6.19 → 0.6.20

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as isImeKey, $a as MOUSE_POINTER, $i as spacingEqual, $n as ELLIPSIS, $r as transformsEqual, $t as registerFontStack, A as withOverrideSource, Aa as colorToHex, Ai as observeViewportInsets, An as clearLinkHover, Ar as inheritedPropertyNames, At as adjacentField, B as FlexDirection, Ba as UiEnvironment, Bi as defineThemeExtension, Bn as editorFor, Br as normalizeStates, Bt as createCanvasTextStyle, C as SCROLLBAR_LINGER_MS, Ca as textContentOf, Ci as uniformBorderRadius, Cn as isNodeInert, Cr as resolveProperty, Ct as hasDrawnText, D as formatExplanation, Da as darkColors, Di as UiInsetRegistry, Dn as isSubtreeInert, Dr as findPropertyDefinition, Dt as selectionRectsIn, E as formatConstraints, Ea as colorsEqualPalette, Ei as UiEnvironmentKeys, En as isPressable, Er as closestPropertyName, Et as paragraphGeometryFrom, F as clampSize, Fa as parseColor, Fi as darkTheme, Fn as setLinkHover, Fr as UiProperties, Ft as detectEditingPlatform, G as CrossAxisAlignment, Ga as resetOverrideWarnings, Gi as withThemeExtension, Gn as nextCaretToggle, Gr as validateSubgrid, Gt as runFontString, H as placeGridItems, Ha as DirtyNodeSet, Hi as noThemeExtensions, Hn as isEditableNode, Hr as validateRole, Ht as fillTextRects, I as constraintsEqual, Ia as rgb8, Ii as lightTheme, In as CARET_BLINK_MS, Ir as UI_ROLES, It as isPrintable, J as parseCrossAxisAlignment, Ja as UiVisualState, Ji as raiseContrast, Jn as isVideoSurface, Jr as pathValuesEqual, Jt as textRunBackgrounds, K as MainAxisAlignment, Ka as writeDeclaredProperty, Ki as contrastRatio, Kn as syncEditorValue, Kr as inputsEqual, Kt as spanOf, L as tightenConstraints, La as rgba, Li as themesEqual, Ln as CARET_WIDTH, Lr as UI_SEMANTIC_STATES, Lt as EditableLayout, M as contentOffset, Ma as colorValuesEqual, Mi as bandOf, Mn as linkHoverOf, Mr as propertyIsInherited, Mt as edgeField, N as LayoutRecord, Na as colorsEqual, Ni as containerBands, Nn as linkOf, Nr as propertyNarrowers, Nt as editingGroupOf, O as formatNumber, Oa as lightColors, Oi as insetsEqual, On as pointerEventsOf, Or as getPropertyNames, Ot as wordRangeIn, P as Constraints, Pa as normalizeColor, Pi as unknownContainerSize, Pn as linkRunAtPointIn, Pr as propertyValuesEqualByName, Pt as commandForKey, Q as capturePointer, Qa as visualStatesEqual, Qi as scaleSpacing, Qn as spannedRunsFor, Qr as transformIsIdentity, Qt as fontStackFor, R as MAX_MEASURES_PER_CHILD, Ra as createEnvironmentKey, Ri as withContrast, Rn as EDITOR_PROP, Rr as isUiRole, Rt as applyCanvasTextStyle, S as SCROLLBAR_FADE_MS, Sa as spansDifferOnlyInPaint, Si as normalizeBorderRadius, Sn as isNodeHitTestable, Sr as resolveNumber, St as UiInputDispatcher, T as describeOverrides, Ta as textStylesEqual, Ti as propertyValuesEqual, Tn as isNodeTabStop, Tr as resolveString, Tt as paragraphGeometry, U as sizeGridTracks, Ua as clearOverrideProperty, Ui as themeExtension, Un as isMultiline, Ur as validateStates, Ut as layoutTextLines, V as parseFlexDirection, Va as UiNode, Vi as hasThemeExtension, Vn as editorOf, Vr as statesEqual, Vt as drawText, W as AlignContent, Wa as overrideSources, Wi as themeExtensionsEqual, Wn as isReadOnly, Wr as UI_SUBGRID_AXES, Wt as runCanvasStyleInto, X as CanvasPlatformSurface, Xa as hasVisualState, Xi as densityFactors, Xn as CharacterCountTextMeasurer, Xr as parseTransform, Xt as bumpFontStack, Y as parseMainAxisAlignment, Ya as defaultVisualState, Yi as relativeLuminance, Yn as videoFrameSize, Yr as UiTransforms, Yt as textRunDecorations, Z as UiPlatformAdapter, Za as visualState, Zi as defaultSpacing, Zn as ParagraphTextMeasurer, Zr as transform, Zt as clearFontStacks, _ as LABEL_PADDING_X, _a as flattenTextSpans, _i as borderRadius, _n as selectableTextNodes, _o as DirtyFlags, _r as previousWordStart, _t as scrollLeaderOf, a as PaintPictureCache, aa as boxShadowArraysEqual, ai as validateGradient, an as computeObjectFitRect, ao as UiInputEvent, ar as lineLimit, at as UiGestureRecognizer, b as snapScroll, ba as spanAtOffset, bi as borderRadiusIsZero, bn as setSelectionRange, br as DEFAULT_LINE_HEIGHT_FACTOR, bt as scrollbarThumbs, c as EMPTY_RECORDING, ca as boxShadowsEqual, ci as isAutoLength, cn as normalizeTextAlign, co as UiPinchEvent, cr as offsetForVerticalMove, ct as isScrollContainer, d as tracePathData, da as shapesEqual, di as isPercentLength, dn as TEXT_MATCHES_PROP, do as UiTextChangeEvent, dr as graphemeBoundaries, dt as UiFocusManager, ea as spacingSteps, ei as MAX_GRADIENT_STOPS, en as DEFAULT_FONT_FAMILY, eo as UiBeforeInputEvent, er as layoutParagraph, et as pointerDeviceOf, f as ScaledImageCache, fa as defaultTypography, fi as minmax, fn as clearMatchRanges, fo as UiWheelDeltaMode, fr as lineEndAt, ft as UiHitTester, g as paintsAfterChildren, ga as editableSpansOf, gi as UiBorderRadiuses, gn as clearSelectionRange, go as UiNodeType, gr as previousGraphemeStart, gt as SCROLLBAR_THICKNESS, h as hasDecorationPhase, ha as defaultTextStyle, hi as resolveLength, hn as TEXT_SELECTION_PROP, ho as wheelDeltaYOf, hr as nextWordEnd, ht as SCROLLBAR_MIN_THUMB, i as traceRoundedRect, ia as boxShadow, ii as radialGradient, in as colorToCss, io as UiGestureEvent, ir as lineIndexForOffset, it as UiTouchScroller, j as accumulatedOffsetTo, ja as colorToRgba, ji as UiContainerSizeSource, jn as hasTextLinks, jr as propertyEffects, jt as comparePositions, k as labelNode, ka as UiBasicColors, ki as noInsets, kn as resolveCursor, kr as inheritedPropertyFlags, kt as textInputOf, l as PaintRecorder, la as defaultShapes, li as isFrLength, ln as normalizeVerticalAlign, lo as UiPointerEvent, lr as selectionRects, lt as UiKeyboardController, m as decorationRect, ma as typographyEqual, mi as repeat, mn as setMatchRanges, mo as noKeyModifiers, mr as nextGraphemeEnd, mt as SCROLLBAR_INSET, n as createCanvasSurface, na as shadowsEqual, ni as gradientsEqual, nn as DEFAULT_FONT_WEIGHT, no as UiEventType, nr as caretRectFor, nt as touchActionFor, o as paintPictures, oa as boxShadowRadius, oi as auto, on as createPaintState, oo as UiKeyboardEvent, or as offsetAtPoint, ot as UiPinchRecognizer, p as decorationColor, pa as isTypographyRole, pi as percent, pn as matchRangesOf, po as UiWheelEvent, pr as lineStartAt, pt as SCROLLBAR_HOVER_ZONE, q as parseAlignContent, qa as writeOverrideProperty, qi as highContrastColors, qn as resolveColorValue, qr as paintValuesEqual, qt as textMeasureRequest, r as Canvas2DRenderer, ra as BOX_SHADOW_BLUR_REACH, ri as linearGradient, rn as DEFAULT_TEXT_COLOR, ro as UiFocusEvent, rr as lineIndexAtY, rt as UiEditingController, s as PaintTarget, sa as boxShadowReach, si as fr, sn as isPaintVisible, so as UiPasteEvent, sr as offsetAtX, st as UiWheelController, t as CanvasSurface, ta as defaultShadows, ti as gradientPaint, tn as DEFAULT_FONT_SIZE, to as UiCompositionEvent, tr as proportionalFontMetrics, tt as prepareInputSurface, u as replayPaint, ua as isShapeName, ui as isMinMaxTrack, un as resolvePaintState, uo as UiSelectionChangeEvent, ur as EditableTextModel, ut as UiPointerController, v as drawOverlayShapes, va as hiddenRangesOf, vi as borderRadiusCorners, vn as selectableTextOf, vo as UiBinding, vr as wordRangeAt, vt as scrollRange, w as describeLength, wa as textSpansEqual, wi as defineProperty, wn as isNodeSelectable, wr as resolvePropertyByName, wt as offsetAtPointIn, x as LayoutEngine, xa as spannedTextOf, xi as borderRadiusValuesEqual, xn as isNodeFocusable, xr as resolveBoolean, xt as scrollbarZoneAt, y as labelOrigin, ya as resolvedSpansOf, yi as borderRadiusEqual, yn as selectionRangeOf, yr as UiGraph, yt as scrollbarThumb, z as isLayoutProtocol, za as findEnvironmentKey, zi as withDensity, zn as caretVisibleAt, zr as isUiSemanticState, zt as buildFontString } from "./CanvasSurface-tAao5BLh.js";
1
+ import { $ as isImeKey, $a as MOUSE_POINTER, $i as spacingEqual, $n as ELLIPSIS, $r as transformsEqual, $t as registerFontStack, A as withOverrideSource, Aa as colorToHex, Ai as observeViewportInsets, An as clearLinkHover, Ar as inheritedPropertyNames, At as adjacentField, B as FlexDirection, Ba as UiEnvironment, Bi as defineThemeExtension, Bn as editorFor, Br as normalizeStates, Bt as createCanvasTextStyle, C as SCROLLBAR_LINGER_MS, Ca as textContentOf, Ci as uniformBorderRadius, Cn as isNodeInert, Cr as resolveProperty, Ct as hasDrawnText, D as formatExplanation, Da as darkColors, Di as UiInsetRegistry, Dn as isSubtreeInert, Dr as findPropertyDefinition, Dt as selectionRectsIn, E as formatConstraints, Ea as colorsEqualPalette, Ei as UiEnvironmentKeys, En as isPressable, Er as closestPropertyName, Et as paragraphGeometryFrom, F as clampSize, Fa as parseColor, Fi as darkTheme, Fn as setLinkHover, Fr as UiProperties, Ft as detectEditingPlatform, G as CrossAxisAlignment, Ga as resetOverrideWarnings, Gi as withThemeExtension, Gn as nextCaretToggle, Gr as validateSubgrid, Gt as runFontString, H as placeGridItems, Ha as DirtyNodeSet, Hi as noThemeExtensions, Hn as isEditableNode, Hr as validateRole, Ht as fillTextRects, I as constraintsEqual, Ia as rgb8, Ii as lightTheme, In as CARET_BLINK_MS, Ir as UI_ROLES, It as isPrintable, J as parseCrossAxisAlignment, Ja as UiVisualState, Ji as raiseContrast, Jn as isVideoSurface, Jr as pathValuesEqual, Jt as textRunBackgrounds, K as MainAxisAlignment, Ka as writeDeclaredProperty, Ki as contrastRatio, Kn as syncEditorValue, Kr as inputsEqual, Kt as spanOf, L as tightenConstraints, La as rgba, Li as themesEqual, Ln as CARET_WIDTH, Lr as UI_SEMANTIC_STATES, Lt as EditableLayout, M as contentOffset, Ma as colorValuesEqual, Mi as bandOf, Mn as linkHoverOf, Mr as propertyIsInherited, Mt as edgeField, N as LayoutRecord, Na as colorsEqual, Ni as containerBands, Nn as linkOf, Nr as propertyNarrowers, Nt as editingGroupOf, O as formatNumber, Oa as lightColors, Oi as insetsEqual, On as pointerEventsOf, Or as getPropertyNames, Ot as wordRangeIn, P as Constraints, Pa as normalizeColor, Pi as unknownContainerSize, Pn as linkRunAtPointIn, Pr as propertyValuesEqualByName, Pt as commandForKey, Q as capturePointer, Qa as visualStatesEqual, Qi as scaleSpacing, Qn as spannedRunsFor, Qr as transformIsIdentity, Qt as fontStackFor, R as MAX_MEASURES_PER_CHILD, Ra as createEnvironmentKey, Ri as withContrast, Rn as EDITOR_PROP, Rr as isUiRole, Rt as applyCanvasTextStyle, S as SCROLLBAR_FADE_MS, Sa as spansDifferOnlyInPaint, Si as normalizeBorderRadius, Sn as isNodeHitTestable, Sr as resolveNumber, St as UiInputDispatcher, T as describeOverrides, Ta as textStylesEqual, Ti as propertyValuesEqual, Tn as isNodeTabStop, Tr as resolveString, Tt as paragraphGeometry, U as sizeGridTracks, Ua as clearOverrideProperty, Ui as themeExtension, Un as isMultiline, Ur as validateStates, Ut as layoutTextLines, V as parseFlexDirection, Va as UiNode, Vi as hasThemeExtension, Vn as editorOf, Vr as statesEqual, Vt as drawText, W as AlignContent, Wa as overrideSources, Wi as themeExtensionsEqual, Wn as isReadOnly, Wr as UI_SUBGRID_AXES, Wt as runCanvasStyleInto, X as CanvasPlatformSurface, Xa as hasVisualState, Xi as densityFactors, Xn as CharacterCountTextMeasurer, Xr as parseTransform, Xt as bumpFontStack, Y as parseMainAxisAlignment, Ya as defaultVisualState, Yi as relativeLuminance, Yn as videoFrameSize, Yr as UiTransforms, Yt as textRunDecorations, Z as UiPlatformAdapter, Za as visualState, Zi as defaultSpacing, Zn as ParagraphTextMeasurer, Zr as transform, Zt as clearFontStacks, _ as LABEL_PADDING_X, _a as flattenTextSpans, _i as borderRadius, _n as selectableTextNodes, _o as DirtyFlags, _r as previousWordStart, _t as scrollLeaderOf, a as PaintPictureCache, aa as boxShadowArraysEqual, ai as validateGradient, an as computeObjectFitRect, ao as UiInputEvent, ar as lineLimit, at as UiGestureRecognizer, b as snapScroll, ba as spanAtOffset, bi as borderRadiusIsZero, bn as setSelectionRange, br as DEFAULT_LINE_HEIGHT_FACTOR, bt as scrollbarThumbs, c as EMPTY_RECORDING, ca as boxShadowsEqual, ci as isAutoLength, cn as normalizeTextAlign, co as UiPinchEvent, cr as offsetForVerticalMove, ct as isScrollContainer, d as tracePathData, da as shapesEqual, di as isPercentLength, dn as TEXT_MATCHES_PROP, do as UiTextChangeEvent, dr as graphemeBoundaries, dt as UiFocusManager, ea as spacingSteps, ei as MAX_GRADIENT_STOPS, en as DEFAULT_FONT_FAMILY, eo as UiBeforeInputEvent, er as layoutParagraph, et as pointerDeviceOf, f as ScaledImageCache, fa as defaultTypography, fi as minmax, fn as clearMatchRanges, fo as UiWheelDeltaMode, fr as lineEndAt, ft as UiHitTester, g as paintsAfterChildren, ga as editableSpansOf, gi as UiBorderRadiuses, gn as clearSelectionRange, go as UiNodeType, gr as previousGraphemeStart, gt as SCROLLBAR_THICKNESS, h as hasDecorationPhase, ha as defaultTextStyle, hi as resolveLength, hn as TEXT_SELECTION_PROP, ho as wheelDeltaYOf, hr as nextWordEnd, ht as SCROLLBAR_MIN_THUMB, i as traceRoundedRect, ia as boxShadow, ii as radialGradient, in as colorToCss, io as UiGestureEvent, ir as lineIndexForOffset, it as UiTouchScroller, j as accumulatedOffsetTo, ja as colorToRgba, ji as UiContainerSizeSource, jn as hasTextLinks, jr as propertyEffects, jt as comparePositions, k as labelNode, ka as UiBasicColors, ki as noInsets, kn as resolveCursor, kr as inheritedPropertyFlags, kt as textInputOf, l as PaintRecorder, la as defaultShapes, li as isFrLength, ln as normalizeVerticalAlign, lo as UiPointerEvent, lr as selectionRects, lt as UiKeyboardController, m as decorationRect, ma as typographyEqual, mi as repeat, mn as setMatchRanges, mo as noKeyModifiers, mr as nextGraphemeEnd, mt as SCROLLBAR_INSET, n as createCanvasSurface, na as shadowsEqual, ni as gradientsEqual, nn as DEFAULT_FONT_WEIGHT, no as UiEventType, nr as caretRectFor, nt as touchActionFor, o as paintPictures, oa as boxShadowRadius, oi as auto, on as createPaintState, oo as UiKeyboardEvent, or as offsetAtPoint, ot as UiPinchRecognizer, p as decorationColor, pa as isTypographyRole, pi as percent, pn as matchRangesOf, po as UiWheelEvent, pr as lineStartAt, pt as SCROLLBAR_HOVER_ZONE, q as parseAlignContent, qa as writeOverrideProperty, qi as highContrastColors, qn as resolveColorValue, qr as paintValuesEqual, qt as textMeasureRequest, r as Canvas2DRenderer, ra as BOX_SHADOW_BLUR_REACH, ri as linearGradient, rn as DEFAULT_TEXT_COLOR, ro as UiFocusEvent, rr as lineIndexAtY, rt as UiEditingController, s as PaintTarget, sa as boxShadowReach, si as fr, sn as isPaintVisible, so as UiPasteEvent, sr as offsetAtX, st as UiWheelController, t as CanvasSurface, ta as defaultShadows, ti as gradientPaint, tn as DEFAULT_FONT_SIZE, to as UiCompositionEvent, tr as proportionalFontMetrics, tt as prepareInputSurface, u as replayPaint, ua as isShapeName, ui as isMinMaxTrack, un as resolvePaintState, uo as UiSelectionChangeEvent, ur as EditableTextModel, ut as UiPointerController, v as drawOverlayShapes, va as hiddenRangesOf, vi as borderRadiusCorners, vn as selectableTextOf, vo as UiBinding, vr as wordRangeAt, vt as scrollRange, w as describeLength, wa as textSpansEqual, wi as defineProperty, wn as isNodeSelectable, wr as resolvePropertyByName, wt as offsetAtPointIn, x as LayoutEngine, xa as spannedTextOf, xi as borderRadiusValuesEqual, xn as isNodeFocusable, xr as resolveBoolean, xt as scrollbarZoneAt, y as labelOrigin, ya as resolvedSpansOf, yi as borderRadiusEqual, yn as selectionRangeOf, yr as UiGraph, yt as scrollbarThumb, z as isLayoutProtocol, za as findEnvironmentKey, zi as withDensity, zn as caretVisibleAt, zr as isUiSemanticState, zt as buildFontString } from "./CanvasSurface-VUJRrcXl.js";
2
2
  import { BehaviorSubject, EMPTY, Subject, combineLatest, distinctUntilChanged, isObservable as isObservable$1, map, of } from "rxjs";
3
3
  //#region src/animation/UiEasing.ts
4
4
  /** Progress equals time. What a spinner and a sweep want. */
@@ -2200,6 +2200,15 @@ function describeValueType(value) {
2200
2200
  var UiGraphBuilder = class {
2201
2201
  graph;
2202
2202
  refs = /* @__PURE__ */ new Map();
2203
+ /**
2204
+ * The definition each node was last reconciled from. A definition is
2205
+ * a description that is never changed once made, so the same object
2206
+ * again has nothing new to say: a lazy list hands back the row it made
2207
+ * for an index on every scroll, and reconciling seventy unchanged rows
2208
+ * (their props, then every node of each row's component) to mount the
2209
+ * four that scrolled in cost a 165 Hz frame its budget.
2210
+ */
2211
+ reconciledFrom = /* @__PURE__ */ new WeakMap();
2203
2212
  modifiers = /* @__PURE__ */ new Map();
2204
2213
  nextChildrenBindingId = 0;
2205
2214
  /**
@@ -2340,9 +2349,12 @@ var UiGraphBuilder = class {
2340
2349
  changed = true;
2341
2350
  } else {
2342
2351
  matched.add(node);
2343
- this.reconcileProps(node, definition.props);
2344
- if (this.reconcileChildren(node, definition.children).changed) changed = true;
2352
+ if (this.reconciledFrom.get(node) !== definition) {
2353
+ this.reconcileProps(node, definition.props);
2354
+ if (this.reconcileChildren(node, definition.children).changed) changed = true;
2355
+ }
2345
2356
  }
2357
+ this.reconciledFrom.set(node, definition);
2346
2358
  if (this.moveBefore(parent, node, cursor)) changed = true;
2347
2359
  cursor = node.nextSibling;
2348
2360
  result.push(node);
@@ -2412,8 +2424,12 @@ var UiGraphBuilder = class {
2412
2424
  if (anchor === void 0) {
2413
2425
  anchor = this.graph.createNode(anchorId, "fragment");
2414
2426
  this.graph.insertBefore(parent, anchor, cursor);
2415
- } else matched.add(anchor);
2427
+ } else {
2428
+ matched.add(anchor);
2429
+ if (this.reconciledFrom.get(anchor) === element) return anchor;
2430
+ }
2416
2431
  this.reconcileChildren(anchor, [resolver.resolve(element, anchorId)]);
2432
+ this.reconciledFrom.set(anchor, element);
2417
2433
  return anchor;
2418
2434
  }
2419
2435
  /**
@@ -2519,8 +2535,7 @@ var UiGraphBuilder = class {
2519
2535
  return true;
2520
2536
  }
2521
2537
  if (node === reference || node.nextSibling === reference) return false;
2522
- this.graph.detachNode(node);
2523
- this.graph.insertBefore(parent, node, reference);
2538
+ this.graph.moveWithin(node, reference);
2524
2539
  return true;
2525
2540
  }
2526
2541
  /**
@@ -2810,6 +2825,15 @@ var UiVirtualWindow = class {
2810
2825
  count;
2811
2826
  estimate;
2812
2827
  overscan;
2828
+ recycle;
2829
+ /**
2830
+ * The slot each mounted item is keyed by, when recycling: an item keeps
2831
+ * its slot while it stays, and one leaving gives its slot to the next
2832
+ * one to come, whose element then matches the node it leaves behind.
2833
+ */
2834
+ slotOf = /* @__PURE__ */ new Map();
2835
+ freeSlots = [];
2836
+ nextSlot = 0;
2813
2837
  keyOf;
2814
2838
  renderItem;
2815
2839
  grid;
@@ -2841,6 +2865,7 @@ var UiVirtualWindow = class {
2841
2865
  this.count = count;
2842
2866
  this.estimate = options.estimatedExtent;
2843
2867
  this.overscan = options.overscan ?? 3;
2868
+ this.recycle = options.recycle === true;
2844
2869
  this.keyOf = options.key ?? ((index) => index);
2845
2870
  this.renderItem = renderItem;
2846
2871
  this.grid = options.grid;
@@ -3027,6 +3052,13 @@ var UiVirtualWindow = class {
3027
3052
  });
3028
3053
  }
3029
3054
  buildChildren() {
3055
+ if (this.recycle) {
3056
+ for (const [index, slot] of this.slotOf) if (index < this.first || index > this.last) {
3057
+ this.slotOf.delete(index);
3058
+ this.freeSlots.push(slot);
3059
+ }
3060
+ for (let index = this.first; index <= this.last; index++) if (!this.slotOf.has(index)) this.slotOf.set(index, this.freeSlots.pop() ?? this.nextSlot++);
3061
+ }
3030
3062
  const children = [];
3031
3063
  if (this.grid?.header !== void 0) children.push(markRow(this.grid.header, {
3032
3064
  key: "lazy:header",
@@ -3080,7 +3112,7 @@ var UiVirtualWindow = class {
3080
3112
  */
3081
3113
  wrap(index, item) {
3082
3114
  const props = {
3083
- key: `lazy:${String(this.keyOf(index))}`,
3115
+ key: this.recycle ? `lazy:slot:${this.slotOf.get(index)}` : `lazy:${String(this.keyOf(index))}`,
3084
3116
  [VIRTUAL_INDEX_PROP]: index
3085
3117
  };
3086
3118
  if (this.grid !== void 0) return markRow(item, props, `Row ${index} of`);
@@ -3200,11 +3232,12 @@ function LazyGrid(props, renderRow) {
3200
3232
  });
3201
3233
  }
3202
3234
  function lazyList(axis, props, renderItem, grid) {
3203
- const { count, estimatedExtent, overscan, itemKey, initialViewportExtent, revision, modifiers, windowRef, ...rest } = props;
3235
+ const { count, estimatedExtent, overscan, recycle, itemKey, initialViewportExtent, revision, modifiers, windowRef, ...rest } = props;
3204
3236
  const window = new UiVirtualWindow(axis, {
3205
3237
  count,
3206
3238
  estimatedExtent,
3207
3239
  overscan,
3240
+ recycle,
3208
3241
  key: itemKey,
3209
3242
  initialViewportExtent,
3210
3243
  revision,
@@ -14375,6 +14408,26 @@ var UiTimerFrameClock = class {
14375
14408
  * has had one for years, and a *nested* worker on Chromium has not.
14376
14409
  * The feature test is the whole guard; there is no version to compare.
14377
14410
  */
14411
+ /** This thread's refreshes seen before they are compared with the display's. */
14412
+ const LOCAL_SAMPLES = 8;
14413
+ /** The host's refreshes the beat's phase is read from: about a sixth of a second at 120 Hz. */
14414
+ const BEAT_SAMPLES = 20;
14415
+ /** Frames whose cost decides the stride. */
14416
+ const COST_SAMPLES = 30;
14417
+ /** Frames seen before the stride is lengthened. */
14418
+ const SLOW_SAMPLES = 12;
14419
+ /** A frame costing more than this share of its refreshes is too slow for them. */
14420
+ const SLOW_FRAME = .75;
14421
+ /** And less than this share of one fewer is cheap enough to take one fewer. */
14422
+ const FAST_FRAME = .6;
14423
+ /** How far off the measured interval the refreshes must come to be another display's, as a fraction. */
14424
+ const NEW_DISPLAY = .15;
14425
+ /** Times the interval is read again on its own result. */
14426
+ const BEAT_PASSES = 3;
14427
+ /** Refreshes apart the two an interval is read between must be, at least. */
14428
+ const MIN_BEAT_SPAN = 6;
14429
+ /** The most refreshes a frame is given: two, sixty at 120 Hz; slower than that is no smoother. */
14430
+ const MAX_STRIDE = 2;
14378
14431
  function localAnimationFrame() {
14379
14432
  const host = globalThis;
14380
14433
  return typeof host.requestAnimationFrame === "function" ? host.requestAnimationFrame.bind(host) : null;
@@ -14420,6 +14473,28 @@ var UiHostFrameClock = class {
14420
14473
  /** This thread's own `requestAnimationFrame`, or null where there is none. */
14421
14474
  local;
14422
14475
  localHandle = null;
14476
+ timeOrigin;
14477
+ /** Gaps between this thread's own refreshes while frames ran back to back. */
14478
+ localIntervals = [];
14479
+ lastLocalTime = null;
14480
+ /** The display's interval and the host's time origin, once the host has said. */
14481
+ display = null;
14482
+ /**
14483
+ * Set once this thread's refreshes have shown they keep no display's
14484
+ * time: frames are then timed here, on the beat the host's refreshes
14485
+ * show (see \`displayInterval\`).
14486
+ */
14487
+ locked = false;
14488
+ /** The host's recent refresh times, which give the beat its phase. */
14489
+ refreshes = [];
14490
+ /** Host refreshes since the last frame drawn on the beat. */
14491
+ refreshesSinceFrame = 0;
14492
+ /** The refresh the last frame here was drawn for: a frame is never drawn for one twice. */
14493
+ lastLocked = -Infinity;
14494
+ /** How long recent frames took here, which decides how many refreshes each one gets. */
14495
+ costs = [];
14496
+ /** Refreshes per frame on the beat: 1, or more when frames cost more than a refresh can hold. */
14497
+ stride = 1;
14423
14498
  constructor(onFrame, onActive, options = {}) {
14424
14499
  this.onFrame = onFrame;
14425
14500
  this.onActive = onActive;
@@ -14428,6 +14503,7 @@ var UiHostFrameClock = class {
14428
14503
  this.minStallMs = options.minStallMs ?? 12;
14429
14504
  this.now = options.now ?? (() => performance.now());
14430
14505
  this.local = options.requestAnimationFrame === void 0 ? localAnimationFrame() : options.requestAnimationFrame;
14506
+ this.timeOrigin = options.timeOrigin ?? (typeof performance !== "undefined" ? performance.timeOrigin : 0);
14431
14507
  }
14432
14508
  /** Whether this clock is driving itself from the compositor. */
14433
14509
  get isLocal() {
@@ -14438,6 +14514,7 @@ var UiHostFrameClock = class {
14438
14514
  if (this.local !== null) {
14439
14515
  if (this.localHandle === null) this.localHandle = this.local((time) => {
14440
14516
  this.localHandle = null;
14517
+ this.recordLocal(time);
14441
14518
  if (this.pending) this.deliver(time);
14442
14519
  });
14443
14520
  return;
@@ -14446,6 +14523,157 @@ var UiHostFrameClock = class {
14446
14523
  this.armTimer();
14447
14524
  }
14448
14525
  /**
14526
+ * Tells the clock the display's interval, measured on the host's own
14527
+ * refreshes, and the host's \`timeOrigin\`, which puts the host's frame
14528
+ * times on this thread's clock.
14529
+ *
14530
+ * A worker's own refreshes are not always the display's. WebKit's come
14531
+ * from a timer near 65 Hz that keeps no display's time: on a 120 Hz
14532
+ * MacBook its frames fell 15 to 16.4 ms apart, so each was shown after
14533
+ * one refresh, two or three, and a scroll juddered at a steady sixty.
14534
+ * Driving frames from the host's ticks instead put the host's thread in
14535
+ * every frame, and WebKit's is uneven while scrolling: at 120 a seventh
14536
+ * of the frames missed their refresh. So once this thread's refreshes
14537
+ * have shown they keep no display's time, frames follow the host's
14538
+ * refreshes, which are the only ones a worker's canvas is shown on in
14539
+ * WebKit anyway, with two differences from a plain hosted clock: each
14540
+ * frame is stamped with its refresh on the display's beat and on this
14541
+ * thread's clock, which is the clock a wheel step's time is on; and
14542
+ * frames too costly for one refresh are given an even two (see
14543
+ * \`recordCost\`).
14544
+ */
14545
+ displayInterval(ms, origin) {
14546
+ if (ms > 0 && ms < MAX_CADENCE_MS) {
14547
+ this.display = {
14548
+ interval: ms,
14549
+ origin
14550
+ };
14551
+ this.compareLocal();
14552
+ }
14553
+ }
14554
+ /** Whether frames are timed here on the display's beat; see \`displayInterval\`. */
14555
+ get isDisplayLocked() {
14556
+ return this.locked;
14557
+ }
14558
+ recordLocal(time) {
14559
+ const previous = this.lastLocalTime;
14560
+ this.lastLocalTime = time;
14561
+ if (previous !== null && this.pending) {
14562
+ const interval = time - previous;
14563
+ if (interval > 0 && interval < MAX_CADENCE_MS) {
14564
+ this.localIntervals.push(interval);
14565
+ if (this.localIntervals.length > LOCAL_SAMPLES) this.localIntervals.shift();
14566
+ }
14567
+ }
14568
+ this.compareLocal();
14569
+ }
14570
+ /**
14571
+ * Gives this thread's refreshes up when they come well slower than the
14572
+ * display's: they cost frames, and in WebKit keep no display's beat
14573
+ * either. Faster than the display is left alone, because that is the
14574
+ * display the measurement was taken on going stale (a window dragged to
14575
+ * a faster screen), not a worker failing to keep up with it.
14576
+ */
14577
+ compareLocal() {
14578
+ if (this.local === null || this.display === null || this.localIntervals.length < LOCAL_SAMPLES) return;
14579
+ const sorted = [...this.localIntervals].sort((a, b) => a - b);
14580
+ if (sorted[Math.floor(sorted.length / 2)] / this.display.interval <= 1.06) return;
14581
+ this.local = null;
14582
+ this.locked = true;
14583
+ }
14584
+ /** A time on the host's clock, on this thread's. */
14585
+ toLocal(host) {
14586
+ return this.display === null ? host : host - this.timeOrigin + this.display.origin;
14587
+ }
14588
+ /**
14589
+ * When the display's refreshes start, on the host's clock: the earliest
14590
+ * in phase of the host's recent refresh times, since a host's callback
14591
+ * can only run late, never early.
14592
+ */
14593
+ beat() {
14594
+ if (this.display === null || this.refreshes.length === 0) return null;
14595
+ const cadence = this.cadenceMs();
14596
+ let interval = cadence !== null && Math.abs(cadence - this.display.interval) > this.display.interval * NEW_DISPLAY ? cadence : this.display.interval;
14597
+ for (let pass = 0; pass < BEAT_PASSES; pass++) interval = this.refineInterval(interval);
14598
+ const latest = this.refreshes[this.refreshes.length - 1];
14599
+ let earliest = Infinity;
14600
+ for (const time of this.refreshes) {
14601
+ let residual = ((time - latest) % interval + interval) % interval;
14602
+ if (residual > interval / 2) residual -= interval;
14603
+ earliest = Math.min(earliest, residual);
14604
+ }
14605
+ return {
14606
+ start: latest + earliest,
14607
+ interval
14608
+ };
14609
+ }
14610
+ /** The interval between the most punctual early and late refreshes, judged by `rough`. */
14611
+ refineInterval(rough) {
14612
+ const half = Math.floor(this.refreshes.length / 2);
14613
+ const first = this.refreshes[0];
14614
+ let early = null;
14615
+ let late = null;
14616
+ let index = 0;
14617
+ for (let i = 0; i < this.refreshes.length; i++) {
14618
+ const time = this.refreshes[i];
14619
+ if (i > 0) index += Math.max(1, Math.round((time - this.refreshes[i - 1]) / rough));
14620
+ const residual = time - first - index * rough;
14621
+ if (i < half) {
14622
+ if (early === null || residual < early.residual) early = {
14623
+ index,
14624
+ residual,
14625
+ time
14626
+ };
14627
+ } else if (late === null || residual < late.residual) late = {
14628
+ index,
14629
+ residual,
14630
+ time
14631
+ };
14632
+ }
14633
+ return early !== null && late !== null && late.index - early.index >= MIN_BEAT_SPAN ? (late.time - early.time) / (late.index - early.index) : rough;
14634
+ }
14635
+ /**
14636
+ * Draws the frame a host refresh asked for, stamped with that refresh on
14637
+ * the display's beat rather than with when its callback happened to run,
14638
+ * and on this thread's clock.
14639
+ */
14640
+ deliverOnBeat(time) {
14641
+ const beat = this.beat();
14642
+ let refresh = time - ((time - beat.start) % beat.interval + beat.interval) % beat.interval;
14643
+ while (refresh < this.lastLocked + (this.stride - .5) * beat.interval) refresh += beat.interval;
14644
+ this.lastLocked = refresh;
14645
+ this.refreshesSinceFrame = 0;
14646
+ const started = this.now();
14647
+ this.deliver(this.toLocal(refresh));
14648
+ this.recordCost(this.now() - started, beat.interval);
14649
+ }
14650
+ /**
14651
+ * Keeps the stride to what frames cost: every refresh while the typical
14652
+ * frame fits in most of one, two once it no longer does. Decided by the
14653
+ * typical frame, not the slow ones: a slow frame (a few rows mounted at
14654
+ * once) shown a refresh late is a moment's hitch, where two refreshes a
14655
+ * frame for a whole scroll halves the rate. Ruled by the slow quarter,
14656
+ * a 165 Hz display drew at 82 with its typical frame in half a refresh.
14657
+ */
14658
+ recordCost(cost, interval) {
14659
+ this.costs.push(cost);
14660
+ if (this.costs.length > COST_SAMPLES) this.costs.shift();
14661
+ if (this.costs.length < SLOW_SAMPLES) return;
14662
+ const sorted = [...this.costs].sort((a, b) => a - b);
14663
+ const typical = sorted[Math.floor(sorted.length / 2)];
14664
+ if (typical > interval * this.stride * SLOW_FRAME && this.stride < MAX_STRIDE) {
14665
+ this.stride++;
14666
+ this.costs.length = 0;
14667
+ } else if (this.stride > 1 && this.costs.length >= COST_SAMPLES && typical < interval * (this.stride - 1) * FAST_FRAME) {
14668
+ this.stride--;
14669
+ this.costs.length = 0;
14670
+ }
14671
+ }
14672
+ /** Refreshes per frame while timed on the display's beat; see \`recordCost\`. */
14673
+ get displayStride() {
14674
+ return this.stride;
14675
+ }
14676
+ /**
14449
14677
  * Arms the timer that draws a frame the host did not ask for.
14450
14678
  *
14451
14679
  * It plays two roles with one mechanism. Before any tick has arrived
@@ -14471,7 +14699,7 @@ var UiHostFrameClock = class {
14471
14699
  this.handle = null;
14472
14700
  if (!this.pending) return;
14473
14701
  this.stalled = this.hosted;
14474
- this.deliver(this.hostTime());
14702
+ this.deliver(this.locked ? this.now() : this.hostTime());
14475
14703
  }, watching ? this.watchMs() : this.selfPaceMs());
14476
14704
  }
14477
14705
  /**
@@ -14572,6 +14800,17 @@ var UiHostFrameClock = class {
14572
14800
  this.stalled = false;
14573
14801
  this.hostOffset = time - this.now();
14574
14802
  this.recordCadence(time);
14803
+ if (this.locked && this.display !== null) {
14804
+ this.refreshes.push(time);
14805
+ if (this.refreshes.length > BEAT_SAMPLES) this.refreshes.shift();
14806
+ this.refreshesSinceFrame++;
14807
+ if (this.pending) {
14808
+ this.clearFallback();
14809
+ this.idleTicks = 0;
14810
+ if (this.refreshesSinceFrame >= this.stride) this.deliverOnBeat(time);
14811
+ return;
14812
+ }
14813
+ }
14575
14814
  this.clearFallback();
14576
14815
  if (!this.pending) {
14577
14816
  this.idleTicks++;