@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.js CHANGED
@@ -68,10 +68,6 @@ function getStylePaddingEnd(props) {
68
68
  }
69
69
  return (isHorizontalRTLProps(props) ? props.stylePaddingLeft : props.stylePaddingRight) || 0;
70
70
  }
71
- function getLogicalHorizontalMaxOffset(state, contentWidth) {
72
- var _a3;
73
- return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0;
74
- }
75
71
  function getHorizontalInsetEnd(state, inset) {
76
72
  if (!inset) {
77
73
  return 0;
@@ -457,6 +453,28 @@ var EDGE_POSITION_EPSILON = 1;
457
453
  var ENABLE_DEVMODE = IS_DEV && false;
458
454
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
459
455
 
456
+ // src/core/cancelImperativeScroll.ts
457
+ function cancelScrollCompletionChecks({ scheduledWork }) {
458
+ scheduledWork.cancel("checkFinishedScrollFrame");
459
+ scheduledWork.cancel("checkFinishedScrollRetryFrame");
460
+ scheduledWork.cancel("checkFinishedScrollFallback");
461
+ scheduledWork.cancel("platformScrollCompletion");
462
+ }
463
+ function settlePendingImperativeScroll(state) {
464
+ var _a3, _b;
465
+ const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
466
+ state.pendingScrollResolve = void 0;
467
+ state.pendingScrollToEnd = void 0;
468
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
469
+ }
470
+ function cancelImperativeScroll(state) {
471
+ cancelScrollCompletionChecks(state);
472
+ state.scheduledWork.cancel("imperativeScrollReady");
473
+ state.scrollingTo = void 0;
474
+ state.scrollTargetPinnedRange = void 0;
475
+ settlePendingImperativeScroll(state);
476
+ }
477
+
460
478
  // src/core/deferredPublicOnScroll.ts
461
479
  function withResolvedContentOffset(state, event, resolvedOffset) {
462
480
  return {
@@ -711,6 +729,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
711
729
  } = state;
712
730
  const contentSize = getContentSize(ctx);
713
731
  resetSharedEdgeGateIfOutsideHysteresis(ctx);
732
+ if (state.props.data.length === 0 && scrollLength > 0 && contentSize <= scrollLength) {
733
+ set$(ctx, "isAtEnd", true);
734
+ set$(ctx, "isNearEnd", true);
735
+ set$(ctx, "isWithinMaintainScrollAtEndThreshold", true);
736
+ return;
737
+ }
714
738
  if (contentSize > 0 && queuedInitialLayout) {
715
739
  const insetEnd = getContentInsetEnd(ctx);
716
740
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -720,7 +744,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
720
744
  set$(
721
745
  ctx,
722
746
  "isWithinMaintainScrollAtEndThreshold",
723
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
747
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
724
748
  );
725
749
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
726
750
  if (!shouldSkipThresholdChecks) {
@@ -964,6 +988,24 @@ function clearPreservedInitialScrollTarget(state) {
964
988
  state.initialScroll = void 0;
965
989
  setInitialScrollSession(state);
966
990
  }
991
+ function supersedeInitialScroll(ctx) {
992
+ var _a3, _b, _c;
993
+ const state = ctx.state;
994
+ const bootstrapInitialScroll = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
995
+ if (state.initialScroll || bootstrapInitialScroll || ((_b = state.scrollingTo) == null ? void 0 : _b.isInitialScroll)) {
996
+ if ((bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle) !== void 0 && typeof cancelAnimationFrame === "function") {
997
+ cancelAnimationFrame(bootstrapInitialScroll.frameHandle);
998
+ }
999
+ if ((_c = state.scrollingTo) == null ? void 0 : _c.isInitialScroll) {
1000
+ cancelScrollCompletionChecks(state);
1001
+ state.scrollingTo = void 0;
1002
+ state.scrollTargetPinnedRange = void 0;
1003
+ }
1004
+ initialScrollCompletion.resetFlags(state);
1005
+ setInitialScrollSession(state, { bootstrap: null });
1006
+ finishInitialScroll(ctx);
1007
+ }
1008
+ }
967
1009
  function finishInitialScroll(ctx, options) {
968
1010
  var _a3, _b, _c;
969
1011
  const state = ctx.state;
@@ -1053,10 +1095,22 @@ function getAlignItemsAtEndPadding(ctx) {
1053
1095
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1054
1096
  }
1055
1097
  function updateContentMetricsState(ctx) {
1098
+ var _a3;
1099
+ const { state } = ctx;
1056
1100
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1057
1101
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1058
1102
  if (previousPadding !== nextPadding) {
1059
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1103
+ const isAnimatedMaintainActive = state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated";
1104
+ const isMaintainExpected = state.didContainersLayout && ((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.animated) && (isAnimatedMaintainActive || previousPadding > nextPadding && peek$(ctx, "isWithinMaintainScrollAtEndThreshold"));
1105
+ if (isMaintainExpected && !isAnimatedMaintainActive && !state.pendingMaintainScrollAtEnd) {
1106
+ state.scheduledWork.microtask(() => {
1107
+ if (!state.maintainingScrollAtEnd && !state.pendingMaintainScrollAtEnd) {
1108
+ set$(ctx, "alignItemsAtEndPadding", getAlignItemsAtEndPadding(ctx));
1109
+ }
1110
+ }, "alignItemsAtEndPaddingFallback");
1111
+ } else if (!isMaintainExpected) {
1112
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1113
+ }
1060
1114
  }
1061
1115
  }
1062
1116
 
@@ -1279,29 +1333,6 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1279
1333
  return clampedOffset;
1280
1334
  }
1281
1335
 
1282
- // src/core/cancelImperativeScroll.ts
1283
- function cancelScrollCompletionChecks({ scheduledWork }) {
1284
- scheduledWork.cancel("checkFinishedScrollFrame");
1285
- scheduledWork.cancel("checkFinishedScrollRetryFrame");
1286
- scheduledWork.cancel("checkFinishedScrollFallback");
1287
- scheduledWork.cancel("platformScrollCompletion");
1288
- }
1289
- function settlePendingImperativeScroll(state) {
1290
- var _a3, _b;
1291
- const resolvePendingScroll = (_b = state.pendingScrollResolve) != null ? _b : (_a3 = state.pendingScrollToEnd) == null ? void 0 : _a3.resolve;
1292
- state.pendingScrollResolve = void 0;
1293
- state.pendingScrollToEnd = void 0;
1294
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1295
- }
1296
- function cancelImperativeScroll(state) {
1297
- cancelScrollCompletionChecks(state);
1298
- state.scheduledWork.cancel("imperativeScrollReady");
1299
- state.runPendingScrollToEnd = void 0;
1300
- state.scrollingTo = void 0;
1301
- state.scrollTargetPinnedRange = void 0;
1302
- settlePendingImperativeScroll(state);
1303
- }
1304
-
1305
1336
  // src/core/finishScrollTo.ts
1306
1337
  function finishScrollTo(ctx) {
1307
1338
  var _a3, _b;
@@ -1532,81 +1563,55 @@ function doScrollTo(ctx, params) {
1532
1563
  }
1533
1564
  }
1534
1565
 
1535
- // src/core/doMaintainScrollAtEnd.ts
1536
- function doMaintainScrollAtEnd(ctx) {
1537
- const state = ctx.state;
1538
- const {
1539
- didContainersLayout,
1540
- pendingNativeMVCPAdjust,
1541
- refScroller,
1542
- props: { maintainScrollAtEnd }
1543
- } = state;
1544
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1545
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1546
- if (pendingNativeMVCPAdjust) {
1547
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1548
- return false;
1549
- }
1550
- if (shouldMaintainScrollAtEnd) {
1551
- state.pendingMaintainScrollAtEnd = false;
1552
- const contentSize = getContentSize(ctx);
1553
- if (contentSize < state.scrollLength) {
1554
- state.scroll = 0;
1555
- }
1556
- if (!state.maintainingScrollAtEnd) {
1557
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1558
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1559
- const scrollAtRequest = state.scroll;
1560
- state.maintainingScrollAtEnd = pendingState;
1561
- requestAnimationFrame(() => {
1562
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1563
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1564
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1565
- state.maintainingScrollAtEnd = activeState;
1566
- const scroller = refScroller.current;
1567
- if (state.props.horizontal && isHorizontalRTL(state)) {
1568
- const currentContentSize = getContentSize(ctx);
1569
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1570
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1571
- scroller == null ? void 0 : scroller.scrollTo({
1572
- animated: maintainScrollAtEnd.animated,
1573
- x: nativeOffset,
1574
- y: 0
1575
- });
1576
- } else {
1577
- scroller == null ? void 0 : scroller.scrollToEnd({
1578
- animated: maintainScrollAtEnd.animated
1579
- });
1580
- }
1581
- setTimeout(
1582
- () => {
1583
- if (state.maintainingScrollAtEnd === activeState) {
1584
- state.maintainingScrollAtEnd = void 0;
1585
- if (state.pendingMaintainScrollAtEnd) {
1586
- doMaintainScrollAtEnd(ctx);
1587
- }
1588
- }
1589
- },
1590
- maintainScrollAtEnd.animated ? 500 : 0
1591
- );
1592
- } else if (state.maintainingScrollAtEnd === pendingState) {
1593
- state.maintainingScrollAtEnd = void 0;
1594
- state.pendingMaintainScrollAtEnd = false;
1566
+ // src/core/scrollRequestTracker.ts
1567
+ function getScrollRequestTracker(ctx) {
1568
+ if (!ctx.scrollRequestTracker) {
1569
+ let currentToken = 0;
1570
+ const start = (resolve) => {
1571
+ const state = ctx.state;
1572
+ state.scheduledWork.cancel("imperativeScrollReady");
1573
+ const token = ++currentToken;
1574
+ settlePendingImperativeScroll(state);
1575
+ state.pendingScrollResolve = resolve;
1576
+ return token;
1577
+ };
1578
+ const runNow = (token, resolve, run) => {
1579
+ const state = ctx.state;
1580
+ if (token !== currentToken) {
1581
+ return;
1582
+ }
1583
+ const didStartScroll = run();
1584
+ if (!didStartScroll || !state.scrollingTo) {
1585
+ if (state.pendingScrollResolve === resolve) {
1586
+ state.pendingScrollResolve = void 0;
1595
1587
  }
1596
- });
1597
- } else {
1598
- state.pendingMaintainScrollAtEnd = true;
1599
- }
1600
- return true;
1588
+ resolve();
1589
+ }
1590
+ };
1591
+ ctx.scrollRequestTracker = {
1592
+ isCurrent: (token) => token === currentToken,
1593
+ runNow,
1594
+ runNowIfIdle: (run) => {
1595
+ const state = ctx.state;
1596
+ if (state.pendingScrollResolve || state.scrollingTo) {
1597
+ return Promise.resolve();
1598
+ }
1599
+ return new Promise((resolve) => {
1600
+ const token = start(resolve);
1601
+ runNow(token, resolve, run);
1602
+ });
1603
+ },
1604
+ start
1605
+ };
1601
1606
  }
1602
- state.pendingMaintainScrollAtEnd = false;
1603
- return false;
1607
+ return ctx.scrollRequestTracker;
1604
1608
  }
1605
1609
 
1606
1610
  // src/utils/requestAdjust.ts
1607
- function requestAdjust(ctx, positionDiff, dataChanged) {
1611
+ function requestAdjust(ctx, positionDiff, source) {
1608
1612
  const state = ctx.state;
1609
1613
  if (Math.abs(positionDiff) > 0.1) {
1614
+ const dataChanged = source === "data";
1610
1615
  const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1611
1616
  const doit = () => {
1612
1617
  if (needsScrollWorkaround) {
@@ -1623,7 +1628,7 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1623
1628
  const readyToRender = peek$(ctx, "readyToRender");
1624
1629
  if (readyToRender) {
1625
1630
  doit();
1626
- if (Platform.OS !== "web") {
1631
+ if (Platform.OS !== "web" && source !== "item-size") {
1627
1632
  const threshold = state.scroll - positionDiff / 2;
1628
1633
  if (!state.ignoreScrollFromMVCP) {
1629
1634
  state.ignoreScrollFromMVCP = {};
@@ -1656,6 +1661,87 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1656
1661
  }
1657
1662
  }
1658
1663
 
1664
+ // src/core/doMaintainScrollAtEnd.ts
1665
+ function finishMaintainScrollAtEnd(ctx) {
1666
+ var _a3;
1667
+ const { state } = ctx;
1668
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1669
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1670
+ state.maintainingScrollAtEnd = void 0;
1671
+ state.pendingMaintainScrollAtEnd = false;
1672
+ if (currentPadding !== nextPadding) {
1673
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1674
+ state.scrollForNextCalculateItemsInView = void 0;
1675
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1676
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1677
+ requestAdjust(ctx, nextScroll - state.scroll);
1678
+ }
1679
+ }
1680
+ function doMaintainScrollAtEnd(ctx) {
1681
+ const state = ctx.state;
1682
+ const {
1683
+ didContainersLayout,
1684
+ didFinishInitialScroll,
1685
+ pendingNativeMVCPAdjust,
1686
+ props: { maintainScrollAtEnd }
1687
+ } = state;
1688
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1689
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1690
+ const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1691
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1692
+ state.pendingMaintainScrollAtEnd = true;
1693
+ return false;
1694
+ }
1695
+ if (pendingNativeMVCPAdjust) {
1696
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1697
+ return false;
1698
+ }
1699
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1700
+ state.pendingMaintainScrollAtEnd = false;
1701
+ const contentSize = getContentSize(ctx);
1702
+ if (contentSize < state.scrollLength) {
1703
+ state.scroll = 0;
1704
+ }
1705
+ if (!state.maintainingScrollAtEnd) {
1706
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1707
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1708
+ const scrollAtRequest = state.scroll;
1709
+ state.maintainingScrollAtEnd = pendingState;
1710
+ requestAnimationFrame(() => {
1711
+ if (state.maintainingScrollAtEnd !== pendingState) {
1712
+ return;
1713
+ }
1714
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1715
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1716
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1717
+ state.maintainingScrollAtEnd = activeState;
1718
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1719
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1720
+ );
1721
+ void scrollPromise.then(() => {
1722
+ if (state.maintainingScrollAtEnd !== activeState) {
1723
+ return;
1724
+ }
1725
+ if (state.pendingMaintainScrollAtEnd) {
1726
+ state.maintainingScrollAtEnd = void 0;
1727
+ doMaintainScrollAtEnd(ctx);
1728
+ } else {
1729
+ finishMaintainScrollAtEnd(ctx);
1730
+ }
1731
+ });
1732
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1733
+ finishMaintainScrollAtEnd(ctx);
1734
+ }
1735
+ });
1736
+ } else {
1737
+ state.pendingMaintainScrollAtEnd = true;
1738
+ }
1739
+ return true;
1740
+ }
1741
+ finishMaintainScrollAtEnd(ctx);
1742
+ return false;
1743
+ }
1744
+
1659
1745
  // src/core/mvcp.ts
1660
1746
  var MVCP_POSITION_EPSILON = 0.1;
1661
1747
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1729,7 +1815,7 @@ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
1729
1815
  state.pendingNativeMVCPAdjust = void 0;
1730
1816
  const remaining = remainingAfterManual - nativeDelta;
1731
1817
  if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
1732
- requestAdjust(ctx, remaining, true);
1818
+ requestAdjust(ctx, remaining, "data");
1733
1819
  }
1734
1820
  }
1735
1821
  function maybeApplyPredictedNativeMVCPAdjust(ctx) {
@@ -1748,7 +1834,7 @@ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
1748
1834
  return;
1749
1835
  }
1750
1836
  pending.manualApplied = manualDesired;
1751
- requestAdjust(ctx, manualDesired, true);
1837
+ requestAdjust(ctx, manualDesired, "data");
1752
1838
  pending.furthestProgressTowardAmount = 0;
1753
1839
  }
1754
1840
  function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
@@ -1794,7 +1880,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1794
1880
  }
1795
1881
  return false;
1796
1882
  }
1797
- function prepareMVCP(ctx, dataChanged) {
1883
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1798
1884
  const state = ctx.state;
1799
1885
  const { idsInView, positions, props } = state;
1800
1886
  const {
@@ -1948,7 +2034,7 @@ function prepareMVCP(ctx, dataChanged) {
1948
2034
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1949
2035
  const shouldSkipAdjustForMaintainedEnd = (state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated") && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1950
2036
  if (!shouldSkipAdjustForMaintainedEnd) {
1951
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
2037
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData ? "data" : adjustmentSource);
1952
2038
  }
1953
2039
  }
1954
2040
  };
@@ -2046,6 +2132,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2046
2132
  state.scrollTime = currentTime;
2047
2133
  const scrollDelta = Math.abs(newScroll - prevScroll);
2048
2134
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
2135
+ const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
2136
+ if (didCancelMaintainScrollAtEnd) {
2137
+ finishMaintainScrollAtEnd(ctx);
2138
+ }
2049
2139
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
2050
2140
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2051
2141
  const scrollLength = state.scrollLength;
@@ -2054,7 +2144,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2054
2144
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2055
2145
  const lastCalculated = state.scrollLastCalculate;
2056
2146
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2057
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2147
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2058
2148
  if (shouldUpdate) {
2059
2149
  state.scrollLastCalculate = state.scroll;
2060
2150
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2883,7 +2973,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2883
2973
  }
2884
2974
  }
2885
2975
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2886
- var _a3, _b, _c;
2976
+ var _a3, _b;
2887
2977
  const state = ctx.state;
2888
2978
  const initialScroll = state.initialScroll;
2889
2979
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2897,19 +2987,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2897
2987
  if (state.didFinishInitialScroll) {
2898
2988
  schedulePreservedEndAnchorCorrection(ctx);
2899
2989
  } else if (scrollingTo) {
2900
- const existingWatchdog = initialScrollWatchdog.get(state);
2901
- scrollingTo.offset = resolvedOffset;
2902
- scrollingTo.targetOffset = resolvedOffset;
2903
2990
  state.initialScroll = {
2904
2991
  ...initialScroll,
2905
2992
  contentOffset: resolvedOffset
2906
2993
  };
2907
- state.hasScrolled = false;
2908
- initialScrollWatchdog.set(state, {
2909
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2910
- targetOffset: resolvedOffset
2994
+ dispatchInitialScroll(ctx, {
2995
+ forceScroll: true,
2996
+ resolvedOffset,
2997
+ target: state.initialScroll
2911
2998
  });
2912
- requestAdjust(ctx, offsetDiff);
2913
2999
  }
2914
3000
  }
2915
3001
  } else {
@@ -3951,6 +4037,9 @@ function setDidLayout(ctx) {
3951
4037
  state.queuedInitialLayout = true;
3952
4038
  checkAtBottom(ctx);
3953
4039
  setInitialRenderState(ctx, { didLayout: true });
4040
+ if (state.pendingMaintainScrollAtEnd) {
4041
+ doMaintainScrollAtEnd(ctx);
4042
+ }
3954
4043
  }
3955
4044
 
3956
4045
  // src/core/calculateItemsInView.ts
@@ -4187,7 +4276,7 @@ function calculateItemsInView(ctx, params = {}) {
4187
4276
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4188
4277
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4189
4278
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4190
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4279
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4191
4280
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4192
4281
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4193
4282
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4288,7 +4377,7 @@ function calculateItemsInView(ctx, params = {}) {
4288
4377
  }
4289
4378
  }
4290
4379
  }
4291
- const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged) : void 0;
4380
+ const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged, mvcpAdjustmentSource) : void 0;
4292
4381
  if (dataChanged) {
4293
4382
  resetLayoutCachesForDataChange(state);
4294
4383
  }
@@ -4702,12 +4791,15 @@ function runOrScheduleMVCPRecalculate(ctx) {
4702
4791
  } else if (Platform.OS === "web") {
4703
4792
  if (!state.mvcpAnchorLock) {
4704
4793
  state.scheduledWork.cancel("mvcpRecalculate");
4705
- calculateItemsInView(ctx, { doMVCP: true });
4794
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4706
4795
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4707
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4796
+ state.scheduledWork.frame(
4797
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4798
+ "mvcpRecalculate"
4799
+ );
4708
4800
  }
4709
4801
  } else {
4710
- calculateItemsInView(ctx, { doMVCP: true });
4802
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4711
4803
  }
4712
4804
  }
4713
4805
  function updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal) {
@@ -4833,7 +4925,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4833
4925
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
4834
4926
  needsRecalculate = true;
4835
4927
  }
4836
- if (prevSizeKnown !== void 0 && Math.abs(prevSizeKnown - size) > 5) {
4928
+ if (prevSizeKnown === void 0 || Math.abs(prevSizeKnown - size) > 5) {
4837
4929
  shouldMaintainScrollAtEnd = true;
4838
4930
  }
4839
4931
  onItemSizeChanged == null ? void 0 : onItemSizeChanged({
@@ -5196,6 +5288,30 @@ function useContainerItemSignals(containerContext) {
5196
5288
  itemKey
5197
5289
  };
5198
5290
  }
5291
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5292
+ let cancelled = false;
5293
+ Promise.resolve().then(() => {
5294
+ var _a3;
5295
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5296
+ if (!cancelled && ctx.state) {
5297
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5298
+ warnDevOnce(
5299
+ `${hookName}-missing-viewability-config`,
5300
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5301
+ );
5302
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5303
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5304
+ warnDevOnce(
5305
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5306
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5307
+ );
5308
+ }
5309
+ }
5310
+ });
5311
+ return () => {
5312
+ cancelled = true;
5313
+ };
5314
+ }
5199
5315
  function useAdaptiveRender() {
5200
5316
  const [mode] = useArr$(["adaptiveRender"]);
5201
5317
  return mode;
@@ -5235,7 +5351,9 @@ function useViewability(callback, configId) {
5235
5351
  const { containerId } = containerContext;
5236
5352
  const key = containerId + (configId != null ? configId : "");
5237
5353
  ctx.mapViewabilityCallbacks.set(key, callback);
5354
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5238
5355
  return () => {
5356
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5239
5357
  ctx.mapViewabilityCallbacks.delete(key);
5240
5358
  };
5241
5359
  }, [ctx, callback, configId, containerContext]);
@@ -5259,7 +5377,9 @@ function useViewabilityAmount(callback) {
5259
5377
  }
5260
5378
  const { containerId } = containerContext;
5261
5379
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5380
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5262
5381
  return () => {
5382
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5263
5383
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5264
5384
  };
5265
5385
  }, [ctx, callback, containerContext]);
