@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.
@@ -76,10 +76,6 @@ function getStylePaddingEnd(props) {
76
76
  }
77
77
  return (isHorizontalRTLProps(props) ? props.stylePaddingLeft : props.stylePaddingRight) || 0;
78
78
  }
79
- function getLogicalHorizontalMaxOffset(state, contentWidth) {
80
- var _a3;
81
- return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0;
82
- }
83
79
  function getHorizontalInsetEnd(state, inset) {
84
80
  if (!inset) {
85
81
  return 0;
@@ -710,6 +706,12 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
710
706
  } = state;
711
707
  const contentSize = getContentSize(ctx);
712
708
  resetSharedEdgeGateIfOutsideHysteresis(ctx);
709
+ if (state.props.data.length === 0 && scrollLength > 0 && contentSize <= scrollLength) {
710
+ set$(ctx, "isAtEnd", true);
711
+ set$(ctx, "isNearEnd", true);
712
+ set$(ctx, "isWithinMaintainScrollAtEndThreshold", true);
713
+ return;
714
+ }
713
715
  if (contentSize > 0 && queuedInitialLayout) {
714
716
  const insetEnd = getContentInsetEnd(ctx);
715
717
  const distanceFromEnd = contentSize - scroll - scrollLength - insetEnd;
@@ -719,7 +721,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
719
721
  set$(
720
722
  ctx,
721
723
  "isWithinMaintainScrollAtEndThreshold",
722
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength
724
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
723
725
  );
724
726
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
725
727
  if (!shouldSkipThresholdChecks) {
@@ -1064,10 +1066,22 @@ function getAlignItemsAtEndPadding(ctx) {
1064
1066
  return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1065
1067
  }
1066
1068
  function updateContentMetricsState(ctx) {
1069
+ var _a3;
1070
+ const { state } = ctx;
1067
1071
  const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1068
1072
  const nextPadding = getAlignItemsAtEndPadding(ctx);
1069
1073
  if (previousPadding !== nextPadding) {
1070
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1074
+ const isAnimatedMaintainActive = state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated";
1075
+ const isMaintainExpected = state.didContainersLayout && ((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.animated) && (isAnimatedMaintainActive || previousPadding > nextPadding && peek$(ctx, "isWithinMaintainScrollAtEndThreshold"));
1076
+ if (isMaintainExpected && !isAnimatedMaintainActive && !state.pendingMaintainScrollAtEnd) {
1077
+ state.scheduledWork.microtask(() => {
1078
+ if (!state.maintainingScrollAtEnd && !state.pendingMaintainScrollAtEnd) {
1079
+ set$(ctx, "alignItemsAtEndPadding", getAlignItemsAtEndPadding(ctx));
1080
+ }
1081
+ }, "alignItemsAtEndPaddingFallback");
1082
+ } else if (!isMaintainExpected) {
1083
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1084
+ }
1071
1085
  }
1072
1086
  }
1073
1087
 
@@ -1425,22 +1439,110 @@ function listenForScrollEnd(ctx, params) {
1425
1439
  scheduledWork.register("platformScrollCompletion", cancel);
1426
1440
  }
1427
1441
 
1442
+ // src/core/scrollRequestTracker.ts
1443
+ function getScrollRequestTracker(ctx) {
1444
+ if (!ctx.scrollRequestTracker) {
1445
+ let currentToken = 0;
1446
+ const start = (resolve) => {
1447
+ const state = ctx.state;
1448
+ state.scheduledWork.cancel("imperativeScrollReady");
1449
+ const token = ++currentToken;
1450
+ settlePendingImperativeScroll(state);
1451
+ state.pendingScrollResolve = resolve;
1452
+ return token;
1453
+ };
1454
+ const runNow = (token, resolve, run) => {
1455
+ const state = ctx.state;
1456
+ if (token !== currentToken) {
1457
+ return;
1458
+ }
1459
+ const didStartScroll = run();
1460
+ if (!didStartScroll || !state.scrollingTo) {
1461
+ if (state.pendingScrollResolve === resolve) {
1462
+ state.pendingScrollResolve = void 0;
1463
+ }
1464
+ resolve();
1465
+ }
1466
+ };
1467
+ ctx.scrollRequestTracker = {
1468
+ isCurrent: (token) => token === currentToken,
1469
+ runNow,
1470
+ runNowIfIdle: (run) => {
1471
+ const state = ctx.state;
1472
+ if (state.pendingScrollResolve || state.scrollingTo) {
1473
+ return Promise.resolve();
1474
+ }
1475
+ return new Promise((resolve) => {
1476
+ const token = start(resolve);
1477
+ runNow(token, resolve, run);
1478
+ });
1479
+ },
1480
+ start
1481
+ };
1482
+ }
1483
+ return ctx.scrollRequestTracker;
1484
+ }
1485
+
1486
+ // src/utils/requestAdjust.ts
1487
+ function requestAdjust(ctx, positionDiff, source) {
1488
+ const state = ctx.state;
1489
+ if (Math.abs(positionDiff) > 0.1) {
1490
+ const doit = () => {
1491
+ {
1492
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
1493
+ if (state.adjustingFromInitialMount) {
1494
+ state.adjustingFromInitialMount--;
1495
+ }
1496
+ }
1497
+ };
1498
+ state.scroll += positionDiff;
1499
+ state.scrollForNextCalculateItemsInView = void 0;
1500
+ const readyToRender = peek$(ctx, "readyToRender");
1501
+ if (readyToRender) {
1502
+ doit();
1503
+ } else {
1504
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1505
+ requestAnimationFrame(doit);
1506
+ }
1507
+ }
1508
+ }
1509
+
1428
1510
  // src/core/doMaintainScrollAtEnd.ts
1511
+ function finishMaintainScrollAtEnd(ctx) {
1512
+ var _a3;
1513
+ const { state } = ctx;
1514
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1515
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1516
+ state.maintainingScrollAtEnd = void 0;
1517
+ state.pendingMaintainScrollAtEnd = false;
1518
+ if (currentPadding !== nextPadding) {
1519
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1520
+ state.scrollForNextCalculateItemsInView = void 0;
1521
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1522
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1523
+ requestAdjust(ctx, nextScroll - state.scroll);
1524
+ }
1525
+ }
1429
1526
  function doMaintainScrollAtEnd(ctx) {
1430
1527
  const state = ctx.state;
1431
1528
  const {
1432
1529
  didContainersLayout,
1530
+ didFinishInitialScroll,
1433
1531
  pendingNativeMVCPAdjust,
1434
- refScroller,
1435
1532
  props: { maintainScrollAtEnd }
1436
1533
  } = state;
1437
1534
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1438
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
1535
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1536
+ const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1537
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1538
+ state.pendingMaintainScrollAtEnd = true;
1539
+ return false;
1540
+ }
1439
1541
  if (pendingNativeMVCPAdjust) {
1440
1542
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1441
1543
  return false;
1442
1544
  }
1443
- if (shouldMaintainScrollAtEnd) {
1545
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1444
1546
  state.pendingMaintainScrollAtEnd = false;
1445
1547
  const contentSize = getContentSize(ctx);
1446
1548
  if (contentSize < state.scrollLength) {
@@ -1452,39 +1554,29 @@ function doMaintainScrollAtEnd(ctx) {
1452
1554
  const scrollAtRequest = state.scroll;
1453
1555
  state.maintainingScrollAtEnd = pendingState;
1454
1556
  requestAnimationFrame(() => {
1557
+ if (state.maintainingScrollAtEnd !== pendingState) {
1558
+ return;
1559
+ }
1455
1560
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1456
1561
  const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1457
- if (isStillWithinThreshold || !didScrollSinceRequest) {
1562
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1458
1563
  state.maintainingScrollAtEnd = activeState;
1459
- const scroller = refScroller.current;
1460
- if (state.props.horizontal && isHorizontalRTL(state)) {
1461
- const currentContentSize = getContentSize(ctx);
1462
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
1463
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
1464
- scroller == null ? void 0 : scroller.scrollTo({
1465
- animated: maintainScrollAtEnd.animated,
1466
- x: nativeOffset,
1467
- y: 0
1468
- });
1469
- } else {
1470
- scroller == null ? void 0 : scroller.scrollToEnd({
1471
- animated: maintainScrollAtEnd.animated
1472
- });
1473
- }
1474
- setTimeout(
1475
- () => {
1476
- if (state.maintainingScrollAtEnd === activeState) {
1477
- state.maintainingScrollAtEnd = void 0;
1478
- if (state.pendingMaintainScrollAtEnd) {
1479
- doMaintainScrollAtEnd(ctx);
1480
- }
1481
- }
1482
- },
1483
- maintainScrollAtEnd.animated ? 500 : 0
1564
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1565
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1484
1566
  );
1567
+ void scrollPromise.then(() => {
1568
+ if (state.maintainingScrollAtEnd !== activeState) {
1569
+ return;
1570
+ }
1571
+ if (state.pendingMaintainScrollAtEnd) {
1572
+ state.maintainingScrollAtEnd = void 0;
1573
+ doMaintainScrollAtEnd(ctx);
1574
+ } else {
1575
+ finishMaintainScrollAtEnd(ctx);
1576
+ }
1577
+ });
1485
1578
  } else if (state.maintainingScrollAtEnd === pendingState) {
1486
- state.maintainingScrollAtEnd = void 0;
1487
- state.pendingMaintainScrollAtEnd = false;
1579
+ finishMaintainScrollAtEnd(ctx);
1488
1580
  }
1489
1581
  });
1490
1582
  } else {
@@ -1492,34 +1584,10 @@ function doMaintainScrollAtEnd(ctx) {
1492
1584
  }
1493
1585
  return true;
1494
1586
  }
1495
- state.pendingMaintainScrollAtEnd = false;
1587
+ finishMaintainScrollAtEnd(ctx);
1496
1588
  return false;
1497
1589
  }
1498
1590
 
1499
- // src/utils/requestAdjust.ts
1500
- function requestAdjust(ctx, positionDiff, dataChanged) {
1501
- const state = ctx.state;
1502
- if (Math.abs(positionDiff) > 0.1) {
1503
- const doit = () => {
1504
- {
1505
- state.scrollAdjustHandler.requestAdjust(positionDiff);
1506
- if (state.adjustingFromInitialMount) {
1507
- state.adjustingFromInitialMount--;
1508
- }
1509
- }
1510
- };
1511
- state.scroll += positionDiff;
1512
- state.scrollForNextCalculateItemsInView = void 0;
1513
- const readyToRender = peek$(ctx, "readyToRender");
1514
- if (readyToRender) {
1515
- doit();
1516
- } else {
1517
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1518
- requestAnimationFrame(doit);
1519
- }
1520
- }
1521
- }
1522
-
1523
1591
  // src/core/mvcp.ts
1524
1592
  var MVCP_POSITION_EPSILON = 0.1;
1525
1593
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -1656,7 +1724,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1656
1724
  }
1657
1725
  return false;
1658
1726
  }
1659
- function prepareMVCP(ctx, dataChanged) {
1727
+ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1660
1728
  const state = ctx.state;
1661
1729
  const { idsInView, positions, props } = state;
1662
1730
  const {
@@ -1908,6 +1976,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1908
1976
  state.scrollTime = currentTime;
1909
1977
  const scrollDelta = Math.abs(newScroll - prevScroll);
1910
1978
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1979
+ const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1980
+ if (didCancelMaintainScrollAtEnd) {
1981
+ finishMaintainScrollAtEnd(ctx);
1982
+ }
1911
1983
  const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
1912
1984
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1913
1985
  const scrollLength = state.scrollLength;
@@ -1916,7 +1988,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1916
1988
  updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
1917
1989
  const lastCalculated = state.scrollLastCalculate;
1918
1990
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1919
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1991
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1920
1992
  if (shouldUpdate) {
1921
1993
  state.scrollLastCalculate = state.scroll;
1922
1994
  state.ignoreScrollFromMVCPIgnored = false;
@@ -2745,7 +2817,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2745
2817
  }
2746
2818
  }
2747
2819
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2748
- var _a3, _b, _c;
2820
+ var _a3, _b;
2749
2821
  const state = ctx.state;
2750
2822
  const initialScroll = state.initialScroll;
2751
2823
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2759,19 +2831,15 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2759
2831
  if (state.didFinishInitialScroll) {
2760
2832
  schedulePreservedEndAnchorCorrection(ctx);
2761
2833
  } else if (scrollingTo) {
2762
- const existingWatchdog = initialScrollWatchdog.get(state);
2763
- scrollingTo.offset = resolvedOffset;
2764
- scrollingTo.targetOffset = resolvedOffset;
2765
2834
  state.initialScroll = {
2766
2835
  ...initialScroll,
2767
2836
  contentOffset: resolvedOffset
2768
2837
  };
2769
- state.hasScrolled = false;
2770
- initialScrollWatchdog.set(state, {
2771
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2772
- targetOffset: resolvedOffset
2838
+ dispatchInitialScroll(ctx, {
2839
+ forceScroll: true,
2840
+ resolvedOffset,
2841
+ target: state.initialScroll
2773
2842
  });
2774
- requestAdjust(ctx, offsetDiff);
2775
2843
  }
2776
2844
  }
2777
2845
  } else {
@@ -2884,6 +2952,7 @@ function abortBootstrapInitialScroll(ctx) {
2884
2952
  // src/core/containerItemMetadata.ts
2885
2953
  function createContainerItemMetadata(state, itemIndex, itemData, itemType) {
2886
2954
  return {
2955
+ data: state.props.data,
2887
2956
  dataChangeEpoch: state.dataChangeEpoch,
2888
2957
  getFixedItemSize: state.props.getFixedItemSize,
2889
2958
  getItemType: state.props.getItemType,
@@ -2892,25 +2961,29 @@ function createContainerItemMetadata(state, itemIndex, itemData, itemType) {
2892
2961
  itemType
2893
2962
  };
2894
2963
  }
2895
- function resolveContainerItemMetadata(state, containerId, itemIndex, itemData) {
2896
- var _a3, _b;
2897
- const { getFixedItemSize, getItemType } = state.props;
2964
+ function updateContainerItemMetadata(state, containerId, itemIndex, itemData, itemType) {
2965
+ var _a3;
2966
+ const { getItemType } = state.props;
2898
2967
  const previousMetadata = state.containerItemMetadata.get(containerId);
2899
- let metadata;
2900
2968
  if ((previousMetadata == null ? void 0 : previousMetadata.dataChangeEpoch) === state.dataChangeEpoch && previousMetadata.getItemType === getItemType && previousMetadata.itemData === itemData && previousMetadata.itemIndex === itemIndex) {
2901
- metadata = previousMetadata;
2902
- } else {
2903
- const itemType = getItemType ? (_a3 = getItemType(itemData, itemIndex)) != null ? _a3 : "" : void 0;
2904
- metadata = createContainerItemMetadata(state, itemIndex, itemData, itemType);
2905
- state.containerItemMetadata.set(containerId, metadata);
2969
+ return previousMetadata;
2906
2970
  }
2971
+ const resolvedItemType = getItemType ? (_a3 = getItemType(itemData, itemIndex)) != null ? _a3 : "" : void 0;
2972
+ const metadata = createContainerItemMetadata(state, itemIndex, itemData, resolvedItemType);
2973
+ state.containerItemMetadata.set(containerId, metadata);
2974
+ return metadata;
2975
+ }
2976
+ function resolveContainerItemMetadata(state, containerId, itemIndex, itemData) {
2977
+ var _a3;
2978
+ const { getFixedItemSize } = state.props;
2979
+ const metadata = updateContainerItemMetadata(state, containerId, itemIndex, itemData);
2907
2980
  if (metadata.getFixedItemSize !== getFixedItemSize) {
2908
2981
  metadata.didResolveFixedItemSize = false;
2909
2982
  metadata.fixedItemSize = void 0;
2910
2983
  metadata.getFixedItemSize = getFixedItemSize;
2911
2984
  }
2912
2985
  if (getFixedItemSize && !metadata.didResolveFixedItemSize) {
2913
- metadata.fixedItemSize = getFixedItemSize(itemData, itemIndex, (_b = metadata.itemType) != null ? _b : "");
2986
+ metadata.fixedItemSize = getFixedItemSize(itemData, itemIndex, (_a3 = metadata.itemType) != null ? _a3 : "");
2914
2987
  metadata.didResolveFixedItemSize = true;
2915
2988
  }
2916
2989
  return metadata;
@@ -3255,6 +3328,7 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3255
3328
  return { didChangePosition: false, didRefreshData: false };
3256
3329
  }
3257
3330
  const itemKey = (_a3 = state.idCache[itemIndex]) != null ? _a3 : getId(state, itemIndex);
3331
+ const metadata = updateContainerItemMetadata(state, containerIndex, itemIndex, item);
3258
3332
  const updateLayout = (_b = options == null ? void 0 : options.updateLayout) != null ? _b : true;
3259
3333
  let didChangePosition = false;
3260
3334
  let didRefreshData = false;
@@ -3265,7 +3339,7 @@ function syncMountedContainer(ctx, containerIndex, itemIndex, options) {
3265
3339
  return { didChangePosition: false, didRefreshData: false };
3266
3340
  }
3267
3341
  const logicalPosition = (positionValue || 0) - ((_c = options == null ? void 0 : options.scrollAdjustPending) != null ? _c : 0);
3268
- const itemSize = (_d = state.sizes.get(itemKey)) != null ? _d : getItemSize(ctx, itemKey, itemIndex, item);
3342
+ const itemSize = (_d = state.sizes.get(itemKey)) != null ? _d : getItemSize(ctx, itemKey, itemIndex, item, void 0, void 0, void 0, metadata);
3269
3343
  const position = toPhysicalHorizontalItemPosition(state, logicalPosition, itemSize, peek$(ctx, "totalSize"));
3270
3344
  const column = columns[itemIndex] || 1;
3271
3345
  const span = columnSpans[itemIndex] || 1;
@@ -3998,6 +4072,9 @@ function setDidLayout(ctx) {
3998
4072
  state.queuedInitialLayout = true;
3999
4073
  checkAtBottom(ctx);
4000
4074
  setInitialRenderState(ctx, { didLayout: true });
4075
+ if (state.pendingMaintainScrollAtEnd) {
4076
+ doMaintainScrollAtEnd(ctx);
4077
+ }
4001
4078
  }
4002
4079
 
4003
4080
  // src/core/calculateItemsInView.ts
@@ -4234,7 +4311,7 @@ function calculateItemsInView(ctx, params = {}) {
4234
4311
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4235
4312
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4236
4313
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4237
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4314
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4238
4315
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4239
4316
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4240
4317
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4553,13 +4630,13 @@ function calculateItemsInView(ctx, params = {}) {
4553
4630
  changedContainerIds.add(containerIndex);
4554
4631
  state.containerItemGenerations[containerIndex] = ((_p = state.containerItemGenerations[containerIndex]) != null ? _p : 0) + 1;
4555
4632
  }
4556
- set$(ctx, `containerItemKey${containerIndex}`, id);
4557
- set$(ctx, `containerItemIndex${containerIndex}`, i);
4558
- set$(ctx, `containerItemData${containerIndex}`, data[i]);
4559
4633
  state.containerItemMetadata.set(
4560
4634
  containerIndex,
4561
4635
  createContainerItemMetadata(state, i, data[i], allocation.itemType)
4562
4636
  );
4637
+ set$(ctx, `containerItemKey${containerIndex}`, id);
4638
+ set$(ctx, `containerItemIndex${containerIndex}`, i);
4639
+ set$(ctx, `containerItemData${containerIndex}`, data[i]);
4563
4640
  containerItemKeys.set(id, containerIndex);
4564
4641
  (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4565
4642
  const containerSticky = `containerSticky${containerIndex}`;
@@ -4738,9 +4815,12 @@ function runOrScheduleMVCPRecalculate(ctx) {
4738
4815
  } else {
4739
4816
  if (!state.mvcpAnchorLock) {
4740
4817
  state.scheduledWork.cancel("mvcpRecalculate");
4741
- calculateItemsInView(ctx, { doMVCP: true });
4818
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4742
4819
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4743
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4820
+ state.scheduledWork.frame(
4821
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4822
+ "mvcpRecalculate"
4823
+ );
4744
4824
  }
4745
4825
  }
4746
4826
  }
@@ -5107,6 +5187,30 @@ function useContainerItemSignals(containerContext) {
5107
5187
  itemKey
5108
5188
  };
5109
5189
  }
5190
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5191
+ let cancelled = false;
5192
+ Promise.resolve().then(() => {
5193
+ var _a3;
5194
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5195
+ if (!cancelled && ctx.state) {
5196
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5197
+ warnDevOnce(
5198
+ `${hookName}-missing-viewability-config`,
5199
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5200
+ );
5201
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5202
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5203
+ warnDevOnce(
5204
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5205
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5206
+ );
5207
+ }
5208
+ }
5209
+ });
5210
+ return () => {
5211
+ cancelled = true;
5212
+ };
5213
+ }
5110
5214
  function useAdaptiveRender() {
5111
5215
  const [mode] = useArr$(["adaptiveRender"]);
5112
5216
  return mode;
@@ -5146,7 +5250,9 @@ function useViewability(callback, configId) {
5146
5250
  const { containerId } = containerContext;
5147
5251
  const key = containerId + (configId != null ? configId : "");
5148
5252
  ctx.mapViewabilityCallbacks.set(key, callback);
5253
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5149
5254
  return () => {
5255
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5150
5256
  ctx.mapViewabilityCallbacks.delete(key);
5151
5257
  };
5152
5258
  }, [ctx, callback, configId, containerContext]);
@@ -5170,7 +5276,9 @@ function useViewabilityAmount(callback) {
5170
5276
  }
5171
5277
  const { containerId } = containerContext;
5172
5278
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5279
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5173
5280
  return () => {
5281
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5174
5282
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5175
5283
  };
5176
5284
  }, [ctx, callback, containerContext]);
@@ -5591,7 +5699,7 @@ var Container = typedMemo(function Container2({
5591
5699
  ]
5592
5700
  );
5593
5701
  const renderedItemInfo = React3.useMemo(
5594
- () => itemKey !== void 0 ? getRenderedItem2(itemKey) : null,
5702
+ () => itemKey !== void 0 ? getRenderedItem2(itemKey, id) : null,
5595
5703
  [itemKey, data, extraData]
5596
5704
  );
5597
5705
  const { renderedItem } = renderedItemInfo || {};
@@ -7043,11 +7151,29 @@ var ScheduledWork = class {
7043
7151
  constructor() {
7044
7152
  this.work = /* @__PURE__ */ new Map();
7045
7153
  }
7154
+ microtask(callback, key) {
7155
+ const pendingWork = this.work.get(key);
7156
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
7157
+ pendingWork.callback = callback;
7158
+ } else {
7159
+ this.cancel(key);
7160
+ const work = { callback, cancel: () => {
7161
+ } };
7162
+ this.work.set(key, work);
7163
+ queueMicrotask(() => {
7164
+ var _a3;
7165
+ if (this.work.get(key) === work) {
7166
+ this.work.delete(key);
7167
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
7168
+ }
7169
+ });
7170
+ }
7171
+ }
7046
7172
  timeout(callback, delay, key) {
7047
7173
  if (key) {
7048
7174
  this.cancel(key);
7049
7175
  }
7050
- const work = [void 0, clearTimeout];
7176
+ const work = { cancel: () => clearTimeout(handle) };
7051
7177
  const handle = setTimeout(() => {
7052
7178
  const workKey = key != null ? key : handle;
7053
7179
  if (this.work.get(workKey) === work) {
@@ -7055,14 +7181,13 @@ var ScheduledWork = class {
7055
7181
  callback();
7056
7182
  }
7057
7183
  }, delay);
7058
- work[0] = handle;
7059
7184
  this.work.set(key != null ? key : handle, work);
7060
7185
  }
7061
7186
  frame(callback, key) {
7062
7187
  this.cancel(key);
7063
- const work = [void 0, cancelAnimationFrame];
7188
+ const work = { cancel: () => cancelAnimationFrame(handle) };
7064
7189
  this.work.set(key, work);
7065
- work[0] = requestAnimationFrame(() => {
7190
+ const handle = requestAnimationFrame(() => {
7066
7191
  if (this.work.get(key) === work) {
7067
7192
  this.work.delete(key);
7068
7193
  callback();
@@ -7071,22 +7196,21 @@ var ScheduledWork = class {
7071
7196
  }
7072
7197
  register(key, cancel) {
7073
7198
  this.cancel(key);
7074
- this.work.set(key, [void 0, cancel]);
7199
+ this.work.set(key, { cancel });
7075
7200
  }
7076
7201
  cancel(key) {
7077
7202
  const work = this.work.get(key);
7078
7203
  if (work) {
7079
7204
  this.work.delete(key);
7080
- const [handle, cancel] = work;
7081
- cancel(handle);
7205
+ work.cancel();
7082
7206
  }
7083
7207
  }
7084
7208
  has(key) {
7085
7209
  return this.work.has(key);
7086
7210
  }
7087
7211
  dispose() {
7088
- for (const [handle, cancel] of this.work.values()) {
7089
- cancel(handle);
7212
+ for (const work of this.work.values()) {
7213
+ work.cancel();
7090
7214
  }
7091
7215
  this.work.clear();
7092
7216
  }
@@ -7143,6 +7267,25 @@ var ScrollAdjustHandler = class {
7143
7267
  }
7144
7268
  };
7145
7269
 
7270
+ // src/core/scrollToEnd.ts
7271
+ function scrollToEnd(ctx, options) {
7272
+ const state = ctx.state;
7273
+ const data = state.props.data;
7274
+ const index = data.length - 1;
7275
+ if (index === -1) {
7276
+ return false;
7277
+ }
7278
+ const paddingBottom = state.props.stylePaddingBottom || 0;
7279
+ const footerSize = peek$(ctx, "footerSize") || 0;
7280
+ scrollToIndex(ctx, {
7281
+ ...options,
7282
+ index,
7283
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7284
+ viewPosition: 1
7285
+ });
7286
+ return true;
7287
+ }
7288
+
7146
7289
  // src/core/updateContentInsetEndAdjustment.ts
7147
7290
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
7148
7291
  const state = ctx.state;
@@ -7205,25 +7348,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
7205
7348
  }
7206
7349
  }
7207
7350
 
7208
- // src/core/scrollToEnd.ts
7209
- function scrollToEnd(ctx, options) {
7210
- const state = ctx.state;
7211
- const data = state.props.data;
7212
- const index = data.length - 1;
7213
- if (index === -1) {
7214
- return false;
7215
- }
7216
- const paddingBottom = state.props.stylePaddingBottom || 0;
7217
- const footerSize = peek$(ctx, "footerSize") || 0;
7218
- scrollToIndex(ctx, {
7219
- ...options,
7220
- index,
7221
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7222
- viewPosition: 1
7223
- });
7224
- return true;
7225
- }
7226
-
7227
7351
  // src/utils/createImperativeHandle.ts
7228
7352
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
7229
7353
  function getAverageItemSizes(state) {
@@ -7246,9 +7370,9 @@ function triggerMountedContainerLayouts(ctx) {
7246
7370
  }
7247
7371
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7248
7372
  const state = ctx.state;
7373
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
7249
7374
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
7250
7375
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
7251
- let imperativeScrollToken = 0;
7252
7376
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
7253
7377
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
7254
7378
  var _a3;
@@ -7270,7 +7394,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7270
7394
  const startedAt = Date.now();
7271
7395
  let stableFrames = 0;
7272
7396
  const check = () => {
7273
- if (token !== imperativeScrollToken) {
7397
+ if (!scrollRequestTracker.isCurrent(token)) {
7274
7398
  return;
7275
7399
  }
7276
7400
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -7288,33 +7412,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7288
7412
  state.scheduledWork.frame(check, "imperativeScrollReady");
7289
7413
  };
7290
7414
  const runScrollRequest = (token, resolve, run, isReady = () => true) => {
7291
- const runNow = () => {
7292
- if (token !== imperativeScrollToken) {
7293
- return;
7294
- }
7295
- const didStartScroll = run();
7296
- if (!didStartScroll || !state.scrollingTo) {
7297
- if (state.pendingScrollResolve === resolve) {
7298
- state.pendingScrollResolve = void 0;
7299
- }
7300
- resolve();
7301
- }
7302
- };
7415
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
7303
7416
  if (isSettlingAfterDataChange() || !isReady()) {
7304
7417
  runWhenReady(token, runNow, isReady);
7305
7418
  } else {
7306
7419
  runNow();
7307
7420
  }
7308
7421
  };
7309
- const startImperativeScroll = (resolve) => {
7310
- state.scheduledWork.cancel("imperativeScrollReady");
7311
- const token = ++imperativeScrollToken;
7312
- settlePendingImperativeScroll(state);
7313
- state.pendingScrollResolve = resolve;
7314
- return token;
7315
- };
7316
7422
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
7317
- const token = startImperativeScroll(resolve);
7423
+ const token = scrollRequestTracker.start(resolve);
7318
7424
  supersedeInitialScroll(ctx);
7319
7425
  runScrollRequest(token, resolve, run, isReady);
7320
7426
  });
@@ -7322,7 +7428,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7322
7428
  const pendingScroll = state.pendingScrollToEnd;
7323
7429
  if (pendingScroll) {
7324
7430
  state.pendingScrollToEnd = void 0;
7325
- if (pendingScroll.token === imperativeScrollToken) {
7431
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
7326
7432
  runScrollRequest(
7327
7433
  pendingScroll.token,
7328
7434
  pendingScroll.resolve,
@@ -7436,7 +7542,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7436
7542
  }),
7437
7543
  scrollToEnd: (options) => new Promise((resolve) => {
7438
7544
  var _a3;
7439
- const token = startImperativeScroll(resolve);
7545
+ const token = scrollRequestTracker.start(resolve);
7440
7546
  state.pendingScrollToEnd = {
7441
7547
  options,
7442
7548
  resolve,
@@ -7538,34 +7644,37 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
7538
7644
  }
7539
7645
 
7540
7646
  // src/utils/getRenderedItem.ts
7541
- function getRenderedItem(ctx, key) {
7542
- var _a3;
7647
+ function getRenderedItem(ctx, key, containerId) {
7648
+ var _a3, _b, _c;
7543
7649
  const state = ctx.state;
7544
7650
  if (!state) {
7545
7651
  return null;
7546
7652
  }
7653
+ const metadata = state.containerItemMetadata.get(containerId);
7654
+ const useAssignedGeneration = metadata && metadata.dataChangeEpoch !== state.dataChangeEpoch;
7547
7655
  const {
7548
7656
  indexByKey,
7549
- props: { data, getItemType, renderItem }
7657
+ props: { data: currentData, getItemType, renderItem }
7550
7658
  } = state;
7551
- const index = indexByKey.get(key);
7659
+ const data = useAssignedGeneration ? metadata.data : currentData;
7660
+ const index = (_a3 = metadata == null ? void 0 : metadata.itemIndex) != null ? _a3 : indexByKey.get(key);
7552
7661
  if (index === void 0) {
7553
7662
  return null;
7554
7663
  }
7555
7664
  let renderedItem = null;
7556
7665
  const extraData = peek$(ctx, "extraData");
7557
- const item = data[index];
7666
+ const item = useAssignedGeneration ? metadata.itemData : data[index];
7558
7667
  if (renderItem && !isNullOrUndefined(item)) {
7559
7668
  const itemProps = {
7560
7669
  data,
7561
7670
  extraData,
7562
7671
  index,
7563
7672
  item,
7564
- type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
7673
+ type: useAssignedGeneration ? (_b = metadata.itemType) != null ? _b : "" : getItemType ? (_c = getItemType(item, index)) != null ? _c : "" : ""
7565
7674
  };
7566
7675
  renderedItem = renderItem(itemProps);
7567
7676
  }
7568
- return { index, item: data[index], renderedItem };
7677
+ return { index, item, renderedItem };
7569
7678
  }
7570
7679
 
7571
7680
  // src/utils/normalizeMaintainScrollAtEnd.ts
@@ -7816,6 +7925,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7816
7925
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7817
7926
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7818
7927
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7928
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7819
7929
  const initialScrollProp = initialScrollAtEnd ? {
7820
7930
  index: Math.max(0, dataProp.length - 1),
7821
7931
  preserveForBottomPadding: true,
@@ -7828,7 +7938,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7828
7938
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7829
7939
  } : {
7830
7940
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7831
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7941
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7942
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7943
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7832
7944
  } : initialScrollUsesOffsetOnly ? {
7833
7945
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7834
7946
  index: 0,
@@ -7934,6 +8046,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7934
8046
  viewabilityConfigCallbackPairs: void 0
7935
8047
  };
7936
8048
  const internalState = ctx.state;
8049
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7937
8050
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7938
8051
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7939
8052
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -8277,7 +8390,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8277
8390
  }, [ctx, usesBootstrapInitialScroll]);
8278
8391
  const fns = React3.useMemo(
8279
8392
  () => ({
8280
- getRenderedItem: (key) => getRenderedItem(ctx, key),
8393
+ getRenderedItem: (key, containerId) => getRenderedItem(ctx, key, containerId),
8281
8394
  onMomentumScrollEnd: (event) => {
8282
8395
  checkFinishedScrollFallback(ctx);
8283
8396
  if (state.props.onMomentumScrollEnd) {
@@ -8287,6 +8400,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8287
8400
  onScroll: (event) => onScroll(ctx, event),
8288
8401
  onScrollBeginDrag: (event) => {
8289
8402
  var _a4, _b2;
8403
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8404
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8405
+ cancelImperativeScroll(state);
8406
+ }
8407
+ if (maintainingScrollAtEnd) {
8408
+ finishMaintainScrollAtEnd(ctx);
8409
+ }
8290
8410
  prepareReachedEdgeForNextUserScroll(ctx);
8291
8411
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8292
8412
  },