@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-native.mjs CHANGED
@@ -47,10 +47,6 @@ function getStylePaddingEnd(props) {
47
47
  }
48
48
  return (isHorizontalRTLProps(props) ? props.stylePaddingLeft : props.stylePaddingRight) || 0;
49
49
  }
50
- function getLogicalHorizontalMaxOffset(state, contentWidth) {
51
- var _a3;
52
- return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0;
53
- }
54
50
  function getHorizontalInsetEnd(state, inset) {
55
51
  if (!inset) {
56
52
  return 0;
@@ -436,6 +432,28 @@ var EDGE_POSITION_EPSILON = 1;
436
432
  var ENABLE_DEVMODE = IS_DEV && false;
437
433
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
438
434
 
435
+ // src/core/cancelImperativeScroll.ts
436
+ function cancelScrollCompletionChecks({ scheduledWork }) {
437
+ scheduledWork.cancel("checkFinishedScrollFrame");
438
+ scheduledWork.cancel("checkFinishedScrollRetryFrame");
439
+ scheduledWork.cancel("checkFinishedScrollFallback");
440
+ scheduledWork.cancel("platformScrollCompletion");
441
+ }
442
+ function settlePendingImperativeScroll(state) {
443
+ var _a3, _b;
444
+ const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
445
+ state.pendingScrollResolve = void 0;
446
+ state.pendingScrollToEnd = void 0;
447
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
448
+ }
449
+ function cancelImperativeScroll(state) {
450
+ cancelScrollCompletionChecks(state);
451
+ state.scheduledWork.cancel("imperativeScrollReady");
452
+ state.scrollingTo = void 0;
453
+ state.scrollTargetPinnedRange = void 0;
454
+ settlePendingImperativeScroll(state);
455
+ }
456
+
439
457
  // src/core/deferredPublicOnScroll.ts
440
458
  function withResolvedContentOffset(state, event, resolvedOffset) {
441
459
  return {
@@ -690,6 +708,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
690
708
  } = state;
691
709
  const contentSize = getContentSize(ctx);
692
710
  resetSharedEdgeGateIfOutsideHysteresis(ctx);
711
+ if (state.props.data.length === 0 && scrollLength > 0 && contentSize <= scrollLength) {
712
+ set$(ctx, "isAtEnd", true);
713
+ set$(ctx, "isNearEnd", true);
714
+ set$(ctx, "isWithinMaintainScrollAtEndThreshold", true);
715
+ return;
716
+ }
693
717
  if (contentSize > 0 && queuedInitialLayout) {
694
718
  const insetEnd = getContentInsetEnd(ctx);
695
719
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -699,7 +723,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
699
723
  set$(
700
724
  ctx,
701
725
  "isWithinMaintainScrollAtEndThreshold",
702
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
726
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
703
727
  );
704
728
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
705
729
  if (!shouldSkipThresholdChecks) {
@@ -943,6 +967,24 @@ function clearPreservedInitialScrollTarget(state) {
943
967
  state.initialScroll = void 0;
944
968
  setInitialScrollSession(state);
945
969
  }
970
+ function supersedeInitialScroll(ctx) {
971
+ var _a3, _b, _c;
972
+ const state = ctx.state;
973
+ const bootstrapInitialScroll = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
974
+ if (state.initialScroll || bootstrapInitialScroll || ((_b = state.scrollingTo) == null ? void 0 : _b.isInitialScroll)) {
975
+ if ((bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle) !== void 0 && typeof cancelAnimationFrame === "function") {
976
+ cancelAnimationFrame(bootstrapInitialScroll.frameHandle);
977
+ }
978
+ if ((_c = state.scrollingTo) == null ? void 0 : _c.isInitialScroll) {
979
+ cancelScrollCompletionChecks(state);
980
+ state.scrollingTo = void 0;
981
+ state.scrollTargetPinnedRange = void 0;
982
+ }
983
+ initialScrollCompletion.resetFlags(state);
984
+ setInitialScrollSession(state, { bootstrap: null });
985
+ finishInitialScroll(ctx);
986
+ }
987
+ }
946
988
  function finishInitialScroll(ctx, options) {
947
989
  var _a3, _b, _c;
948
990
  const state = ctx.state;
@@ -1032,10 +1074,22 @@ function getAlignItemsAtEndPadding(ctx) {
1032
1074
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1033
1075
  }
1034
1076
  function updateContentMetricsState(ctx) {
1077
+ var _a3;
1078
+ const { state } = ctx;
1035
1079
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1036
1080
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1037
1081
  if (previousPadding !== nextPadding) {
1038
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1082
+ const isAnimatedMaintainActive = state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated";
1083
+ const isMaintainExpected = state.didContainersLayout && ((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.animated) && (isAnimatedMaintainActive || previousPadding > nextPadding && peek$(ctx, "isWithinMaintainScrollAtEndThreshold"));
1084
+ if (isMaintainExpected && !isAnimatedMaintainActive && !state.pendingMaintainScrollAtEnd) {
1085
+ state.scheduledWork.microtask(() => {
1086
+ if (!state.maintainingScrollAtEnd && !state.pendingMaintainScrollAtEnd) {
1087
+ set$(ctx, "alignItemsAtEndPadding", getAlignItemsAtEndPadding(ctx));
1088
+ }
1089
+ }, "alignItemsAtEndPaddingFallback");
1090
+ } else if (!isMaintainExpected) {
1091
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1092
+ }
1039
1093
  }
1040
1094
  }
1041
1095
 
@@ -1258,29 +1312,6 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1258
1312
  return clampedOffset;
1259
1313
  }
1260
1314
 
1261
- // src/core/cancelImperativeScroll.ts
1262
- function cancelScrollCompletionChecks({ scheduledWork }) {
1263
- scheduledWork.cancel("checkFinishedScrollFrame");
1264
- scheduledWork.cancel("checkFinishedScrollRetryFrame");
1265
- scheduledWork.cancel("checkFinishedScrollFallback");
1266
- scheduledWork.cancel("platformScrollCompletion");
1267
- }
1268
- function settlePendingImperativeScroll(state) {
1269
- var _a3, _b;
1270
- const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
1271
- state.pendingScrollResolve = void 0;
1272
- state.pendingScrollToEnd = void 0;
1273
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1274
- }
1275
- function cancelImperativeScroll(state) {
1276
- cancelScrollCompletionChecks(state);
1277
- state.scheduledWork.cancel("imperativeScrollReady");
1278
- state.runPendingScrollToEnd = void 0;
1279
- state.scrollingTo = void 0;
1280
- state.scrollTargetPinnedRange = void 0;
1281
- settlePendingImperativeScroll(state);
1282
- }
1283
-
1284
1315
  // src/core/finishScrollTo.ts
1285
1316
  function finishScrollTo(ctx) {
1286
1317
  var _a3, _b;
@@ -1511,81 +1542,55 @@ function doScrollTo(ctx, params) {
1511
1542
  }
1512
1543
  }
1513
1544
 
1514
- // src/core/doMaintainScrollAtEnd.ts
1515
- function doMaintainScrollAtEnd(ctx) {
1516
- const state = ctx.state;
1517
- const {
1518
- didContainersLayout,
1519
- pendingNativeMVCPAdjust,
1520
- refScroller,
1521
- props: { maintainScrollAtEnd }
1522
- } = state;
1523
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1524
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1525
- if (pendingNativeMVCPAdjust) {
1526
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1527
- return false;
1528
- }
1529
- if (shouldMaintainScrollAtEnd) {
1530
- state.pendingMaintainScrollAtEnd = false;
1531
- const contentSize = getContentSize(ctx);
1532
- if (contentSize < state.scrollLength) {
1533
- state.scroll = 0;
1534
- }
1535
- if (!state.maintainingScrollAtEnd) {
1536
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1537
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1538
- const scrollAtRequest = state.scroll;
1539
- state.maintainingScrollAtEnd = pendingState;
1540
- requestAnimationFrame(() => {
1541
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1542
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1543
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1544
- state.maintainingScrollAtEnd = activeState;
1545
- const scroller = refScroller.current;
1546
- if (state.props.horizontal && isHorizontalRTL(state)) {
1547
- const currentContentSize = getContentSize(ctx);
1548
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1549
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1550
- scroller == null ? void 0 : scroller.scrollTo({
1551
- animated: maintainScrollAtEnd.animated,
1552
- x: nativeOffset,
1553
- y: 0
1554
- });
1555
- } else {
1556
- scroller == null ? void 0 : scroller.scrollToEnd({
1557
- animated: maintainScrollAtEnd.animated
1558
- });
1559
- }
1560
- setTimeout(
1561
- () => {
1562
- if (state.maintainingScrollAtEnd === activeState) {
1563
- state.maintainingScrollAtEnd = void 0;
1564
- if (state.pendingMaintainScrollAtEnd) {
1565
- doMaintainScrollAtEnd(ctx);
1566
- }
1567
- }
1568
- },
1569
- maintainScrollAtEnd.animated ? 500 : 0
1570
- );
1571
- } else if (state.maintainingScrollAtEnd === pendingState) {
1572
- state.maintainingScrollAtEnd = void 0;
1573
- state.pendingMaintainScrollAtEnd = false;
1545
+ // src/core/scrollRequestTracker.ts
1546
+ function getScrollRequestTracker(ctx) {
1547
+ if (!ctx.scrollRequestTracker) {
1548
+ let currentToken = 0;
1549
+ const start = (resolve) => {
1550
+ const state = ctx.state;
1551
+ state.scheduledWork.cancel("imperativeScrollReady");
1552
+ const token = ++currentToken;
1553
+ settlePendingImperativeScroll(state);
1554
+ state.pendingScrollResolve = resolve;
1555
+ return token;
1556
+ };
1557
+ const runNow = (token, resolve, run) => {
1558
+ const state = ctx.state;
1559
+ if (token !== currentToken) {
1560
+ return;
1561
+ }
1562
+ const didStartScroll = run();
1563
+ if (!didStartScroll || !state.scrollingTo) {
1564
+ if (state.pendingScrollResolve === resolve) {
1565
+ state.pendingScrollResolve = void 0;
1574
1566
  }
1575
- });
1576
- } else {
1577
- state.pendingMaintainScrollAtEnd = true;
1578
- }
1579
- return true;
1567
+ resolve();
1568
+ }
1569
+ };
1570
+ ctx.scrollRequestTracker = {
1571
+ isCurrent: (token) => token === currentToken,
1572
+ runNow,
1573
+ runNowIfIdle: (run) => {
1574
+ const state = ctx.state;
1575
+ if (state.pendingScrollResolve || state.scrollingTo) {
1576
+ return Promise.resolve();
1577
+ }
1578
+ return new Promise((resolve) => {
1579
+ const token = start(resolve);
1580
+ runNow(token, resolve, run);
1581
+ });
1582
+ },
1583
+ start
1584
+ };
1580
1585
  }
1581
- state.pendingMaintainScrollAtEnd = false;
1582
- return false;
1586
+ return ctx.scrollRequestTracker;
1583
1587
  }
1584
1588
 
1585
1589
  // src/utils/requestAdjust.ts
1586
- function requestAdjust(ctx, positionDiff, dataChanged) {
1590
+ function requestAdjust(ctx, positionDiff, source) {
1587
1591
  const state = ctx.state;
1588
1592
  if (Math.abs(positionDiff) > 0.1) {
1593
+ const dataChanged = source === "data";
1589
1594
  const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1590
1595
  const doit = () => {
1591
1596
  if (needsScrollWorkaround) {
@@ -1602,7 +1607,7 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1602
1607
  const readyToRender = peek$(ctx, "readyToRender");
1603
1608
  if (readyToRender) {
1604
1609
  doit();
1605
- if (Platform.OS !== "web") {
1610
+ if (Platform.OS !== "web" && source !== "item-size") {
1606
1611
  const threshold = state.scroll - positionDiff / 2;
1607
1612
  if (!state.ignoreScrollFromMVCP) {
1608
1613
  state.ignoreScrollFromMVCP = {};
@@ -1635,6 +1640,87 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1635
1640
  }
1636
1641
  }
1637
1642
 
1643
+ // src/core/doMaintainScrollAtEnd.ts
1644
+ function finishMaintainScrollAtEnd(ctx) {
1645
+ var _a3;
1646
+ const { state } = ctx;
1647
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1648
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1649
+ state.maintainingScrollAtEnd = void 0;
1650
+ state.pendingMaintainScrollAtEnd = false;
1651
+ if (currentPadding !== nextPadding) {
1652
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1653
+ state.scrollForNextCalculateItemsInView = void 0;
1654
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1655
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1656
+ requestAdjust(ctx, nextScroll - state.scroll);
1657
+ }
1658
+ }
1659
+ function doMaintainScrollAtEnd(ctx) {
1660
+ const state = ctx.state;
1661
+ const {
1662
+ didContainersLayout,
1663
+ didFinishInitialScroll,
1664
+ pendingNativeMVCPAdjust,
1665
+ props: { maintainScrollAtEnd }
1666
+ } = state;
1667
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1668
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1669
+ const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1670
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1671
+ state.pendingMaintainScrollAtEnd = true;
1672
+ return false;
1673
+ }
1674
+ if (pendingNativeMVCPAdjust) {
1675
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1676
+ return false;
1677
+ }
1678
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1679
+ state.pendingMaintainScrollAtEnd = false;
1680
+ const contentSize = getContentSize(ctx);
1681
+ if (contentSize < state.scrollLength) {
1682
+ state.scroll = 0;
1683
+ }
1684
+ if (!state.maintainingScrollAtEnd) {
1685
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1686
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1687
+ const scrollAtRequest = state.scroll;
1688
+ state.maintainingScrollAtEnd = pendingState;
1689
+ requestAnimationFrame(() => {
1690
+ if (state.maintainingScrollAtEnd !== pendingState) {
1691
+ return;
1692
+ }
1693
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1694
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1695
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1696
+ state.maintainingScrollAtEnd = activeState;
1697
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1698
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1699
+ );
1700
+ void scrollPromise.then(() => {
1701
+ if (state.maintainingScrollAtEnd !== activeState) {
1702
+ return;
1703
+ }
1704
+ if (state.pendingMaintainScrollAtEnd) {
1705
+ state.maintainingScrollAtEnd = void 0;
1706
+ doMaintainScrollAtEnd(ctx);
1707
+ } else {
1708
+ finishMaintainScrollAtEnd(ctx);
1709
+ }
1710
+ });
1711
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1712
+ finishMaintainScrollAtEnd(ctx);
1713
+ }
1714
+ });
1715
+ } else {
1716
+ state.pendingMaintainScrollAtEnd = true;
1717
+ }
1718
+ return true;
1719
+ }
1720
+ finishMaintainScrollAtEnd(ctx);
1721
+ return false;
1722
+ }
1723
+
1638
1724
  // src/core/mvcp.ts
1639
1725
  var MVCP_POSITION_EPSILON = 0.1;
1640
1726
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1708,7 +1794,7 @@ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
1708
1794
  state.pendingNativeMVCPAdjust = void 0;
1709
1795
  const remaining = remainingAfterManual - nativeDelta;
1710
1796
  if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
1711
- requestAdjust(ctx, remaining, true);
1797
+ requestAdjust(ctx, remaining, "data");
1712
1798
  }
1713
1799
  }
1714
1800
  function maybeApplyPredictedNativeMVCPAdjust(ctx) {
@@ -1727,7 +1813,7 @@ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
1727
1813
  return;
1728
1814
  }
1729
1815
  pending.manualApplied = manualDesired;
1730
- requestAdjust(ctx, manualDesired, true);
1816
+ requestAdjust(ctx, manualDesired, "data");
1731
1817
  pending.furthestProgressTowardAmount = 0;
1732
1818
  }
1733
1819
  function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
@@ -1773,7 +1859,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1773
1859
  }
1774
1860
  return false;
1775
1861
  }
1776
- function prepareMVCP(ctx, dataChanged) {
1862
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1777
1863
  const state = ctx.state;
1778
1864
  const { idsInView, positions, props } = state;
1779
1865
  const {
@@ -1927,7 +2013,7 @@ function prepareMVCP(ctx, dataChanged) {
1927
2013
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1928
2014
  const shouldSkipAdjustForMaintainedEnd = (state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated") && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1929
2015
  if (!shouldSkipAdjustForMaintainedEnd) {
1930
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
2016
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData ? "data" : adjustmentSource);
1931
2017
  }
1932
2018
  }
1933
2019
  };
@@ -2025,6 +2111,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2025
2111
  state.scrollTime = currentTime;
2026
2112
  const scrollDelta = Math.abs(newScroll - prevScroll);
2027
2113
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
2114
+ const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
2115
+ if (didCancelMaintainScrollAtEnd) {
2116
+ finishMaintainScrollAtEnd(ctx);
2117
+ }
2028
2118
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
2029
2119
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2030
2120
  const scrollLength = state.scrollLength;
@@ -2033,7 +2123,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2033
2123
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2034
2124
  const lastCalculated = state.scrollLastCalculate;
2035
2125
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2036
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2126
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2037
2127
  if (shouldUpdate) {
2038
2128
  state.scrollLastCalculate = state.scroll;
2039
2129
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2862,7 +2952,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2862
2952
  }
2863
2953
  }
2864
2954
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2865
- var _a3, _b, _c;
2955
+ var _a3, _b;
2866
2956
  const state = ctx.state;
2867
2957
  const initialScroll = state.initialScroll;
2868
2958
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2876,19 +2966,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2876
2966
  if (state.didFinishInitialScroll) {
2877
2967
  schedulePreservedEndAnchorCorrection(ctx);
2878
2968
  } else if (scrollingTo) {
2879
- const existingWatchdog = initialScrollWatchdog.get(state);
2880
- scrollingTo.offset = resolvedOffset;
2881
- scrollingTo.targetOffset = resolvedOffset;
2882
2969
  state.initialScroll = {
2883
2970
  ...initialScroll,
2884
2971
  contentOffset: resolvedOffset
2885
2972
  };
2886
- state.hasScrolled = false;
2887
- initialScrollWatchdog.set(state, {
2888
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2889
- targetOffset: resolvedOffset
2973
+ dispatchInitialScroll(ctx, {
2974
+ forceScroll: true,
2975
+ resolvedOffset,
2976
+ target: state.initialScroll
2890
2977
  });
2891
- requestAdjust(ctx, offsetDiff);
2892
2978
  }
2893
2979
  }
2894
2980
  } else {
@@ -3930,6 +4016,9 @@ function setDidLayout(ctx) {
3930
4016
  state.queuedInitialLayout = true;
3931
4017
  checkAtBottom(ctx);
3932
4018
  setInitialRenderState(ctx, { didLayout: true });
4019
+ if (state.pendingMaintainScrollAtEnd) {
4020
+ doMaintainScrollAtEnd(ctx);
4021
+ }
3933
4022
  }
3934
4023
 
3935
4024
  // src/core/calculateItemsInView.ts
@@ -4166,7 +4255,7 @@ function calculateItemsInView(ctx, params = {}) {
4166
4255
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4167
4256
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4168
4257
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4169
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4258
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4170
4259
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4171
4260
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4172
4261
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4267,7 +4356,7 @@ function calculateItemsInView(ctx, params = {}) {
4267
4356
  }
4268
4357
  }
4269
4358
  }
4270
- const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged) : void 0;
4359
+ const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged, mvcpAdjustmentSource) : void 0;
4271
4360
  if (dataChanged) {
4272
4361
  resetLayoutCachesForDataChange(state);
4273
4362
  }
@@ -4681,12 +4770,15 @@ function runOrScheduleMVCPRecalculate(ctx) {
4681
4770
  } else if (Platform.OS === "web") {
4682
4771
  if (!state.mvcpAnchorLock) {
4683
4772
  state.scheduledWork.cancel("mvcpRecalculate");
4684
- calculateItemsInView(ctx, { doMVCP: true });
4773
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4685
4774
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4686
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4775
+ state.scheduledWork.frame(
4776
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4777
+ "mvcpRecalculate"
4778
+ );
4687
4779
  }
4688
4780
  } else {
4689
- calculateItemsInView(ctx, { doMVCP: true });
4781
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4690
4782
  }
4691
4783
  }
4692
4784
  function updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal) {
@@ -4812,7 +4904,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4812
4904
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
4813
4905
  needsRecalculate = true;
4814
4906
  }
4815
- if (prevSizeKnown !== void 0 && Math.abs(prevSizeKnown - size) > 5) {
4907
+ if (prevSizeKnown === void 0 || Math.abs(prevSizeKnown - size) > 5) {
4816
4908
  shouldMaintainScrollAtEnd = true;
4817
4909
  }
4818
4910
  onItemSizeChanged == null ? void 0 : onItemSizeChanged({
@@ -5175,6 +5267,30 @@ function useContainerItemSignals(containerContext) {
5175
5267
  itemKey
5176
5268
  };
5177
5269
  }
5270
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5271
+ let cancelled = false;
5272
+ Promise.resolve().then(() => {
5273
+ var _a3;
5274
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5275
+ if (!cancelled && ctx.state) {
5276
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5277
+ warnDevOnce(
5278
+ `${hookName}-missing-viewability-config`,
5279
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5280
+ );
5281
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5282
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5283
+ warnDevOnce(
5284
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5285
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5286
+ );
5287
+ }
5288
+ }
5289
+ });
5290
+ return () => {
5291
+ cancelled = true;
5292
+ };
5293
+ }
5178
5294
  function useAdaptiveRender() {
5179
5295
  const [mode] = useArr$(["adaptiveRender"]);
5180
5296
  return mode;
@@ -5214,7 +5330,9 @@ function useViewability(callback, configId) {
5214
5330
  const { containerId } = containerContext;
5215
5331
  const key = containerId + (configId != null ? configId : "");
5216
5332
  ctx.mapViewabilityCallbacks.set(key, callback);
5333
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5217
5334
  return () => {
5335
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5218
5336
  ctx.mapViewabilityCallbacks.delete(key);
5219
5337
  };
5220
5338
  }, [ctx, callback, configId, containerContext]);
@@ -5238,7 +5356,9 @@ function useViewabilityAmount(callback) {
5238
5356
  }
5239
5357
  const { containerId } = containerContext;
5240
5358
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5359
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5241
5360
  return () => {
5361
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5242
5362
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5243
5363
  };
5244
5364
  }, [ctx, callback, containerContext]);
