@legendapp/list 3.3.5 → 3.3.7

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.mjs CHANGED
@@ -55,10 +55,6 @@ function getStylePaddingEnd(props) {
55
55
  }
56
56
  return (isHorizontalRTLProps(props) ? props.stylePaddingLeft : props.stylePaddingRight) || 0;
57
57
  }
58
- function getLogicalHorizontalMaxOffset(state, contentWidth) {
59
- var _a3;
60
- return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0;
61
- }
62
58
  function getHorizontalInsetEnd(state, inset) {
63
59
  if (!inset) {
64
60
  return 0;
@@ -689,6 +685,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
689
685
  } = state;
690
686
  const contentSize = getContentSize(ctx);
691
687
  resetSharedEdgeGateIfOutsideHysteresis(ctx);
688
+ if (state.props.data.length === 0 && scrollLength > 0 && contentSize <= scrollLength) {
689
+ set$(ctx, "isAtEnd", true);
690
+ set$(ctx, "isNearEnd", true);
691
+ set$(ctx, "isWithinMaintainScrollAtEndThreshold", true);
692
+ return;
693
+ }
692
694
  if (contentSize > 0 && queuedInitialLayout) {
693
695
  const insetEnd = getContentInsetEnd(ctx);
694
696
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -698,7 +700,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
698
700
  set$(
699
701
  ctx,
700
702
  "isWithinMaintainScrollAtEndThreshold",
701
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
703
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
702
704
  );
703
705
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
704
706
  if (!shouldSkipThresholdChecks) {
@@ -1043,10 +1045,22 @@ function getAlignItemsAtEndPadding(ctx) {
1043
1045
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1044
1046
  }
1045
1047
  function updateContentMetricsState(ctx) {
1048
+ var _a3;
1049
+ const { state } = ctx;
1046
1050
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1047
1051
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1048
1052
  if (previousPadding !== nextPadding) {
1049
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1053
+ const isAnimatedMaintainActive = state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated";
1054
+ const isMaintainExpected = state.didContainersLayout && ((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.animated) && (isAnimatedMaintainActive || previousPadding > nextPadding && peek$(ctx, "isWithinMaintainScrollAtEndThreshold"));
1055
+ if (isMaintainExpected && !isAnimatedMaintainActive && !state.pendingMaintainScrollAtEnd) {
1056
+ state.scheduledWork.microtask(() => {
1057
+ if (!state.maintainingScrollAtEnd && !state.pendingMaintainScrollAtEnd) {
1058
+ set$(ctx, "alignItemsAtEndPadding", getAlignItemsAtEndPadding(ctx));
1059
+ }
1060
+ }, "alignItemsAtEndPaddingFallback");
1061
+ } else if (!isMaintainExpected) {
1062
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1063
+ }
1050
1064
  }
1051
1065
  }
1052
1066
 
@@ -1404,22 +1418,110 @@ function listenForScrollEnd(ctx, params) {
1404
1418
  scheduledWork.register("platformScrollCompletion", cancel);
1405
1419
  }
1406
1420
 
1421
+ // src/core/scrollRequestTracker.ts
1422
+ function getScrollRequestTracker(ctx) {
1423
+ if (!ctx.scrollRequestTracker) {
1424
+ let currentToken = 0;
1425
+ const start = (resolve) => {
1426
+ const state = ctx.state;
1427
+ state.scheduledWork.cancel("imperativeScrollReady");
1428
+ const token = ++currentToken;
1429
+ settlePendingImperativeScroll(state);
1430
+ state.pendingScrollResolve = resolve;
1431
+ return token;
1432
+ };
1433
+ const runNow = (token, resolve, run) => {
1434
+ const state = ctx.state;
1435
+ if (token !== currentToken) {
1436
+ return;
1437
+ }
1438
+ const didStartScroll = run();
1439
+ if (!didStartScroll || !state.scrollingTo) {
1440
+ if (state.pendingScrollResolve === resolve) {
1441
+ state.pendingScrollResolve = void 0;
1442
+ }
1443
+ resolve();
1444
+ }
1445
+ };
1446
+ ctx.scrollRequestTracker = {
1447
+ isCurrent: (token) => token === currentToken,
1448
+ runNow,
1449
+ runNowIfIdle: (run) => {
1450
+ const state = ctx.state;
1451
+ if (state.pendingScrollResolve || state.scrollingTo) {
1452
+ return Promise.resolve();
1453
+ }
1454
+ return new Promise((resolve) => {
1455
+ const token = start(resolve);
1456
+ runNow(token, resolve, run);
1457
+ });
1458
+ },
1459
+ start
1460
+ };
1461
+ }
1462
+ return ctx.scrollRequestTracker;
1463
+ }
1464
+
1465
+ // src/utils/requestAdjust.ts
1466
+ function requestAdjust(ctx, positionDiff, source) {
1467
+ const state = ctx.state;
1468
+ if (Math.abs(positionDiff) > 0.1) {
1469
+ const doit = () => {
1470
+ {
1471
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
1472
+ if (state.adjustingFromInitialMount) {
1473
+ state.adjustingFromInitialMount--;
1474
+ }
1475
+ }
1476
+ };
1477
+ state.scroll += positionDiff;
1478
+ state.scrollForNextCalculateItemsInView = void 0;
1479
+ const readyToRender = peek$(ctx, "readyToRender");
1480
+ if (readyToRender) {
1481
+ doit();
1482
+ } else {
1483
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1484
+ requestAnimationFrame(doit);
1485
+ }
1486
+ }
1487
+ }
1488
+
1407
1489
  // src/core/doMaintainScrollAtEnd.ts
1490
+ function finishMaintainScrollAtEnd(ctx) {
1491
+ var _a3;
1492
+ const { state } = ctx;
1493
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1494
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1495
+ state.maintainingScrollAtEnd = void 0;
1496
+ state.pendingMaintainScrollAtEnd = false;
1497
+ if (currentPadding !== nextPadding) {
1498
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1499
+ state.scrollForNextCalculateItemsInView = void 0;
1500
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1501
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1502
+ requestAdjust(ctx, nextScroll - state.scroll);
1503
+ }
1504
+ }
1408
1505
  function doMaintainScrollAtEnd(ctx) {
1409
1506
  const state = ctx.state;
1410
1507
  const {
1411
1508
  didContainersLayout,
1509
+ didFinishInitialScroll,
1412
1510
  pendingNativeMVCPAdjust,
1413
- refScroller,
1414
1511
  props: { maintainScrollAtEnd }
1415
1512
  } = state;
1416
1513
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1417
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1514
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1515
+ const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1516
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1517
+ state.pendingMaintainScrollAtEnd = true;
1518
+ return false;
1519
+ }
1418
1520
  if (pendingNativeMVCPAdjust) {
1419
1521
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1420
1522
  return false;
1421
1523
  }
1422
- if (shouldMaintainScrollAtEnd) {
1524
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1423
1525
  state.pendingMaintainScrollAtEnd = false;
1424
1526
  const contentSize = getContentSize(ctx);
1425
1527
  if (contentSize < state.scrollLength) {
@@ -1431,39 +1533,29 @@ function doMaintainScrollAtEnd(ctx) {
1431
1533
  const scrollAtRequest = state.scroll;
1432
1534
  state.maintainingScrollAtEnd = pendingState;
1433
1535
  requestAnimationFrame(() => {
1536
+ if (state.maintainingScrollAtEnd !== pendingState) {
1537
+ return;
1538
+ }
1434
1539
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1435
1540
  const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1436
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1541
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1437
1542
  state.maintainingScrollAtEnd = activeState;
1438
- const scroller = refScroller.current;
1439
- if (state.props.horizontal && isHorizontalRTL(state)) {
1440
- const currentContentSize = getContentSize(ctx);
1441
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1442
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1443
- scroller == null ? void 0 : scroller.scrollTo({
1444
- animated: maintainScrollAtEnd.animated,
1445
- x: nativeOffset,
1446
- y: 0
1447
- });
1448
- } else {
1449
- scroller == null ? void 0 : scroller.scrollToEnd({
1450
- animated: maintainScrollAtEnd.animated
1451
- });
1452
- }
1453
- setTimeout(
1454
- () => {
1455
- if (state.maintainingScrollAtEnd === activeState) {
1456
- state.maintainingScrollAtEnd = void 0;
1457
- if (state.pendingMaintainScrollAtEnd) {
1458
- doMaintainScrollAtEnd(ctx);
1459
- }
1460
- }
1461
- },
1462
- maintainScrollAtEnd.animated ? 500 : 0
1543
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1544
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1463
1545
  );
1546
+ void scrollPromise.then(() => {
1547
+ if (state.maintainingScrollAtEnd !== activeState) {
1548
+ return;
1549
+ }
1550
+ if (state.pendingMaintainScrollAtEnd) {
1551
+ state.maintainingScrollAtEnd = void 0;
1552
+ doMaintainScrollAtEnd(ctx);
1553
+ } else {
1554
+ finishMaintainScrollAtEnd(ctx);
1555
+ }
1556
+ });
1464
1557
  } else if (state.maintainingScrollAtEnd === pendingState) {
1465
- state.maintainingScrollAtEnd = void 0;
1466
- state.pendingMaintainScrollAtEnd = false;
1558
+ finishMaintainScrollAtEnd(ctx);
1467
1559
  }
1468
1560
  });
1469
1561
  } else {
@@ -1471,34 +1563,10 @@ function doMaintainScrollAtEnd(ctx) {
1471
1563
  }
1472
1564
  return true;
1473
1565
  }
1474
- state.pendingMaintainScrollAtEnd = false;
1566
+ finishMaintainScrollAtEnd(ctx);
1475
1567
  return false;
1476
1568
  }
1477
1569
 
1478
- // src/utils/requestAdjust.ts
1479
- function requestAdjust(ctx, positionDiff, dataChanged) {
1480
- const state = ctx.state;
1481
- if (Math.abs(positionDiff) > 0.1) {
1482
- const doit = () => {
1483
- {
1484
- state.scrollAdjustHandler.requestAdjust(positionDiff);
1485
- if (state.adjustingFromInitialMount) {
1486
- state.adjustingFromInitialMount--;
1487
- }
1488
- }
1489
- };
1490
- state.scroll += positionDiff;
1491
- state.scrollForNextCalculateItemsInView = void 0;
1492
- const readyToRender = peek$(ctx, "readyToRender");
1493
- if (readyToRender) {
1494
- doit();
1495
- } else {
1496
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1497
- requestAnimationFrame(doit);
1498
- }
1499
- }
1500
- }
1501
-
1502
1570
  // src/core/mvcp.ts
1503
1571
  var MVCP_POSITION_EPSILON = 0.1;
1504
1572
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1635,7 +1703,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1635
1703
  }
1636
1704
  return false;
1637
1705
  }
1638
- function prepareMVCP(ctx, dataChanged) {
1706
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1639
1707
  const state = ctx.state;
1640
1708
  const { idsInView, positions, props } = state;
1641
1709
  const {
@@ -1887,6 +1955,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1887
1955
  state.scrollTime = currentTime;
1888
1956
  const scrollDelta = Math.abs(newScroll - prevScroll);
1889
1957
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1958
+ const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1959
+ if (didCancelMaintainScrollAtEnd) {
1960
+ finishMaintainScrollAtEnd(ctx);
1961
+ }
1890
1962
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
1891
1963
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1892
1964
  const scrollLength = state.scrollLength;
@@ -1895,7 +1967,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1895
1967
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
1896
1968
  const lastCalculated = state.scrollLastCalculate;
1897
1969
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1898
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1970
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1899
1971
  if (shouldUpdate) {
1900
1972
  state.scrollLastCalculate = state.scroll;
1901
1973
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2724,7 +2796,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2724
2796
  }
2725
2797
  }
2726
2798
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2727
- var _a3, _b, _c;
2799
+ var _a3, _b;
2728
2800
  const state = ctx.state;
2729
2801
  const initialScroll = state.initialScroll;
2730
2802
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2738,19 +2810,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2738
2810
  if (state.didFinishInitialScroll) {
2739
2811
  schedulePreservedEndAnchorCorrection(ctx);
2740
2812
  } else if (scrollingTo) {
2741
- const existingWatchdog = initialScrollWatchdog.get(state);
2742
- scrollingTo.offset = resolvedOffset;
2743
- scrollingTo.targetOffset = resolvedOffset;
2744
2813
  state.initialScroll = {
2745
2814
  ...initialScroll,
2746
2815
  contentOffset: resolvedOffset
2747
2816
  };
2748
- state.hasScrolled = false;
2749
- initialScrollWatchdog.set(state, {
2750
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2751
- targetOffset: resolvedOffset
2817
+ dispatchInitialScroll(ctx, {
2818
+ forceScroll: true,
2819
+ resolvedOffset,
2820
+ target: state.initialScroll
2752
2821
  });
2753
- requestAdjust(ctx, offsetDiff);
2754
2822
  }
2755
2823
  }
2756
2824
  } else {
@@ -2863,6 +2931,7 @@ function abortBootstrapInitialScroll(ctx) {
2863
2931
  // src/core/containerItemMetadata.ts
2864
2932
  function createContainerItemMetadata(state, itemIndex, itemData, itemType) {
2865
2933
  return {
2934
+ data: state.props.data,
2866
2935
  dataChangeEpoch: state.dataChangeEpoch,
2867
2936
  getFixedItemSize: state.props.getFixedItemSize,
2868
2937
  getItemType: state.props.getItemType,
@@ -2871,25 +2940,29 @@ function createContainerItemMetadata(state, itemIndex, itemData, itemType) {
2871
2940
  itemType
2872
2941
  };
2873
2942
  }
2874
- function resolveContainerItemMetadata(state, containerId, itemIndex, itemData) {
2875
- var _a3, _b;
2876
- const { getFixedItemSize, getItemType } = state.props;
2943
+ function updateContainerItemMetadata(state, containerId, itemIndex, itemData, itemType) {
2944
+ var _a3;
2945
+ const { getItemType } = state.props;
2877
2946
  const previousMetadata = state.containerItemMetadata.get(containerId);
2878
- let metadata;
2879
2947
  if ((previousMetadata == null ? void 0 : previousMetadata.dataChangeEpoch) === state.dataChangeEpoch && previousMetadata.getItemType === getItemType && previousMetadata.itemData === itemData && previousMetadata.itemIndex === itemIndex) {
2880
- metadata = previousMetadata;
2881
- } else {
2882
- const itemType = getItemType ? (_a3 = getItemType(itemData, itemIndex)) != null ? _a3 : "" : void 0;
2883
- metadata = createContainerItemMetadata(state, itemIndex, itemData, itemType);
2884
- state.containerItemMetadata.set(containerId, metadata);
2948
+ return previousMetadata;
2885
2949
  }
2950
+ const resolvedItemType = getItemType ? (_a3 = getItemType(itemData, itemIndex)) != null ? _a3 : "" : void 0;
2951
+ const metadata = createContainerItemMetadata(state, itemIndex, itemData, resolvedItemType);
2952
+ state.containerItemMetadata.set(containerId, metadata);
2953
+ return metadata;
2954
+ }
2955
+ function resolveContainerItemMetadata(state, containerId, itemIndex, itemData) {
2956
+ var _a3;
2957
+ const { getFixedItemSize } = state.props;
2958
+ const metadata = updateContainerItemMetadata(state, containerId, itemIndex, itemData);
2886
2959
  if (metadata.getFixedItemSize !== getFixedItemSize) {
2887
2960
  metadata.didResolveFixedItemSize = false;
2888
2961
  metadata.fixedItemSize = void 0;
2889
2962
  metadata.getFixedItemSize = getFixedItemSize;
2890
2963
  }
2891
2964
  if (getFixedItemSize && !metadata.didResolveFixedItemSize) {
2892
- metadata.fixedItemSize = getFixedItemSize(itemData, itemIndex, (_b = metadata.itemType) != null ? _b : "");
2965
+ metadata.fixedItemSize = getFixedItemSize(itemData, itemIndex, (_a3 = metadata.itemType) != null ? _a3 : "");
2893
2966
  metadata.didResolveFixedItemSize = true;
2894
2967
  }
2895
2968
  return metadata;
@@ -3234,6 +3307,7 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3234
3307
  return { didChangePosition: false, didRefreshData: false };
3235
3308
  }
3236
3309
  const itemKey = (_a3 = state.idCache[itemIndex]) != null ? _a3 : getId(state, itemIndex);
3310
+ const metadata = updateContainerItemMetadata(state, containerIndex, itemIndex, item);
3237
3311
  const updateLayout = (_b = options == null ? void 0 : options.updateLayout) != null ? _b : true;
3238
3312
  let didChangePosition = false;
3239
3313
  let didRefreshData = false;
@@ -3244,7 +3318,7 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3244
3318
  return { didChangePosition: false, didRefreshData: false };
3245
3319
  }
3246
3320
  const logicalPosition = (positionValue || 0) - ((_c = options == null ? void 0 : options.scrollAdjustPending) != null ? _c : 0);
3247
- const itemSize = (_d = state.sizes.get(itemKey)) != null ? _d : getItemSize(ctx, itemKey, itemIndex, item);
3321
+ const itemSize = (_d = state.sizes.get(itemKey)) != null ? _d : getItemSize(ctx, itemKey, itemIndex, item, void 0, void 0, void 0, metadata);
3248
3322
  const position = toPhysicalHorizontalItemPosition(state, logicalPosition, itemSize, peek$(ctx, "totalSize"));
3249
3323
  const column = columns[itemIndex] || 1;
3250
3324
  const span = columnSpans[itemIndex] || 1;
@@ -3977,6 +4051,9 @@ function setDidLayout(ctx) {
3977
4051
  state.queuedInitialLayout = true;
3978
4052
  checkAtBottom(ctx);
3979
4053
  setInitialRenderState(ctx, { didLayout: true });
4054
+ if (state.pendingMaintainScrollAtEnd) {
4055
+ doMaintainScrollAtEnd(ctx);
4056
+ }
3980
4057
  }
3981
4058
 
3982
4059
  // src/core/calculateItemsInView.ts
@@ -4213,7 +4290,7 @@ function calculateItemsInView(ctx, params = {}) {
4213
4290
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4214
4291
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4215
4292
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4216
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4293
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4217
4294
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4218
4295
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4219
4296
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4532,13 +4609,13 @@ function calculateItemsInView(ctx, params = {}) {
4532
4609
  changedContainerIds.add(containerIndex);
4533
4610
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4534
4611
  }
4535
- set$(ctx, `containerItemKey${containerIndex}`, id);
4536
- set$(ctx, `containerItemIndex${containerIndex}`, i);
4537
- set$(ctx, `containerItemData${containerIndex}`, data[i]);
4538
4612
  state.containerItemMetadata.set(
4539
4613
  containerIndex,
4540
4614
  createContainerItemMetadata(state, i, data[i], allocation.itemType)
4541
4615
  );
4616
+ set$(ctx, `containerItemKey${containerIndex}`, id);
4617
+ set$(ctx, `containerItemIndex${containerIndex}`, i);
4618
+ set$(ctx, `containerItemData${containerIndex}`, data[i]);
4542
4619
  containerItemKeys.set(id, containerIndex);
4543
4620
  (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4544
4621
  const containerSticky = `containerSticky${containerIndex}`;
@@ -4717,9 +4794,12 @@ function runOrScheduleMVCPRecalculate(ctx) {
4717
4794
  } else {
4718
4795
  if (!state.mvcpAnchorLock) {
4719
4796
  state.scheduledWork.cancel("mvcpRecalculate");
4720
- calculateItemsInView(ctx, { doMVCP: true });
4797
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4721
4798
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4722
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4799
+ state.scheduledWork.frame(
4800
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4801
+ "mvcpRecalculate"
4802
+ );
4723
4803
  }
4724
4804
  }
4725
4805
  }
@@ -5086,6 +5166,30 @@ function useContainerItemSignals(containerContext) {
5086
5166
  itemKey
5087
5167
  };
5088
5168
  }
5169
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5170
+ let cancelled = false;
5171
+ Promise.resolve().then(() => {
5172
+ var _a3;
5173
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5174
+ if (!cancelled && ctx.state) {
5175
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5176
+ warnDevOnce(
5177
+ `${hookName}-missing-viewability-config`,
5178
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5179
+ );
5180
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5181
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5182
+ warnDevOnce(
5183
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5184
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5185
+ );
5186
+ }
5187
+ }
5188
+ });
5189
+ return () => {
5190
+ cancelled = true;
5191
+ };
5192
+ }
5089
5193
  function useAdaptiveRender() {
5090
5194
  const [mode] = useArr$(["adaptiveRender"]);
5091
5195
  return mode;
@@ -5125,7 +5229,9 @@ function useViewability(callback, configId) {
5125
5229
  const { containerId } = containerContext;
5126
5230
  const key = containerId + (configId != null ? configId : "");
5127
5231
  ctx.mapViewabilityCallbacks.set(key, callback);
5232
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5128
5233
  return () => {
5234
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5129
5235
  ctx.mapViewabilityCallbacks.delete(key);
5130
5236
  };
5131
5237
  }, [ctx, callback, configId, containerContext]);
@@ -5149,7 +5255,9 @@ function useViewabilityAmount(callback) {
5149
5255
  }
5150
5256
  const { containerId } = containerContext;
5151
5257
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5258
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5152
5259
  return () => {
5260
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5153
5261
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5154
5262
  };
5155
5263
  }, [ctx, callback, containerContext]);
@@ -5570,7 +5678,7 @@ var Container = typedMemo(function Container2({
5570
5678
  ]
5571
5679
  );
5572
5680
  const renderedItemInfo = useMemo(
5573
- () => itemKey !== void 0 ? getRenderedItem2(itemKey) : null,
5681
+ () => itemKey !== void 0 ? getRenderedItem2(itemKey, id) : null,
5574
5682
  [itemKey, data, extraData]
5575
5683
  );
5576
5684
  const { renderedItem } = renderedItemInfo || {};
@@ -7022,11 +7130,29 @@ var ScheduledWork = class {
7022
7130
  constructor() {
7023
7131
  this.work = /* @__PURE__ */ new Map();
7024
7132
  }
7133
+ microtask(callback, key) {
7134
+ const pendingWork = this.work.get(key);
7135
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
7136
+ pendingWork.callback = callback;
7137
+ } else {
7138
+ this.cancel(key);
7139
+ const work = { callback, cancel: () => {
7140
+ } };
7141
+ this.work.set(key, work);
7142
+ queueMicrotask(() => {
7143
+ var _a3;
7144
+ if (this.work.get(key) === work) {
7145
+ this.work.delete(key);
7146
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
7147
+ }
7148
+ });
7149
+ }
7150
+ }
7025
7151
  timeout(callback, delay, key) {
7026
7152
  if (key) {
7027
7153
  this.cancel(key);
7028
7154
  }
7029
- const work = [void 0, clearTimeout];
7155
+ const work = { cancel: () => clearTimeout(handle) };
7030
7156
  const handle = setTimeout(() => {
7031
7157
  const workKey = key != null ? key : handle;
7032
7158
  if (this.work.get(workKey) === work) {
@@ -7034,14 +7160,13 @@ var ScheduledWork = class {
7034
7160
  callback();
7035
7161
  }
7036
7162
  }, delay);
7037
- work[0] = handle;
7038
7163
  this.work.set(key != null ? key : handle, work);
7039
7164
  }
7040
7165
  frame(callback, key) {
7041
7166
  this.cancel(key);
7042
- const work = [void 0, cancelAnimationFrame];
7167
+ const work = { cancel: () => cancelAnimationFrame(handle) };
7043
7168
  this.work.set(key, work);
7044
- work[0] = requestAnimationFrame(() => {
7169
+ const handle = requestAnimationFrame(() => {
7045
7170
  if (this.work.get(key) === work) {
7046
7171
  this.work.delete(key);
7047
7172
  callback();
@@ -7050,22 +7175,21 @@ var ScheduledWork = class {
7050
7175
  }
7051
7176
  register(key, cancel) {
7052
7177
  this.cancel(key);
7053
- this.work.set(key, [void 0, cancel]);
7178
+ this.work.set(key, { cancel });
7054
7179
  }
7055
7180
  cancel(key) {
7056
7181
  const work = this.work.get(key);
7057
7182
  if (work) {
7058
7183
  this.work.delete(key);
7059
- const [handle, cancel] = work;
7060
- cancel(handle);
7184
+ work.cancel();
7061
7185
  }
7062
7186
  }
7063
7187
  has(key) {
7064
7188
  return this.work.has(key);
7065
7189
  }
7066
7190
  dispose() {
7067
- for (const [handle, cancel] of this.work.values()) {
7068
- cancel(handle);
7191
+ for (const work of this.work.values()) {
7192
+ work.cancel();
7069
7193
  }
7070
7194
  this.work.clear();
7071
7195
  }
@@ -7122,6 +7246,25 @@ var ScrollAdjustHandler = class {
7122
7246
  }
7123
7247
  };
7124
7248
 
7249
+ // src/core/scrollToEnd.ts
7250
+ function scrollToEnd(ctx, options) {
7251
+ const state = ctx.state;
7252
+ const data = state.props.data;
7253
+ const index = data.length - 1;
7254
+ if (index === -1) {
7255
+ return false;
7256
+ }
7257
+ const paddingBottom = state.props.stylePaddingBottom || 0;
7258
+ const footerSize = peek$(ctx, "footerSize") || 0;
7259
+ scrollToIndex(ctx, {
7260
+ ...options,
7261
+ index,
7262
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7263
+ viewPosition: 1
7264
+ });
7265
+ return true;
7266
+ }
7267
+
7125
7268
  // src/core/updateContentInsetEndAdjustment.ts
7126
7269
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
7127
7270
  const state = ctx.state;
@@ -7184,25 +7327,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
7184
7327
  }
7185
7328
  }
7186
7329
 
7187
- // src/core/scrollToEnd.ts
7188
- function scrollToEnd(ctx, options) {
7189
- const state = ctx.state;
7190
- const data = state.props.data;
7191
- const index = data.length - 1;
7192
- if (index === -1) {
7193
- return false;
7194
- }
7195
- const paddingBottom = state.props.stylePaddingBottom || 0;
7196
- const footerSize = peek$(ctx, "footerSize") || 0;
7197
- scrollToIndex(ctx, {
7198
- ...options,
7199
- index,
7200
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7201
- viewPosition: 1
7202
- });
7203
- return true;
7204
- }
7205
-
7206
7330
  // src/utils/createImperativeHandle.ts
7207
7331
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
7208
7332
  function getAverageItemSizes(state) {
@@ -7225,9 +7349,9 @@ function triggerMountedContainerLayouts(ctx) {
7225
7349
  }
7226
7350
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7227
7351
  const state = ctx.state;
7352
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
7228
7353
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
7229
7354
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
7230
- let imperativeScrollToken = 0;
7231
7355
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
7232
7356
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
7233
7357
  var _a3;
@@ -7249,7 +7373,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7249
7373
  const startedAt = Date.now();
7250
7374
  let stableFrames = 0;
7251
7375
  const check = () => {
7252
- if (token !== imperativeScrollToken) {
7376
+ if (!scrollRequestTracker.isCurrent(token)) {
7253
7377
  return;
7254
7378
  }
7255
7379
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -7267,33 +7391,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7267
7391
  state.scheduledWork.frame(check, "imperativeScrollReady");
7268
7392
  };
7269
7393
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
7270
- const runNow = () => {
7271
- if (token !== imperativeScrollToken) {
7272
- return;
7273
- }
7274
- const didStartScroll = run();
7275
- if (!didStartScroll || !state.scrollingTo) {
7276
- if (state.pendingScrollResolve === resolve) {
7277
- state.pendingScrollResolve = void 0;
7278
- }
7279
- resolve();
7280
- }
7281
- };
7394
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
7282
7395
  if (isSettlingAfterDataChange() || !isReady()) {
7283
7396
  runWhenReady(token, runNow, isReady);
7284
7397
  } else {
7285
7398
  runNow();
7286
7399
  }
7287
7400
  };
7288
- const startImperativeScroll = (resolve) => {
7289
- state.scheduledWork.cancel("imperativeScrollReady");
7290
- const token = ++imperativeScrollToken;
7291
- settlePendingImperativeScroll(state);
7292
- state.pendingScrollResolve = resolve;
7293
- return token;
7294
- };
7295
7401
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
7296
- const token = startImperativeScroll(resolve);
7402
+ const token = scrollRequestTracker.start(resolve);
7297
7403
  supersedeInitialScroll(ctx);
7298
7404
  runScrollRequest(token, resolve, run, isReady);
7299
7405
  });
@@ -7301,7 +7407,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7301
7407
  const pendingScroll = state.pendingScrollToEnd;
7302
7408
  if (pendingScroll) {
7303
7409
  state.pendingScrollToEnd = void 0;
7304
- if (pendingScroll.token === imperativeScrollToken) {
7410
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
7305
7411
  runScrollRequest(
7306
7412
  pendingScroll.token,
7307
7413
  pendingScroll.resolve,
@@ -7415,7 +7521,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7415
7521
  }),
7416
7522
  scrollToEnd: (options) => new Promise((resolve) => {
7417
7523
  var _a3;
7418
- const token = startImperativeScroll(resolve);
7524
+ const token = scrollRequestTracker.start(resolve);
7419
7525
  state.pendingScrollToEnd = {
7420
7526
  options,
7421
7527
  resolve,
@@ -7517,34 +7623,37 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
7517
7623
  }
7518
7624
 
7519
7625
  // src/utils/getRenderedItem.ts
7520
- function getRenderedItem(ctx, key) {
7521
- var _a3;
7626
+ function getRenderedItem(ctx, key, containerId) {
7627
+ var _a3, _b, _c;
7522
7628
  const state = ctx.state;
7523
7629
  if (!state) {
7524
7630
  return null;
7525
7631
  }
7632
+ const metadata = state.containerItemMetadata.get(containerId);
7633
+ const useAssignedGeneration = metadata && metadata.dataChangeEpoch !== state.dataChangeEpoch;
7526
7634
  const {
7527
7635
  indexByKey,
7528
- props: { data, getItemType, renderItem }
7636
+ props: { data: currentData, getItemType, renderItem }
7529
7637
  } = state;
7530
- const index = indexByKey.get(key);
7638
+ const data = useAssignedGeneration ? metadata.data : currentData;
7639
+ const index = (_a3 = metadata == null ? void 0 : metadata.itemIndex) != null ? _a3 : indexByKey.get(key);
7531
7640
  if (index === void 0) {
7532
7641
  return null;
7533
7642
  }
7534
7643
  let renderedItem = null;
7535
7644
  const extraData = peek$(ctx, "extraData");
7536
- const item = data[index];
7645
+ const item = useAssignedGeneration ? metadata.itemData : data[index];
7537
7646
  if (renderItem && !isNullOrUndefined(item)) {
7538
7647
  const itemProps = {
7539
7648
  data,
7540
7649
  extraData,
7541
7650
  index,
7542
7651
  item,
7543
- type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
7652
+ type: useAssignedGeneration ? (_b = metadata.itemType) != null ? _b : "" : getItemType ? (_c = getItemType(item, index)) != null ? _c : "" : ""
7544
7653
  };
7545
7654
  renderedItem = renderItem(itemProps);
7546
7655
  }
7547
- return { index, item: data[index], renderedItem };
7656
+ return { index, item, renderedItem };
7548
7657
  }
7549
7658
 
7550
7659
  // src/utils/normalizeMaintainScrollAtEnd.ts
@@ -7795,6 +7904,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7795
7904
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7796
7905
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7797
7906
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7907
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7798
7908
  const initialScrollProp = initialScrollAtEnd ? {
7799
7909
  index: Math.max(0, dataProp.length - 1),
7800
7910
  preserveForBottomPadding: true,
@@ -7807,7 +7917,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7807
7917
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7808
7918
  } : {
7809
7919
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7810
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7920
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7921
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7922
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7811
7923
  } : initialScrollUsesOffsetOnly ? {
7812
7924
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7813
7925
  index: 0,
@@ -7913,6 +8025,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7913
8025
  viewabilityConfigCallbackPairs: void 0
7914
8026
  };
7915
8027
  const internalState = ctx.state;
8028
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7916
8029
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7917
8030
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7918
8031
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -8256,7 +8369,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8256
8369
  }, [ctx, usesBootstrapInitialScroll]);
8257
8370
  const fns = useMemo(
8258
8371
  () => ({
8259
- getRenderedItem: (key) => getRenderedItem(ctx, key),
8372
+ getRenderedItem: (key, containerId) => getRenderedItem(ctx, key, containerId),
8260
8373
  onMomentumScrollEnd: (event) => {
8261
8374
  checkFinishedScrollFallback(ctx);
8262
8375
  if (state.props.onMomentumScrollEnd) {
@@ -8266,6 +8379,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8266
8379
  onScroll: (event) => onScroll(ctx, event),
8267
8380
  onScrollBeginDrag: (event) => {
8268
8381
  var _a4, _b2;
8382
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8383
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8384
+ cancelImperativeScroll(state);
8385
+ }
8386
+ if (maintainingScrollAtEnd) {
8387
+ finishMaintainScrollAtEnd(ctx);
8388
+ }
8269
8389
  prepareReachedEdgeForNextUserScroll(ctx);
8270
8390
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8271
8391
  },