@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.
@@ -76,10 +76,6 @@ function getStylePaddingEnd(props) {
76
76
  }
77
77
  return (isHorizontalRTLProps(props) ? props.stylePaddingLeft : props.stylePaddingRight) || 0;
78
78
  }
79
- function getLogicalHorizontalMaxOffset(state, contentWidth) {
80
- var _a3;
81
- return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0;
82
- }
83
79
  function getHorizontalInsetEnd(state, inset) {
84
80
  if (!inset) {
85
81
  return 0;
@@ -434,6 +430,28 @@ var EDGE_POSITION_EPSILON = 1;
434
430
  var ENABLE_DEVMODE = IS_DEV && false;
435
431
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
436
432
 
433
+ // src/core/cancelImperativeScroll.ts
434
+ function cancelScrollCompletionChecks({ scheduledWork }) {
435
+ scheduledWork.cancel("checkFinishedScrollFrame");
436
+ scheduledWork.cancel("checkFinishedScrollRetryFrame");
437
+ scheduledWork.cancel("checkFinishedScrollFallback");
438
+ scheduledWork.cancel("platformScrollCompletion");
439
+ }
440
+ function settlePendingImperativeScroll(state) {
441
+ var _a3, _b;
442
+ const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
443
+ state.pendingScrollResolve = void 0;
444
+ state.pendingScrollToEnd = void 0;
445
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
446
+ }
447
+ function cancelImperativeScroll(state) {
448
+ cancelScrollCompletionChecks(state);
449
+ state.scheduledWork.cancel("imperativeScrollReady");
450
+ state.scrollingTo = void 0;
451
+ state.scrollTargetPinnedRange = void 0;
452
+ settlePendingImperativeScroll(state);
453
+ }
454
+
437
455
  // src/core/deferredPublicOnScroll.ts
438
456
  function withResolvedContentOffset(state, event, resolvedOffset) {
439
457
  return {
@@ -688,6 +706,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
688
706
  } = state;
689
707
  const contentSize = getContentSize(ctx);
690
708
  resetSharedEdgeGateIfOutsideHysteresis(ctx);
709
+ if (state.props.data.length === 0 && scrollLength > 0 && contentSize <= scrollLength) {
710
+ set$(ctx, "isAtEnd", true);
711
+ set$(ctx, "isNearEnd", true);
712
+ set$(ctx, "isWithinMaintainScrollAtEndThreshold", true);
713
+ return;
714
+ }
691
715
  if (contentSize > 0 && queuedInitialLayout) {
692
716
  const insetEnd = getContentInsetEnd(ctx);
693
717
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -697,7 +721,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
697
721
  set$(
698
722
  ctx,
699
723
  "isWithinMaintainScrollAtEndThreshold",
700
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
724
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
701
725
  );
702
726
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
703
727
  if (!shouldSkipThresholdChecks) {
@@ -935,6 +959,24 @@ function clearPreservedInitialScrollTarget(state) {
935
959
  state.initialScroll = void 0;
936
960
  setInitialScrollSession(state);
937
961
  }
962
+ function supersedeInitialScroll(ctx) {
963
+ var _a3, _b, _c;
964
+ const state = ctx.state;
965
+ const bootstrapInitialScroll = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
966
+ if (state.initialScroll || bootstrapInitialScroll || ((_b = state.scrollingTo) == null ? void 0 : _b.isInitialScroll)) {
967
+ if ((bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle) !== void 0 && typeof cancelAnimationFrame === "function") {
968
+ cancelAnimationFrame(bootstrapInitialScroll.frameHandle);
969
+ }
970
+ if ((_c = state.scrollingTo) == null ? void 0 : _c.isInitialScroll) {
971
+ cancelScrollCompletionChecks(state);
972
+ state.scrollingTo = void 0;
973
+ state.scrollTargetPinnedRange = void 0;
974
+ }
975
+ initialScrollCompletion.resetFlags(state);
976
+ setInitialScrollSession(state, { bootstrap: null });
977
+ finishInitialScroll(ctx);
978
+ }
979
+ }
938
980
  function finishInitialScroll(ctx, options) {
939
981
  var _a3, _b, _c;
940
982
  const state = ctx.state;
@@ -1024,10 +1066,22 @@ function getAlignItemsAtEndPadding(ctx) {
1024
1066
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1025
1067
  }
1026
1068
  function updateContentMetricsState(ctx) {
1069
+ var _a3;
1070
+ const { state } = ctx;
1027
1071
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1028
1072
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1029
1073
  if (previousPadding !== nextPadding) {
1030
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1074
+ const isAnimatedMaintainActive = state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated";
1075
+ const isMaintainExpected = state.didContainersLayout && ((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.animated) && (isAnimatedMaintainActive || previousPadding > nextPadding && peek$(ctx, "isWithinMaintainScrollAtEndThreshold"));
1076
+ if (isMaintainExpected && !isAnimatedMaintainActive && !state.pendingMaintainScrollAtEnd) {
1077
+ state.scheduledWork.microtask(() => {
1078
+ if (!state.maintainingScrollAtEnd && !state.pendingMaintainScrollAtEnd) {
1079
+ set$(ctx, "alignItemsAtEndPadding", getAlignItemsAtEndPadding(ctx));
1080
+ }
1081
+ }, "alignItemsAtEndPaddingFallback");
1082
+ } else if (!isMaintainExpected) {
1083
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1084
+ }
1031
1085
  }
1032
1086
  }
1033
1087
 
@@ -1248,29 +1302,6 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1248
1302
  return clampedOffset;
1249
1303
  }
1250
1304
 
1251
- // src/core/cancelImperativeScroll.ts
1252
- function cancelScrollCompletionChecks({ scheduledWork }) {
1253
- scheduledWork.cancel("checkFinishedScrollFrame");
1254
- scheduledWork.cancel("checkFinishedScrollRetryFrame");
1255
- scheduledWork.cancel("checkFinishedScrollFallback");
1256
- scheduledWork.cancel("platformScrollCompletion");
1257
- }
1258
- function settlePendingImperativeScroll(state) {
1259
- var _a3, _b;
1260
- const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
1261
- state.pendingScrollResolve = void 0;
1262
- state.pendingScrollToEnd = void 0;
1263
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1264
- }
1265
- function cancelImperativeScroll(state) {
1266
- cancelScrollCompletionChecks(state);
1267
- state.scheduledWork.cancel("imperativeScrollReady");
1268
- state.runPendingScrollToEnd = void 0;
1269
- state.scrollingTo = void 0;
1270
- state.scrollTargetPinnedRange = void 0;
1271
- settlePendingImperativeScroll(state);
1272
- }
1273
-
1274
1305
  // src/core/finishScrollTo.ts
1275
1306
  function finishScrollTo(ctx) {
1276
1307
  var _a3, _b;
@@ -1408,22 +1439,110 @@ function listenForScrollEnd(ctx, params) {
1408
1439
  scheduledWork.register("platformScrollCompletion", cancel);
1409
1440
  }
1410
1441
 
1442
+ // src/core/scrollRequestTracker.ts
1443
+ function getScrollRequestTracker(ctx) {
1444
+ if (!ctx.scrollRequestTracker) {
1445
+ let currentToken = 0;
1446
+ const start = (resolve) => {
1447
+ const state = ctx.state;
1448
+ state.scheduledWork.cancel("imperativeScrollReady");
1449
+ const token = ++currentToken;
1450
+ settlePendingImperativeScroll(state);
1451
+ state.pendingScrollResolve = resolve;
1452
+ return token;
1453
+ };
1454
+ const runNow = (token, resolve, run) => {
1455
+ const state = ctx.state;
1456
+ if (token !== currentToken) {
1457
+ return;
1458
+ }
1459
+ const didStartScroll = run();
1460
+ if (!didStartScroll || !state.scrollingTo) {
1461
+ if (state.pendingScrollResolve === resolve) {
1462
+ state.pendingScrollResolve = void 0;
1463
+ }
1464
+ resolve();
1465
+ }
1466
+ };
1467
+ ctx.scrollRequestTracker = {
1468
+ isCurrent: (token) => token === currentToken,
1469
+ runNow,
1470
+ runNowIfIdle: (run) => {
1471
+ const state = ctx.state;
1472
+ if (state.pendingScrollResolve || state.scrollingTo) {
1473
+ return Promise.resolve();
1474
+ }
1475
+ return new Promise((resolve) => {
1476
+ const token = start(resolve);
1477
+ runNow(token, resolve, run);
1478
+ });
1479
+ },
1480
+ start
1481
+ };
1482
+ }
1483
+ return ctx.scrollRequestTracker;
1484
+ }
1485
+
1486
+ // src/utils/requestAdjust.ts
1487
+ function requestAdjust(ctx, positionDiff, source) {
1488
+ const state = ctx.state;
1489
+ if (Math.abs(positionDiff) > 0.1) {
1490
+ const doit = () => {
1491
+ {
1492
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
1493
+ if (state.adjustingFromInitialMount) {
1494
+ state.adjustingFromInitialMount--;
1495
+ }
1496
+ }
1497
+ };
1498
+ state.scroll += positionDiff;
1499
+ state.scrollForNextCalculateItemsInView = void 0;
1500
+ const readyToRender = peek$(ctx, "readyToRender");
1501
+ if (readyToRender) {
1502
+ doit();
1503
+ } else {
1504
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1505
+ requestAnimationFrame(doit);
1506
+ }
1507
+ }
1508
+ }
1509
+
1411
1510
  // src/core/doMaintainScrollAtEnd.ts
1511
+ function finishMaintainScrollAtEnd(ctx) {
1512
+ var _a3;
1513
+ const { state } = ctx;
1514
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1515
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1516
+ state.maintainingScrollAtEnd = void 0;
1517
+ state.pendingMaintainScrollAtEnd = false;
1518
+ if (currentPadding !== nextPadding) {
1519
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1520
+ state.scrollForNextCalculateItemsInView = void 0;
1521
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1522
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1523
+ requestAdjust(ctx, nextScroll - state.scroll);
1524
+ }
1525
+ }
1412
1526
  function doMaintainScrollAtEnd(ctx) {
1413
1527
  const state = ctx.state;
1414
1528
  const {
1415
1529
  didContainersLayout,
1530
+ didFinishInitialScroll,
1416
1531
  pendingNativeMVCPAdjust,
1417
- refScroller,
1418
1532
  props: { maintainScrollAtEnd }
1419
1533
  } = state;
1420
1534
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1421
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1535
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1536
+ const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1537
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1538
+ state.pendingMaintainScrollAtEnd = true;
1539
+ return false;
1540
+ }
1422
1541
  if (pendingNativeMVCPAdjust) {
1423
1542
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1424
1543
  return false;
1425
1544
  }
1426
- if (shouldMaintainScrollAtEnd) {
1545
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1427
1546
  state.pendingMaintainScrollAtEnd = false;
1428
1547
  const contentSize = getContentSize(ctx);
1429
1548
  if (contentSize < state.scrollLength) {
@@ -1435,39 +1554,29 @@ function doMaintainScrollAtEnd(ctx) {
1435
1554
  const scrollAtRequest = state.scroll;
1436
1555
  state.maintainingScrollAtEnd = pendingState;
1437
1556
  requestAnimationFrame(() => {
1557
+ if (state.maintainingScrollAtEnd !== pendingState) {
1558
+ return;
1559
+ }
1438
1560
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1439
1561
  const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1440
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1562
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1441
1563
  state.maintainingScrollAtEnd = activeState;
1442
- const scroller = refScroller.current;
1443
- if (state.props.horizontal && isHorizontalRTL(state)) {
1444
- const currentContentSize = getContentSize(ctx);
1445
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1446
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1447
- scroller == null ? void 0 : scroller.scrollTo({
1448
- animated: maintainScrollAtEnd.animated,
1449
- x: nativeOffset,
1450
- y: 0
1451
- });
1452
- } else {
1453
- scroller == null ? void 0 : scroller.scrollToEnd({
1454
- animated: maintainScrollAtEnd.animated
1455
- });
1456
- }
1457
- setTimeout(
1458
- () => {
1459
- if (state.maintainingScrollAtEnd === activeState) {
1460
- state.maintainingScrollAtEnd = void 0;
1461
- if (state.pendingMaintainScrollAtEnd) {
1462
- doMaintainScrollAtEnd(ctx);
1463
- }
1464
- }
1465
- },
1466
- maintainScrollAtEnd.animated ? 500 : 0
1564
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1565
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1467
1566
  );
1567
+ void scrollPromise.then(() => {
1568
+ if (state.maintainingScrollAtEnd !== activeState) {
1569
+ return;
1570
+ }
1571
+ if (state.pendingMaintainScrollAtEnd) {
1572
+ state.maintainingScrollAtEnd = void 0;
1573
+ doMaintainScrollAtEnd(ctx);
1574
+ } else {
1575
+ finishMaintainScrollAtEnd(ctx);
1576
+ }
1577
+ });
1468
1578
  } else if (state.maintainingScrollAtEnd === pendingState) {
1469
- state.maintainingScrollAtEnd = void 0;
1470
- state.pendingMaintainScrollAtEnd = false;
1579
+ finishMaintainScrollAtEnd(ctx);
1471
1580
  }
1472
1581
  });
1473
1582
  } else {
@@ -1475,34 +1584,10 @@ function doMaintainScrollAtEnd(ctx) {
1475
1584
  }
1476
1585
  return true;
1477
1586
  }
1478
- state.pendingMaintainScrollAtEnd = false;
1587
+ finishMaintainScrollAtEnd(ctx);
1479
1588
  return false;
1480
1589
  }
1481
1590
 
1482
- // src/utils/requestAdjust.ts
1483
- function requestAdjust(ctx, positionDiff, dataChanged) {
1484
- const state = ctx.state;
1485
- if (Math.abs(positionDiff) > 0.1) {
1486
- const doit = () => {
1487
- {
1488
- state.scrollAdjustHandler.requestAdjust(positionDiff);
1489
- if (state.adjustingFromInitialMount) {
1490
- state.adjustingFromInitialMount--;
1491
- }
1492
- }
1493
- };
1494
- state.scroll += positionDiff;
1495
- state.scrollForNextCalculateItemsInView = void 0;
1496
- const readyToRender = peek$(ctx, "readyToRender");
1497
- if (readyToRender) {
1498
- doit();
1499
- } else {
1500
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1501
- requestAnimationFrame(doit);
1502
- }
1503
- }
1504
- }
1505
-
1506
1591
  // src/core/mvcp.ts
1507
1592
  var MVCP_POSITION_EPSILON = 0.1;
1508
1593
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1639,7 +1724,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1639
1724
  }
1640
1725
  return false;
1641
1726
  }
1642
- function prepareMVCP(ctx, dataChanged) {
1727
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1643
1728
  const state = ctx.state;
1644
1729
  const { idsInView, positions, props } = state;
1645
1730
  const {
@@ -1891,6 +1976,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1891
1976
  state.scrollTime = currentTime;
1892
1977
  const scrollDelta = Math.abs(newScroll - prevScroll);
1893
1978
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1979
+ const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1980
+ if (didCancelMaintainScrollAtEnd) {
1981
+ finishMaintainScrollAtEnd(ctx);
1982
+ }
1894
1983
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
1895
1984
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1896
1985
  const scrollLength = state.scrollLength;
@@ -1899,7 +1988,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1899
1988
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
1900
1989
  const lastCalculated = state.scrollLastCalculate;
1901
1990
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1902
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1991
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1903
1992
  if (shouldUpdate) {
1904
1993
  state.scrollLastCalculate = state.scroll;
1905
1994
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2728,7 +2817,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2728
2817
  }
2729
2818
  }
2730
2819
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2731
- var _a3, _b, _c;
2820
+ var _a3, _b;
2732
2821
  const state = ctx.state;
2733
2822
  const initialScroll = state.initialScroll;
2734
2823
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2742,19 +2831,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2742
2831
  if (state.didFinishInitialScroll) {
2743
2832
  schedulePreservedEndAnchorCorrection(ctx);
2744
2833
  } else if (scrollingTo) {
2745
- const existingWatchdog = initialScrollWatchdog.get(state);
2746
- scrollingTo.offset = resolvedOffset;
2747
- scrollingTo.targetOffset = resolvedOffset;
2748
2834
  state.initialScroll = {
2749
2835
  ...initialScroll,
2750
2836
  contentOffset: resolvedOffset
2751
2837
  };
2752
- state.hasScrolled = false;
2753
- initialScrollWatchdog.set(state, {
2754
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2755
- targetOffset: resolvedOffset
2838
+ dispatchInitialScroll(ctx, {
2839
+ forceScroll: true,
2840
+ resolvedOffset,
2841
+ target: state.initialScroll
2756
2842
  });
2757
- requestAdjust(ctx, offsetDiff);
2758
2843
  }
2759
2844
  }
2760
2845
  } else {
@@ -3981,6 +4066,9 @@ function setDidLayout(ctx) {
3981
4066
  state.queuedInitialLayout = true;
3982
4067
  checkAtBottom(ctx);
3983
4068
  setInitialRenderState(ctx, { didLayout: true });
4069
+ if (state.pendingMaintainScrollAtEnd) {
4070
+ doMaintainScrollAtEnd(ctx);
4071
+ }
3984
4072
  }
3985
4073
 
3986
4074
  // src/core/calculateItemsInView.ts
@@ -4217,7 +4305,7 @@ function calculateItemsInView(ctx, params = {}) {
4217
4305
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4218
4306
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4219
4307
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4220
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4308
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4221
4309
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4222
4310
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4223
4311
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4721,9 +4809,12 @@ function runOrScheduleMVCPRecalculate(ctx) {
4721
4809
  } else {
4722
4810
  if (!state.mvcpAnchorLock) {
4723
4811
  state.scheduledWork.cancel("mvcpRecalculate");
4724
- calculateItemsInView(ctx, { doMVCP: true });
4812
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4725
4813
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4726
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4814
+ state.scheduledWork.frame(
4815
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4816
+ "mvcpRecalculate"
4817
+ );
4727
4818
  }
4728
4819
  }
4729
4820
  }
@@ -4850,7 +4941,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4850
4941
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
4851
4942
  needsRecalculate = true;
4852
4943
  }
4853
- if (prevSizeKnown !== void 0 && Math.abs(prevSizeKnown - size) > 5) {
4944
+ if (prevSizeKnown === void 0 || Math.abs(prevSizeKnown - size) > 5) {
4854
4945
  shouldMaintainScrollAtEnd = true;
4855
4946
  }
4856
4947
  onItemSizeChanged == null ? void 0 : onItemSizeChanged({
@@ -5090,6 +5181,30 @@ function useContainerItemSignals(containerContext) {
5090
5181
  itemKey
5091
5182
  };
5092
5183
  }
5184
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5185
+ let cancelled = false;
5186
+ Promise.resolve().then(() => {
5187
+ var _a3;
5188
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5189
+ if (!cancelled && ctx.state) {
5190
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5191
+ warnDevOnce(
5192
+ `${hookName}-missing-viewability-config`,
5193
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5194
+ );
5195
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5196
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5197
+ warnDevOnce(
5198
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5199
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5200
+ );
5201
+ }
5202
+ }
5203
+ });
5204
+ return () => {
5205
+ cancelled = true;
5206
+ };
5207
+ }
5093
5208
  function useAdaptiveRender() {
5094
5209
  const [mode] = useArr$(["adaptiveRender"]);
5095
5210
  return mode;
@@ -5129,7 +5244,9 @@ function useViewability(callback, configId) {
5129
5244
  const { containerId } = containerContext;
5130
5245
  const key = containerId + (configId != null ? configId : "");
5131
5246
  ctx.mapViewabilityCallbacks.set(key, callback);
5247
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5132
5248
  return () => {
5249
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5133
5250
  ctx.mapViewabilityCallbacks.delete(key);
5134
5251
  };
5135
5252
  }, [ctx, callback, configId, containerContext]);
@@ -5153,7 +5270,9 @@ function useViewabilityAmount(callback) {
5153
5270
  }
5154
5271
  const { containerId } = containerContext;
5155
5272
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5273
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5156
5274
  return () => {
5275
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5157
5276
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5158
5277
  };
5159
5278
  }, [ctx, callback, containerContext]);
@@ -5750,13 +5869,11 @@ function useDOMOrder(ref) {
5750
5869
  }, [ctx]);
5751
5870
  }
5752
5871
  function useFreshDataTransitionVisibility(readyToRender, transitionEpoch) {
5753
- const completedTransitionEpoch = React3.useRef(transitionEpoch);
5754
- const isTransitionPending = completedTransitionEpoch.current !== transitionEpoch;
5872
+ const [completedTransitionEpoch, setCompletedTransitionEpoch] = React3.useState(transitionEpoch);
5873
+ const isTransitionPending = completedTransitionEpoch !== transitionEpoch;
5755
5874
  React3.useLayoutEffect(() => {
5756
- if (!readyToRender) {
5757
- completedTransitionEpoch.current = transitionEpoch;
5758
- }
5759
- }, [readyToRender, transitionEpoch]);
5875
+ setCompletedTransitionEpoch(transitionEpoch);
5876
+ }, [transitionEpoch]);
5760
5877
  return readyToRender && !isTransitionPending;
5761
5878
  }
