@legendapp/list 3.3.10 → 3.4.0

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.
@@ -409,6 +409,14 @@ var EDGE_POSITION_EPSILON = 1;
409
409
  var ENABLE_DEVMODE = IS_DEV && false;
410
410
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
411
411
 
412
+ // src/core/endOfContentTarget.ts
413
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
414
+ return -stylePaddingEnd - footerSize;
415
+ }
416
+ function isEndOfContentTarget(target, dataLength) {
417
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
418
+ }
419
+
412
420
  // src/core/cancelImperativeScroll.ts
413
421
  function cancelScrollCompletionChecks({ scheduledWork }) {
414
422
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -593,26 +601,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
593
601
  scrollPosition: context.scrollPosition
594
602
  });
595
603
  };
604
+ let reached = !!wasReached;
596
605
  if (!wasReached) {
597
- if (!within) {
598
- return false;
599
- }
600
- onReached(distance);
601
- updateSnapshot();
602
- return true;
603
- }
604
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
605
- if (reset) {
606
- setSnapshot(void 0);
607
- return false;
608
- }
609
- if (within) {
610
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
611
- if (changed) {
606
+ if (within && onReached(distance)) {
612
607
  updateSnapshot();
608
+ reached = true;
609
+ }
610
+ } else {
611
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
612
+ if (reset) {
613
+ setSnapshot(void 0);
614
+ reached = false;
615
+ } else if (within) {
616
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
617
+ if (changed) {
618
+ updateSnapshot();
619
+ }
613
620
  }
614
621
  }
615
- return true;
622
+ return reached;
616
623
  };
617
624
 
618
625
  // src/utils/edgeReachedGate.ts
@@ -659,7 +666,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
659
666
  return void 0;
660
667
  }
661
668
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
662
- state.edgeReachedGate = "closed";
663
669
  resetEdgeLatch(ctx, allowedEdge);
664
670
  return allowedEdge;
665
671
  }
@@ -700,7 +706,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
700
706
  set$(
701
707
  ctx,
702
708
  "isWithinMaintainScrollAtEndThreshold",
703
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
709
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
704
710
  );
705
711
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
706
712
  if (!shouldSkipThresholdChecks) {
@@ -716,11 +722,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
716
722
  scrollPosition: scroll
717
723
  },
718
724
  (distance) => {
719
- var _a4, _b;
720
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
725
+ let dispatched = false;
726
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
721
727
  markReachedEdge(ctx);
722
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
728
+ state.props.onEndReached({ distanceFromEnd: distance });
729
+ dispatched = true;
723
730
  }
731
+ return dispatched;
724
732
  },
725
733
  (snapshot) => {
726
734
  state.endReachedSnapshot = snapshot;
@@ -753,7 +761,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
753
761
  }
754
762
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
755
763
  set$(ctx, "isNearStart", scroll <= threshold);
756
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
764
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
757
765
  if (!shouldSkipThresholdChecks) {
758
766
  state.isStartReached = checkThreshold(
759
767
  scroll,
@@ -767,11 +775,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
767
775
  scrollPosition: scroll
768
776
  },
769
777
  (distance) => {
770
- var _a3, _b;
771
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
778
+ let dispatched = false;
779
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
772
780
  markReachedEdge(ctx);
773
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
781
+ state.props.onStartReached({ distanceFromStart: distance });
782
+ dispatched = true;
774
783
  }
784
+ return dispatched;
775
785
  },
