@legendapp/list 3.3.5 → 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;
@@ -733,6 +729,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
733
729
  } = state;
734
730
  const contentSize = getContentSize(ctx);
735
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
+ }
736
738
  if (contentSize > 0 && queuedInitialLayout) {
737
739
  const insetEnd = getContentInsetEnd(ctx);
738
740
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -742,7 +744,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
742
744
  set$(
743
745
  ctx,
744
746
  "isWithinMaintainScrollAtEndThreshold",
745
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
747
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
746
748
  );
747
749
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
748
750
  if (!shouldSkipThresholdChecks) {
@@ -1093,10 +1095,22 @@ function getAlignItemsAtEndPadding(ctx) {
1093
1095
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1094
1096
  }
1095
1097
  function updateContentMetricsState(ctx) {
1098
+ var _a3;
1099
+ const { state } = ctx;
1096
1100
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1097
1101
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1098
1102
  if (previousPadding !== nextPadding) {
1099
- 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
+ }
1100
1114
  }
1101
1115
  }
1102
1116
 
@@ -1549,81 +1563,55 @@ function doScrollTo(ctx, params) {
1549
1563
  }
1550
1564
  }
1551
1565
 
1552
- // src/core/doMaintainScrollAtEnd.ts
1553
- function doMaintainScrollAtEnd(ctx) {
1554
- const state = ctx.state;
1555
- const {
1556
- didContainersLayout,
1557
- pendingNativeMVCPAdjust,
1558
- refScroller,
1559
- props: { maintainScrollAtEnd }
1560
- } = state;
1561
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1562
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1563
- if (pendingNativeMVCPAdjust) {
1564
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1565
- return false;
1566
- }
1567
- if (shouldMaintainScrollAtEnd) {
1568
- state.pendingMaintainScrollAtEnd = false;
1569
- const contentSize = getContentSize(ctx);
1570
- if (contentSize < state.scrollLength) {
1571
- state.scroll = 0;
1572
- }
1573
- if (!state.maintainingScrollAtEnd) {
1574
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1575
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1576
- const scrollAtRequest = state.scroll;
1577
- state.maintainingScrollAtEnd = pendingState;
1578
- requestAnimationFrame(() => {
1579
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1580
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1581
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1582
- state.maintainingScrollAtEnd = activeState;
1583
- const scroller = refScroller.current;
1584
- if (state.props.horizontal && isHorizontalRTL(state)) {
1585
- const currentContentSize = getContentSize(ctx);
1586
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1587
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1588
- scroller == null ? void 0 : scroller.scrollTo({
1589
- animated: maintainScrollAtEnd.animated,
1590
- x: nativeOffset,
1591
- y: 0
1592
- });
1593
- } else {
1594
- scroller == null ? void 0 : scroller.scrollToEnd({
1595
- animated: maintainScrollAtEnd.animated
1596
- });
1597
- }
1598
- setTimeout(
1599
- () => {
1600
- if (state.maintainingScrollAtEnd === activeState) {
1601
- state.maintainingScrollAtEnd = void 0;
1602
- if (state.pendingMaintainScrollAtEnd) {
1603
- doMaintainScrollAtEnd(ctx);
1604
- }
1605
- }
1606
- },
1607
- maintainScrollAtEnd.animated ? 500 : 0
1608
- );
1609
- } else if (state.maintainingScrollAtEnd === pendingState) {
1610
- state.maintainingScrollAtEnd = void 0;
1611
- 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;
1612
1587
  }
1613
- });
1614
- } else {
1615
- state.pendingMaintainScrollAtEnd = true;
1616
- }
1617
- 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
+ };
1618
1606
  }
1619
- state.pendingMaintainScrollAtEnd = false;
1620
- return false;
1607
+ return ctx.scrollRequestTracker;
1621
1608
  }
1622
1609
 
1623
1610
  // src/utils/requestAdjust.ts