5762
5879
 
@@ -6397,10 +6514,9 @@ function ScrollAdjust() {
6397
6514
  const lastScrollOffsetRef = React3__namespace.useRef(0);
6398
6515
  const lastScrollAdjustUserOffsetRef = React3__namespace.useRef(0);
6399
6516
  const resetPaddingRafRef = React3__namespace.useRef(void 0);
6400
- const resetPaddingBaselineRef = React3__namespace.useRef(void 0);
6517
+ const temporaryPaddingRef = React3__namespace.useRef(void 0);
6401
6518
  const contentNodeRef = React3__namespace.useRef(null);
6402
6519
  const callback = React3__namespace.useCallback(() => {
6403
- var _a3;
6404
6520
  const scrollAdjust = peek$(ctx, "scrollAdjust");
6405
6521
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
6406
6522
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
@@ -6424,19 +6540,28 @@ function ScrollAdjust() {
6424
6540
  const nextScroll = currentScroll + scrollDelta;
6425
6541
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
6426
6542
  if (needsTemporaryPadding) {
6427
- const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
6428
- resetPaddingBaselineRef.current = previousPaddingEnd;
6543
+ const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp];
6544
+ const previousTemporaryPadding = temporaryPaddingRef.current;
6545
+ const baselinePaddingEnd = (previousTemporaryPadding == null ? void 0 : previousTemporaryPadding.value) === currentInlinePaddingEnd ? previousTemporaryPadding.baseline : currentInlinePaddingEnd;
6429
6546
  const pad = (nextScroll + viewportSize - totalSize) * 2;
6430
- contentNode.style[axis.paddingEndProp] = `${pad}px`;
6547
+ const currentPaddingEnd = Number.parseFloat(
6548
+ window.getComputedStyle(contentNode)[axis.paddingEndProp]
6549
+ );
6550
+ const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`;
6551
+ temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd };
6552
+ contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd;
6431
6553
  void contentNode.offsetHeight;
6432
6554
  scrollBy();
6433
6555
  if (resetPaddingRafRef.current !== void 0) {
6434
6556
  cancelAnimationFrame(resetPaddingRafRef.current);
6435
6557
  }
6436
6558
  resetPaddingRafRef.current = requestAnimationFrame(() => {
6559
+ const temporaryPadding = temporaryPaddingRef.current;
6437
6560
  resetPaddingRafRef.current = void 0;
6438
- resetPaddingBaselineRef.current = void 0;
6439
- contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
6561
+ temporaryPaddingRef.current = void 0;
6562
+ if (contentNode.style[axis.paddingEndProp] === (temporaryPadding == null ? void 0 : temporaryPadding.value)) {
6563
+ contentNode.style[axis.paddingEndProp] = temporaryPadding.baseline;
6564
+ }
6440
6565
  });
6441
6566
  } else {
6442
6567
  scrollBy();
@@ -7020,11 +7145,29 @@ var ScheduledWork = class {
7020
7145
  constructor() {
7021
7146
  this.work = /* @__PURE__ */ new Map();
7022
7147
  }
7148
+ microtask(callback, key) {
7149
+ const pendingWork = this.work.get(key);
7150
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
7151
+ pendingWork.callback = callback;
7152
+ } else {
7153
+ this.cancel(key);
7154
+ const work = { callback, cancel: () => {
7155
+ } };
7156
+ this.work.set(key, work);
7157
+ queueMicrotask(() => {
7158
+ var _a3;
7159
+ if (this.work.get(key) === work) {
7160
+ this.work.delete(key);
7161
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
7162
+ }
7163
+ });
7164
+ }
7165
+ }
7023
7166
  timeout(callback, delay, key) {
7024
7167
  if (key) {
7025
7168
  this.cancel(key);
7026
7169
  }
7027
- const work = [void 0, clearTimeout];
7170
+ const work = { cancel: () => clearTimeout(handle) };
7028
7171
  const handle = setTimeout(() => {
7029
7172
  const workKey = key != null ? key : handle;
7030
7173
  if (this.work.get(workKey) === work) {
@@ -7032,14 +7175,13 @@ var ScheduledWork = class {
7032
7175
  callback();
7033
7176
  }
7034
7177
  }, delay);
7035
- work[0] = handle;
7036
7178
  this.work.set(key != null ? key : handle, work);
7037
7179
  }
7038
7180
  frame(callback, key) {
7039
7181
  this.cancel(key);
7040
- const work = [void 0, cancelAnimationFrame];
7182
+ const work = { cancel: () => cancelAnimationFrame(handle) };
7041
7183
  this.work.set(key, work);
7042
- work[0] = requestAnimationFrame(() => {
7184
+ const handle = requestAnimationFrame(() => {
7043
7185
  if (this.work.get(key) === work) {
7044
7186
  this.work.delete(key);
7045
7187
  callback();
@@ -7048,21 +7190,21 @@ var ScheduledWork = class {
7048
7190
  }
7049
7191
  register(key, cancel) {
7050
7192
  this.cancel(key);
7051
- this.work.set(key, [void 0, cancel]);
7193
+ this.work.set(key, { cancel });
7052
7194
  }
7053
7195
  cancel(key) {
7054
7196
  const work = this.work.get(key);
7055
7197
  if (work) {
7056
7198
  this.work.delete(key);
7057
- work[1](work[0]);
7199
+ work.cancel();
7058
7200
  }
7059
7201
  }
7060
7202
  has(key) {
7061
7203
  return this.work.has(key);
7062
7204
  }
7063
7205
  dispose() {
7064
- for (const [handle, cancel] of this.work.values()) {
7065
- cancel(handle);
7206
+ for (const work of this.work.values()) {
7207
+ work.cancel();
7066
7208
  }
7067
7209
  this.work.clear();
7068
7210
  }
@@ -7119,6 +7261,25 @@ var ScrollAdjustHandler = class {
7119
7261
  }
7120
7262
  };
7121
7263
 
7264
+ // src/core/scrollToEnd.ts
7265
+ function scrollToEnd(ctx, options) {
7266
+ const state = ctx.state;
7267
+ const data = state.props.data;
7268
+ const index = data.length - 1;
7269
+ if (index === -1) {
7270
+ return false;
7271
+ }
7272
+ const paddingBottom = state.props.stylePaddingBottom || 0;
7273
+ const footerSize = peek$(ctx, "footerSize") || 0;
7274
+ scrollToIndex(ctx, {
7275
+ ...options,
7276
+ index,
7277
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7278
+ viewPosition: 1
7279
+ });
7280
+ return true;
7281
+ }
7282
+
7122
7283
  // src/core/updateContentInsetEndAdjustment.ts
7123
7284
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
7124
7285
  const state = ctx.state;
@@ -7181,25 +7342,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
7181
7342
  }
7182
7343
  }
7183
7344
 
7184
- // src/core/scrollToEnd.ts
7185
- function scrollToEnd(ctx, options) {
7186
- const state = ctx.state;
7187
- const data = state.props.data;
7188
- const index = data.length - 1;
7189
- if (index === -1) {
7190
- return false;
7191
- }
7192
- const paddingBottom = state.props.stylePaddingBottom || 0;
7193
- const footerSize = peek$(ctx, "footerSize") || 0;
7194
- scrollToIndex(ctx, {
7195
- ...options,
7196
- index,
7197
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7198
- viewPosition: 1
7199
- });
7200
- return true;
7201
- }
7202
-
7203
7345
  // src/utils/createImperativeHandle.ts
7204
7346
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
7205
7347
  function getAverageItemSizes(state) {
@@ -7222,9 +7364,9 @@ function triggerMountedContainerLayouts(ctx) {
7222
7364
  }
7223
7365
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7224
7366
  const state = ctx.state;
7367
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
7225
7368
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
7226
7369
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
7227
- let imperativeScrollToken = 0;
7228
7370
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
7229
7371
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
7230
7372
  var _a3;
@@ -7246,7 +7388,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7246
7388
  const startedAt = Date.now();
7247
7389
  let stableFrames = 0;
7248
7390
  const check = () => {
7249
- if (token !== imperativeScrollToken) {
7391
+ if (!scrollRequestTracker.isCurrent(token)) {
7250
7392
  return;
7251
7393
  }
7252
7394
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -7264,40 +7406,23 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7264
7406
  state.scheduledWork.frame(check, "imperativeScrollReady");
7265
7407
  };
7266
7408
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
7267
- const runNow = () => {
7268
- if (token !== imperativeScrollToken) {
7269
- return;
7270
- }
7271
- const didStartScroll = run();
7272
- if (!didStartScroll || !state.scrollingTo) {
7273
- if (state.pendingScrollResolve === resolve) {
7274
- state.pendingScrollResolve = void 0;
7275
- }
7276
- resolve();
7277
- }
7278
- };
7409
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
7279
7410
  if (isSettlingAfterDataChange() || !isReady()) {
7280
7411
  runWhenReady(token, runNow, isReady);
7281
7412
  } else {
7282
7413
  runNow();
7283
7414
  }
7284
7415
  };
7285
- const startImperativeScroll = (resolve) => {
7286
- state.scheduledWork.cancel("imperativeScrollReady");
7287
- const token = ++imperativeScrollToken;
7288
- settlePendingImperativeScroll(state);
7289
- state.pendingScrollResolve = resolve;
7290
- return token;
7291
- };
7292
7416
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
7293
- const token = startImperativeScroll(resolve);
7417
+ const token = scrollRequestTracker.start(resolve);
7418
+ supersedeInitialScroll(ctx);
7294
7419
  runScrollRequest(token, resolve, run, isReady);
7295
7420
  });
7296
7421
  state.runPendingScrollToEnd = () => {
7297
7422
  const pendingScroll = state.pendingScrollToEnd;
7298
7423
  if (pendingScroll) {
7299
7424
  state.pendingScrollToEnd = void 0;
7300
- if (pendingScroll.token === imperativeScrollToken) {
7425
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
7301
7426
  runScrollRequest(
7302
7427
  pendingScroll.token,
7303
7428
  pendingScroll.resolve,
@@ -7411,15 +7536,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7411
7536
  }),
7412
7537
  scrollToEnd: (options) => new Promise((resolve) => {
7413
7538
  var _a3;
7414
- const token = startImperativeScroll(resolve);
7539
+ const token = scrollRequestTracker.start(resolve);
7415
7540
  state.pendingScrollToEnd = {
7416
7541
  options,
7417
7542
  resolve,
7418
7543
  token
7419
7544
  };
7420
- if (scheduleImperativeScrollCommit) {
7421
- scheduleImperativeScrollCommit();
7422
- } else {
7545
+ scheduleImperativeScrollCommit == null ? void 0 : scheduleImperativeScrollCommit();
7546
+ supersedeInitialScroll(ctx);
7547
+ if (!scheduleImperativeScrollCommit) {
7423
7548
  (_a3 = state.runPendingScrollToEnd) == null ? void 0 : _a3.call(state);
7424
7549
  }
7425
7550
  }),
@@ -7791,6 +7916,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7791
7916
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7792
7917
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7793
7918
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7919
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7794
7920
  const initialScrollProp = initialScrollAtEnd ? {
7795
7921
  index: Math.max(0, dataProp.length - 1),
7796
7922
  preserveForBottomPadding: true,
@@ -7803,7 +7929,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7803
7929
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7804
7930
  } : {
7805
7931
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7806
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7932
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7933
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7934
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7807
7935
  } : initialScrollUsesOffsetOnly ? {
7808
7936
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7809
7937
  index: 0,
@@ -7909,6 +8037,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7909
8037
  viewabilityConfigCallbackPairs: void 0
7910
8038
  };
7911
8039
  const internalState = ctx.state;
8040
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7912
8041
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7913
8042
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7914
8043
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -8262,6 +8391,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8262
8391
  onScroll: (event) => onScroll(ctx, event),
8263
8392
  onScrollBeginDrag: (event) => {
8264
8393
  var _a4, _b2;
8394
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8395
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8396
+ cancelImperativeScroll(state);
8397
+ }
8398
+ if (maintainingScrollAtEnd) {
8399
+ finishMaintainScrollAtEnd(ctx);
8400
+ }
8265
8401
  prepareReachedEdgeForNextUserScroll(ctx);
8266
8402
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8267
8403
  },