776
786
  (snapshot) => {
777
787
  state.startReachedSnapshot = snapshot;
@@ -884,6 +894,11 @@ function resetInitialRenderState(ctx, {
884
894
  if (resetLayout) {
885
895
  state.didContainersLayout = false;
886
896
  state.queuedInitialLayout = false;
897
+ state.edgeReachedGate = void 0;
898
+ state.isStartReached = null;
899
+ state.isEndReached = null;
900
+ state.startReachedSnapshot = void 0;
901
+ state.endReachedSnapshot = void 0;
887
902
  }
888
903
  if (resetInitialScroll) {
889
904
  state.didFinishInitialScroll = false;
@@ -898,7 +913,7 @@ function setInitialRenderState(ctx, {
898
913
  const { state } = ctx;
899
914
  const {
900
915
  loadStartTime,
901
- props: { onLoad }
916
+ props: { onLoad, onReady }
902
917
  } = state;
903
918
  if (didLayout) {
904
919
  state.didContainersLayout = true;
@@ -919,6 +934,7 @@ function setInitialRenderState(ctx, {
919
934
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
920
935
  }
921
936
  }
937
+ onReady == null ? void 0 : onReady();
922
938
  }
923
939
  }
924
940
 
@@ -959,6 +975,7 @@ function supersedeInitialScroll(ctx) {
959
975
  function finishInitialScroll(ctx, options) {
960
976
  var _a3, _b, _c;
961
977
  const state = ctx.state;
978
+ const freshDataTransitionEpoch = state.freshDataTransitionEpoch;
962
979
  if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
963
980
  syncInitialScrollOffset(state, options.resolvedOffset);
964
981
  } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
@@ -968,9 +985,13 @@ function finishInitialScroll(ctx, options) {
968
985
  }
969
986
  }
970
987
  const complete = () => {
971
- var _a4, _b2, _c2, _d, _e;
972
- const shouldReleaseDeferredPublicOnScroll = ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
973
- const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
988
+ var _a4, _b2, _c2, _d, _e, _f;
989
+ if (freshDataTransitionEpoch !== state.freshDataTransitionEpoch) {
990
+ (_a4 = options == null ? void 0 : options.onFinished) == null ? void 0 : _a4.call(options);
991
+ return;
992
+ }
993
+ const shouldReleaseDeferredPublicOnScroll = ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap";
994
+ const finalScrollOffset = (_e = (_d = (_c2 = options == null ? void 0 : options.resolvedOffset) != null ? _c2 : state.scrollPending) != null ? _d : state.scroll) != null ? _e : 0;
974
995
  initialScrollWatchdog.clear(state);
975
996
  if ((options == null ? void 0 : options.preserveTarget) && state.initialScroll) {
976
997
  state.clearPreservedInitialScrollOnNextFinish = void 0;
@@ -999,7 +1020,7 @@ function finishInitialScroll(ctx, options) {
999
1020
  if (shouldReleaseDeferredPublicOnScroll) {
1000
1021
  releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1001
1022
  }
1002
- (_e = options == null ? void 0 : options.onFinished) == null ? void 0 : _e.call(options);
1023
+ (_f = options == null ? void 0 : options.onFinished) == null ? void 0 : _f.call(options);
1003
1024
  };
1004
1025
  if (options == null ? void 0 : options.waitForCompletionFrame) {
1005
1026
  requestAnimationFrame(complete);
@@ -1257,10 +1278,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1257
1278
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1258
1279
  const trailingInset = getContentInsetEnd(ctx);
1259
1280
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1260
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1261
- const footerSize = peek$(ctx, "footerSize") || 0;
1262
- offset += footerSize;
1263
- }
1264
1281
  }
1265
1282
  return offset;
1266
1283
  }
@@ -1281,153 +1298,21 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1281
1298
  return clampedOffset;
1282
1299
  }
1283
1300
 
1284
- // src/core/finishScrollTo.ts
1285
- function finishScrollTo(ctx) {
1286
- var _a3, _b;
1287
- const state = ctx.state;
1288
- if (state == null ? void 0 : state.scrollingTo) {
1289
- cancelScrollCompletionChecks(state);
1290
- const resolvePendingScroll = state.pendingScrollResolve;
1291
- state.pendingScrollResolve = void 0;
1292
- const scrollingTo = state.scrollingTo;
1293
- state.scrollHistory.length = 0;
1294
- state.scrollingTo = void 0;
1295
- state.scrollTargetPinnedRange = void 0;
1296
- if (state.pendingTotalSize !== void 0) {
1297
- addTotalSize(ctx, null, state.pendingTotalSize);
1298
- }
1299
- {
1300
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1301
- }
1302
- if (scrollingTo.isInitialScroll || state.initialScroll) {
1303
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
1304
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
1305
- finishInitialScroll(ctx, {
1306
- onFinished: () => {
1307
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1308
- },
1309
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1310
- recalculateItems: true,
1311
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
1312
- syncObservedOffset: isOffsetSession,
1313
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1314
- });
1315
- return;
1316
- }
1317
- recalculateSettledScroll(ctx);
1318
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1319
- }
1320
- }
1321
-
1322
- // src/core/doScrollTo.ts
1323
- var SCROLL_END_IDLE_MS = 80;
1324
- var SCROLL_END_MAX_MS = 1500;
1325
- var SMOOTH_SCROLL_DURATION_MS = 320;
1326
- var SCROLL_END_TARGET_EPSILON = 1;
1327
- function doScrollTo(ctx, params) {
1328
- var _a3, _b;
1329
- const state = ctx.state;
1330
- const { animated, horizontal, offset } = params;
1331
- state.scheduledWork.cancel("platformScrollCompletion");
1332
- const scroller = state.refScroller.current;
1333
- const node = scroller == null ? void 0 : scroller.getScrollableNode();
1334
- if (!scroller || !node) {
1335
- return;
1336
- }
1337
- const isAnimated = !!animated;
1338
- const isHorizontal = !!horizontal;
1339
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
1340
- const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
1341
- const top = isHorizontal ? 0 : offset;
1342
- scroller.scrollTo({ animated: isAnimated, x: left, y: top });
1343
- if (isAnimated) {
1344
- const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
1345
- listenForScrollEnd(ctx, {
1346
- readOffset: () => scroller.getCurrentScrollOffset(),
1347
- target,
1348
- targetOffset: offset
1349
- });
1350
- } else {
1351
- state.scroll = offset;
1352
- const targetToken = state.scrollingTo;
1353
- state.scheduledWork.timeout(
1354
- () => {
1355
- if (targetToken === state.scrollingTo) {
1356
- finishScrollTo(ctx);
1357
- }
1358
- },
1359
- 100,
1360
- "platformScrollCompletion"
1361
- );
1362
- }
1363
- }
1364
- function listenForScrollEnd(ctx, params) {
1365
- const { readOffset, target, targetOffset } = params;
1366
- if (!target) {
1367
- finishScrollTo(ctx);
1368
- return;
1369
- }
1370
- const supportsScrollEnd = "onscrollend" in target;
1371
- let idleTimeout;
1372
- let settled = false;
1373
- const { scheduledWork, scrollingTo: targetToken } = ctx.state;
1374
- const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
1375
- const cleanup = () => {
1376
- target.removeEventListener("scroll", onScroll2);
1377
- if (supportsScrollEnd) {
1378
- target.removeEventListener("scrollend", onScrollEnd);
1379
- }
1380
- if (idleTimeout !== void 0) {
1381
- clearTimeout(idleTimeout);
1382
- }
1383
- clearTimeout(maxTimeout);
1384
- };
1385
- const cancel = () => {
1386
- if (!settled) {
1387
- settled = true;
1388
- cleanup();
1389
- }
1390
- };
1391
- const finish = (reason) => {
1392
- if (settled) return;
1393
- if (targetToken !== ctx.state.scrollingTo) {
1394
- scheduledWork.cancel("platformScrollCompletion");
1395
- return;
1396
- }
1397
- const currentOffset = readOffset();
1398
- const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
1399
- if (reason === "scrollend" && !isNearTarget) {
1400
- return;
1401
- }
1402
- scheduledWork.cancel("platformScrollCompletion");
1403
- finishScrollTo(ctx);
1404
- };
1405
- const onScroll2 = () => {
1406
- if (idleTimeout !== void 0) {
1407
- clearTimeout(idleTimeout);
1408
- }
1409
- idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
1410
- };
1411
- const onScrollEnd = () => finish("scrollend");
1412
- target.addEventListener("scroll", onScroll2);
1413
- if (supportsScrollEnd) {
1414
- target.addEventListener("scrollend", onScrollEnd);
1415
- } else {
1416
- idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
1417
- }
1418
- scheduledWork.register("platformScrollCompletion", cancel);
1419
- }
1420
-
1421
1301
  // src/core/scrollRequestTracker.ts
1422
1302
  function getScrollRequestTracker(ctx) {
1423
1303
  if (!ctx.scrollRequestTracker) {
1424
1304
  let currentToken = 0;
1425
- const start = (resolve) => {
1305
+ let waitingToken;
1306
+ const start = (resolve, isInternal = false) => {
1426
1307
  const state = ctx.state;
1308
+ if (!isInternal && (state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd)) {
1309
+ finishMaintainScrollAtEnd(ctx);
1310
+ }
1427
1311
  state.scheduledWork.cancel("imperativeScrollReady");
1428
1312
  const token = ++currentToken;
1429
1313
  settlePendingImperativeScroll(state);
1430
1314
  state.pendingScrollResolve = resolve;
1315
+ waitingToken = token;
1431
1316
  return token;
1432
1317
  };
1433
1318
  const runNow = (token, resolve, run) => {
@@ -1435,6 +1320,7 @@ function getScrollRequestTracker(ctx) {
1435
1320
  if (token !== currentToken) {
1436
1321
  return;
1437
1322
  }
1323
+ waitingToken = void 0;
1438
1324
  const didStartScroll = run();
1439
1325
  if (!didStartScroll || !state.scrollingTo) {
1440
1326
  if (state.pendingScrollResolve === resolve) {
@@ -1445,6 +1331,7 @@ function getScrollRequestTracker(ctx) {
1445
1331
  };
1446
1332
  ctx.scrollRequestTracker = {
1447
1333
  isCurrent: (token) => token === currentToken,
1334
+ isWaitingToRun: () => waitingToken === currentToken && !!ctx.state.pendingScrollResolve,
1448
1335
  runNow,
1449
1336
  runNowIfIdle: (run) => {
1450
1337
  const state = ctx.state;
@@ -1452,7 +1339,7 @@ function getScrollRequestTracker(ctx) {
1452
1339
  return Promise.resolve();
1453
1340
  }
1454
1341
  return new Promise((resolve) => {
1455
- const token = start(resolve);
1342
+ const token = start(resolve, true);
1456
1343
  runNow(token, resolve, run);
1457
1344
  });
1458
1345
  },
@@ -1486,87 +1373,6 @@ function requestAdjust(ctx, positionDiff, source) {
1486
1373
  }
1487
1374
  }
1488
1375
 
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
- }
1505
- function doMaintainScrollAtEnd(ctx) {
1506
- const state = ctx.state;
1507
- const {
1508
- didContainersLayout,
1509
- didFinishInitialScroll,
1510
- pendingNativeMVCPAdjust,
1511
- props: { maintainScrollAtEnd }
1512
- } = state;
1513
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1514
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1515
- const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1516
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1517
- state.pendingMaintainScrollAtEnd = true;
1518
- return false;
1519
- }
1520
- if (pendingNativeMVCPAdjust) {
1521
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1522
- return false;
1523
- }
1524
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1525
- state.pendingMaintainScrollAtEnd = false;
1526
- const contentSize = getContentSize(ctx);
1527
- if (contentSize < state.scrollLength) {
1528
- state.scroll = 0;
1529
- }
1530
- if (!state.maintainingScrollAtEnd) {
1531
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1532
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1533
- const scrollAtRequest = state.scroll;
1534
- state.maintainingScrollAtEnd = pendingState;
1535
- requestAnimationFrame(() => {
1536
- if (state.maintainingScrollAtEnd !== pendingState) {
1537
- return;
1538
- }
1539
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1540
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1541
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1542
- state.maintainingScrollAtEnd = activeState;
1543
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1544
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
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
- });
1557
- } else if (state.maintainingScrollAtEnd === pendingState) {
1558
- finishMaintainScrollAtEnd(ctx);
1559
- }
1560
- });
1561
- } else {
1562
- state.pendingMaintainScrollAtEnd = true;
1563
- }
1564
- return true;
1565
- }
1566
- finishMaintainScrollAtEnd(ctx);
1567
- return false;
1568
- }
1569
-
1570
1376
  // src/core/mvcp.ts
1571
1377
  var MVCP_POSITION_EPSILON = 0.1;
1572
1378
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2004,6 +1810,285 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2004
1810
  }
2005
1811
  }
2006
1812
 
1813
+ // src/core/doMaintainScrollAtEnd.ts
1814
+ function interruptMaintainScrollAtEnd(ctx) {
1815
+ var _a3;
1816
+ const { state } = ctx;
1817
+ const maintaining = state.maintainingScrollAtEnd;
1818
+ const cancelActiveScroll = !!(maintaining === "animated" || maintaining === "instant" || maintaining && ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isScrollToEnd));
1819
+ if (cancelActiveScroll) {
1820
+ cancelImperativeScroll(state);
1821
+ }
1822
+ if (maintaining || state.pendingMaintainScrollAtEnd) {
1823
+ finishMaintainScrollAtEnd(ctx);
1824
+ }
1825
+ return cancelActiveScroll;
1826
+ }
1827
+ function finishMaintainScrollAtEnd(ctx) {
1828
+ var _a3;
1829
+ const { state } = ctx;
1830
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1831
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1832
+ state.maintainingScrollAtEnd = void 0;
1833
+ state.pendingMaintainScrollAtEnd = false;
1834
+ if (currentPadding !== nextPadding) {
1835
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1836
+ state.scrollForNextCalculateItemsInView = void 0;
1837
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1838
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1839
+ requestAdjust(ctx, nextScroll - state.scroll);
1840
+ }
1841
+ }
1842
+ function doMaintainScrollAtEnd(ctx, options) {
1843
+ var _a3, _b, _c, _d, _e, _f;
1844
+ const state = ctx.state;
1845
+ let didMaintain = false;
1846
+ if (state.isDragging) {
1847
+ finishMaintainScrollAtEnd(ctx);
1848
+ } else {
1849
+ const {
1850
+ didContainersLayout,
1851
+ didFinishInitialScroll,
1852
+ pendingNativeMVCPAdjust,
1853
+ props: { maintainScrollAtEnd }
1854
+ } = state;
1855
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1856
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1857
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1858
+ const shouldMaintainScrollAtEnd = !!(!((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) && (!state.scrollingTo || state.scrollingTo.isScrollToEnd) && (isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain || ((_b = state.scrollingTo) == null ? void 0 : _b.isScrollToEnd)) && maintainScrollAtEnd && didFinishInitialScroll);
1859
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1860
+ const scroller = state.refScroller.current;
1861
+ const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1862
+ const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1863
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1864
+ state.pendingMaintainScrollAtEnd = true;
1865
+ state.scrollPending = observed;
1866
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1867
+ }
1868
+ }
1869
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1870
+ state.pendingMaintainScrollAtEnd = true;
1871
+ } else if (pendingNativeMVCPAdjust) {
1872
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1873
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1874
+ if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1875
+ state.pendingMaintainScrollAtEnd = false;
1876
+ ctx.scrollToEnd({ animated: false });
1877
+ didMaintain = true;
1878
+ } else {
1879
+ state.pendingMaintainScrollAtEnd = true;
1880
+ (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1881
+ }
1882
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1883
+ state.pendingMaintainScrollAtEnd = false;
1884
+ const contentSize = getContentSize(ctx);
1885
+ if (contentSize < state.scrollLength) {
1886
+ state.scroll = 0;
1887
+ }
1888
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1889
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1890
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1891
+ const scrollAtRequest = state.scroll;
1892
+ state.maintainingScrollAtEnd = pendingState;
1893
+ const run = () => {
1894
+ if (state.maintainingScrollAtEnd === pendingState) {
1895
+ if (state.isDragging) {
1896
+ finishMaintainScrollAtEnd(ctx);
1897
+ } else {
1898
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1899
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1900
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1901
+ if (state.scrollingTo || state.pendingScrollResolve) {
1902
+ state.pendingMaintainScrollAtEnd = true;
1903
+ } else {
1904
+ state.maintainingScrollAtEnd = activeState;
1905
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1906
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1907
+ );
1908
+ void scrollPromise.then(() => {
1909
+ if (state.maintainingScrollAtEnd === activeState) {
1910
+ if (state.pendingMaintainScrollAtEnd) {
1911
+ state.maintainingScrollAtEnd = void 0;
1912
+ doMaintainScrollAtEnd(ctx);
1913
+ } else {
1914
+ finishMaintainScrollAtEnd(ctx);
1915
+ }
1916
+ }
1917
+ });
1918
+ }
1919
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1920
+ finishMaintainScrollAtEnd(ctx);
1921
+ }
1922
+ }
1923
+ }
1924
+ };
1925
+ if (immediate) {
1926
+ run();
1927
+ } else {
1928
+ requestAnimationFrame(run);
1929
+ }
1930
+ } else {
1931
+ state.pendingMaintainScrollAtEnd = true;
1932
+ }
1933
+ didMaintain = true;
1934
+ } else {
1935
+ finishMaintainScrollAtEnd(ctx);
1936
+ }
1937
+ }
1938
+ return didMaintain;
1939
+ }
1940
+
1941
+ // src/core/finishScrollTo.ts
1942
+ function finishScrollTo(ctx) {
1943
+ var _a3, _b, _c;
1944
+ const state = ctx.state;
1945
+ if (state == null ? void 0 : state.scrollingTo) {
1946
+ cancelScrollCompletionChecks(state);
1947
+ const resolvePendingScroll = ((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) ? void 0 : state.pendingScrollResolve;
1948
+ if (resolvePendingScroll) {
1949
+ state.pendingScrollResolve = void 0;
1950
+ }
1951
+ const scrollingTo = state.scrollingTo;
1952
+ state.scrollHistory.length = 0;
1953
+ state.scrollingTo = void 0;
1954
+ state.scrollTargetPinnedRange = void 0;
1955
+ if (state.pendingTotalSize !== void 0) {
1956
+ addTotalSize(ctx, null, state.pendingTotalSize);
1957
+ }
1958
+ {
1959
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1960
+ }
1961
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
1962
+ const isOffsetSession = ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) === "offset";
1963
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_c = state.initialScroll) == null ? void 0 : _c.viewPosition) === 1;
1964
+ finishInitialScroll(ctx, {
1965
+ onFinished: () => {
1966
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1967
+ },
1968
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1969
+ recalculateItems: true,
1970
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
1971
+ syncObservedOffset: isOffsetSession,
1972
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1973
+ });
1974
+ return;
1975
+ }
1976
+ recalculateSettledScroll(ctx);
1977
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1978
+ if (state.pendingMaintainScrollAtEnd) {
1979
+ state.maintainingScrollAtEnd = void 0;
1980
+ doMaintainScrollAtEnd(ctx);
1981
+ }
1982
+ }
1983
+ }
1984
+
1985
+ // src/core/doScrollTo.ts
1986
+ var SCROLL_END_IDLE_MS = 80;
1987
+ var SCROLL_END_MAX_MS = 1500;
1988
+ var SMOOTH_SCROLL_DURATION_MS = 320;
1989
+ var SCROLL_END_TARGET_EPSILON = 1;
1990
+ function doScrollTo(ctx, params) {
1991
+ var _a3, _b, _c, _d, _e, _f;
1992
+ const state = ctx.state;
1993
+ const { animated, horizontal, offset } = params;
1994
+ state.scheduledWork.cancel("platformScrollCompletion");
1995
+ const scroller = state.refScroller.current;
1996
+ const node = scroller == null ? void 0 : scroller.getScrollableNode();
1997
+ if (!scroller || !node) {
1998
+ return;
1999
+ }
2000
+ const isAnimated = !!animated;
2001
+ const isHorizontal = !!horizontal;
2002
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2003
+ const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
2004
+ const top = isHorizontal ? 0 : offset;
2005
+ scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2006
+ if (isAnimated) {
2007
+ const targetOffset = Math.max(0, Math.min(offset, (_b = (_a3 = scroller.getMaxScrollOffset) == null ? void 0 : _a3.call(scroller)) != null ? _b : offset));
2008
+ if (Math.abs(((_d = (_c = scroller.getCurrentScrollOffset) == null ? void 0 : _c.call(scroller)) != null ? _d : Number.NaN) - targetOffset) <= SCROLL_END_TARGET_EPSILON) {
2009
+ const targetToken = state.scrollingTo;
2010
+ state.scheduledWork.frame(() => {
2011
+ if (targetToken === state.scrollingTo) finishScrollTo(ctx);
2012
+ }, "platformScrollCompletion");
2013
+ return;
2014
+ }
2015
+ const target = (_f = (_e = scroller.getScrollEventTarget) == null ? void 0 : _e.call(scroller)) != null ? _f : null;
2016
+ listenForScrollEnd(ctx, {
2017
+ readOffset: () => scroller.getCurrentScrollOffset(),
2018
+ target,
2019
+ targetOffset
2020
+ });
2021
+ } else {
2022
+ state.scroll = offset;
2023
+ const targetToken = state.scrollingTo;
2024
+ state.scheduledWork.timeout(
2025
+ () => {
2026
+ if (targetToken === state.scrollingTo) {
2027
+ finishScrollTo(ctx);
2028
+ }
2029
+ },
2030
+ 100,
2031
+ "platformScrollCompletion"
2032
+ );
2033
+ }
2034
+ }
2035
+ function listenForScrollEnd(ctx, params) {
2036
+ const { readOffset, target, targetOffset } = params;
2037
+ if (!target) {
2038
+ finishScrollTo(ctx);
2039
+ return;
2040
+ }
2041
+ const supportsScrollEnd = "onscrollend" in target;
2042
+ let idleTimeout;
2043
+ let settled = false;
2044
+ const { scheduledWork, scrollingTo: targetToken } = ctx.state;
2045
+ const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2046
+ const cleanup = () => {
2047
+ target.removeEventListener("scroll", onScroll2);
2048
+ if (supportsScrollEnd) {
2049
+ target.removeEventListener("scrollend", onScrollEnd);
2050
+ }
2051
+ if (idleTimeout !== void 0) {
2052
+ clearTimeout(idleTimeout);
2053
+ }
2054
+ clearTimeout(maxTimeout);
2055
+ };
2056
+ const cancel = () => {
2057
+ if (!settled) {
2058
+ settled = true;
2059
+ cleanup();
2060
+ }
2061
+ };
2062
+ const finish = (reason) => {
2063
+ if (settled) return;
2064
+ if (targetToken !== ctx.state.scrollingTo) {
2065
+ scheduledWork.cancel("platformScrollCompletion");
2066
+ return;
2067
+ }
2068
+ const currentOffset = readOffset();
2069
+ const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2070
+ if (reason === "scrollend" && !isNearTarget) {
2071
+ return;
2072
+ }
2073
+ scheduledWork.cancel("platformScrollCompletion");
2074
+ finishScrollTo(ctx);
2075
+ };
2076
+ const onScroll2 = () => {
2077
+ if (idleTimeout !== void 0) {
2078
+ clearTimeout(idleTimeout);
2079
+ }
2080
+ idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2081
+ };
2082
+ const onScrollEnd = () => finish("scrollend");
2083
+ target.addEventListener("scroll", onScroll2);
2084
+ if (supportsScrollEnd) {
2085
+ target.addEventListener("scrollend", onScrollEnd);
2086
+ } else {
2087
+ idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2088
+ }
2089
+ scheduledWork.register("platformScrollCompletion", cancel);
2090
+ }
2091
+
2007
2092
  // src/core/scrollTo.ts