@@ -5662,13 +5782,11 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) {
5662
5782
  return /* @__PURE__ */ React2.createElement(ContainerSlotBase, { ...props });
5663
5783
  });
5664
5784
  function useFreshDataTransitionVisibility(readyToRender, transitionEpoch) {
5665
- const completedTransitionEpoch = useRef(transitionEpoch);
5666
- const isTransitionPending = completedTransitionEpoch.current !== transitionEpoch;
5785
+ const [completedTransitionEpoch, setCompletedTransitionEpoch] = useState(transitionEpoch);
5786
+ const isTransitionPending = completedTransitionEpoch !== transitionEpoch;
5667
5787
  useLayoutEffect(() => {
5668
- if (!readyToRender) {
5669
- completedTransitionEpoch.current = transitionEpoch;
5670
- }
5671
- }, [readyToRender, transitionEpoch]);
5788
+ setCompletedTransitionEpoch(transitionEpoch);
5789
+ }, [transitionEpoch]);
5672
5790
  return readyToRender && !isTransitionPending;
5673
5791
  }
5674
5792
 
@@ -6347,11 +6465,29 @@ var ScheduledWork = class {
6347
6465
  constructor() {
6348
6466
  this.work = /* @__PURE__ */ new Map();
6349
6467
  }
6468
+ microtask(callback, key) {
6469
+ const pendingWork = this.work.get(key);
6470
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
6471
+ pendingWork.callback = callback;
6472
+ } else {
6473
+ this.cancel(key);
6474
+ const work = { callback, cancel: () => {
6475
+ } };
6476
+ this.work.set(key, work);
6477
+ queueMicrotask(() => {
6478
+ var _a3;
6479
+ if (this.work.get(key) === work) {
6480
+ this.work.delete(key);
6481
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
6482
+ }
6483
+ });
6484
+ }
6485
+ }
6350
6486
  timeout(callback, delay, key) {
6351
6487
  if (key) {
6352
6488
  this.cancel(key);
6353
6489
  }
6354
- const work = [void 0, clearTimeout];
6490
+ const work = { cancel: () => clearTimeout(handle) };
6355
6491
  const handle = setTimeout(() => {
6356
6492
  const workKey = key != null ? key : handle;
6357
6493
  if (this.work.get(workKey) === work) {
@@ -6359,14 +6495,13 @@ var ScheduledWork = class {
6359
6495
  callback();
6360
6496
  }
6361
6497
  }, delay);
6362
- work[0] = handle;
6363
6498
  this.work.set(key != null ? key : handle, work);
6364
6499
  }
6365
6500
  frame(callback, key) {
6366
6501
  this.cancel(key);
6367
- const work = [void 0, cancelAnimationFrame];
6502
+ const work = { cancel: () => cancelAnimationFrame(handle) };
6368
6503
  this.work.set(key, work);
6369
- work[0] = requestAnimationFrame(() => {
6504
+ const handle = requestAnimationFrame(() => {
6370
6505
  if (this.work.get(key) === work) {
6371
6506
  this.work.delete(key);
6372
6507
  callback();
@@ -6375,21 +6510,21 @@ var ScheduledWork = class {
6375
6510
  }
6376
6511
  register(key, cancel) {
6377
6512
  this.cancel(key);
6378
- this.work.set(key, [void 0, cancel]);
6513
+ this.work.set(key, { cancel });
6379
6514
  }
6380
6515
  cancel(key) {
6381
6516
  const work = this.work.get(key);
6382
6517
  if (work) {
6383
6518
  this.work.delete(key);
6384
- work[1](work[0]);
6519
+ work.cancel();
6385
6520
  }
6386
6521
  }
6387
6522
  has(key) {
6388
6523
  return this.work.has(key);
6389
6524
  }
6390
6525
  dispose() {
6391
- for (const [handle, cancel] of this.work.values()) {
6392
- cancel(handle);
6526
+ for (const work of this.work.values()) {
6527
+ work.cancel();
6393
6528
  }
6394
6529
  this.work.clear();
6395
6530
  }
@@ -6446,6 +6581,25 @@ var ScrollAdjustHandler = class {
6446
6581
  }
6447
6582
  };
6448
6583
 
6584
+ // src/core/scrollToEnd.ts
6585
+ function scrollToEnd(ctx, options) {
6586
+ const state = ctx.state;
6587
+ const data = state.props.data;
6588
+ const index = data.length - 1;
6589
+ if (index === -1) {
6590
+ return false;
6591
+ }
6592
+ const paddingBottom = state.props.stylePaddingBottom || 0;
6593
+ const footerSize = peek$(ctx, "footerSize") || 0;
6594
+ scrollToIndex(ctx, {
6595
+ ...options,
6596
+ index,
6597
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6598
+ viewPosition: 1
6599
+ });
6600
+ return true;
6601
+ }
6602
+
6449
6603
  // src/core/updateContentInsetEndAdjustment.ts
6450
6604
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
6451
6605
  const state = ctx.state;
@@ -6520,25 +6674,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
6520
6674
  }
6521
6675
  }
6522
6676
 
6523
- // src/core/scrollToEnd.ts
6524
- function scrollToEnd(ctx, options) {
6525
- const state = ctx.state;
6526
- const data = state.props.data;
6527
- const index = data.length - 1;
6528
- if (index === -1) {
6529
- return false;
6530
- }
6531
- const paddingBottom = state.props.stylePaddingBottom || 0;
6532
- const footerSize = peek$(ctx, "footerSize") || 0;
6533
- scrollToIndex(ctx, {
6534
- ...options,
6535
- index,
6536
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6537
- viewPosition: 1
6538
- });
6539
- return true;
6540
- }
6541
-
6542
6677
  // src/utils/createImperativeHandle.ts
6543
6678
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
6544
6679
  function getAverageItemSizes(state) {
@@ -6565,9 +6700,9 @@ function triggerMountedContainerLayouts(ctx) {
6565
6700
  }
6566
6701
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6567
6702
  const state = ctx.state;
6703
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
6568
6704
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
6569
6705
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
6570
- let imperativeScrollToken = 0;
6571
6706
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
6572
6707
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
6573
6708
  var _a3;
@@ -6589,7 +6724,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6589
6724
  const startedAt = Date.now();
6590
6725
  let stableFrames = 0;
6591
6726
  const check = () => {
6592
- if (token !== imperativeScrollToken) {
6727
+ if (!scrollRequestTracker.isCurrent(token)) {
6593
6728
  return;
6594
6729
  }
6595
6730
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -6607,40 +6742,23 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6607
6742
  state.scheduledWork.frame(check, "imperativeScrollReady");
6608
6743
  };
6609
6744
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
6610
- const runNow = () => {
6611
- if (token !== imperativeScrollToken) {
6612
- return;
6613
- }
6614
- const didStartScroll = run();
6615
- if (!didStartScroll || !state.scrollingTo) {
6616
- if (state.pendingScrollResolve === resolve) {
6617
- state.pendingScrollResolve = void 0;
6618
- }
6619
- resolve();
6620
- }
6621
- };
6745
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
6622
6746
  if (isSettlingAfterDataChange() || !isReady()) {
6623
6747
  runWhenReady(token, runNow, isReady);
6624
6748
  } else {
6625
6749
  runNow();
6626
6750
  }
6627
6751
  };
6628
- const startImperativeScroll = (resolve) => {
6629
- state.scheduledWork.cancel("imperativeScrollReady");
6630
- const token = ++imperativeScrollToken;
6631
- settlePendingImperativeScroll(state);
6632
- state.pendingScrollResolve = resolve;
6633
- return token;
6634
- };
6635
6752
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
6636
- const token = startImperativeScroll(resolve);
6753
+ const token = scrollRequestTracker.start(resolve);
6754
+ supersedeInitialScroll(ctx);
6637
6755
  runScrollRequest(token, resolve, run, isReady);
6638
6756
  });
6639
6757
  state.runPendingScrollToEnd = () => {
6640
6758
  const pendingScroll = state.pendingScrollToEnd;
6641
6759
  if (pendingScroll) {
6642
6760
  state.pendingScrollToEnd = void 0;
6643
- if (pendingScroll.token === imperativeScrollToken) {
6761
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
6644
6762
  runScrollRequest(
6645
6763
  pendingScroll.token,
6646
6764
  pendingScroll.resolve,
@@ -6754,15 +6872,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6754
6872
  }),
6755
6873
  scrollToEnd: (options) => new Promise((resolve) => {
6756
6874
  var _a3;
6757
- const token = startImperativeScroll(resolve);
6875
+ const token = scrollRequestTracker.start(resolve);
6758
6876
  state.pendingScrollToEnd = {
6759
6877
  options,
6760
6878
  resolve,
6761
6879
  token
6762
6880
  };
6763
- if (scheduleImperativeScrollCommit) {
6764
- scheduleImperativeScrollCommit();
6765
- } else {
6881
+ scheduleImperativeScrollCommit == null ? void 0 : scheduleImperativeScrollCommit();
6882
+ supersedeInitialScroll(ctx);
6883
+ if (!scheduleImperativeScrollCommit) {
6766
6884
  (_a3 = state.runPendingScrollToEnd) == null ? void 0 : _a3.call(state);
6767
6885
  }
6768
6886
  }),
@@ -7134,6 +7252,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7134
7252
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7135
7253
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7136
7254
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7255
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7137
7256
  const initialScrollProp = initialScrollAtEnd ? {
7138
7257
  index: Math.max(0, dataProp.length - 1),
7139
7258
  preserveForBottomPadding: true,
@@ -7146,7 +7265,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7146
7265
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7147
7266
  } : {
7148
7267
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7149
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7268
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7269
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7270
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7150
7271
  } : initialScrollUsesOffsetOnly ? {
7151
7272
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7152
7273
  index: 0,
@@ -7252,6 +7373,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7252
7373
  viewabilityConfigCallbackPairs: void 0
7253
7374
  };
7254
7375
  const internalState = ctx.state;
7376
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7255
7377
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7256
7378
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7257
7379
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -7613,6 +7735,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7613
7735
  onScroll: (event) => onScroll(ctx, event),
7614
7736
  onScrollBeginDrag: (event) => {
7615
7737
  var _a4, _b2;
7738
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
7739
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
7740
+ cancelImperativeScroll(state);
7741
+ }
7742
+ if (maintainingScrollAtEnd) {
7743
+ finishMaintainScrollAtEnd(ctx);
7744
+ }
7616
7745
  prepareReachedEdgeForNextUserScroll(ctx);
7617
7746
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7618
7747
  },