@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.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;
@@ -712,6 +708,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
712
708
  } = state;
713
709
  const contentSize = getContentSize(ctx);
714
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
+ }
715
717
  if (contentSize > 0 && queuedInitialLayout) {
716
718
  const insetEnd = getContentInsetEnd(ctx);
717
719
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -721,7 +723,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
721
723
  set$(
722
724
  ctx,
723
725
  "isWithinMaintainScrollAtEndThreshold",
724
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
726
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
725
727
  );
726
728
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
727
729
  if (!shouldSkipThresholdChecks) {
@@ -1072,10 +1074,22 @@ function getAlignItemsAtEndPadding(ctx) {
1072
1074
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1073
1075
  }
1074
1076
  function updateContentMetricsState(ctx) {
1077
+ var _a3;
1078
+ const { state } = ctx;
1075
1079
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1076
1080
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1077
1081
  if (previousPadding !== nextPadding) {
1078
- 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
+ }
1079
1093
  }
1080
1094
  }
1081
1095
 
@@ -1528,81 +1542,55 @@ function doScrollTo(ctx, params) {
1528
1542
  }
1529
1543
  }
1530
1544
 
1531
- // src/core/doMaintainScrollAtEnd.ts
1532
- function doMaintainScrollAtEnd(ctx) {
1533
- const state = ctx.state;
1534
- const {
1535
- didContainersLayout,
1536
- pendingNativeMVCPAdjust,
1537
- refScroller,
1538
- props: { maintainScrollAtEnd }
1539
- } = state;
1540
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1541
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1542
- if (pendingNativeMVCPAdjust) {
1543
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1544
- return false;
1545
- }
1546
- if (shouldMaintainScrollAtEnd) {
1547
- state.pendingMaintainScrollAtEnd = false;
1548
- const contentSize = getContentSize(ctx);
1549
- if (contentSize < state.scrollLength) {
1550
- state.scroll = 0;
1551
- }
1552
- if (!state.maintainingScrollAtEnd) {
1553
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1554
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1555
- const scrollAtRequest = state.scroll;
1556
- state.maintainingScrollAtEnd = pendingState;
1557
- requestAnimationFrame(() => {
1558
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1559
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1560
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1561
- state.maintainingScrollAtEnd = activeState;
1562
- const scroller = refScroller.current;
1563
- if (state.props.horizontal && isHorizontalRTL(state)) {
1564
- const currentContentSize = getContentSize(ctx);
1565
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1566
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1567
- scroller == null ? void 0 : scroller.scrollTo({
1568
- animated: maintainScrollAtEnd.animated,
1569
- x: nativeOffset,
1570
- y: 0
1571
- });
1572
- } else {
1573
- scroller == null ? void 0 : scroller.scrollToEnd({
1574
- animated: maintainScrollAtEnd.animated
1575
- });
1576
- }
1577
- setTimeout(
1578
- () => {
1579
- if (state.maintainingScrollAtEnd === activeState) {
1580
- state.maintainingScrollAtEnd = void 0;
1581
- if (state.pendingMaintainScrollAtEnd) {
1582
- doMaintainScrollAtEnd(ctx);
1583
- }
1584
- }
1585
- },
1586
- maintainScrollAtEnd.animated ? 500 : 0
1587
- );
1588
- } else if (state.maintainingScrollAtEnd === pendingState) {
1589
- state.maintainingScrollAtEnd = void 0;
1590
- 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;
1591
1566
  }
1592
- });
1593
- } else {
1594
- state.pendingMaintainScrollAtEnd = true;
1595
- }
1596
- 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
+ };
1597
1585
  }
1598
- state.pendingMaintainScrollAtEnd = false;
1599
- return false;
1586
+ return ctx.scrollRequestTracker;
1600
1587
  }
1601
1588
 
1602
1589
  // src/utils/requestAdjust.ts