2008
2093
  function getAverageSizeSnapshot(state) {
2009
2094
  if (Object.keys(state.averageSizes).length === 0) {
@@ -2154,7 +2239,9 @@ function scrollTo(ctx, params) {
2154
2239
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2155
2240
  }
2156
2241
  }
2157
- state.scrollPending = targetOffset;
2242
+ if (!animated || isInitialScroll) {
2243
+ state.scrollPending = targetOffset;
2244
+ }
2158
2245
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2159
2246
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2160
2247
  if (animated) {
@@ -2531,13 +2618,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2531
2618
  queueBootstrapInitialScrollReevaluation(ctx.state);
2532
2619
  }
2533
2620
  function createInitialScrollAtEndTarget(options) {
2534
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2621
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2535
2622
  return {
2536
2623
  contentOffset: void 0,
2537
- index: Math.max(0, dataLength - 1),
2624
+ index: index != null ? index : Math.max(0, dataLength - 1),
2538
2625
  preserveForBottomPadding: true,
2539
2626
  preserveForFooterLayout,
2540
- viewOffset: -stylePaddingEnd - footerSize,
2627
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2541
2628
  viewPosition: 1
2542
2629
  };
2543
2630
  }
@@ -2553,13 +2640,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2553
2640
  function createRetargetedBottomAlignedInitialScroll(options) {
2554
2641
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2555
2642
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2643
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2556
2644
  return {
2557
2645
  ...target,
2558
2646
  contentOffset: void 0,
2559
2647
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2560
2648
  preserveForBottomPadding: true,
2561
2649
  preserveForFooterLayout,
2562
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2650
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2563
2651
  viewPosition: 1
2564
2652
  };
2565
2653
  }
@@ -2573,7 +2661,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2573
2661
  }