@@ -5683,13 +5803,11 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) {
5683
5803
  return /* @__PURE__ */ React2__namespace.createElement(ContainerSlotBase, { ...props });
5684
5804
  });
5685
5805
  function useFreshDataTransitionVisibility(readyToRender, transitionEpoch) {
5686
- const completedTransitionEpoch = React2.useRef(transitionEpoch);
5687
- const isTransitionPending = completedTransitionEpoch.current !== transitionEpoch;
5806
+ const [completedTransitionEpoch, setCompletedTransitionEpoch] = React2.useState(transitionEpoch);
5807
+ const isTransitionPending = completedTransitionEpoch !== transitionEpoch;
5688
5808
  React2.useLayoutEffect(() => {
5689
- if (!readyToRender) {
5690
- completedTransitionEpoch.current = transitionEpoch;
5691
- }
5692
- }, [readyToRender, transitionEpoch]);
5809
+ setCompletedTransitionEpoch(transitionEpoch);
5810
+ }, [transitionEpoch]);
5693
5811
  return readyToRender && !isTransitionPending;
5694
5812
  }
5695
5813
 
@@ -6368,11 +6486,29 @@ var ScheduledWork = class {
6368
6486
  constructor() {
6369
6487
  this.work = /* @__PURE__ */ new Map();
6370
6488
  }
6489
+ microtask(callback, key) {
6490
+ const pendingWork = this.work.get(key);
6491
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
6492
+ pendingWork.callback = callback;
6493
+ } else {
6494
+ this.cancel(key);
6495
+ const work = { callback, cancel: () => {
6496
+ } };
6497
+ this.work.set(key, work);
6498
+ queueMicrotask(() => {
6499
+ var _a3;
6500
+ if (this.work.get(key) === work) {
6501
+ this.work.delete(key);
6502
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
6503
+ }
6504
+ });
6505
+ }
6506
+ }
6371
6507
  timeout(callback, delay, key) {
6372
6508
  if (key) {
6373
6509
  this.cancel(key);
6374
6510
  }
6375
- const work = [void 0, clearTimeout];
6511
+ const work = { cancel: () => clearTimeout(handle) };
6376
6512
  const handle = setTimeout(() => {
6377
6513
  const workKey = key != null ? key : handle;
6378
6514
  if (this.work.get(workKey) === work) {
@@ -6380,14 +6516,13 @@ var ScheduledWork = class {
6380
6516
  callback();
6381
6517
  }
6382
6518
  }, delay);
6383
- work[0] = handle;
6384
6519
  this.work.set(key != null ? key : handle, work);
6385
6520
  }
6386
6521
  frame(callback, key) {
6387
6522
  this.cancel(key);
6388
- const work = [void 0, cancelAnimationFrame];
6523
+ const work = { cancel: () => cancelAnimationFrame(handle) };
6389
6524
  this.work.set(key, work);
6390
- work[0] = requestAnimationFrame(() => {
6525
+ const handle = requestAnimationFrame(() => {
6391
6526
  if (this.work.get(key) === work) {
6392
6527
  this.work.delete(key);
6393
6528
  callback();
@@ -6396,21 +6531,21 @@ var ScheduledWork = class {
6396
6531
  }
6397
6532
  register(key, cancel) {
6398
6533
  this.cancel(key);
6399
- this.work.set(key, [void 0, cancel]);
6534
+ this.work.set(key, { cancel });
6400
6535
  }
6401
6536
  cancel(key) {
6402
6537
  const work = this.work.get(key);
6403
6538
  if (work) {
6404
6539
  this.work.delete(key);
6405
- work[1](work[0]);
6540
+ work.cancel();
6406
6541
  }
6407
6542
  }
6408
6543
  has(key) {
6409
6544
  return this.work.has(key);
6410
6545
  }
6411
6546
  dispose() {
6412
- for (const [handle, cancel] of this.work.values()) {
6413
- cancel(handle);
6547
+ for (const work of this.work.values()) {
6548
+ work.cancel();
6414
6549
  }
6415
6550
  this.work.clear();
6416
6551
  }
@@ -6467,6 +6602,25 @@ var ScrollAdjustHandler = class {
6467
6602
  }
6468
6603
  };
6469
6604
 
6605
+ // src/core/scrollToEnd.ts
6606
+ function scrollToEnd(ctx, options) {
6607
+ const state = ctx.state;
6608
+ const data = state.props.data;
6609
+ const index = data.length - 1;
6610
+ if (index === -1) {
6611
+ return false;
6612
+ }
6613
+ const paddingBottom = state.props.stylePaddingBottom || 0;
6614
+ const footerSize = peek$(ctx, "footerSize") || 0;
6615
+ scrollToIndex(ctx, {
6616
+ ...options,
6617
+ index,
6618
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6619
+ viewPosition: 1
6620
+ });
6621
+ return true;
6622
+ }
6623
+
6470
6624
  // src/core/updateContentInsetEndAdjustment.ts
6471
6625
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
6472
6626
  const state = ctx.state;
@@ -6541,25 +6695,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
6541
6695
  }
6542
6696
  }
6543
6697
 
6544
- // src/core/scrollToEnd.ts
6545
- function scrollToEnd(ctx, options) {
6546
- const state = ctx.state;
6547
- const data = state.props.data;
6548
- const index = data.length - 1;
6549
- if (index === -1) {
6550
- return false;
6551
- }
6552
- const paddingBottom = state.props.stylePaddingBottom || 0;
6553
- const footerSize = peek$(ctx, "footerSize") || 0;
6554
- scrollToIndex(ctx, {
6555
- ...options,
6556
- index,
6557
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6558
- viewPosition: 1
6559
- });
6560
- return true;
6561
- }
6562
-
6563
6698
  // src/utils/createImperativeHandle.ts
6564
6699
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
6565
6700
  function getAverageItemSizes(state) {
@@ -6586,9 +6721,9 @@ function triggerMountedContainerLayouts(ctx) {
6586
6721
  }
6587
6722
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6588
6723
  const state = ctx.state;
6724
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
6589
6725
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
6590
6726
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
6591
- let imperativeScrollToken = 0;
6592
6727
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
6593
6728
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
6594
6729
  var _a3;
@@ -6610,7 +6745,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6610
6745
  const startedAt = Date.now();
6611
6746
  let stableFrames = 0;
6612
6747
  const check = () => {
6613
- if (token !== imperativeScrollToken) {
6748
+ if (!scrollRequestTracker.isCurrent(token)) {
6614
6749
  return;
6615
6750
  }
6616
6751
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -6628,40 +6763,23 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6628
6763
  state.scheduledWork.frame(check, "imperativeScrollReady");
6629
6764
  };
6630
6765
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
6631
- const runNow = () => {
6632
- if (token !== imperativeScrollToken) {
6633
- return;
6634
- }
6635
- const didStartScroll = run();
6636
- if (!didStartScroll || !state.scrollingTo) {
6637
- if (state.pendingScrollResolve === resolve) {
6638
- state.pendingScrollResolve = void 0;
6639
- }
6640
- resolve();
6641
- }
6642
- };
6766
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
6643
6767
  if (isSettlingAfterDataChange() || !isReady()) {
6644
6768
  runWhenReady(token, runNow, isReady);
6645
6769
  } else {
6646
6770
  runNow();
6647
6771
  }
6648
6772
  };
6649
- const startImperativeScroll = (resolve) => {
6650
- state.scheduledWork.cancel("imperativeScrollReady");
6651
- const token = ++imperativeScrollToken;
6652
- settlePendingImperativeScroll(state);
6653
- state.pendingScrollResolve = resolve;
6654
- return token;
6655
- };
6656
6773
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
6657
- const token = startImperativeScroll(resolve);
6774
+ const token = scrollRequestTracker.start(resolve);
6775
+ supersedeInitialScroll(ctx);
6658
6776
  runScrollRequest(token, resolve, run, isReady);
6659
6777
  });
6660
6778
  state.runPendingScrollToEnd = () => {
6661
6779
  const pendingScroll = state.pendingScrollToEnd;
6662
6780
  if (pendingScroll) {
6663
6781
  state.pendingScrollToEnd = void 0;
6664
- if (pendingScroll.token === imperativeScrollToken) {
6782
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
6665
6783
  runScrollRequest(
6666
6784
  pendingScroll.token,
6667
6785
  pendingScroll.resolve,
@@ -6775,15 +6893,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6775
6893
  }),
6776
6894
  scrollToEnd: (options) => new Promise((resolve) => {
6777
6895
  var _a3;
6778
- const token = startImperativeScroll(resolve);
6896
+ const token = scrollRequestTracker.start(resolve);
6779
6897
  state.pendingScrollToEnd = {
6780
6898
  options,
6781
6899
  resolve,
6782
6900
  token
6783
6901
  };
6784
- if (scheduleImperativeScrollCommit) {
6785
- scheduleImperativeScrollCommit();
6786
- } else {
6902
+ scheduleImperativeScrollCommit == null ? void 0 : scheduleImperativeScrollCommit();
6903
+ supersedeInitialScroll(ctx);
6904
+ if (!scheduleImperativeScrollCommit) {
6787
6905
  (_a3 = state.runPendingScrollToEnd) == null ? void 0 : _a3.call(state);
6788
6906
  }
6789
6907
  }),
@@ -7155,6 +7273,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7155
7273
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7156
7274
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7157
7275
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7276
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7158
7277
  const initialScrollProp = initialScrollAtEnd ? {
7159
7278
  index: Math.max(0, dataProp.length - 1),
7160
7279
  preserveForBottomPadding: true,
@@ -7167,7 +7286,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7167
7286
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7168
7287
  } : {
7169
7288
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7170
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7289
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7290
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7291
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7171
7292
  } : initialScrollUsesOffsetOnly ? {
7172
7293
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7173
7294
  index: 0,
@@ -7273,6 +7394,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7273
7394
  viewabilityConfigCallbackPairs: void 0
7274
7395
  };
7275
7396
  const internalState = ctx.state;
7397
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7276
7398
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7277
7399
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7278
7400
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -7634,6 +7756,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7634
7756
  onScroll: (event) => onScroll(ctx, event),
7635
7757
  onScrollBeginDrag: (event) => {
7636
7758
  var _a4, _b2;
7759
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
7760
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
7761
+ cancelImperativeScroll(state);
7762
+ }
7763
+ if (maintainingScrollAtEnd) {
7764
+ finishMaintainScrollAtEnd(ctx);
7765
+ }
7637
7766
  prepareReachedEdgeForNextUserScroll(ctx);
7638
7767
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7639
7768
  },