@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.js CHANGED
@@ -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 {
@@ -3998,6 +4066,9 @@ function setDidLayout(ctx) {
3998
4066
  state.queuedInitialLayout = true;
3999
4067
  checkAtBottom(ctx);
4000
4068
  setInitialRenderState(ctx, { didLayout: true });
4069
+ if (state.pendingMaintainScrollAtEnd) {
4070
+ doMaintainScrollAtEnd(ctx);
4071
+ }
4001
4072
  }
4002
4073
 
4003
4074
  // src/core/calculateItemsInView.ts
@@ -4234,7 +4305,7 @@ function calculateItemsInView(ctx, params = {}) {
4234
4305
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4235
4306
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4236
4307
  const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4237
- const { dataChanged, doMVCP, forceFullItemPositions } = params;
4308
+ const { dataChanged, doMVCP, forceFullItemPositions, mvcpAdjustmentSource } = params;
4238
4309
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4239
4310
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
4240
4311
  const prevNumContainers = peek$(ctx, "numContainers");
@@ -4738,9 +4809,12 @@ function runOrScheduleMVCPRecalculate(ctx) {
4738
4809
  } else {
4739
4810
  if (!state.mvcpAnchorLock) {
4740
4811
  state.scheduledWork.cancel("mvcpRecalculate");
4741
- calculateItemsInView(ctx, { doMVCP: true });
4812
+ calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" });
4742
4813
  } else if (!state.scheduledWork.has("mvcpRecalculate")) {
4743
- state.scheduledWork.frame(() => calculateItemsInView(ctx, { doMVCP: true }), "mvcpRecalculate");
4814
+ state.scheduledWork.frame(
4815
+ () => calculateItemsInView(ctx, { doMVCP: true, mvcpAdjustmentSource: "item-size" }),
4816
+ "mvcpRecalculate"
4817
+ );
4744
4818
  }
4745
4819
  }
4746
4820
  }
@@ -5107,6 +5181,30 @@ function useContainerItemSignals(containerContext) {
5107
5181
  itemKey
5108
5182
  };
5109
5183
  }
5184
+ function scheduleViewabilityConfigWarning(ctx, hookName, configId) {
5185
+ let cancelled = false;
5186
+ Promise.resolve().then(() => {
5187
+ var _a3;
5188
+ const viewabilityConfigCallbackPairs = (_a3 = ctx.state) == null ? void 0 : _a3.viewabilityConfigCallbackPairs;
5189
+ if (!cancelled && ctx.state) {
5190
+ if (!(viewabilityConfigCallbackPairs == null ? void 0 : viewabilityConfigCallbackPairs.length)) {
5191
+ warnDevOnce(
5192
+ `${hookName}-missing-viewability-config`,
5193
+ `${hookName} requires viewability to be configured on the owning LegendList. Add viewabilityConfig with a visibility threshold.`
5194
+ );
5195
+ } else if (hookName === "useViewability" && !viewabilityConfigCallbackPairs.some((pair) => pair.viewabilityConfig.id === (configId != null ? configId : ""))) {
5196
+ const configDescription = configId ? ` for configId "${configId}"` : "";
5197
+ warnDevOnce(
5198
+ `${hookName}-missing-viewability-config-${configId != null ? configId : "default"}`,
5199
+ `${hookName}${configDescription} requires a matching viewabilityConfig.id on the owning LegendList. Add an id to viewabilityConfig and pass the same id to useViewability.`
5200
+ );
5201
+ }
5202
+ }
5203
+ });
5204
+ return () => {
5205
+ cancelled = true;
5206
+ };
5207
+ }
5110
5208
  function useAdaptiveRender() {
5111
5209
  const [mode] = useArr$(["adaptiveRender"]);
5112
5210
  return mode;
@@ -5146,7 +5244,9 @@ function useViewability(callback, configId) {
5146
5244
  const { containerId } = containerContext;
5147
5245
  const key = containerId + (configId != null ? configId : "");
5148
5246
  ctx.mapViewabilityCallbacks.set(key, callback);
5247
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewability", configId) : void 0;
5149
5248
  return () => {
5249
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5150
5250
  ctx.mapViewabilityCallbacks.delete(key);
5151
5251
  };
5152
5252
  }, [ctx, callback, configId, containerContext]);
@@ -5170,7 +5270,9 @@ function useViewabilityAmount(callback) {
5170
5270
  }
5171
5271
  const { containerId } = containerContext;
5172
5272
  ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
5273
+ const cancelConfigWarning = IS_DEV ? scheduleViewabilityConfigWarning(ctx, "useViewabilityAmount") : void 0;
5173
5274
  return () => {
5275
+ cancelConfigWarning == null ? void 0 : cancelConfigWarning();
5174
5276
  ctx.mapViewabilityAmountCallbacks.delete(containerId);
5175
5277
  };