2574
2662
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2575
2663
  dataLength,
2576
- footerSize: 0,
2664
+ footerSize: peek$(ctx, "footerSize") || 0,
2577
2665
  preserveForFooterLayout: void 0,
2578
2666
  stylePaddingEnd
2579
2667
  });
@@ -2596,13 +2684,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2596
2684
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2597
2685
  }
2598
2686
  function getPreservedEndAnchorOffsetDiff(ctx) {
2599
- var _a3;
2600
2687
  const state = ctx.state;
2601
2688
  const initialScroll = state.initialScroll;
2602
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2689
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2603
2690
  return;
2604
2691
  }
2605
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2692
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
2606
2693
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2607
2694
  }
2608
2695
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2630,6 +2717,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2630
2717
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2631
2718
  if (hasObservedNativeScrollAfterRequest) {
2632
2719
  activeCorrection.lastRequestTime = Date.now();
2720
+ {
2721
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2722
+ }
2633
2723
  requestAdjust(ctx, offsetDiff);
2634
2724
  }
2635
2725
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2752,10 +2842,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2752
2842
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2753
2843
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2754
2844
  const state = ctx.state;
2755
- if (!initialScrollAtEnd) {
2845
+ const initialScroll = state.initialScroll;
2846
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2756
2847
  return;
2757
2848
  }