1603
- function requestAdjust(ctx, positionDiff, dataChanged) {
1590
+ function requestAdjust(ctx, positionDiff, source) {
1604
1591
  const state = ctx.state;
1605
1592
  if (Math.abs(positionDiff) > 0.1) {
1593
+ const dataChanged = source === "data";
1606
1594
  const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1607
1595
  const doit = () => {
1608
1596
  if (needsScrollWorkaround) {
@@ -1619,7 +1607,7 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1619
1607
  const readyToRender = peek$(ctx, "readyToRender");
1620
1608
  if (readyToRender) {
1621
1609
  doit();
1622
- if (Platform.OS !== "web") {
1610
+ if (Platform.OS !== "web" && source !== "item-size") {
1623
1611
  const threshold = state.scroll - positionDiff / 2;
1624
1612
  if (!state.ignoreScrollFromMVCP) {
1625
1613
  state.ignoreScrollFromMVCP = {};
@@ -1652,6 +1640,87 @@ function requestAdjust(ctx, positionDiff, dataChanged) {
1652
1640
  }
1653
1641
  }
1654
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
+
1655
1724
  // src/core/mvcp.ts
1656
1725
  var MVCP_POSITION_EPSILON = 0.1;
1657
1726
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1725,7 +1794,7 @@ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
1725
1794
  state.pendingNativeMVCPAdjust = void 0;
1726
1795
  const remaining = remainingAfterManual - nativeDelta;
1727
1796
  if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
1728
- requestAdjust(ctx, remaining, true);
1797
+ requestAdjust(ctx, remaining, "data");
1729
1798
  }
1730
1799
  }
1731
1800
  function maybeApplyPredictedNativeMVCPAdjust(ctx) {
@@ -1744,7 +1813,7 @@ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
1744
1813
  return;
1745
1814
  }
1746
1815
  pending.manualApplied = manualDesired;
1747
- requestAdjust(ctx, manualDesired, true);
1816
+ requestAdjust(ctx, manualDesired, "data");
1748
1817
  pending.furthestProgressTowardAmount = 0;
1749
1818
  }
1750
1819
  function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
@@ -1790,7 +1859,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1790
1859
  }
1791
1860
  return false;
1792
1861
  }
1793
- function prepareMVCP(ctx, dataChanged) {
1862
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1794
1863
  const state = ctx.state;
1795
1864
  const { idsInView, positions, props } = state;
1796
1865
  const {
@@ -1944,7 +2013,7 @@ function prepareMVCP(ctx, dataChanged) {
1944
2013
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1945
2014
  const shouldSkipAdjustForMaintainedEnd = (state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated") && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1946
2015
  if (!shouldSkipAdjustForMaintainedEnd) {
1947
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
2016
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData ? "data" : adjustmentSource);
1948
2017
  }
1949
2018
  }
1950
2019
  };
@@ -2042,6 +2111,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2042
2111
  state.scrollTime = currentTime;
2043
2112
  const scrollDelta = Math.abs(newScroll - prevScroll);
2044
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
+ }
2045
2118
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
2046
2119
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2047
2120
  const scrollLength = state.scrollLength;
@@ -2050,7 +2123,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2050
2123
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2051
2124
  const lastCalculated = state.scrollLastCalculate;
2052
2125
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2053
- 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;
2054
2127
  if (shouldUpdate) {
2055
2128
  state.scrollLastCalculate = state.scroll;
2056
2129
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2879,7 +2952,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2879
2952
  }
2880
2953
  }
2881
2954
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2882
- var _a3, _b, _c;
2955
+ var _a3, _b;
2883
2956
  const state = ctx.state;
2884
2957
  const initialScroll = state.initialScroll;
2885
2958
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2893,19 +2966,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2893
2966
  if (state.didFinishInitialScroll) {
2894
2967
  schedulePreservedEndAnchorCorrection(ctx);
2895
2968
  } else if (scrollingTo) {
2896
- const existingWatchdog = initialScrollWatchdog.get(state);
2897
- scrollingTo.offset = resolvedOffset;
2898
- scrollingTo.targetOffset = resolvedOffset;
2899
2969
  state.initialScroll = {
2900
2970
  ...initialScroll,
2901
2971
  contentOffset: resolvedOffset
2902
2972
  };
2903
- state.hasScrolled = false;
2904
- initialScrollWatchdog.set(state, {
2905
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2906
- targetOffset: resolvedOffset
2973
+ dispatchInitialScroll(ctx, {
2974
+ forceScroll: true,
2975
+ resolvedOffset,
2976
+ target: state.initialScroll
2907
2977
  });
2908
- requestAdjust(ctx, offsetDiff);
2909
2978
  }
2910
2979
  }
2911
2980
  } else {
@@ -3947,6 +4016,9 @@ function setDidLayout(ctx) {
3947
4016
  state.queuedInitialLayout = true;
3948
4017
  checkAtBottom(ctx);
3949
4018
  setInitialRenderState(ctx, { didLayout: true });
4019
+ if (state.pendingMaintainScrollAtEnd) {
4020
+ doMaintainScrollAtEnd(ctx);
4021
+ }
3950
4022
  }
3951
4023
 
3952
4024
  // src/core/calculateItemsInView.ts
@@ -4183,7 +4255,7 @@ function calculateItemsInView(ctx, params = {}) {
4183
4255
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4184
4256
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4185
4257
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4186
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4258
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4187
4259
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4188
4260
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4189
4261
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4284,7 +4356,7 @@ function calculateItemsInView(ctx, params = {}) {
4284
4356
  }
4285
4357
  }
4286
4358
  }
4287
- const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged) : void 0;
4359
+ const checkMVCP = doMVCP && !suppressInitialScrollSideEffects ? prepareMVCP(ctx, dataChanged, mvcpAdjustmentSource) : void 0;
4288
4360
  if (dataChanged) {
4289
4361
  resetLayoutCachesForDataChange(state);
4290
4362
  }
@@ -4698,12 +4770,15 @@ function runOrScheduleMVCPRecalculate(ctx) {
4698
4770
  } else if (Platform.OS === "web") {
4699
4771
  if (!state.mvcpAnchorLock) {
4700
4772
  state.scheduledWork.cancel("mvcpRecalculate");
4701
- calculateItemsInView(ctx, { doMVCP: true });
4773
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4702
4774
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4703
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4775
+ state.scheduledWork.frame(
4776
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4777
+ "mvcpRecalculate"
4778
+ );
4704
4779
  }
4705
4780
  } else {
4706
- calculateItemsInView(ctx, { doMVCP: true });
4781
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4707
4782
  }
4708
4783
  }