5176
5278
  }, [ctx, callback, containerContext]);
@@ -7043,11 +7145,29 @@ var ScheduledWork = class {
7043
7145
  constructor() {
7044
7146
  this.work = /* @__PURE__ */ new Map();
7045
7147
  }
7148
+ microtask(callback, key) {
7149
+ const pendingWork = this.work.get(key);
7150
+ if (pendingWork == null ? void 0 : pendingWork.callback) {
7151
+ pendingWork.callback = callback;
7152
+ } else {
7153
+ this.cancel(key);
7154
+ const work = { callback, cancel: () => {
7155
+ } };
7156
+ this.work.set(key, work);
7157
+ queueMicrotask(() => {
7158
+ var _a3;
7159
+ if (this.work.get(key) === work) {
7160
+ this.work.delete(key);
7161
+ (_a3 = work.callback) == null ? void 0 : _a3.call(work);
7162
+ }
7163
+ });
7164
+ }
7165
+ }
7046
7166
  timeout(callback, delay, key) {
7047
7167
  if (key) {
7048
7168
  this.cancel(key);
7049
7169
  }
7050
- const work = [void 0, clearTimeout];
7170
+ const work = { cancel: () => clearTimeout(handle) };
7051
7171
  const handle = setTimeout(() => {
7052
7172
  const workKey = key != null ? key : handle;
7053
7173
  if (this.work.get(workKey) === work) {
@@ -7055,14 +7175,13 @@ var ScheduledWork = class {
7055
7175
  callback();
7056
7176
  }
7057
7177
  }, delay);
7058
- work[0] = handle;
7059
7178
  this.work.set(key != null ? key : handle, work);
7060
7179
  }
7061
7180
  frame(callback, key) {
7062
7181
  this.cancel(key);
7063
- const work = [void 0, cancelAnimationFrame];
7182
+ const work = { cancel: () => cancelAnimationFrame(handle) };
7064
7183
  this.work.set(key, work);
7065
- work[0] = requestAnimationFrame(() => {
7184
+ const handle = requestAnimationFrame(() => {
7066
7185
  if (this.work.get(key) === work) {
7067
7186
  this.work.delete(key);
7068
7187
  callback();
@@ -7071,22 +7190,21 @@ var ScheduledWork = class {
7071
7190
  }
7072
7191
  register(key, cancel) {
7073
7192
  this.cancel(key);
7074
- this.work.set(key, [void 0, cancel]);
7193
+ this.work.set(key, { cancel });
7075
7194
  }
7076
7195
  cancel(key) {
7077
7196
  const work = this.work.get(key);
7078
7197
  if (work) {
7079
7198
  this.work.delete(key);
7080
- const [handle, cancel] = work;
7081
- cancel(handle);
7199
+ work.cancel();
7082
7200
  }
7083
7201
  }
7084
7202
  has(key) {
7085
7203
  return this.work.has(key);
7086
7204
  }
7087
7205
  dispose() {
7088
- for (const [handle, cancel] of this.work.values()) {
7089
- cancel(handle);
7206
+ for (const work of this.work.values()) {
7207
+ work.cancel();
7090
7208
  }
7091
7209
  this.work.clear();
7092
7210
  }
@@ -7143,6 +7261,25 @@ var ScrollAdjustHandler = class {
7143
7261
  }
7144
7262
  };
7145
7263
 
7264
+ // src/core/scrollToEnd.ts
7265
+ function scrollToEnd(ctx, options) {
7266
+ const state = ctx.state;
7267
+ const data = state.props.data;
7268
+ const index = data.length - 1;
7269
+ if (index === -1) {
7270
+ return false;
7271
+ }
7272
+ const paddingBottom = state.props.stylePaddingBottom || 0;
7273
+ const footerSize = peek$(ctx, "footerSize") || 0;
7274
+ scrollToIndex(ctx, {
7275
+ ...options,
7276
+ index,
7277
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7278
+ viewPosition: 1
7279
+ });
7280
+ return true;
7281
+ }
7282
+
7146
7283
  // src/core/updateContentInsetEndAdjustment.ts
7147
7284
  function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment) {
7148
7285
  const state = ctx.state;
@@ -7205,25 +7342,6 @@ function createColumnWrapperStyle(contentContainerStyle) {
7205
7342
  }
7206
7343
  }
7207
7344
 
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
7345
  // src/utils/createImperativeHandle.ts
7228
7346
  var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
7229
7347
  function getAverageItemSizes(state) {
@@ -7246,9 +7364,9 @@ function triggerMountedContainerLayouts(ctx) {
7246
7364
  }
7247
7365
  function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7248
7366
  const state = ctx.state;
7367
+ const scrollRequestTracker = getScrollRequestTracker(ctx);
7249
7368
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
7250
7369
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
7251
- let imperativeScrollToken = 0;
7252
7370
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.scheduledWork.has("mvcpRecalculate") || state.ignoreScrollFromMVCP !== void 0;
7253
7371
  const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
7254
7372
  var _a3;
@@ -7270,7 +7388,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7270
7388
  const startedAt = Date.now();
7271
7389
  let stableFrames = 0;
7272
7390
  const check = () => {
7273
- if (token !== imperativeScrollToken) {
7391
+ if (!scrollRequestTracker.isCurrent(token)) {
7274
7392
  return;
7275
7393
  }
7276
7394
  if (isSettlingAfterDataChange() || !isReady()) {
@@ -7288,33 +7406,15 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7288
7406
  state.scheduledWork.frame(check, "imperativeScrollReady");
7289
7407
  };
7290
7408
  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
- };
7409
+ const runNow = () => scrollRequestTracker.runNow(token, resolve, run);
7303
7410
  if (isSettlingAfterDataChange() || !isReady()) {
7304
7411
  runWhenReady(token, runNow, isReady);
7305
7412
  } else {
7306
7413
  runNow();
7307
7414
  }
7308
7415
  };
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
7416
  const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
7317
- const token = startImperativeScroll(resolve);
7417
+ const token = scrollRequestTracker.start(resolve);
7318
7418
  supersedeInitialScroll(ctx);
7319
7419
  runScrollRequest(token, resolve, run, isReady);
7320
7420
  });
@@ -7322,7 +7422,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7322
7422
  const pendingScroll = state.pendingScrollToEnd;
7323
7423
  if (pendingScroll) {
7324
7424
  state.pendingScrollToEnd = void 0;
7325
- if (pendingScroll.token === imperativeScrollToken) {
7425
+ if (scrollRequestTracker.isCurrent(pendingScroll.token)) {
7326
7426
  runScrollRequest(
7327
7427
  pendingScroll.token,
7328
7428
  pendingScroll.resolve,
@@ -7436,7 +7536,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7436
7536
  }),
7437
7537
  scrollToEnd: (options) => new Promise((resolve) => {
7438
7538
  var _a3;
7439
- const token = startImperativeScroll(resolve);
7539
+ const token = scrollRequestTracker.start(resolve);
7440
7540
  state.pendingScrollToEnd = {
7441
7541
  options,
7442
7542
  resolve,
@@ -7816,6 +7916,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7816
7916
  const shouldInitializeHorizontalRTL = !initialScrollAtEnd && !hasInitialScrollIndex && !hasInitialScrollOffset && isHorizontalRTLProps({ horizontal, rtl });
7817
7917
  const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && (hasInitialScrollOffset || shouldInitializeHorizontalRTL);
7818
7918
  const usesBootstrapInitialScroll = initialScrollAtEnd || hasInitialScrollIndex;
7919
+ const shouldBottomAlignNumericInitialScrollIndex = typeof initialScrollIndexProp === "number" && !hasInitialScrollOffset && dataProp.length > 1 && initialScrollIndexProp >= dataProp.length - 1;
7819
7920
  const initialScrollProp = initialScrollAtEnd ? {
7820
7921
  index: Math.max(0, dataProp.length - 1),
7821
7922
  preserveForBottomPadding: true,
@@ -7828,7 +7929,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7828
7929
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7829
7930
  } : {
7830
7931
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7831
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
7932
+ preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7933
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7934
+ viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7832
7935
  } : initialScrollUsesOffsetOnly ? {
7833
7936
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
7834
7937
  index: 0,
@@ -7934,6 +8037,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7934
8037
  viewabilityConfigCallbackPairs: void 0
7935
8038
  };
7936
8039
  const internalState = ctx.state;
8040
+ ctx.scrollToEnd = (options) => scrollToEnd(ctx, options);
7937
8041
  internalState.triggerCalculateItemsInView = (params) => calculateItemsInView(ctx, params);
7938
8042
  internalState.reprocessCurrentScroll = () => updateScroll(ctx, internalState.scroll, true);
7939
8043
  set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPositionConfig);
@@ -8287,6 +8391,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8287
8391
  onScroll: (event) => onScroll(ctx, event),
8288
8392
  onScrollBeginDrag: (event) => {
8289
8393
  var _a4, _b2;
8394
+ const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8395
+ if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8396
+ cancelImperativeScroll(state);
8397
+ }
8398
+ if (maintainingScrollAtEnd) {
8399
+ finishMaintainScrollAtEnd(ctx);
8400
+ }
8290
8401
  prepareReachedEdgeForNextUserScroll(ctx);
8291
8402
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8292
8403
  },