2758
- const initialScroll = state.initialScroll;
2759
2849
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2760
2850
  return;
2761
2851
  }
@@ -2773,6 +2863,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2773
2863
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2774
2864
  dataLength,
2775
2865
  footerSize,
2866
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
2776
2867
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2777
2868
  stylePaddingEnd
2778
2869
  });
@@ -3199,6 +3290,11 @@ function handleInitialScrollDataChange(ctx, options) {
3199
3290
  const state = ctx.state;
3200
3291
  const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3201
3292
  const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3293
+ if (didStartFreshData) {
3294
+ cancelImperativeScroll(state);
3295
+ state.maintainingScrollAtEnd = void 0;
3296
+ state.pendingMaintainScrollAtEnd = false;
3297
+ }
3202
3298
  if (dataLength > 0) {
3203
3299
  state.hasHadNonEmptyData = true;
3204
3300
  }
@@ -3917,7 +4013,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
3917
4013
  }
3918
4014
 
3919
4015
  // src/utils/findAvailableContainers.ts
3920
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
4016
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
3921
4017
  var _a3;
3922
4018
  const numNeeded = needNewContainers.length;
3923
4019
  if (numNeeded === 0) {
@@ -3926,16 +4022,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3926
4022
  const numContainers = peek$(ctx, "numContainers");
3927
4023
  const state = ctx.state;
3928
4024
  const { containerItemMetadata, stickyContainerPool } = state;
4025
+ const { data, getItemType } = state.props;
4026
+ const hasItemTypes = !!getItemType;
3929
4027
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
3930
4028
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
3931
- const requests = needNewContainers.map((itemIndex, order) => ({
3932
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
3933
- itemIndex,
3934
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
3935
- order
3936
- }));
3937
- const normalRequests = requests.filter((request) => !request.isSticky);
3938
- const stickyRequests = requests.filter((request) => request.isSticky);
4029
+ const requests = new Array(numNeeded);
4030
+ const normalRequests = [];
4031
+ const stickyRequests = [];
4032
+ for (let order = 0; order < numNeeded; order++) {
4033
+ const itemIndex = needNewContainers[order];
4034
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
4035
+ const request = {
4036
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4037
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4038
+ itemIndex,
4039
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4040
+ order,
4041
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4042
+ };
4043
+ requests[order] = request;
4044
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4045
+ }
3939
4046
  const normalCandidates = [];
3940
4047
  const stickyCandidates = [];
3941
4048
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -3991,7 +4098,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3991
4098
  }
3992
4099
  };
3993
4100
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
3994
- if (getRequiredItemType) {
4101
+ if (hasItemTypes) {
3995
4102
  assignMatching(
3996
4103
  pendingRequests,
3997
4104
  candidates,
@@ -4015,6 +4122,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4015
4122
  }
4016
4123
  assign(request, containerIndex);
4017
4124
  }
4125
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4126
+ const containerIndices = [];
4127
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4128
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4129
+ }
4018
4130
  if (pendingRemovalChanged) {
4019
4131
  pendingRemoval.length = 0;
4020
4132
  if (pendingRemovalSet) {
@@ -4044,12 +4156,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4044
4156
  function comparatorByDistance(a, b) {
4045
4157
  return b.distance - a.distance;
4046
4158
  }
4159
+ function comparatorByRenderPriority(a, b) {
4160
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4161
+ }
4162
+ function comparatorByIndex(a, b) {
4163
+ return a - b;
4164
+ }
4165
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4166
+ if (requests.length >= 2) {
4167
+ if (groupByType) {
4168
+ const groups = /* @__PURE__ */ new Map();
4169
+ for (const request of requests) {
4170
+ const group = groups.get(request.itemType);
4171
+ if (group) {
4172
+ group.push(request);
4173
+ } else {
4174
+ groups.set(request.itemType, [request]);
4175
+ }
4176
+ }
4177
+ for (const group of groups.values()) {
4178
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4179
+ }
4180
+ } else {
4181
+ containerIndices.length = 0;
4182
+ let indicesSorted = true;
4183
+ for (let i = 0; i < requests.length; i++) {
4184
+ const containerIndex = allocations[requests[i].order].containerIndex;
4185
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4186
+ indicesSorted = false;
4187
+ }
4188
+ containerIndices.push(containerIndex);
4189
+ }
4190
+ if (!indicesSorted) {
4191
+ containerIndices.sort(comparatorByIndex);
4192
+ }
4193
+ requests.sort(comparatorByRenderPriority);
4194
+ for (let i = 0; i < requests.length; i++) {
4195
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4196
+ }
4197
+ }
4198
+ }
4199
+ }
4047
4200
 