1624
- function requestAdjust(ctx, positionDiff, dataChanged) {
1611
+ function requestAdjust(ctx, positionDiff, source) {
1625
1612
  const state = ctx.state;
1626
1613
  if (Math.abs(positionDiff) > 0.1) {
1614
+ const dataChanged = source === "data";
1627
1615
  const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1628
1616
  const doit = () => {
1629
1617
  if (needsScrollWorkaround) {
@@ -1640,7 +1628,7 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1640
1628
  const readyToRender = peek$(ctx, "readyToRender");
1641
1629
  if (readyToRender) {
1642
1630
  doit();
1643
- if (Platform.OS !== "web") {
1631
+ if (Platform.OS !== "web" && source !== "item-size") {
1644
1632
  const threshold = state.scroll - positionDiff / 2;
1645
1633
  if (!state.ignoreScrollFromMVCP) {
1646
1634
  state.ignoreScrollFromMVCP = {};
@@ -1673,6 +1661,87 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1673
1661
  }
1674
1662
  }
1675
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
+
1676
1745
  // src/core/mvcp.ts
1677
1746
  var MVCP_POSITION_EPSILON = 0.1;
1678
1747
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1746,7 +1815,7 @@ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
1746
1815
  state.pendingNativeMVCPAdjust = void 0;
1747
1816
  const remaining = remainingAfterManual - nativeDelta;
1748
1817
  if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
1749
- requestAdjust(ctx, remaining, true);
1818
+ requestAdjust(ctx, remaining, "data");
1750
1819
  }
1751
1820
  }
1752
1821
  function maybeApplyPredictedNativeMVCPAdjust(ctx) {
@@ -1765,7 +1834,7 @@ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
1765
1834
  return;
1766
1835
  }
1767
1836
  pending.manualApplied = manualDesired;
1768
- requestAdjust(ctx, manualDesired, true);
1837
+ requestAdjust(ctx, manualDesired, "data");
1769
1838
  pending.furthestProgressTowardAmount = 0;
1770
1839
  }
1771
1840
  function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
@@ -1811,7 +1880,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1811
1880
  }
1812
1881
  return false;
1813
1882
  }
1814
- function prepareMVCP(ctx, dataChanged) {
1883
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1815
1884
  const state = ctx.state;
1816
1885
  const { idsInView, positions, props } = state;
1817
1886
  const {
@@ -1965,7 +2034,7 @@ function prepareMVCP(ctx, dataChanged) {
1965
2034
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1966
2035
  const shouldSkipAdjustForMaintainedEnd = (state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated") && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1967
2036
  if (!shouldSkipAdjustForMaintainedEnd) {
1968
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
2037
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData ? "data" : adjustmentSource);
1969
2038
  }
1970
2039
  }
1971
2040
  };
@@ -2063,6 +2132,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2063
2132
  state.scrollTime = currentTime;
2064
2133
  const scrollDelta = Math.abs(newScroll - prevScroll);
2065
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
+ }
2066
2139
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
2067
2140
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2068
2141
  const scrollLength = state.scrollLength;
@@ -2071,7 +2144,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2071
2144
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2072
2145
  const lastCalculated = state.scrollLastCalculate;
2073
2146
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2074
- 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;
2075
2148
  if (shouldUpdate) {
2076
2149
  state.scrollLastCalculate = state.scroll;
2077
2150
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2900,7 +2973,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2900
2973
  }
2901
2974
  }
2902
2975
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2903
- var _a3, _b, _c;
2976
+ var _a3, _b;
2904
2977
  const state = ctx.state;
2905
2978
  const initialScroll = state.initialScroll;
2906
2979
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2914,19 +2987,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2914
2987
  if (state.didFinishInitialScroll) {
2915
2988
  schedulePreservedEndAnchorCorrection(ctx);
2916
2989
  } else if (scrollingTo) {
2917
- const existingWatchdog = initialScrollWatchdog.get(state);
2918
- scrollingTo.offset = resolvedOffset;
2919
- scrollingTo.targetOffset = resolvedOffset;
2920
2990
  state.initialScroll = {
2921
2991
  ...initialScroll,
2922
2992
  contentOffset: resolvedOffset
2923
2993
  };
2924
- state.hasScrolled = false;
2925
- initialScrollWatchdog.set(state, {
2926
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2927
- targetOffset: resolvedOffset
2994
+ dispatchInitialScroll(ctx, {
2995
+ forceScroll: true,
2996
+ resolvedOffset,
2997
+ target: state.initialScroll
2928
2998
  });
2929
- requestAdjust(ctx, offsetDiff);
2930
2999
  }
2931
3000
  }
2932
3001
  } else {
@@ -3968,6 +4037,9 @@ function setDidLayout(ctx) {
3968
4037
  state.queuedInitialLayout = true;
3969
4038
  checkAtBottom(ctx);
3970
4039
  setInitialRenderState(ctx, { didLayout: true });
4040
+ if (state.pendingMaintainScrollAtEnd) {
4041
+ doMaintainScrollAtEnd(ctx);
4042
+ }
3971
4043
  }
3972
4044
 
3973
4045
  // src/core/calculateItemsInView.ts
@@ -4204,7 +4276,7 @@ function calculateItemsInView(ctx, params = {}) {
4204
4276
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4205
4277
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4206
4278
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4207
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4279
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4208
4280
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4209
4281
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4210
4282
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4305,7 +4377,7 @@ function calculateItemsInView(ctx, params = {}) {
4305
4377
  }
4306
4378
  }
4307
4379
  }
4308
- const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged) : void 0;
4380
+ const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged, mvcpAdjustmentSource) : void 0;
4309
4381
  if (dataChanged) {
4310
4382
  resetLayoutCachesForDataChange(state);
4311
4383
  }