4709
4784
  function updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal) {
@@ -5192,6 +5267,30 @@ function useContainerItemSignals(containerContext) {
5192
5267
  itemKey
5193
5268
  };
5194
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
+ }
5195
5294
  function useAdaptiveRender() {
5196
5295
  const [mode] = useArr$(["adaptiveRender"]);
5197
5296
  return mode;
@@ -5231,7 +5330,9 @@ function useViewability(callback, configId) {
5231
5330
  const { containerId } = containerContext;
5232
5331
  const key = containerId + (configId != null ? configId : "");
5233
5332
  ctx.mapViewabilityCallbacks.set(key, callback);
5333
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5234
5334
  return () => {
5335
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5235
5336
  ctx.mapViewabilityCallbacks.delete(key);
5236
5337
  };
5237
5338
  }, [ctx, callback, configId, containerContext]);
@@ -5255,7 +5356,9 @@ function useViewabilityAmount(callback) {
5255
5356
  }
5256
5357
  const { containerId } = containerContext;
5257
5358
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5359
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5258
5360
  return () => {
5361
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5259
5362
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5260
5363
  };
5261
5364
  }, [ctx, callback, containerContext]);
@@ -6362,11 +6465,29 @@ var ScheduledWork = class {
6362
6465
  constructor() {
6363
6466
  this.work = /* @__PURE__ */ new Map();
6364
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
+ }
6365
6486
  timeout(callback, delay, key) {
6366
6487
  if (key) {
6367
6488
  this.cancel(key);
6368
6489
  }
6369
- const work = [void 0, clearTimeout];
6490
+ const work = { cancel: () => clearTimeout(handle) };
6370
6491
  const handle = setTimeout(() => {
6371
6492
  const workKey = key != null ? key : handle;
6372
6493
  if (this.work.get(workKey) === work) {
@@ -6374,14 +6495,13 @@ var ScheduledWork = class {
6374
6495
  callback();
6375
6496
  }
6376
6497
  }, delay);
6377
- work[0] = handle;
6378
6498
  this.work.set(key != null ? key : handle, work);
6379
6499
  }
6380
6500
  frame(callback, key) {
6381
6501
  this.cancel(key);
6382
- const work = [void 0, cancelAnimationFrame];
6502
+ const work = { cancel: () => cancelAnimationFrame(handle) };
6383
6503
  this.work.set(key, work);
6384
- work[0] = requestAnimationFrame(() => {
6504
+ const handle = requestAnimationFrame(() => {
6385
6505
  if (this.work.get(key) === work) {
6386
6506
  this.work.delete(key);
6387
6507
  callback();
@@ -6390,22 +6510,21 @@ var ScheduledWork = class {
6390
6510
  }
6391
6511
  register(key, cancel) {
6392
6512
  this.cancel(key);
6393
- this.work.set(key, [void 0, cancel]);
6513
+ this.work.set(key, { cancel });
6394
6514
  }
6395
6515
  cancel(key) {
6396
6516
  const work = this.work.get(key);
6397
6517
  if (work) {
6398
6518
  this.work.delete(key);
6399
- const [handle, cancel] = work;
6400
- cancel(handle);
6519
+ work.cancel();
6401
6520
  }
6402
6521
  }
6403
6522
  has(key) {
6404
6523
  return this.work.has(key);
6405
6524
  }
6406
6525
  dispose() {
6407
- for (const [handle, cancel] of this.work.values()) {
6408
- cancel(handle);
6526
+ for (const work of this.work.values()) {
6527
+ work.cancel();
6409
6528
  }
6410
6529
  this.work.clear();
6411
6530
  }
@@ -6462,6 +6581,25 @@ var ScrollAdjustHandler = class {
6462
6581
  }
6463
6582
  };
6464
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
+
6465
6603
  // src/core/updateContentInsetEndAdjustment.ts
6466
6604
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
6467
6605
  const state = ctx.state;
@@ -6536,25 +6674,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
6536
6674
  }
6537
6675
  }
6538
6676
 
6539
- // src/core/scrollToEnd.ts
6540
- function scrollToEnd(ctx, options) {
6541
- const state = ctx.state;
6542
- const data = state.props.data;
6543
- const index = data.length - 1;
6544
- if (index === -1) {
6545
- return false;
6546
- }
6547
- const paddingBottom = state.props.stylePaddingBottom || 0;
6548
- const footerSize = peek$(ctx, "footerSize") || 0;
6549
- scrollToIndex(ctx, {
6550
- ...options,
6551
- index,
6552
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6553
- viewPosition: 1
6554
- });
6555
- return true;
6556
- }
6557
-
6558
6677
  // src/utils/createImperativeHandle.ts
6559
6678
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
6560
6679
  function getAverageItemSizes(state) {
@@ -6581,9 +6700,9 @@ function triggerMountedContainerLayouts(ctx) {
6581
6700
  }
6582
6701
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6583
6702
  const state = ctx.state;
6703
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
6584
6704
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
6585
6705
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
6586
- let imperativeScrollToken = 0;
6587
6706
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
6588
6707
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
6589
6708
  var _a3;
@@ -6605,7 +6724,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6605
6724
  const startedAt = Date.now();
6606
6725
  let stableFrames = 0;
6607
6726
  const check = () => {
6608
- if (token !== imperativeScrollToken) {
6727
+ if (!scrollRequestTracker.isCurrent(token)) {
6609
6728
  return;
6610
6729
  }
6611
6730
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -6623,33 +6742,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6623
6742
  state.scheduledWork.frame(check, "imperativeScrollReady");
6624
6743
  };
6625
6744
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
6626
- const runNow = () => {
6627
- if (token !== imperativeScrollToken) {
6628
- return;
6629
- }
6630
- const didStartScroll = run();
6631
- if (!didStartScroll || !state.scrollingTo) {
6632
- if (state.pendingScrollResolve === resolve) {
6633
- state.pendingScrollResolve = void 0;
6634
- }
6635
- resolve();
6636
- }
6637
- };
6745
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
6638
6746
  if (isSettlingAfterDataChange() || !isReady()) {
6639
6747
  runWhenReady(token, runNow, isReady);
6640
6748
  } else {
6641
6749
  runNow();
6642
6750
  }
6643
6751
  };
6644
- const startImperativeScroll = (resolve) => {
6645
- state.scheduledWork.cancel("imperativeScrollReady");
6646
- const token = ++imperativeScrollToken;
6647
- settlePendingImperativeScroll(state);
6648
- state.pendingScrollResolve = resolve;
6649
- return token;
6650
- };
6651
6752
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
6652
- const token = startImperativeScroll(resolve);
6753
+ const token = scrollRequestTracker.start(resolve);
6653
6754
  supersedeInitialScroll(ctx);
6654
6755
  runScrollRequest(token, resolve, run, isReady);
6655
6756
  });
@@ -6657,7 +6758,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6657
6758
  const pendingScroll = state.pendingScrollToEnd;
6658
6759
  if (pendingScroll) {
6659
6760
  state.pendingScrollToEnd = void 0;
6660
- if (pendingScroll.token === imperativeScrollToken) {
6761
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
6661
6762
  runScrollRequest(
6662
6763
  pendingScroll.token,
6663
6764
  pendingScroll.resolve,
@@ -6771,7 +6872,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6771
6872
  }),
6772
6873
  scrollToEnd: (options) => new Promise((resolve) => {
6773
6874
  var _a3;
6774
- const token = startImperativeScroll(resolve);
6875
+ const token = scrollRequestTracker.start(resolve);
6775
6876
  state.pendingScrollToEnd = {
6776
6877
  options,
6777
6878
  resolve,
@@ -7151,6 +7252,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7151
7252
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7152
7253
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7153
7254
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7255
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7154
7256
  const initialScrollProp = initialScrollAtEnd ? {
7155
7257
  index: Math.max(0, dataProp.length - 1),
7156
7258
  preserveForBottomPadding: true,
@@ -7163,7 +7265,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7163
7265
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7164
7266
  } : {
7165
7267
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7166
- 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
7167
7271
  } : initialScrollUsesOffsetOnly ? {
7168
7272
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7169
7273
  index: 0,
@@ -7269,6 +7373,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7269
7373
  viewabilityConfigCallbackPairs: void 0
7270
7374
  };
7271
7375
  const internalState = ctx.state;
7376
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7272
7377
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7273
7378
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7274
7379
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -7630,6 +7735,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7630
7735
  onScroll: (event) => onScroll(ctx, event),
7631
7736
  onScrollBeginDrag: (event) => {
7632
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
+ }
7633
7745
  prepareReachedEdgeForNextUserScroll(ctx);
7634
7746
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7635
7747
  },