4048
4201
  // src/utils/setDidLayout.ts
4049
4202
  function setDidLayout(ctx) {
4050
4203
  const state = ctx.state;
4051
4204
  state.queuedInitialLayout = true;
4052
- checkAtBottom(ctx);
4205
+ checkThresholds(ctx);
4053
4206
  setInitialRenderState(ctx, { didLayout: true });
4054
4207
  if (state.pendingMaintainScrollAtEnd) {
4055
4208
  doMaintainScrollAtEnd(ctx);
@@ -4277,9 +4430,9 @@ function calculateItemsInView(ctx, params = {}) {
4277
4430
  enableScrollForNextCalculateItemsInView,
4278
4431
  idCache,
4279
4432
  indexByKey,
4280
- minIndexSizeChanged,
4433
+ positionRecalculationStartIndex,
4281
4434
  positions,
4282
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4435
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4283
4436
  scrollForNextCalculateItemsInView,
4284
4437
  scrollLength,
4285
4438
  sizes,
@@ -4395,21 +4548,21 @@ function calculateItemsInView(ctx, params = {}) {
4395
4548
  if (dataChanged) {
4396
4549
  resetLayoutCachesForDataChange(state);
4397
4550
  }
4398
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4399
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4551
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4552
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4400
4553
  updateItemPositions(ctx, dataChanged, {
4401
4554
  doMVCP,
4402
4555
  // A changed size shifts every following position. The scrolling early-exit
4403
4556
  // would leave the untouched suffix in the old coordinate space.
4404
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4557
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4405
4558
  optimizeForVisibleWindow,
4406
4559
  scrollBottomBuffered,
4407
4560
  scrollVelocity: speed,
4408
4561
  startIndex
4409
4562
  });
4410
4563
  totalSize = getContentSize(ctx);
4411
- if (minIndexSizeChanged !== void 0) {
4412
- state.minIndexSizeChanged = void 0;
4564
+ if (positionRecalculationStartIndex !== void 0) {
4565
+ state.positionRecalculationStartIndex = void 0;
4413
4566
  }
4414
4567
  let protectedContainerKeys;
4415
4568
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4585,18 +4738,18 @@ function calculateItemsInView(ctx, params = {}) {
4585
4738
  set$(ctx, "activeStickyIndex", -1);
4586
4739
  }
4587
4740
  if (needNewContainers.length > 0) {
4588
- const getRequiredItemType = getItemType ? (i) => {
4589
- const itemType = getItemType(data[i], i);
4590
- return itemType !== void 0 ? String(itemType) : "";
4591
- } : void 0;
4592
4741
  const availableContainerAllocations = findAvailableContainers(
4593
4742
  ctx,
4594
4743
  needNewContainers,
4595
4744
  startBuffered,
4596
4745
  endBuffered,
4597
4746
  pendingRemoval,
4598
- getRequiredItemType,
4599
- protectedContainerKeys
4747
+ protectedContainerKeys,
4748
+ // Initial end alignment has no scroll velocity yet. Keep its
4749
+ // target-side rows first until the initial scroll settles.
4750
+ // After a pause velocity can be zero on the first movement;
4751
+ // use its displacement without treating initial insets as motion.
4752
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4600
4753
  );
4601
4754
  for (const allocation of availableContainerAllocations) {
4602
4755
  const i = allocation.itemIndex;
@@ -4743,6 +4896,7 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4743
4896
  const nextAnchorIndex = anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex;
4744
4897
  let nextAnchorKey;
4745
4898
  let isReady = true;
4899
+ let canUpdateSize = true;
4746
4900
  let nextSize = 0;
4747
4901
  if (anchoredEndSpace) {
4748
4902
  const { anchorIndex, anchorMaxSize, anchorOffset = 0 } = anchoredEndSpace;
@@ -4764,25 +4918,30 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4764
4918
  contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap);
4765
4919
  contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props);
4766
4920
  isReady = !hasUnknownTailSize;
4767
- nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4921
+ const knownSizeBound = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4922
+ nextSize = hasUnknownTailSize ? Math.min(previousSize || 0, knownSizeBound) : knownSizeBound;
4768
4923
  } else if (anchorIndex >= 0) {
4769
4924
  isReady = false;
4925
+ canUpdateSize = false;
4770
4926
  }
4771
4927
  }
4772
4928
  const didSizeChange = previousSize !== nextSize && (previousSize !== void 0 || anchoredEndSpace !== void 0);
4773
4929
  const didEffectiveSizeChange = (previousSize || 0) !== nextSize;
4930
+ const canApplySizeChange = canUpdateSize && (isReady || previousSize !== void 0);
4774
4931
  const didReadyAnchorChange = previousReadyAnchorIndex !== nextAnchorIndex || previousReadyAnchorKey !== nextAnchorKey;
4775
- if (isReady && (didSizeChange || didReadyAnchorChange)) {
4932
+ const didBecomeReady = isReady && state.anchoredEndSpacePendingReady;
4933
+ state.anchoredEndSpacePendingReady = !isReady;
4934
+ if (canApplySizeChange && didSizeChange) {
4935
+ set$(ctx, "anchoredEndSpaceSize", nextSize);
4936
+ (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4937
+ }
4938
+ if (canApplySizeChange && didEffectiveSizeChange) {
4939
+ updateContentMetricsState(ctx);
4940
+ updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4941
+ }
4942
+ if (isReady && (didSizeChange || didReadyAnchorChange || didBecomeReady)) {
4776
4943
  state.anchoredEndSpaceReadyAnchorIndex = nextAnchorIndex;
4777
4944
  state.anchoredEndSpaceReadyAnchorKey = nextAnchorKey;
4778
- if (didSizeChange) {
4779
- set$(ctx, "anchoredEndSpaceSize", nextSize);
4780
- (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4781
- }
4782
- if (didEffectiveSizeChange) {
4783
- updateContentMetricsState(ctx);
4784
- updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4785
- }
4786
4945
  (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize });
4787
4946
  }
4788
4947
  return nextSize;
@@ -4873,6 +5032,7 @@ function updateItemSizesBatch(ctx, measurements) {
4873
5032
  var _a3;
4874
5033
  const state = ctx.state;
4875
5034
  const result = {};
5035
+ const previousTotalSize = state.totalSize;
4876
5036
  for (const measurement of measurements) {
4877
5037
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4878
5038
  if (ownsMeasuredItem) {
@@ -4883,6 +5043,9 @@ function updateItemSizesBatch(ctx, measurements) {
4883
5043
  mergeItemSizeUpdateResult(result, nextResult);
4884
5044
  }
4885
5045
  }
5046
+ if (state.totalSize !== previousTotalSize) {
5047
+ set$(ctx, "totalSize", state.totalSize);
5048
+ }
4886
5049
  flushItemSizeUpdates(ctx, result);
4887
5050
  }
4888
5051
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -4921,12 +5084,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4921
5084
  }
4922
5085
  let needsRecalculate = !didContainersLayout;
4923
5086
  let shouldMaintainScrollAtEnd = false;
4924
- let minIndexSizeChanged;
5087
+ let positionRecalculationStartIndex;
4925
5088
  const prevSizeKnown = state.sizesKnown.get(itemKey);
4926
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5089
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
4927
5090
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
4928
5091
  if (diff !== 0) {
4929
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5092
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
4930
5093
  const { startBuffered, endBuffered } = state;
4931
5094
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
4932
5095
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -4943,8 +5106,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4943
5106
  size
4944
5107
  });
4945
5108
  }
4946
- if (minIndexSizeChanged !== void 0) {
4947
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5109
+ if (positionRecalculationStartIndex !== void 0) {
5110
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
4948
5111
  }
4949
5112
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
4950
5113
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -4961,7 +5124,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4961
5124
  didMeasureUserScrollAnchorResetItem
4962
5125
  };
4963
5126
  }
4964
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5127
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
4965
5128
  var _a3, _b;
4966
5129
  const state = ctx.state;
4967
5130
  const {
@@ -4984,7 +5147,16 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4984
5147
  fixedItemSize,
4985
5148
  itemType
4986
5149
  } : void 0;
4987
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5150
+ const prevSize = getItemSize(
5151
+ ctx,
5152
+ itemKey,
5153
+ index,
5154
+ itemData,
5155
+ void 0,
5156
+ void 0,
5157
+ notifyTotalSize,
5158
+ resolvedItemSize
5159
+ );
4988
5160
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
4989
5161
  const prevSizeKnown = sizesKnown.get(itemKey);
4990
5162
  const size = Math.round(rawSize) ;
@@ -5006,7 +5178,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5006
5178
  }
5007
5179
  }
5008
5180
  if (!prevSize || Math.abs(prevSize - size) > 0.1) {
5009
- setSize(ctx, itemKey, size);
5181
+ setSize(ctx, itemKey, size, notifyTotalSize);
5010
5182
  return size - prevSize;
5011
5183
  }
5012
5184
  return 0;
@@ -5782,7 +5954,9 @@ function sortDOMElements(container, indexByElement) {
5782
5954
  break;
5783
5955
  }
5784
5956
  }