@@ -4719,12 +4791,15 @@ function runOrScheduleMVCPRecalculate(ctx) {
4719
4791
  } else if (Platform.OS === "web") {
4720
4792
  if (!state.mvcpAnchorLock) {
4721
4793
  state.scheduledWork.cancel("mvcpRecalculate");
4722
- calculateItemsInView(ctx, { doMVCP: true });
4794
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4723
4795
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4724
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4796
+ state.scheduledWork.frame(
4797
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4798
+ "mvcpRecalculate"
4799
+ );
4725
4800
  }
4726
4801
  } else {
4727
- calculateItemsInView(ctx, { doMVCP: true });
4802
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4728
4803
  }
4729
4804
  }
4730
4805
  function updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal) {
@@ -5213,6 +5288,30 @@ function useContainerItemSignals(containerContext) {
5213
5288
  itemKey
5214
5289
  };
5215
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
+ }
5216
5315
  function useAdaptiveRender() {
5217
5316
  const [mode] = useArr$(["adaptiveRender"]);
5218
5317
  return mode;
@@ -5252,7 +5351,9 @@ function useViewability(callback, configId) {
5252
5351
  const { containerId } = containerContext;
5253
5352
  const key = containerId + (configId != null ? configId : "");
5254
5353
  ctx.mapViewabilityCallbacks.set(key, callback);
5354
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5255
5355
  return () => {
5356
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5256
5357
  ctx.mapViewabilityCallbacks.delete(key);
5257
5358
  };
5258
5359
  }, [ctx, callback, configId, containerContext]);
@@ -5276,7 +5377,9 @@ function useViewabilityAmount(callback) {
5276
5377
  }
5277
5378
  const { containerId } = containerContext;
5278
5379
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5380
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5279
5381
  return () => {
5382
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5280
5383
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5281
5384
  };
5282
5385
  }, [ctx, callback, containerContext]);
