@legendapp/list 3.3.4 → 3.3.6

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/react.mjs CHANGED
@@ -55,10 +55,6 @@ function getStylePaddingEnd(props) {
55
55
  }
56
56
  return (isHorizontalRTLProps(props) ? props.stylePaddingLeft : props.stylePaddingRight) || 0;
57
57
  }
58
- function getLogicalHorizontalMaxOffset(state, contentWidth) {
59
- var _a3;
60
- return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0;
61
- }
62
58
  function getHorizontalInsetEnd(state, inset) {
63
59
  if (!inset) {
64
60
  return 0;
@@ -413,6 +409,28 @@ var EDGE_POSITION_EPSILON = 1;
413
409
  var ENABLE_DEVMODE = IS_DEV && false;
414
410
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
415
411
 
412
+ // src/core/cancelImperativeScroll.ts
413
+ function cancelScrollCompletionChecks({ scheduledWork }) {
414
+ scheduledWork.cancel("checkFinishedScrollFrame");
415
+ scheduledWork.cancel("checkFinishedScrollRetryFrame");
416
+ scheduledWork.cancel("checkFinishedScrollFallback");
417
+ scheduledWork.cancel("platformScrollCompletion");
418
+ }
419
+ function settlePendingImperativeScroll(state) {
420
+ var _a3, _b;
421
+ const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
422
+ state.pendingScrollResolve = void 0;
423
+ state.pendingScrollToEnd = void 0;
424
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
425
+ }
426
+ function cancelImperativeScroll(state) {
427
+ cancelScrollCompletionChecks(state);
428
+ state.scheduledWork.cancel("imperativeScrollReady");
429
+ state.scrollingTo = void 0;
430
+ state.scrollTargetPinnedRange = void 0;
431
+ settlePendingImperativeScroll(state);
432
+ }
433
+
416
434
  // src/core/deferredPublicOnScroll.ts
417
435
  function withResolvedContentOffset(state, event, resolvedOffset) {
418
436
  return {
@@ -667,6 +685,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
667
685
  } = state;
668
686
  const contentSize = getContentSize(ctx);
669
687
  resetSharedEdgeGateIfOutsideHysteresis(ctx);
688
+ if (state.props.data.length === 0 && scrollLength > 0 && contentSize <= scrollLength) {
689
+ set$(ctx, "isAtEnd", true);
690
+ set$(ctx, "isNearEnd", true);
691
+ set$(ctx, "isWithinMaintainScrollAtEndThreshold", true);
692
+ return;
693
+ }
670
694
  if (contentSize > 0 && queuedInitialLayout) {
671
695
  const insetEnd = getContentInsetEnd(ctx);
672
696
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -676,7 +700,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
676
700
  set$(
677
701
  ctx,
678
702
  "isWithinMaintainScrollAtEndThreshold",
679
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
703
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
680
704
  );
681
705
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
682
706
  if (!shouldSkipThresholdChecks) {
@@ -914,6 +938,24 @@ function clearPreservedInitialScrollTarget(state) {
914
938
  state.initialScroll = void 0;
915
939
  setInitialScrollSession(state);
916
940
  }
941
+ function supersedeInitialScroll(ctx) {
942
+ var _a3, _b, _c;
943
+ const state = ctx.state;
944
+ const bootstrapInitialScroll = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
945
+ if (state.initialScroll || bootstrapInitialScroll || ((_b = state.scrollingTo) == null ? void 0 : _b.isInitialScroll)) {
946
+ if ((bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle) !== void 0 && typeof cancelAnimationFrame === "function") {
947
+ cancelAnimationFrame(bootstrapInitialScroll.frameHandle);
948
+ }
949
+ if ((_c = state.scrollingTo) == null ? void 0 : _c.isInitialScroll) {
950
+ cancelScrollCompletionChecks(state);
951
+ state.scrollingTo = void 0;
952
+ state.scrollTargetPinnedRange = void 0;
953
+ }
954
+ initialScrollCompletion.resetFlags(state);
955
+ setInitialScrollSession(state, { bootstrap: null });
956
+ finishInitialScroll(ctx);
957
+ }
958
+ }
917
959
  function finishInitialScroll(ctx, options) {
918
960
  var _a3, _b, _c;
919
961
  const state = ctx.state;
@@ -1003,10 +1045,22 @@ function getAlignItemsAtEndPadding(ctx) {
1003
1045
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1004
1046
  }
1005
1047
  function updateContentMetricsState(ctx) {
1048
+ var _a3;
1049
+ const { state } = ctx;
1006
1050
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1007
1051
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1008
1052
  if (previousPadding !== nextPadding) {
1009
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1053
+ const isAnimatedMaintainActive = state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated";
1054
+ const isMaintainExpected = state.didContainersLayout && ((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.animated) && (isAnimatedMaintainActive || previousPadding > nextPadding && peek$(ctx, "isWithinMaintainScrollAtEndThreshold"));
1055
+ if (isMaintainExpected && !isAnimatedMaintainActive && !state.pendingMaintainScrollAtEnd) {
1056
+ state.scheduledWork.microtask(() => {
1057
+ if (!state.maintainingScrollAtEnd && !state.pendingMaintainScrollAtEnd) {
1058
+ set$(ctx, "alignItemsAtEndPadding", getAlignItemsAtEndPadding(ctx));
1059
+ }
1060
+ }, "alignItemsAtEndPaddingFallback");
1061
+ } else if (!isMaintainExpected) {
1062
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1063
+ }
1010
1064
  }
1011
1065
  }
1012
1066
 
@@ -1227,29 +1281,6 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1227
1281
  return clampedOffset;
1228
1282
  }
1229
1283
 
1230
- // src/core/cancelImperativeScroll.ts
1231
- function cancelScrollCompletionChecks({ scheduledWork }) {
1232
- scheduledWork.cancel("checkFinishedScrollFrame");
1233
- scheduledWork.cancel("checkFinishedScrollRetryFrame");
1234
- scheduledWork.cancel("checkFinishedScrollFallback");
1235
- scheduledWork.cancel("platformScrollCompletion");
1236
- }
1237
- function settlePendingImperativeScroll(state) {
1238
- var _a3, _b;
1239
- const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
1240
- state.pendingScrollResolve = void 0;
1241
- state.pendingScrollToEnd = void 0;
1242
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1243
- }
1244
- function cancelImperativeScroll(state) {
1245
- cancelScrollCompletionChecks(state);
1246
- state.scheduledWork.cancel("imperativeScrollReady");
1247
- state.runPendingScrollToEnd = void 0;
1248
- state.scrollingTo = void 0;
1249
- state.scrollTargetPinnedRange = void 0;
1250
- settlePendingImperativeScroll(state);
1251
- }
1252
-
1253
1284
  // src/core/finishScrollTo.ts
1254
1285
  function finishScrollTo(ctx) {
1255
1286
  var _a3, _b;
@@ -1387,22 +1418,110 @@ function listenForScrollEnd(ctx, params) {
1387
1418
  scheduledWork.register("platformScrollCompletion", cancel);
1388
1419
  }
1389
1420
 
1421
+ // src/core/scrollRequestTracker.ts
1422
+ function getScrollRequestTracker(ctx) {
1423
+ if (!ctx.scrollRequestTracker) {
1424
+ let currentToken = 0;
1425
+ const start = (resolve) => {
1426
+ const state = ctx.state;
1427
+ state.scheduledWork.cancel("imperativeScrollReady");
1428
+ const token = ++currentToken;
1429
+ settlePendingImperativeScroll(state);
1430
+ state.pendingScrollResolve = resolve;
1431
+ return token;
1432
+ };
1433
+ const runNow = (token, resolve, run) => {
1434
+ const state = ctx.state;
1435
+ if (token !== currentToken) {
1436
+ return;
1437
+ }
1438
+ const didStartScroll = run();
1439
+ if (!didStartScroll || !state.scrollingTo) {
1440
+ if (state.pendingScrollResolve === resolve) {
1441
+ state.pendingScrollResolve = void 0;
1442
+ }
1443
+ resolve();
1444
+ }
1445
+ };
1446
+ ctx.scrollRequestTracker = {
1447
+ isCurrent: (token) => token === currentToken,
1448
+ runNow,
1449
+ runNowIfIdle: (run) => {
1450
+ const state = ctx.state;
1451
+ if (state.pendingScrollResolve || state.scrollingTo) {
1452
+ return Promise.resolve();
1453
+ }
1454
+ return new Promise((resolve) => {
1455
+ const token = start(resolve);
1456
+ runNow(token, resolve, run);
1457
+ });
1458
+ },
1459
+ start
1460
+ };
1461
+ }
1462
+ return ctx.scrollRequestTracker;
1463
+ }
1464
+
1465
+ // src/utils/requestAdjust.ts
1466
+ function requestAdjust(ctx, positionDiff, source) {
1467
+ const state = ctx.state;
1468
+ if (Math.abs(positionDiff) > 0.1) {
1469
+ const doit = () => {
1470
+ {
1471
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
1472
+ if (state.adjustingFromInitialMount) {
1473
+ state.adjustingFromInitialMount--;
1474
+ }
1475
+ }
1476
+ };
1477
+ state.scroll += positionDiff;
1478
+ state.scrollForNextCalculateItemsInView = void 0;
1479
+ const readyToRender = peek$(ctx, "readyToRender");
1480
+ if (readyToRender) {
1481
+ doit();
1482
+ } else {
1483
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1484
+ requestAnimationFrame(doit);
1485
+ }
1486
+ }
1487
+ }
1488
+
1390
1489
  // src/core/doMaintainScrollAtEnd.ts
1490
+ function finishMaintainScrollAtEnd(ctx) {
1491
+ var _a3;
1492
+ const { state } = ctx;
1493
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1494
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1495
+ state.maintainingScrollAtEnd = void 0;
1496
+ state.pendingMaintainScrollAtEnd = false;
1497
+ if (currentPadding !== nextPadding) {
1498
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1499
+ state.scrollForNextCalculateItemsInView = void 0;
1500
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1501
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1502
+ requestAdjust(ctx, nextScroll - state.scroll);
1503
+ }
1504
+ }
1391
1505
  function doMaintainScrollAtEnd(ctx) {
1392
1506
  const state = ctx.state;
1393
1507
  const {
1394
1508
  didContainersLayout,
1509
+ didFinishInitialScroll,
1395
1510
  pendingNativeMVCPAdjust,
1396
- refScroller,
1397
1511
  props: { maintainScrollAtEnd }
1398
1512
  } = state;
1399
1513
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1400
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1514
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1515
+ const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1516
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1517
+ state.pendingMaintainScrollAtEnd = true;
1518
+ return false;
1519
+ }
1401
1520
  if (pendingNativeMVCPAdjust) {
1402
1521
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1403
1522
  return false;
1404
1523
  }
1405
- if (shouldMaintainScrollAtEnd) {
1524
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1406
1525
  state.pendingMaintainScrollAtEnd = false;
1407
1526
  const contentSize = getContentSize(ctx);
1408
1527
  if (contentSize < state.scrollLength) {
@@ -1414,39 +1533,29 @@ function doMaintainScrollAtEnd(ctx) {
1414
1533
  const scrollAtRequest = state.scroll;
1415
1534
  state.maintainingScrollAtEnd = pendingState;
1416
1535
  requestAnimationFrame(() => {
1536
+ if (state.maintainingScrollAtEnd !== pendingState) {
1537
+ return;
1538
+ }
1417
1539
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1418
1540
  const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1419
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1541
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1420
1542
  state.maintainingScrollAtEnd = activeState;
1421
- const scroller = refScroller.current;
1422
- if (state.props.horizontal && isHorizontalRTL(state)) {
1423
- const currentContentSize = getContentSize(ctx);
1424
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1425
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1426
- scroller == null ? void 0 : scroller.scrollTo({
1427
- animated: maintainScrollAtEnd.animated,
1428
- x: nativeOffset,
1429
- y: 0
1430
- });
1431
- } else {
1432
- scroller == null ? void 0 : scroller.scrollToEnd({
1433
- animated: maintainScrollAtEnd.animated
1434
- });
1435
- }
1436
- setTimeout(
1437
- () => {
1438
- if (state.maintainingScrollAtEnd === activeState) {
1439
- state.maintainingScrollAtEnd = void 0;
1440
- if (state.pendingMaintainScrollAtEnd) {
1441
- doMaintainScrollAtEnd(ctx);
1442
- }
1443
- }
1444
- },
1445
- maintainScrollAtEnd.animated ? 500 : 0
1543
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1544
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1446
1545
  );
1546
+ void scrollPromise.then(() => {
1547
+ if (state.maintainingScrollAtEnd !== activeState) {
1548
+ return;
1549
+ }
1550
+ if (state.pendingMaintainScrollAtEnd) {
1551
+ state.maintainingScrollAtEnd = void 0;
1552
+ doMaintainScrollAtEnd(ctx);
1553
+ } else {
1554
+ finishMaintainScrollAtEnd(ctx);
1555
+ }
1556
+ });
1447
1557
  } else if (state.maintainingScrollAtEnd === pendingState) {
1448
- state.maintainingScrollAtEnd = void 0;
1449
- state.pendingMaintainScrollAtEnd = false;
1558
+ finishMaintainScrollAtEnd(ctx);
1450
1559
  }
1451
1560
  });
1452
1561
  } else {
@@ -1454,34 +1563,10 @@ function doMaintainScrollAtEnd(ctx) {
1454
1563
  }
1455
1564
  return true;
1456
1565
  }
1457
- state.pendingMaintainScrollAtEnd = false;
1566
+ finishMaintainScrollAtEnd(ctx);
1458
1567
  return false;
1459
1568
  }
1460
1569
 
1461
- // src/utils/requestAdjust.ts
1462
- function requestAdjust(ctx, positionDiff, dataChanged) {
1463
- const state = ctx.state;
1464
- if (Math.abs(positionDiff) > 0.1) {
1465
- const doit = () => {
1466
- {
1467
- state.scrollAdjustHandler.requestAdjust(positionDiff);
1468
- if (state.adjustingFromInitialMount) {
1469
- state.adjustingFromInitialMount--;
1470
- }
1471
- }
1472
- };
1473
- state.scroll += positionDiff;
1474
- state.scrollForNextCalculateItemsInView = void 0;
1475
- const readyToRender = peek$(ctx, "readyToRender");
1476
- if (readyToRender) {
1477
- doit();
1478
- } else {
1479
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1480
- requestAnimationFrame(doit);
1481
- }
1482
- }
1483
- }
1484
-
1485
1570
  // src/core/mvcp.ts
1486
1571
  var MVCP_POSITION_EPSILON = 0.1;
1487
1572
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1618,7 +1703,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1618
1703
  }
1619
1704
  return false;
1620
1705
  }
1621
- function prepareMVCP(ctx, dataChanged) {
1706
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1622
1707
  const state = ctx.state;
1623
1708
  const { idsInView, positions, props } = state;
1624
1709
  const {
@@ -1870,6 +1955,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1870
1955
  state.scrollTime = currentTime;
1871
1956
  const scrollDelta = Math.abs(newScroll - prevScroll);
1872
1957
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1958
+ const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1959
+ if (didCancelMaintainScrollAtEnd) {
1960
+ finishMaintainScrollAtEnd(ctx);
1961
+ }
1873
1962
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
1874
1963
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1875
1964
  const scrollLength = state.scrollLength;
@@ -1878,7 +1967,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1878
1967
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
1879
1968
  const lastCalculated = state.scrollLastCalculate;
1880
1969
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1881
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1970
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1882
1971
  if (shouldUpdate) {
1883
1972
  state.scrollLastCalculate = state.scroll;
1884
1973
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2707,7 +2796,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2707
2796
  }
2708
2797
  }
2709
2798
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2710
- var _a3, _b, _c;
2799
+ var _a3, _b;
2711
2800
  const state = ctx.state;
2712
2801
  const initialScroll = state.initialScroll;
2713
2802
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2721,19 +2810,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2721
2810
  if (state.didFinishInitialScroll) {
2722
2811
  schedulePreservedEndAnchorCorrection(ctx);
2723
2812
  } else if (scrollingTo) {
2724
- const existingWatchdog = initialScrollWatchdog.get(state);
2725
- scrollingTo.offset = resolvedOffset;
2726
- scrollingTo.targetOffset = resolvedOffset;
2727
2813
  state.initialScroll = {
2728
2814
  ...initialScroll,
2729
2815
  contentOffset: resolvedOffset
2730
2816
  };
2731
- state.hasScrolled = false;
2732
- initialScrollWatchdog.set(state, {
2733
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2734
- targetOffset: resolvedOffset
2817
+ dispatchInitialScroll(ctx, {
2818
+ forceScroll: true,
2819
+ resolvedOffset,
2820
+ target: state.initialScroll
2735
2821
  });
2736
- requestAdjust(ctx, offsetDiff);
2737
2822
  }
2738
2823
  }
2739
2824
  } else {
@@ -3960,6 +4045,9 @@ function setDidLayout(ctx) {
3960
4045
  state.queuedInitialLayout = true;
3961
4046
  checkAtBottom(ctx);
3962
4047
  setInitialRenderState(ctx, { didLayout: true });
4048
+ if (state.pendingMaintainScrollAtEnd) {
4049
+ doMaintainScrollAtEnd(ctx);
4050
+ }
3963
4051
  }
3964
4052
 
3965
4053
  // src/core/calculateItemsInView.ts
@@ -4196,7 +4284,7 @@ function calculateItemsInView(ctx, params = {}) {
4196
4284
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4197
4285
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4198
4286
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4199
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4287
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4200
4288
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4201
4289
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4202
4290
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4700,9 +4788,12 @@ function runOrScheduleMVCPRecalculate(ctx) {
4700
4788
  } else {
4701
4789
  if (!state.mvcpAnchorLock) {
4702
4790
  state.scheduledWork.cancel("mvcpRecalculate");
4703
- calculateItemsInView(ctx, { doMVCP: true });
4791
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4704
4792
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4705
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4793
+ state.scheduledWork.frame(
4794
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4795
+ "mvcpRecalculate"
4796
+ );
4706
4797
  }
4707
4798
  }
4708
4799
  }
@@ -4829,7 +4920,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4829
4920
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
4830
4921
  needsRecalculate = true;
4831
4922
  }
4832
- if (prevSizeKnown !== void 0 && Math.abs(prevSizeKnown - size) > 5) {
4923
+ if (prevSizeKnown === void 0 || Math.abs(prevSizeKnown - size) > 5) {
4833
4924
  shouldMaintainScrollAtEnd = true;
4834
4925
  }
4835
4926
  onItemSizeChanged == null ? void 0 : onItemSizeChanged({
@@ -5069,6 +5160,30 @@ function useContainerItemSignals(containerContext) {
5069
5160
  itemKey
5070
5161
  };
5071
5162
  }
5163
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5164
+ let cancelled = false;
5165
+ Promise.resolve().then(() => {
5166
+ var _a3;
5167
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5168
+ if (!cancelled && ctx.state) {
5169
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5170
+ warnDevOnce(
5171
+ `${hookName}-missing-viewability-config`,
5172
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5173
+ );
5174
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5175
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5176
+ warnDevOnce(
5177
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5178
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5179
+ );
5180
+ }
5181
+ }
5182
+ });
5183
+ return () => {
5184
+ cancelled = true;
5185
+ };
5186
+ }
5072
5187
  function useAdaptiveRender() {
5073
5188
  const [mode] = useArr$(["adaptiveRender"]);
5074
5189
  return mode;
@@ -5108,7 +5223,9 @@ function useViewability(callback, configId) {
5108
5223
  const { containerId } = containerContext;
5109
5224
  const key = containerId + (configId != null ? configId : "");
5110
5225
  ctx.mapViewabilityCallbacks.set(key, callback);
5226
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5111
5227
  return () => {
5228
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5112
5229
  ctx.mapViewabilityCallbacks.delete(key);
5113
5230
  };
5114
5231
  }, [ctx, callback, configId, containerContext]);
@@ -5132,7 +5249,9 @@ function useViewabilityAmount(callback) {
5132
5249
  }
5133
5250
  const { containerId } = containerContext;
5134
5251
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5252
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5135
5253
  return () => {
5254
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5136
5255
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5137
5256
  };
5138
5257
  }, [ctx, callback, containerContext]);
@@ -5729,13 +5848,11 @@ function useDOMOrder(ref) {
5729
5848
  }, [ctx]);
5730
5849
  }
5731
5850
  function useFreshDataTransitionVisibility(readyToRender, transitionEpoch) {
5732
- const completedTransitionEpoch = useRef(transitionEpoch);
5733
- const isTransitionPending = completedTransitionEpoch.current !== transitionEpoch;
5851
+ const [completedTransitionEpoch, setCompletedTransitionEpoch] = useState(transitionEpoch);
5852
+ const isTransitionPending = completedTransitionEpoch !== transitionEpoch;
5734
5853
  useLayoutEffect(() => {
5735
- if (!readyToRender) {
5736
- completedTransitionEpoch.current = transitionEpoch;
5737
- }
5738
- }, [readyToRender, transitionEpoch]);
5854
+ setCompletedTransitionEpoch(transitionEpoch);
5855
+ }, [transitionEpoch]);
5739
5856
  return readyToRender && !isTransitionPending;
5740
5857
  }