5785
- if (nextStableElement) {
5957
+ if ("moveBefore" in container && typeof container.moveBefore === "function" && container.isConnected) {
5958
+ container.moveBefore(element, nextStableElement);
5959
+ } else if (nextStableElement) {
5786
5960
  container.insertBefore(element, nextStableElement);
5787
5961
  } else {
5788
5962
  container.appendChild(element);
@@ -5829,13 +6003,18 @@ function findLIS(arr) {
5829
6003
  // src/hooks/useDOMOrder.ts
5830
6004
  function useDOMOrder(ref) {
5831
6005
  const ctx = useStateContext();
5832
- const debounceRef = useRef(void 0);
5833
6006
  useEffect(() => {
6007
+ let timeoutId;
6008
+ let lastUpdateTime = 0;
6009
+ let delay = 500;
5834
6010
  const unsubscribe = listen$(ctx, "lastPositionUpdate", () => {
5835
- if (debounceRef.current !== void 0) {
5836
- clearTimeout(debounceRef.current);
5837
- }
5838
- debounceRef.current = setTimeout(() => {
6011
+ const container = ref.current;
6012
+ const now = Date.now();
6013
+ delay = container && "moveBefore" in container && typeof container.moveBefore === "function" && container.isConnected && (now - lastUpdateTime >= 1e3 || timeoutId !== void 0 && delay === 0) && now - ctx.state.scrollTime >= 500 ? 0 : 500;
6014
+ lastUpdateTime = now;
6015
+ clearTimeout(timeoutId);
6016
+ timeoutId = setTimeout(() => {
6017
+ timeoutId = void 0;
5839
6018
  const parent = ref.current;
5840
6019
  if (parent) {
5841
6020
  const indexByElement = /* @__PURE__ */ new Map();
@@ -5848,14 +6027,11 @@ function useDOMOrder(ref) {
5848
6027
  }
5849
6028
  sortDOMElements(parent, indexByElement);
5850
6029
  }
5851
- debounceRef.current = void 0;
5852
- }, 500);
6030
+ }, delay);
5853
6031
  });
5854
6032
  return () => {
5855
6033
  unsubscribe();
5856
- if (debounceRef.current !== void 0) {
5857
- clearTimeout(debounceRef.current);
5858
- }
6034
+ clearTimeout(timeoutId);
5859
6035
  };
5860
6036
  }, [ctx]);
5861
6037
  }
@@ -6225,6 +6401,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6225
6401
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6226
6402
  },
6227
6403
  getCurrentScrollOffset,
6404
+ getMaxScrollOffset,
6228
6405
  getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6229
6406
  getScrollEventTarget: () => getScrollTarget(),
6230
6407
  getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
@@ -6330,6 +6507,22 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6330
6507
  scrollEventCoalescer.cancel();
6331
6508
  };
6332
6509
  }, [emitScrollEnd, getScrollTarget, handleScroll, scrollEventCoalescer]);