@@ -6383,11 +6486,29 @@ var ScheduledWork = class {
6383
6486
  constructor() {
6384
6487
  this.work = /* @__PURE__ */ new Map();
6385
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
+ }
6386
6507
  timeout(callback, delay, key) {
6387
6508
  if (key) {
6388
6509
  this.cancel(key);
6389
6510
  }
6390
- const work = [void 0, clearTimeout];
6511
+ const work = { cancel: () => clearTimeout(handle) };
6391
6512
  const handle = setTimeout(() => {
6392
6513
  const workKey = key != null ? key : handle;
6393
6514
  if (this.work.get(workKey) === work) {
@@ -6395,14 +6516,13 @@ var ScheduledWork = class {
6395
6516
  callback();
6396
6517
  }
6397
6518
  }, delay);
6398
- work[0] = handle;
6399
6519
  this.work.set(key != null ? key : handle, work);
6400
6520
  }
6401
6521
  frame(callback, key) {
6402
6522
  this.cancel(key);
6403
- const work = [void 0, cancelAnimationFrame];
6523
+ const work = { cancel: () => cancelAnimationFrame(handle) };
6404
6524
  this.work.set(key, work);
6405
- work[0] = requestAnimationFrame(() => {
6525
+ const handle = requestAnimationFrame(() => {
6406
6526
  if (this.work.get(key) === work) {
6407
6527
  this.work.delete(key);
6408
6528
  callback();
@@ -6411,22 +6531,21 @@ var ScheduledWork = class {
6411
6531
  }
6412
6532
  register(key, cancel) {
6413
6533
  this.cancel(key);
6414
- this.work.set(key, [void 0, cancel]);
6534
+ this.work.set(key, { cancel });
6415
6535
  }
6416
6536
  cancel(key) {
6417
6537
  const work = this.work.get(key);
6418
6538
  if (work) {
6419
6539
  this.work.delete(key);
6420
- const [handle, cancel] = work;
6421
- cancel(handle);
6540
+ work.cancel();
6422
6541
  }
6423
6542
  }
6424
6543
  has(key) {
6425
6544
  return this.work.has(key);
6426
6545
  }
6427
6546
  dispose() {
6428
- for (const [handle, cancel] of this.work.values()) {
6429
- cancel(handle);
6547
+ for (const work of this.work.values()) {
6548
+ work.cancel();
6430
6549
  }
6431
6550
  this.work.clear();
6432
6551
  }
@@ -6483,6 +6602,25 @@ var ScrollAdjustHandler = class {
6483
6602
  }
6484
6603
  };
6485
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
+
6486
6624
  // src/core/updateContentInsetEndAdjustment.ts
6487
6625
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
6488
6626
  const state = ctx.state;
@@ -6557,25 +6695,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
6557
6695
  }
6558
6696
  }
6559
6697
 
6560
- // src/core/scrollToEnd.ts
6561
- function scrollToEnd(ctx, options) {
6562
- const state = ctx.state;
6563
- const data = state.props.data;
6564
- const index = data.length - 1;
6565
- if (index === -1) {
6566
- return false;
6567
- }
6568
- const paddingBottom = state.props.stylePaddingBottom || 0;
6569
- const footerSize = peek$(ctx, "footerSize") || 0;
6570
- scrollToIndex(ctx, {
6571
- ...options,
6572
- index,
6573
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6574
- viewPosition: 1
6575
- });
6576
- return true;
6577
- }
6578
-
6579
6698
  // src/utils/createImperativeHandle.ts
6580
6699
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
6581
6700
  function getAverageItemSizes(state) {
@@ -6602,9 +6721,9 @@ function triggerMountedContainerLayouts(ctx) {
6602
6721
  }
6603
6722
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6604
6723
  const state = ctx.state;
6724
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
6605
6725
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
6606
6726
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
6607
- let imperativeScrollToken = 0;
6608
6727
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
6609
6728
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
6610
6729
  var _a3;
@@ -6626,7 +6745,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6626
6745
  const startedAt = Date.now();
6627
6746
  let stableFrames = 0;
6628
6747
  const check = () => {
6629
- if (token !== imperativeScrollToken) {
6748
+ if (!scrollRequestTracker.isCurrent(token)) {
6630
6749
  return;
6631
6750
  }
6632
6751
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -6644,33 +6763,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6644
6763
  state.scheduledWork.frame(check, "imperativeScrollReady");
6645
6764
  };
6646
6765
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
6647
- const runNow = () => {
6648
- if (token !== imperativeScrollToken) {
6649
- return;
6650
- }
6651
- const didStartScroll = run();
6652
- if (!didStartScroll || !state.scrollingTo) {
6653
- if (state.pendingScrollResolve === resolve) {
6654
- state.pendingScrollResolve = void 0;
6655
- }
6656
- resolve();
6657
- }
6658
- };
6766
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
6659
6767
  if (isSettlingAfterDataChange() || !isReady()) {
6660
6768
  runWhenReady(token, runNow, isReady);
6661
6769
  } else {
6662
6770
  runNow();
6663
6771
  }
6664
6772
  };
6665
- const startImperativeScroll = (resolve) => {
6666
- state.scheduledWork.cancel("imperativeScrollReady");
6667
- const token = ++imperativeScrollToken;
6668
- settlePendingImperativeScroll(state);
6669
- state.pendingScrollResolve = resolve;
6670
- return token;
6671
- };
6672
6773
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
6673
- const token = startImperativeScroll(resolve);
6774
+ const token = scrollRequestTracker.start(resolve);
6674
6775
  supersedeInitialScroll(ctx);
6675
6776
  runScrollRequest(token, resolve, run, isReady);
6676
6777
  });
@@ -6678,7 +6779,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6678
6779
  const pendingScroll = state.pendingScrollToEnd;
6679
6780
  if (pendingScroll) {
6680
6781
  state.pendingScrollToEnd = void 0;
6681
- if (pendingScroll.token === imperativeScrollToken) {
6782
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
6682
6783
  runScrollRequest(
6683
6784
  pendingScroll.token,
6684
6785
  pendingScroll.resolve,
@@ -6792,7 +6893,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6792
6893
  }),
6793
6894
  scrollToEnd: (options) => new Promise((resolve) => {
6794
6895
  var _a3;
6795
- const token = startImperativeScroll(resolve);
6896
+ const token = scrollRequestTracker.start(resolve);
6796
6897
  state.pendingScrollToEnd = {
6797
6898
  options,
6798
6899
  resolve,
@@ -7172,6 +7273,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7172
7273
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7173
7274
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7174
7275
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7276
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7175
7277
  const initialScrollProp = initialScrollAtEnd ? {
7176
7278
  index: Math.max(0, dataProp.length - 1),
7177
7279
  preserveForBottomPadding: true,
@@ -7184,7 +7286,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7184
7286
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7185
7287
  } : {
7186
7288
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7187
- 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
7188
7292
  } : initialScrollUsesOffsetOnly ? {
7189
7293
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7190
7294
  index: 0,
@@ -7290,6 +7394,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7290
7394
  viewabilityConfigCallbackPairs: void 0
7291
7395
  };
7292
7396
  const internalState = ctx.state;
7397
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7293
7398
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7294
7399
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7295
7400
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -7651,6 +7756,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7651
7756
  onScroll: (event) => onScroll(ctx, event),
7652
7757
  onScrollBeginDrag: (event) => {
7653
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
+ }
7654
7766
  prepareReachedEdgeForNextUserScroll(ctx);
7655
7767
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7656
7768
  },