5741
5858
 
@@ -6376,10 +6493,9 @@ function ScrollAdjust() {
6376
6493
  const lastScrollOffsetRef = React3.useRef(0);
6377
6494
  const lastScrollAdjustUserOffsetRef = React3.useRef(0);
6378
6495
  const resetPaddingRafRef = React3.useRef(void 0);
6379
- const resetPaddingBaselineRef = React3.useRef(void 0);
6496
+ const temporaryPaddingRef = React3.useRef(void 0);
6380
6497
  const contentNodeRef = React3.useRef(null);
6381
6498
  const callback = React3.useCallback(() => {
6382
- var _a3;
6383
6499
  const scrollAdjust = peek$(ctx, "scrollAdjust");
6384
6500
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
6385
6501
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
@@ -6403,19 +6519,28 @@ function ScrollAdjust() {
6403
6519
  const nextScroll = currentScroll + scrollDelta;
6404
6520
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
6405
6521
  if (needsTemporaryPadding) {
6406
- const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
6407
- resetPaddingBaselineRef.current = previousPaddingEnd;
6522
+ const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp];
6523
+ const previousTemporaryPadding = temporaryPaddingRef.current;
6524
+ const baselinePaddingEnd = (previousTemporaryPadding == null ? void 0 : previousTemporaryPadding.value) === currentInlinePaddingEnd ? previousTemporaryPadding.baseline : currentInlinePaddingEnd;
6408
6525
  const pad = (nextScroll + viewportSize - totalSize) * 2;
6409
- contentNode.style[axis.paddingEndProp] = `${pad}px`;
6526
+ const currentPaddingEnd = Number.parseFloat(
6527
+ window.getComputedStyle(contentNode)[axis.paddingEndProp]
6528
+ );
6529
+ const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`;
6530
+ temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd };
6531
+ contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd;
6410
6532
  void contentNode.offsetHeight;
6411
6533
  scrollBy();
6412
6534
  if (resetPaddingRafRef.current !== void 0) {
6413
6535
  cancelAnimationFrame(resetPaddingRafRef.current);
6414
6536
  }
6415
6537
  resetPaddingRafRef.current = requestAnimationFrame(() => {
6538
+ const temporaryPadding = temporaryPaddingRef.current;
6416
6539
  resetPaddingRafRef.current = void 0;
6417
- resetPaddingBaselineRef.current = void 0;
6418
- contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
6540
+ temporaryPaddingRef.current = void 0;
6541
+ if (contentNode.style[axis.paddingEndProp] === (temporaryPadding == null ? void 0 : temporaryPadding.value)) {
6542
+ contentNode.style[axis.paddingEndProp] = temporaryPadding.baseline;
6543
+ }
6419
6544
  });
6420
6545
  } else {
6421
6546
  scrollBy();
@@ -6999,11 +7124,29 @@ var ScheduledWork = class {
6999
7124
  constructor() {
7000
7125
  this.work = /* @__PURE__ */ new Map();
7001
7126
  }
7127
+ microtask(callback, key) {
7128
+ const pendingWork = this.work.get(key);
7129
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
7130
+ pendingWork.callback = callback;
7131
+ } else {
7132
+ this.cancel(key);
7133
+ const work = { callback, cancel: () => {
7134
+ } };
7135
+ this.work.set(key, work);
7136
+ queueMicrotask(() => {
7137
+ var _a3;
7138
+ if (this.work.get(key) === work) {
7139
+ this.work.delete(key);
7140
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
7141
+ }
7142
+ });
7143
+ }
7144
+ }
7002
7145
  timeout(callback, delay, key) {
7003
7146
  if (key) {
7004
7147
  this.cancel(key);
7005
7148
  }
7006
- const work = [void 0, clearTimeout];
7149
+ const work = { cancel: () => clearTimeout(handle) };
7007
7150
  const handle = setTimeout(() => {
7008
7151
  const workKey = key != null ? key : handle;
7009
7152
  if (this.work.get(workKey) === work) {
@@ -7011,14 +7154,13 @@ var ScheduledWork = class {
7011
7154
  callback();
7012
7155
  }
7013
7156
  }, delay);
7014
- work[0] = handle;
7015
7157
  this.work.set(key != null ? key : handle, work);
7016
7158
  }
7017
7159
  frame(callback, key) {
7018
7160
  this.cancel(key);
7019
- const work = [void 0, cancelAnimationFrame];
7161
+ const work = { cancel: () => cancelAnimationFrame(handle) };
7020
7162
  this.work.set(key, work);
7021
- work[0] = requestAnimationFrame(() => {
7163
+ const handle = requestAnimationFrame(() => {
7022
7164
  if (this.work.get(key) === work) {
7023
7165
  this.work.delete(key);
7024
7166
  callback();
@@ -7027,21 +7169,21 @@ var ScheduledWork = class {
7027
7169
  }
7028
7170
  register(key, cancel) {
7029
7171
  this.cancel(key);
7030
- this.work.set(key, [void 0, cancel]);
7172
+ this.work.set(key, { cancel });
7031
7173
  }
7032
7174
  cancel(key) {
7033
7175
  const work = this.work.get(key);
7034
7176
  if (work) {
7035
7177
  this.work.delete(key);
7036
- work[1](work[0]);
7178
+ work.cancel();
7037
7179
  }
7038
7180
  }
7039
7181
  has(key) {
7040
7182
  return this.work.has(key);
7041
7183
  }
7042
7184
  dispose() {
7043
- for (const [handle, cancel] of this.work.values()) {
7044
- cancel(handle);
7185
+ for (const work of this.work.values()) {
7186
+ work.cancel();
7045
7187
  }
7046
7188
  this.work.clear();
7047
7189
  }
@@ -7098,6 +7240,25 @@ var ScrollAdjustHandler = class {
7098
7240
  }
7099
7241
  };
7100
7242
 
7243
+ // src/core/scrollToEnd.ts
7244
+ function scrollToEnd(ctx, options) {
7245
+ const state = ctx.state;
7246
+ const data = state.props.data;
7247
+ const index = data.length - 1;
7248
+ if (index === -1) {
7249
+ return false;
7250
+ }
7251
+ const paddingBottom = state.props.stylePaddingBottom || 0;
7252
+ const footerSize = peek$(ctx, "footerSize") || 0;
7253
+ scrollToIndex(ctx, {
7254
+ ...options,
7255
+ index,
7256
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7257
+ viewPosition: 1
7258
+ });
7259
+ return true;
7260
+ }
7261
+
7101
7262
  // src/core/updateContentInsetEndAdjustment.ts
7102
7263
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
7103
7264
  const state = ctx.state;
@@ -7160,25 +7321,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
7160
7321
  }
7161
7322
  }
7162
7323
 
7163
- // src/core/scrollToEnd.ts
7164
- function scrollToEnd(ctx, options) {
7165
- const state = ctx.state;
7166
- const data = state.props.data;
7167
- const index = data.length - 1;
7168
- if (index === -1) {
7169
- return false;
7170
- }
7171
- const paddingBottom = state.props.stylePaddingBottom || 0;
7172
- const footerSize = peek$(ctx, "footerSize") || 0;
7173
- scrollToIndex(ctx, {
7174
- ...options,
7175
- index,
7176
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7177
- viewPosition: 1
7178
- });
7179
- return true;
7180
- }
7181
-
7182
7324
  // src/utils/createImperativeHandle.ts
7183
7325
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
7184
7326
  function getAverageItemSizes(state) {
@@ -7201,9 +7343,9 @@ function triggerMountedContainerLayouts(ctx) {
7201
7343
  }
7202
7344
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7203
7345
  const state = ctx.state;
7346
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
7204
7347
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
7205
7348
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
7206
- let imperativeScrollToken = 0;
7207
7349
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
7208
7350
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
7209
7351
  var _a3;
@@ -7225,7 +7367,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7225
7367
  const startedAt = Date.now();
7226
7368
  let stableFrames = 0;
7227
7369
  const check = () => {
7228
- if (token !== imperativeScrollToken) {
7370
+ if (!scrollRequestTracker.isCurrent(token)) {
7229
7371
  return;
7230
7372
  }
7231
7373
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -7243,40 +7385,23 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7243
7385
  state.scheduledWork.frame(check, "imperativeScrollReady");
7244
7386
  };
7245
7387
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
7246
- const runNow = () => {
7247
- if (token !== imperativeScrollToken) {
7248
- return;
7249
- }
7250
- const didStartScroll = run();
7251
- if (!didStartScroll || !state.scrollingTo) {
7252
- if (state.pendingScrollResolve === resolve) {
7253
- state.pendingScrollResolve = void 0;
7254
- }
7255
- resolve();
7256
- }
7257
- };
7388
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
7258
7389
  if (isSettlingAfterDataChange() || !isReady()) {
7259
7390
  runWhenReady(token, runNow, isReady);
7260
7391
  } else {
7261
7392
  runNow();
7262
7393
  }
7263
7394
  };
7264
- const startImperativeScroll = (resolve) => {
7265
- state.scheduledWork.cancel("imperativeScrollReady");
7266
- const token = ++imperativeScrollToken;
7267
- settlePendingImperativeScroll(state);
7268
- state.pendingScrollResolve = resolve;
7269
- return token;
7270
- };
7271
7395
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
7272
- const token = startImperativeScroll(resolve);
7396
+ const token = scrollRequestTracker.start(resolve);
7397
+ supersedeInitialScroll(ctx);
7273
7398
  runScrollRequest(token, resolve, run, isReady);
7274
7399
  });
7275
7400
  state.runPendingScrollToEnd = () => {
7276
7401
  const pendingScroll = state.pendingScrollToEnd;
7277
7402
  if (pendingScroll) {
7278
7403
  state.pendingScrollToEnd = void 0;
7279
- if (pendingScroll.token === imperativeScrollToken) {
7404
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
7280
7405
  runScrollRequest(
7281
7406
  pendingScroll.token,
7282
7407
  pendingScroll.resolve,
@@ -7390,15 +7515,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7390
7515
  }),
7391
7516
  scrollToEnd: (options) => new Promise((resolve) => {
7392
7517
  var _a3;
7393
- const token = startImperativeScroll(resolve);
7518
+ const token = scrollRequestTracker.start(resolve);
7394
7519
  state.pendingScrollToEnd = {
7395
7520
  options,
7396
7521
  resolve,
7397
7522
  token
7398
7523
  };
7399
- if (scheduleImperativeScrollCommit) {
7400
- scheduleImperativeScrollCommit();
7401
- } else {
7524
+ scheduleImperativeScrollCommit == null ? void 0 : scheduleImperativeScrollCommit();
7525
+ supersedeInitialScroll(ctx);
7526
+ if (!scheduleImperativeScrollCommit) {
7402
7527
  (_a3 = state.runPendingScrollToEnd) == null ? void 0 : _a3.call(state);
7403
7528
  }
7404
7529
  }),
@@ -7770,6 +7895,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7770
7895
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7771
7896
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7772
7897
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7898
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7773
7899
  const initialScrollProp = initialScrollAtEnd ? {
7774
7900
  index: Math.max(0, dataProp.length - 1),
7775
7901
  preserveForBottomPadding: true,
@@ -7782,7 +7908,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7782
7908
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7783
7909
  } : {
7784
7910
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7785
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7911
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7912
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7913
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7786
7914
  } : initialScrollUsesOffsetOnly ? {
7787
7915
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7788
7916
  index: 0,
@@ -7888,6 +8016,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7888
8016
  viewabilityConfigCallbackPairs: void 0
7889
8017
  };
7890
8018
  const internalState = ctx.state;
8019
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7891
8020
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7892
8021
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7893
8022
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -8241,6 +8370,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8241
8370
  onScroll: (event) => onScroll(ctx, event),
8242
8371
  onScrollBeginDrag: (event) => {
8243
8372
  var _a4, _b2;
8373
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8374
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8375
+ cancelImperativeScroll(state);
8376
+ }
8377
+ if (maintainingScrollAtEnd) {
8378
+ finishMaintainScrollAtEnd(ctx);
8379
+ }
8244
8380
  prepareReachedEdgeForNextUserScroll(ctx);
8245
8381
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8246
8382
  },