6510
+ useEffect(() => {
6511
+ const target = getScrollTarget();
6512
+ if (!target) return;
6513
+ const onWheel = (nativeEvent) => {
6514
+ const event = nativeEvent;
6515
+ const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6516
+ if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6517
+ const offset = getCurrentScrollOffset();
6518
+ if (interruptMaintainScrollAtEnd(ctx)) {
6519
+ scrollToLocalOffset(offset, false);
6520
+ }
6521
+ }
6522
+ };
6523
+ target.addEventListener("wheel", onWheel, { passive: true });
6524
+ return () => target.removeEventListener("wheel", onWheel);
6525
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6333
6526
  useEffect(() => {
6334
6527
  const doScroll = () => {
6335
6528
  if (contentOffset) {
@@ -6539,8 +6732,11 @@ function ScrollAdjust() {
6539
6732
  window.getComputedStyle(contentNode)[axis.paddingEndProp]
6540
6733
  );
6541
6734
  const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`;
6542
- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd };
6543
6735
  contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd;
6736
+ temporaryPaddingRef.current = {
6737
+ baseline: baselinePaddingEnd,
6738
+ value: contentNode.style[axis.paddingEndProp]
6739
+ };
6544
6740
  void contentNode.offsetHeight;
6545
6741
  scrollBy();
6546
6742
  if (resetPaddingRafRef.current !== void 0) {
@@ -6683,6 +6879,7 @@ var ListComponent = typedMemo(function ListComponent2({
6683
6879
  renderScrollComponent,
6684
6880
  onLayoutFooter,
6685
6881
  onInternalScrollBeginDrag,
6882
+ onInternalScrollEndDrag,
6686
6883
  onInternalScrollEnd,
6687
6884
  scrollAdjustHandler,
6688
6885
  snapToIndices,
@@ -6873,7 +7070,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6873
7070
  for (const key in state.averageSizes) {
6874
7071
  delete state.averageSizes[key];
6875
7072
  }
6876
- state.minIndexSizeChanged = 0;
7073
+ state.positionRecalculationStartIndex = 0;
6877
7074
  state.scrollForNextCalculateItemsInView = void 0;
6878
7075
  }
6879
7076
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -7041,7 +7238,7 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7041
7238
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7042
7239
  }
7043
7240
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
7044
- doMaintainScrollAtEnd(ctx);
7241
+ doMaintainScrollAtEnd(ctx, { immediate: true });
7045
7242
  }
7046
7243
  checkThresholds(ctx);
7047
7244
  if (state) {
@@ -7270,14 +7467,17 @@ function scrollToEnd(ctx, options) {
7270
7467
  if (index === -1) {
7271
7468
  return false;
7272
7469
  }
7273
- const paddingBottom = state.props.stylePaddingBottom || 0;
7470
+ const paddingBottom = getStylePaddingEnd(state.props);
7274
7471
  const footerSize = peek$(ctx, "footerSize") || 0;
7275
7472
  scrollToIndex(ctx, {
7276
7473
  ...options,
7277
7474
  index,
7278
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7475
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
7279
7476
  viewPosition: 1
7280
7477
  });
7478
+ if (state.scrollingTo) {
7479
+ state.scrollingTo.isScrollToEnd = true;
7480
+ }
7281
7481
  return true;
7282
7482
  }
7283
7483
 
@@ -7459,7 +7659,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7459
7659
  for (const key in state.averageSizes) {
7460
7660
  delete state.averageSizes[key];
7461
7661
  }
7462
- state.minIndexSizeChanged = 0;
7662
+ state.positionRecalculationStartIndex = 0;
7463
7663
  state.scrollForNextCalculateItemsInView = void 0;
7464
7664
  state.pendingTotalSize = void 0;
7465
7665
  state.totalSize = 0;
@@ -7853,10 +8053,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7853
8053
  onMetricsChange,
7854
8054
  onLayout: onLayoutProp,
7855
8055
  onLoad,
8056
+ onReady,
7856
8057
  onMomentumScrollEnd,
7857
8058
  onRefresh,
7858
8059
  onScroll: onScrollProp,
7859
8060
  onScrollBeginDrag,
8061
+ onScrollEndDrag,
7860
8062
  onStartReached,
7861
8063
  onStartReachedThreshold = 0.5,
7862
8064
  onStickyHeaderChange,
@@ -7925,17 +8127,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7925
8127
  const initialScrollProp = initialScrollAtEnd ? {
7926
8128
  index: Math.max(0, dataProp.length - 1),
7927
8129
  preserveForBottomPadding: true,
7928
- viewOffset: -stylePaddingEndState,
8130
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
7929
8131
  viewPosition: 1
7930
8132
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7931
8133
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7932
8134
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7933
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
8135
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7934
8136
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7935
8137
  } : {
7936
8138
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7937
8139
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7938
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
8140
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7939
8141
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7940
8142
  } : initialScrollUsesOffsetOnly ? {
7941
8143
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -7995,6 +8197,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7995
8197
  endReachedSnapshot: void 0,
7996
8198
  firstFullyOnScreenIndex: -1,
7997
8199
  freshDataTransitionEpoch: 0,
8200
+ handledDataChangeEpoch: 0,
8201
+ handledFreshDataTransitionEpoch: 0,
7998
8202
  hasHadNonEmptyData: dataProp.length > 0,
7999
8203
  idCache: [],
8000
8204
  idsInView: [],
@@ -8011,11 +8215,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8011
8215
  lastLayout: void 0,
8012
8216
  lastScrollDelta: 0,
8013
8217
  loadStartTime: Date.now(),
8014
- minIndexSizeChanged: 0,
8015
8218
  nativeContentInset: void 0,
8016
8219
  nativeMarginTop: 0,
8017
8220
  pendingDataComparison: void 0,
8018
8221
  pendingNativeMVCPAdjust: void 0,
8222
+ positionRecalculationStartIndex: 0,
8019
8223
  positions: [],
8020
8224
  props: {},
8021
8225
  queuedCalculateItemsInView: 0,
@@ -8054,8 +8258,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8054
8258
  refState.current = ctx.state;
8055
8259
  }
8056
8260
  const state = refState.current;
8057
- const handledDataChangeEpochRef = useRef(state.dataChangeEpoch);
8058
- const handledFreshDataTransitionEpochRef = useRef(state.freshDataTransitionEpoch);
8059
8261
  const isFirstLocal = state.isFirst;
8060
8262
  const previousAdaptiveRender = state.props.adaptiveRender;
8061
8263
  const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
@@ -8120,8 +8322,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8120
8322
  onItemSizeChanged,
8121
8323
  onLoad,
8122
8324
  onMomentumScrollEnd,
8325
+ onReady,
8123
8326
  onScroll: throttleScrollFn,
8124
8327
  onScrollBeginDrag,
8328
+ onScrollEndDrag,
8125
8329
  onStartReached,
8126
8330
  onStartReachedThreshold,
8127
8331
  onStickyHeaderChange,
@@ -8205,10 +8409,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8205
8409
  useDevChecks(props);
8206
8410
  }
8207
8411
  useLayoutEffect(() => {
8208
- const didDataChange = handledDataChangeEpochRef.current !== dataChangeEpoch;
8209
- const didStartFreshData = handledFreshDataTransitionEpochRef.current !== freshDataTransitionEpoch;
8210
- handledDataChangeEpochRef.current = dataChangeEpoch;
8211
- handledFreshDataTransitionEpochRef.current = freshDataTransitionEpoch;
8412
+ const didDataChange = state.handledDataChangeEpoch !== dataChangeEpoch;
8413
+ const didStartFreshData = state.handledFreshDataTransitionEpoch !== freshDataTransitionEpoch;
8414
+ state.handledDataChangeEpoch = dataChangeEpoch;
8415
+ state.handledFreshDataTransitionEpoch = freshDataTransitionEpoch;
8212
8416
  if (didStartFreshData) {
8213
8417
  resetInitialRenderState(ctx, {
8214
8418
  resetInitialScroll: !!initialScrollProp,
@@ -8409,17 +8613,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8409
8613
  onScroll: (event) => onScroll(ctx, event),
8410
8614
  onScrollBeginDrag: (event) => {
8411
8615
  var _a4, _b2;
8412
- const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8413
- if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8414
- cancelImperativeScroll(state);
8415
- }
8416
- if (maintainingScrollAtEnd) {
8417
- finishMaintainScrollAtEnd(ctx);
8418
- }
8616
+ state.isDragging = true;
8617
+ interruptMaintainScrollAtEnd(ctx);
8419
8618
  prepareReachedEdgeForNextUserScroll(ctx);
8420
8619
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8421
8620
  },
8422
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
8621
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
8622
+ onScrollEndDrag: (event) => {
8623
+ var _a4, _b2;
8624
+ state.isDragging = false;
8625
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8626
+ }
8423
8627
  }),
8424
8628
  []
8425
8629
  );
@@ -8443,6 +8647,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8443
8647
  ListHeaderComponent,
8444
8648
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
8445
8649
  onInternalScrollEnd: fns.onScrollEnd,
8650
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
8446
8651
  onLayout,
8447
8652
  onLayoutFooter,
8448
8653
  onMomentumScrollEnd: fns.onMomentumScrollEnd,