@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.
package/react.js CHANGED
@@ -430,6 +430,14 @@ var EDGE_POSITION_EPSILON = 1;
430
430
  var ENABLE_DEVMODE = IS_DEV && false;
431
431
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
432
432
 
433
+ // src/core/endOfContentTarget.ts
434
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
435
+ return -stylePaddingEnd - footerSize;
436
+ }
437
+ function isEndOfContentTarget(target, dataLength) {
438
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
439
+ }
440
+
433
441
  // src/core/cancelImperativeScroll.ts
434
442
  function cancelScrollCompletionChecks({ scheduledWork }) {
435
443
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -614,26 +622,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
614
622
  scrollPosition: context.scrollPosition
615
623
  });
616
624
  };
625
+ let reached = !!wasReached;
617
626
  if (!wasReached) {
618
- if (!within) {
619
- return false;
620
- }
621
- onReached(distance);
622
- updateSnapshot();
623
- return true;
624
- }
625
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
626
- if (reset) {
627
- setSnapshot(void 0);
628
- return false;
629
- }
630
- if (within) {
631
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
632
- if (changed) {
627
+ if (within && onReached(distance)) {
633
628
  updateSnapshot();
629
+ reached = true;
630
+ }
631
+ } else {
632
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
633
+ if (reset) {
634
+ setSnapshot(void 0);
635
+ reached = false;
636
+ } else if (within) {
637
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
638
+ if (changed) {
639
+ updateSnapshot();
640
+ }
634
641
  }
635
642
  }
636
- return true;
643
+ return reached;
637
644
  };
638
645
 
639
646
  // src/utils/edgeReachedGate.ts
@@ -680,7 +687,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
680
687
  return void 0;
681
688
  }
682
689
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
683
- state.edgeReachedGate = "closed";
684
690
  resetEdgeLatch(ctx, allowedEdge);
685
691
  return allowedEdge;
686
692
  }
@@ -721,7 +727,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
721
727
  set$(
722
728
  ctx,
723
729
  "isWithinMaintainScrollAtEndThreshold",
724
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
730
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
725
731
  );
726
732
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
727
733
  if (!shouldSkipThresholdChecks) {
@@ -737,11 +743,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
737
743
  scrollPosition: scroll
738
744
  },
739
745
  (distance) => {
740
- var _a4, _b;
741
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
746
+ let dispatched = false;
747
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
742
748
  markReachedEdge(ctx);
743
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
749
+ state.props.onEndReached({ distanceFromEnd: distance });
750
+ dispatched = true;
744
751
  }
752
+ return dispatched;
745
753
  },
746
754
  (snapshot) => {
747
755
  state.endReachedSnapshot = snapshot;
@@ -774,7 +782,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
774
782
  }
775
783
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
776
784
  set$(ctx, "isNearStart", scroll <= threshold);
777
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
785
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
778
786
  if (!shouldSkipThresholdChecks) {
779
787
  state.isStartReached = checkThreshold(
780
788
  scroll,
@@ -788,11 +796,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
788
796
  scrollPosition: scroll
789
797
  },
790
798
  (distance) => {
791
- var _a3, _b;
792
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
799
+ let dispatched = false;
800
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
793
801
  markReachedEdge(ctx);
794
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
802
+ state.props.onStartReached({ distanceFromStart: distance });
803
+ dispatched = true;
795
804
  }
805
+ return dispatched;
796
806
  },
797
807
  (snapshot) => {
798
808
  state.startReachedSnapshot = snapshot;
@@ -905,6 +915,11 @@ function resetInitialRenderState(ctx, {
905
915
  if (resetLayout) {
906
916
  state.didContainersLayout = false;
907
917
  state.queuedInitialLayout = false;
918
+ state.edgeReachedGate = void 0;
919
+ state.isStartReached = null;
920
+ state.isEndReached = null;
921
+ state.startReachedSnapshot = void 0;
922
+ state.endReachedSnapshot = void 0;
908
923
  }
909
924
  if (resetInitialScroll) {
910
925
  state.didFinishInitialScroll = false;
@@ -919,7 +934,7 @@ function setInitialRenderState(ctx, {
919
934
  const { state } = ctx;
920
935
  const {
921
936
  loadStartTime,
922
- props: { onLoad }
937
+ props: { onLoad, onReady }
923
938
  } = state;
924
939
  if (didLayout) {
925
940
  state.didContainersLayout = true;
@@ -940,6 +955,7 @@ function setInitialRenderState(ctx, {
940
955
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
941
956
  }
942
957
  }
958
+ onReady == null ? void 0 : onReady();
943
959
  }
944
960
  }
945
961
 
@@ -980,6 +996,7 @@ function supersedeInitialScroll(ctx) {
980
996
  function finishInitialScroll(ctx, options) {
981
997
  var _a3, _b, _c;
982
998
  const state = ctx.state;
999
+ const freshDataTransitionEpoch = state.freshDataTransitionEpoch;
983
1000
  if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
984
1001
  syncInitialScrollOffset(state, options.resolvedOffset);
985
1002
  } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
@@ -989,9 +1006,13 @@ function finishInitialScroll(ctx, options) {
989
1006
  }
990
1007
  }
991
1008
  const complete = () => {
992
- var _a4, _b2, _c2, _d, _e;
993
- const shouldReleaseDeferredPublicOnScroll = ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
994
- const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
1009
+ var _a4, _b2, _c2, _d, _e, _f;
1010
+ if (freshDataTransitionEpoch !== state.freshDataTransitionEpoch) {
1011
+ (_a4 = options == null ? void 0 : options.onFinished) == null ? void 0 : _a4.call(options);
1012
+ return;
1013
+ }
1014
+ const shouldReleaseDeferredPublicOnScroll = ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap";
1015
+ const finalScrollOffset = (_e = (_d = (_c2 = options == null ? void 0 : options.resolvedOffset) != null ? _c2 : state.scrollPending) != null ? _d : state.scroll) != null ? _e : 0;
995
1016
  initialScrollWatchdog.clear(state);
996
1017
  if ((options == null ? void 0 : options.preserveTarget) && state.initialScroll) {
997
1018
  state.clearPreservedInitialScrollOnNextFinish = void 0;
@@ -1020,7 +1041,7 @@ function finishInitialScroll(ctx, options) {
1020
1041
  if (shouldReleaseDeferredPublicOnScroll) {
1021
1042
  releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1022
1043
  }
1023
- (_e = options == null ? void 0 : options.onFinished) == null ? void 0 : _e.call(options);
1044
+ (_f = options == null ? void 0 : options.onFinished) == null ? void 0 : _f.call(options);
1024
1045
  };
1025
1046
  if (options == null ? void 0 : options.waitForCompletionFrame) {
1026
1047
  requestAnimationFrame(complete);
@@ -1278,10 +1299,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1278
1299
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1279
1300
  const trailingInset = getContentInsetEnd(ctx);
1280
1301
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1281
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1282
- const footerSize = peek$(ctx, "footerSize") || 0;
1283
- offset += footerSize;
1284
- }
1285
1302
  }
1286
1303
  return offset;
1287
1304
  }
@@ -1302,153 +1319,21 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1302
1319
  return clampedOffset;
1303
1320
  }
1304
1321
 
1305
- // src/core/finishScrollTo.ts
1306
- function finishScrollTo(ctx) {
1307
- var _a3, _b;
1308
- const state = ctx.state;
1309
- if (state == null ? void 0 : state.scrollingTo) {
1310
- cancelScrollCompletionChecks(state);
1311
- const resolvePendingScroll = state.pendingScrollResolve;
1312
- state.pendingScrollResolve = void 0;
1313
- const scrollingTo = state.scrollingTo;
1314
- state.scrollHistory.length = 0;
1315
- state.scrollingTo = void 0;
1316
- state.scrollTargetPinnedRange = void 0;
1317
- if (state.pendingTotalSize !== void 0) {
1318
- addTotalSize(ctx, null, state.pendingTotalSize);
1319
- }
1320
- {
1321
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1322
- }
1323
- if (scrollingTo.isInitialScroll || state.initialScroll) {
1324
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
1325
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
1326
- finishInitialScroll(ctx, {
1327
- onFinished: () => {
1328
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1329
- },
1330
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1331
- recalculateItems: true,
1332
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
1333
- syncObservedOffset: isOffsetSession,
1334
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1335
- });
1336
- return;
1337
- }
1338
- recalculateSettledScroll(ctx);
1339
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1340
- }
1341
- }
1342
-
1343
- // src/core/doScrollTo.ts
1344
- var SCROLL_END_IDLE_MS = 80;
1345
- var SCROLL_END_MAX_MS = 1500;
1346
- var SMOOTH_SCROLL_DURATION_MS = 320;
1347
- var SCROLL_END_TARGET_EPSILON = 1;
1348
- function doScrollTo(ctx, params) {
1349
- var _a3, _b;
1350
- const state = ctx.state;
1351
- const { animated, horizontal, offset } = params;
1352
- state.scheduledWork.cancel("platformScrollCompletion");
1353
- const scroller = state.refScroller.current;
1354
- const node = scroller == null ? void 0 : scroller.getScrollableNode();
1355
- if (!scroller || !node) {
1356
- return;
1357
- }
1358
- const isAnimated = !!animated;
1359
- const isHorizontal = !!horizontal;
1360
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
1361
- const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
1362
- const top = isHorizontal ? 0 : offset;
1363
- scroller.scrollTo({ animated: isAnimated, x: left, y: top });
1364
- if (isAnimated) {
1365
- const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
1366
- listenForScrollEnd(ctx, {
1367
- readOffset: () => scroller.getCurrentScrollOffset(),
1368
- target,
1369
- targetOffset: offset
1370
- });
1371
- } else {
1372
- state.scroll = offset;
1373
- const targetToken = state.scrollingTo;
1374
- state.scheduledWork.timeout(
1375
- () => {
1376
- if (targetToken === state.scrollingTo) {
1377
- finishScrollTo(ctx);
1378
- }
1379
- },
1380
- 100,
1381
- "platformScrollCompletion"
1382
- );
1383
- }
1384
- }
1385
- function listenForScrollEnd(ctx, params) {
1386
- const { readOffset, target, targetOffset } = params;
1387
- if (!target) {
1388
- finishScrollTo(ctx);
1389
- return;
1390
- }
1391
- const supportsScrollEnd = "onscrollend" in target;
1392
- let idleTimeout;
1393
- let settled = false;
1394
- const { scheduledWork, scrollingTo: targetToken } = ctx.state;
1395
- const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
1396
- const cleanup = () => {
1397
- target.removeEventListener("scroll", onScroll2);
1398
- if (supportsScrollEnd) {
1399
- target.removeEventListener("scrollend", onScrollEnd);
1400
- }
1401
- if (idleTimeout !== void 0) {
1402
- clearTimeout(idleTimeout);
1403
- }
1404
- clearTimeout(maxTimeout);
1405
- };
1406
- const cancel = () => {
1407
- if (!settled) {
1408
- settled = true;
1409
- cleanup();
1410
- }
1411
- };
1412
- const finish = (reason) => {
1413
- if (settled) return;
1414
- if (targetToken !== ctx.state.scrollingTo) {
1415
- scheduledWork.cancel("platformScrollCompletion");
1416
- return;
1417
- }
1418
- const currentOffset = readOffset();
1419
- const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
1420
- if (reason === "scrollend" && !isNearTarget) {
1421
- return;
1422
- }
1423
- scheduledWork.cancel("platformScrollCompletion");
1424
- finishScrollTo(ctx);
1425
- };
1426
- const onScroll2 = () => {
1427
- if (idleTimeout !== void 0) {
1428
- clearTimeout(idleTimeout);
1429
- }
1430
- idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
1431
- };
1432
- const onScrollEnd = () => finish("scrollend");
1433
- target.addEventListener("scroll", onScroll2);
1434
- if (supportsScrollEnd) {
1435
- target.addEventListener("scrollend", onScrollEnd);
1436
- } else {
1437
- idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
1438
- }
1439
- scheduledWork.register("platformScrollCompletion", cancel);
1440
- }
1441
-
1442
1322
  // src/core/scrollRequestTracker.ts
1443
1323
  function getScrollRequestTracker(ctx) {
1444
1324
  if (!ctx.scrollRequestTracker) {
1445
1325
  let currentToken = 0;
1446
- const start = (resolve) => {
1326
+ let waitingToken;
1327
+ const start = (resolve, isInternal = false) => {
1447
1328
  const state = ctx.state;
1329
+ if (!isInternal && (state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd)) {
1330
+ finishMaintainScrollAtEnd(ctx);
1331
+ }
1448
1332
  state.scheduledWork.cancel("imperativeScrollReady");
1449
1333
  const token = ++currentToken;
1450
1334
  settlePendingImperativeScroll(state);
1451
1335
  state.pendingScrollResolve = resolve;
1336
+ waitingToken = token;
1452
1337
  return token;
1453
1338
  };
1454
1339
  const runNow = (token, resolve, run) => {
@@ -1456,6 +1341,7 @@ function getScrollRequestTracker(ctx) {
1456
1341
  if (token !== currentToken) {
1457
1342
  return;
1458
1343
  }
1344
+ waitingToken = void 0;
1459
1345
  const didStartScroll = run();
1460
1346
  if (!didStartScroll || !state.scrollingTo) {
1461
1347
  if (state.pendingScrollResolve === resolve) {
@@ -1466,6 +1352,7 @@ function getScrollRequestTracker(ctx) {
1466
1352
  };
1467
1353
  ctx.scrollRequestTracker = {
1468
1354
  isCurrent: (token) => token === currentToken,
1355
+ isWaitingToRun: () => waitingToken === currentToken && !!ctx.state.pendingScrollResolve,
1469
1356
  runNow,
1470
1357
  runNowIfIdle: (run) => {
1471
1358
  const state = ctx.state;
@@ -1473,7 +1360,7 @@ function getScrollRequestTracker(ctx) {
1473
1360
  return Promise.resolve();
1474
1361
  }
1475
1362
  return new Promise((resolve) => {
1476
- const token = start(resolve);
1363
+ const token = start(resolve, true);
1477
1364
  runNow(token, resolve, run);
1478
1365
  });
1479
1366
  },
@@ -1507,87 +1394,6 @@ function requestAdjust(ctx, positionDiff, source) {
1507
1394
  }
1508
1395
  }
1509
1396
 
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
- }
1526
- function doMaintainScrollAtEnd(ctx) {
1527
- const state = ctx.state;
1528
- const {
1529
- didContainersLayout,
1530
- didFinishInitialScroll,
1531
- pendingNativeMVCPAdjust,
1532
- props: { maintainScrollAtEnd }
1533
- } = state;
1534
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1535
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1536
- const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1537
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1538
- state.pendingMaintainScrollAtEnd = true;
1539
- return false;
1540
- }
1541
- if (pendingNativeMVCPAdjust) {
1542
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1543
- return false;
1544
- }
1545
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1546
- state.pendingMaintainScrollAtEnd = false;
1547
- const contentSize = getContentSize(ctx);
1548
- if (contentSize < state.scrollLength) {
1549
- state.scroll = 0;
1550
- }
1551
- if (!state.maintainingScrollAtEnd) {
1552
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1553
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1554
- const scrollAtRequest = state.scroll;
1555
- state.maintainingScrollAtEnd = pendingState;
1556
- requestAnimationFrame(() => {
1557
- if (state.maintainingScrollAtEnd !== pendingState) {
1558
- return;
1559
- }
1560
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1561
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1562
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1563
- state.maintainingScrollAtEnd = activeState;
1564
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1565
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
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
- });
1578
- } else if (state.maintainingScrollAtEnd === pendingState) {
1579
- finishMaintainScrollAtEnd(ctx);
1580
- }
1581
- });
1582
- } else {
1583
- state.pendingMaintainScrollAtEnd = true;
1584
- }
1585
- return true;
1586
- }
1587
- finishMaintainScrollAtEnd(ctx);
1588
- return false;
1589
- }
1590
-
1591
1397
  // src/core/mvcp.ts
1592
1398
  var MVCP_POSITION_EPSILON = 0.1;
1593
1399
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2025,6 +1831,285 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2025
1831
  }
2026
1832
  }
2027
1833
 
1834
+ // src/core/doMaintainScrollAtEnd.ts
1835
+ function interruptMaintainScrollAtEnd(ctx) {
1836
+ var _a3;
1837
+ const { state } = ctx;
1838
+ const maintaining = state.maintainingScrollAtEnd;
1839
+ const cancelActiveScroll = !!(maintaining === "animated" || maintaining === "instant" || maintaining && ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isScrollToEnd));
1840
+ if (cancelActiveScroll) {
1841
+ cancelImperativeScroll(state);
1842
+ }
1843
+ if (maintaining || state.pendingMaintainScrollAtEnd) {
1844
+ finishMaintainScrollAtEnd(ctx);
1845
+ }
1846
+ return cancelActiveScroll;
1847
+ }
1848
+ function finishMaintainScrollAtEnd(ctx) {
1849
+ var _a3;
1850
+ const { state } = ctx;
1851
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1852
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1853
+ state.maintainingScrollAtEnd = void 0;
1854
+ state.pendingMaintainScrollAtEnd = false;
1855
+ if (currentPadding !== nextPadding) {
1856
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1857
+ state.scrollForNextCalculateItemsInView = void 0;
1858
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1859
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1860
+ requestAdjust(ctx, nextScroll - state.scroll);
1861
+ }
1862
+ }
1863
+ function doMaintainScrollAtEnd(ctx, options) {
1864
+ var _a3, _b, _c, _d, _e, _f;
1865
+ const state = ctx.state;
1866
+ let didMaintain = false;
1867
+ if (state.isDragging) {
1868
+ finishMaintainScrollAtEnd(ctx);
1869
+ } else {
1870
+ const {
1871
+ didContainersLayout,
1872
+ didFinishInitialScroll,
1873
+ pendingNativeMVCPAdjust,
1874
+ props: { maintainScrollAtEnd }
1875
+ } = state;
1876
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1877
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1878
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1879
+ 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);
1880
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1881
+ const scroller = state.refScroller.current;
1882
+ const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1883
+ const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1884
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1885
+ state.pendingMaintainScrollAtEnd = true;
1886
+ state.scrollPending = observed;
1887
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1888
+ }
1889
+ }
1890
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1891
+ state.pendingMaintainScrollAtEnd = true;
1892
+ } else if (pendingNativeMVCPAdjust) {
1893
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1894
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1895
+ if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1896
+ state.pendingMaintainScrollAtEnd = false;
1897
+ ctx.scrollToEnd({ animated: false });
1898
+ didMaintain = true;
1899
+ } else {
1900
+ state.pendingMaintainScrollAtEnd = true;
1901
+ (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1902
+ }
1903
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1904
+ state.pendingMaintainScrollAtEnd = false;
1905
+ const contentSize = getContentSize(ctx);
1906
+ if (contentSize < state.scrollLength) {
1907
+ state.scroll = 0;
1908
+ }
1909
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1910
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1911
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1912
+ const scrollAtRequest = state.scroll;
1913
+ state.maintainingScrollAtEnd = pendingState;
1914
+ const run = () => {
1915
+ if (state.maintainingScrollAtEnd === pendingState) {
1916
+ if (state.isDragging) {
1917
+ finishMaintainScrollAtEnd(ctx);
1918
+ } else {
1919
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1920
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1921
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1922
+ if (state.scrollingTo || state.pendingScrollResolve) {
1923
+ state.pendingMaintainScrollAtEnd = true;
1924
+ } else {
1925
+ state.maintainingScrollAtEnd = activeState;
1926
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1927
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1928
+ );
1929
+ void scrollPromise.then(() => {
1930
+ if (state.maintainingScrollAtEnd === activeState) {
1931
+ if (state.pendingMaintainScrollAtEnd) {
1932
+ state.maintainingScrollAtEnd = void 0;
1933
+ doMaintainScrollAtEnd(ctx);
1934
+ } else {
1935
+ finishMaintainScrollAtEnd(ctx);
1936
+ }
1937
+ }
1938
+ });
1939
+ }
1940
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1941
+ finishMaintainScrollAtEnd(ctx);
1942
+ }
1943
+ }
1944
+ }
1945
+ };
1946
+ if (immediate) {
1947
+ run();
1948
+ } else {
1949
+ requestAnimationFrame(run);
1950
+ }
1951
+ } else {
1952
+ state.pendingMaintainScrollAtEnd = true;
1953
+ }
1954
+ didMaintain = true;
1955
+ } else {
1956
+ finishMaintainScrollAtEnd(ctx);
1957
+ }
1958
+ }
1959
+ return didMaintain;
1960
+ }
1961
+
1962
+ // src/core/finishScrollTo.ts
1963
+ function finishScrollTo(ctx) {
1964
+ var _a3, _b, _c;
1965
+ const state = ctx.state;
1966
+ if (state == null ? void 0 : state.scrollingTo) {
1967
+ cancelScrollCompletionChecks(state);
1968
+ const resolvePendingScroll = ((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) ? void 0 : state.pendingScrollResolve;
1969
+ if (resolvePendingScroll) {
1970
+ state.pendingScrollResolve = void 0;
1971
+ }
1972
+ const scrollingTo = state.scrollingTo;
1973
+ state.scrollHistory.length = 0;
1974
+ state.scrollingTo = void 0;
1975
+ state.scrollTargetPinnedRange = void 0;
1976
+ if (state.pendingTotalSize !== void 0) {
1977
+ addTotalSize(ctx, null, state.pendingTotalSize);
1978
+ }
1979
+ {
1980
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1981
+ }
1982
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
1983
+ const isOffsetSession = ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) === "offset";
1984
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_c = state.initialScroll) == null ? void 0 : _c.viewPosition) === 1;
1985
+ finishInitialScroll(ctx, {
1986
+ onFinished: () => {
1987
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1988
+ },
1989
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1990
+ recalculateItems: true,
1991
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
1992
+ syncObservedOffset: isOffsetSession,
1993
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1994
+ });
1995
+ return;
1996
+ }
1997
+ recalculateSettledScroll(ctx);
1998
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1999
+ if (state.pendingMaintainScrollAtEnd) {
2000
+ state.maintainingScrollAtEnd = void 0;
2001
+ doMaintainScrollAtEnd(ctx);
2002
+ }
2003
+ }
2004
+ }
2005
+
2006
+ // src/core/doScrollTo.ts
2007
+ var SCROLL_END_IDLE_MS = 80;
2008
+ var SCROLL_END_MAX_MS = 1500;
2009
+ var SMOOTH_SCROLL_DURATION_MS = 320;
2010
+ var SCROLL_END_TARGET_EPSILON = 1;
2011
+ function doScrollTo(ctx, params) {
2012
+ var _a3, _b, _c, _d, _e, _f;
2013
+ const state = ctx.state;
2014
+ const { animated, horizontal, offset } = params;
2015
+ state.scheduledWork.cancel("platformScrollCompletion");
2016
+ const scroller = state.refScroller.current;
2017
+ const node = scroller == null ? void 0 : scroller.getScrollableNode();
2018
+ if (!scroller || !node) {
2019
+ return;
2020
+ }
2021
+ const isAnimated = !!animated;
2022
+ const isHorizontal = !!horizontal;
2023
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2024
+ const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
2025
+ const top = isHorizontal ? 0 : offset;
2026
+ scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2027
+ if (isAnimated) {
2028
+ const targetOffset = Math.max(0, Math.min(offset, (_b = (_a3 = scroller.getMaxScrollOffset) == null ? void 0 : _a3.call(scroller)) != null ? _b : offset));
2029
+ if (Math.abs(((_d = (_c = scroller.getCurrentScrollOffset) == null ? void 0 : _c.call(scroller)) != null ? _d : Number.NaN) - targetOffset) <= SCROLL_END_TARGET_EPSILON) {
2030
+ const targetToken = state.scrollingTo;
2031
+ state.scheduledWork.frame(() => {
2032
+ if (targetToken === state.scrollingTo) finishScrollTo(ctx);
2033
+ }, "platformScrollCompletion");
2034
+ return;
2035
+ }
2036
+ const target = (_f = (_e = scroller.getScrollEventTarget) == null ? void 0 : _e.call(scroller)) != null ? _f : null;
2037
+ listenForScrollEnd(ctx, {
2038
+ readOffset: () => scroller.getCurrentScrollOffset(),
2039
+ target,
2040
+ targetOffset
2041
+ });
2042
+ } else {
2043
+ state.scroll = offset;
2044
+ const targetToken = state.scrollingTo;
2045
+ state.scheduledWork.timeout(
2046
+ () => {
2047
+ if (targetToken === state.scrollingTo) {
2048
+ finishScrollTo(ctx);
2049
+ }
2050
+ },
2051
+ 100,
2052
+ "platformScrollCompletion"
2053
+ );
2054
+ }
2055
+ }
2056
+ function listenForScrollEnd(ctx, params) {
2057
+ const { readOffset, target, targetOffset } = params;
2058
+ if (!target) {
2059
+ finishScrollTo(ctx);
2060
+ return;
2061
+ }
2062
+ const supportsScrollEnd = "onscrollend" in target;
2063
+ let idleTimeout;
2064
+ let settled = false;
2065
+ const { scheduledWork, scrollingTo: targetToken } = ctx.state;
2066
+ const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2067
+ const cleanup = () => {
2068
+ target.removeEventListener("scroll", onScroll2);
2069
+ if (supportsScrollEnd) {
2070
+ target.removeEventListener("scrollend", onScrollEnd);
2071
+ }
2072
+ if (idleTimeout !== void 0) {
2073
+ clearTimeout(idleTimeout);
2074
+ }
2075
+ clearTimeout(maxTimeout);
2076
+ };
2077
+ const cancel = () => {
2078
+ if (!settled) {
2079
+ settled = true;
2080
+ cleanup();
2081
+ }
2082
+ };
2083
+ const finish = (reason) => {
2084
+ if (settled) return;
2085
+ if (targetToken !== ctx.state.scrollingTo) {
2086
+ scheduledWork.cancel("platformScrollCompletion");
2087
+ return;
2088
+ }
2089
+ const currentOffset = readOffset();
2090
+ const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2091
+ if (reason === "scrollend" && !isNearTarget) {
2092
+ return;
2093
+ }
2094
+ scheduledWork.cancel("platformScrollCompletion");
2095
+ finishScrollTo(ctx);
2096
+ };
2097
+ const onScroll2 = () => {
2098
+ if (idleTimeout !== void 0) {
2099
+ clearTimeout(idleTimeout);
2100
+ }
2101
+ idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2102
+ };
2103
+ const onScrollEnd = () => finish("scrollend");
2104
+ target.addEventListener("scroll", onScroll2);
2105
+ if (supportsScrollEnd) {
2106
+ target.addEventListener("scrollend", onScrollEnd);
2107
+ } else {
2108
+ idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2109
+ }
2110
+ scheduledWork.register("platformScrollCompletion", cancel);
2111
+ }
2112
+
2028
2113
  // src/core/scrollTo.ts
2029
2114
  function getAverageSizeSnapshot(state) {
2030
2115
  if (Object.keys(state.averageSizes).length === 0) {
@@ -2175,7 +2260,9 @@ function scrollTo(ctx, params) {
2175
2260
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2176
2261
  }
2177
2262
  }
2178
- state.scrollPending = targetOffset;
2263
+ if (!animated || isInitialScroll) {
2264
+ state.scrollPending = targetOffset;
2265
+ }
2179
2266
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2180
2267
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2181
2268
  if (animated) {
@@ -2552,13 +2639,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2552
2639
  queueBootstrapInitialScrollReevaluation(ctx.state);
2553
2640
  }
2554
2641
  function createInitialScrollAtEndTarget(options) {
2555
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2642
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2556
2643
  return {
2557
2644
  contentOffset: void 0,
2558
- index: Math.max(0, dataLength - 1),
2645
+ index: index != null ? index : Math.max(0, dataLength - 1),
2559
2646
  preserveForBottomPadding: true,
2560
2647
  preserveForFooterLayout,
2561
- viewOffset: -stylePaddingEnd - footerSize,
2648
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2562
2649
  viewPosition: 1
2563
2650
  };
2564
2651
  }
@@ -2574,13 +2661,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2574
2661
  function createRetargetedBottomAlignedInitialScroll(options) {
2575
2662
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2576
2663
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2664
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2577
2665
  return {
2578
2666
  ...target,
2579
2667
  contentOffset: void 0,
2580
2668
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2581
2669
  preserveForBottomPadding: true,
2582
2670
  preserveForFooterLayout,
2583
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2671
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2584
2672
  viewPosition: 1
2585
2673
  };
2586
2674
  }
@@ -2594,7 +2682,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2594
2682
  }
2595
2683
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2596
2684
  dataLength,
2597
- footerSize: 0,
2685
+ footerSize: peek$(ctx, "footerSize") || 0,
2598
2686
  preserveForFooterLayout: void 0,
2599
2687
  stylePaddingEnd
2600
2688
  });
@@ -2617,13 +2705,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2617
2705
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2618
2706
  }
2619
2707
  function getPreservedEndAnchorOffsetDiff(ctx) {
2620
- var _a3;
2621
2708
  const state = ctx.state;
2622
2709
  const initialScroll = state.initialScroll;
2623
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2710
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2624
2711
  return;
2625
2712
  }
2626
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2713
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
2627
2714
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2628
2715
  }
2629
2716
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2651,6 +2738,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2651
2738
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2652
2739
  if (hasObservedNativeScrollAfterRequest) {
2653
2740
  activeCorrection.lastRequestTime = Date.now();
2741
+ {
2742
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2743
+ }
2654
2744
  requestAdjust(ctx, offsetDiff);
2655
2745
  }
2656
2746
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2773,10 +2863,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2773
2863
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2774
2864
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2775
2865
  const state = ctx.state;
2776
- if (!initialScrollAtEnd) {
2866
+ const initialScroll = state.initialScroll;
2867
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2777
2868
  return;
2778
2869
  }
2779
- const initialScroll = state.initialScroll;
2780
2870
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2781
2871
  return;
2782
2872
  }
@@ -2794,6 +2884,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2794
2884
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2795
2885
  dataLength,
2796
2886
  footerSize,
2887
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
2797
2888
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2798
2889
  stylePaddingEnd
2799
2890
  });
@@ -3220,6 +3311,11 @@ function handleInitialScrollDataChange(ctx, options) {
3220
3311
  const state = ctx.state;
3221
3312
  const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3222
3313
  const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3314
+ if (didStartFreshData) {
3315
+ cancelImperativeScroll(state);
3316
+ state.maintainingScrollAtEnd = void 0;
3317
+ state.pendingMaintainScrollAtEnd = false;
3318
+ }
3223
3319
  if (dataLength > 0) {
3224
3320
  state.hasHadNonEmptyData = true;
3225
3321
  }
@@ -3938,7 +4034,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
3938
4034
  }
3939
4035
 
3940
4036
  // src/utils/findAvailableContainers.ts
3941
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
4037
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
3942
4038
  var _a3;
3943
4039
  const numNeeded = needNewContainers.length;
3944
4040
  if (numNeeded === 0) {
@@ -3947,16 +4043,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3947
4043
  const numContainers = peek$(ctx, "numContainers");
3948
4044
  const state = ctx.state;
3949
4045
  const { containerItemMetadata, stickyContainerPool } = state;
4046
+ const { data, getItemType } = state.props;
4047
+ const hasItemTypes = !!getItemType;
3950
4048
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
3951
4049
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
3952
- const requests = needNewContainers.map((itemIndex, order) => ({
3953
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
3954
- itemIndex,
3955
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
3956
- order
3957
- }));
3958
- const normalRequests = requests.filter((request) => !request.isSticky);
3959
- const stickyRequests = requests.filter((request) => request.isSticky);
4050
+ const requests = new Array(numNeeded);
4051
+ const normalRequests = [];
4052
+ const stickyRequests = [];
4053
+ for (let order = 0; order < numNeeded; order++) {
4054
+ const itemIndex = needNewContainers[order];
4055
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
4056
+ const request = {
4057
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4058
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4059
+ itemIndex,
4060
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4061
+ order,
4062
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4063
+ };
4064
+ requests[order] = request;
4065
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4066
+ }
3960
4067
  const normalCandidates = [];
3961
4068
  const stickyCandidates = [];
3962
4069
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -4012,7 +4119,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4012
4119
  }
4013
4120
  };
4014
4121
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
4015
- if (getRequiredItemType) {
4122
+ if (hasItemTypes) {
4016
4123
  assignMatching(
4017
4124
  pendingRequests,
4018
4125
  candidates,
@@ -4036,6 +4143,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4036
4143
  }
4037
4144
  assign(request, containerIndex);
4038
4145
  }
4146
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4147
+ const containerIndices = [];
4148
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4149
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4150
+ }
4039
4151
  if (pendingRemovalChanged) {
4040
4152
  pendingRemoval.length = 0;
4041
4153
  if (pendingRemovalSet) {
@@ -4065,12 +4177,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4065
4177
  function comparatorByDistance(a, b) {
4066
4178
  return b.distance - a.distance;
4067
4179
  }
4180
+ function comparatorByRenderPriority(a, b) {
4181
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4182
+ }
4183
+ function comparatorByIndex(a, b) {
4184
+ return a - b;
4185
+ }
4186
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4187
+ if (requests.length >= 2) {
4188
+ if (groupByType) {
4189
+ const groups = /* @__PURE__ */ new Map();
4190
+ for (const request of requests) {
4191
+ const group = groups.get(request.itemType);
4192
+ if (group) {
4193
+ group.push(request);
4194
+ } else {
4195
+ groups.set(request.itemType, [request]);
4196
+ }
4197
+ }
4198
+ for (const group of groups.values()) {
4199
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4200
+ }
4201
+ } else {
4202
+ containerIndices.length = 0;
4203
+ let indicesSorted = true;
4204
+ for (let i = 0; i < requests.length; i++) {
4205
+ const containerIndex = allocations[requests[i].order].containerIndex;
4206
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4207
+ indicesSorted = false;
4208
+ }
4209
+ containerIndices.push(containerIndex);
4210
+ }
4211
+ if (!indicesSorted) {
4212
+ containerIndices.sort(comparatorByIndex);
4213
+ }
4214
+ requests.sort(comparatorByRenderPriority);
4215
+ for (let i = 0; i < requests.length; i++) {
4216
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4217
+ }
4218
+ }
4219
+ }
4220
+ }
4068
4221
 
4069
4222
  // src/utils/setDidLayout.ts
4070
4223
  function setDidLayout(ctx) {
4071
4224
  const state = ctx.state;
4072
4225
  state.queuedInitialLayout = true;
4073
- checkAtBottom(ctx);
4226
+ checkThresholds(ctx);
4074
4227
  setInitialRenderState(ctx, { didLayout: true });
4075
4228
  if (state.pendingMaintainScrollAtEnd) {
4076
4229
  doMaintainScrollAtEnd(ctx);
@@ -4298,9 +4451,9 @@ function calculateItemsInView(ctx, params = {}) {
4298
4451
  enableScrollForNextCalculateItemsInView,
4299
4452
  idCache,
4300
4453
  indexByKey,
4301
- minIndexSizeChanged,
4454
+ positionRecalculationStartIndex,
4302
4455
  positions,
4303
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4456
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4304
4457
  scrollForNextCalculateItemsInView,
4305
4458
  scrollLength,
4306
4459
  sizes,
@@ -4416,21 +4569,21 @@ function calculateItemsInView(ctx, params = {}) {
4416
4569
  if (dataChanged) {
4417
4570
  resetLayoutCachesForDataChange(state);
4418
4571
  }
4419
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4420
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4572
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4573
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4421
4574
  updateItemPositions(ctx, dataChanged, {
4422
4575
  doMVCP,
4423
4576
  // A changed size shifts every following position. The scrolling early-exit
4424
4577
  // would leave the untouched suffix in the old coordinate space.
4425
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4578
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4426
4579
  optimizeForVisibleWindow,
4427
4580
  scrollBottomBuffered,
4428
4581
  scrollVelocity: speed,
4429
4582
  startIndex
4430
4583
  });
4431
4584
  totalSize = getContentSize(ctx);
4432
- if (minIndexSizeChanged !== void 0) {
4433
- state.minIndexSizeChanged = void 0;
4585
+ if (positionRecalculationStartIndex !== void 0) {
4586
+ state.positionRecalculationStartIndex = void 0;
4434
4587
  }
4435
4588
  let protectedContainerKeys;
4436
4589
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4606,18 +4759,18 @@ function calculateItemsInView(ctx, params = {}) {
4606
4759
  set$(ctx, "activeStickyIndex", -1);
4607
4760
  }
4608
4761
  if (needNewContainers.length > 0) {
4609
- const getRequiredItemType = getItemType ? (i) => {
4610
- const itemType = getItemType(data[i], i);
4611
- return itemType !== void 0 ? String(itemType) : "";
4612
- } : void 0;
4613
4762
  const availableContainerAllocations = findAvailableContainers(
4614
4763
  ctx,
4615
4764
  needNewContainers,
4616
4765
  startBuffered,
4617
4766
  endBuffered,
4618
4767
  pendingRemoval,
4619
- getRequiredItemType,
4620
- protectedContainerKeys
4768
+ protectedContainerKeys,
4769
+ // Initial end alignment has no scroll velocity yet. Keep its
4770
+ // target-side rows first until the initial scroll settles.
4771
+ // After a pause velocity can be zero on the first movement;
4772
+ // use its displacement without treating initial insets as motion.
4773
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4621
4774
  );
4622
4775
  for (const allocation of availableContainerAllocations) {
4623
4776
  const i = allocation.itemIndex;
@@ -4764,6 +4917,7 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4764
4917
  const nextAnchorIndex = anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex;
4765
4918
  let nextAnchorKey;
4766
4919
  let isReady = true;
4920
+ let canUpdateSize = true;
4767
4921
  let nextSize = 0;
4768
4922
  if (anchoredEndSpace) {
4769
4923
  const { anchorIndex, anchorMaxSize, anchorOffset = 0 } = anchoredEndSpace;
@@ -4785,25 +4939,30 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4785
4939
  contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap);
4786
4940
  contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props);
4787
4941
  isReady = !hasUnknownTailSize;
4788
- nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4942
+ const knownSizeBound = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4943
+ nextSize = hasUnknownTailSize ? Math.min(previousSize || 0, knownSizeBound) : knownSizeBound;
4789
4944
  } else if (anchorIndex >= 0) {
4790
4945
  isReady = false;
4946
+ canUpdateSize = false;
4791
4947
  }
4792
4948
  }
4793
4949
  const didSizeChange = previousSize !== nextSize && (previousSize !== void 0 || anchoredEndSpace !== void 0);
4794
4950
  const didEffectiveSizeChange = (previousSize || 0) !== nextSize;
4951
+ const canApplySizeChange = canUpdateSize && (isReady || previousSize !== void 0);
4795
4952
  const didReadyAnchorChange = previousReadyAnchorIndex !== nextAnchorIndex || previousReadyAnchorKey !== nextAnchorKey;
4796
- if (isReady && (didSizeChange || didReadyAnchorChange)) {
4953
+ const didBecomeReady = isReady && state.anchoredEndSpacePendingReady;
4954
+ state.anchoredEndSpacePendingReady = !isReady;
4955
+ if (canApplySizeChange && didSizeChange) {
4956
+ set$(ctx, "anchoredEndSpaceSize", nextSize);
4957
+ (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4958
+ }
4959
+ if (canApplySizeChange && didEffectiveSizeChange) {
4960
+ updateContentMetricsState(ctx);
4961
+ updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4962
+ }
4963
+ if (isReady && (didSizeChange || didReadyAnchorChange || didBecomeReady)) {
4797
4964
  state.anchoredEndSpaceReadyAnchorIndex = nextAnchorIndex;
4798
4965
  state.anchoredEndSpaceReadyAnchorKey = nextAnchorKey;
4799
- if (didSizeChange) {
4800
- set$(ctx, "anchoredEndSpaceSize", nextSize);
4801
- (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4802
- }
4803
- if (didEffectiveSizeChange) {
4804
- updateContentMetricsState(ctx);
4805
- updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4806
- }
4807
4966
  (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize });
4808
4967
  }
4809
4968
  return nextSize;
@@ -4894,6 +5053,7 @@ function updateItemSizesBatch(ctx, measurements) {
4894
5053
  var _a3;
4895
5054
  const state = ctx.state;
4896
5055
  const result = {};
5056
+ const previousTotalSize = state.totalSize;
4897
5057
  for (const measurement of measurements) {
4898
5058
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4899
5059
  if (ownsMeasuredItem) {
@@ -4904,6 +5064,9 @@ function updateItemSizesBatch(ctx, measurements) {
4904
5064
  mergeItemSizeUpdateResult(result, nextResult);
4905
5065
  }
4906
5066
  }
5067
+ if (state.totalSize !== previousTotalSize) {
5068
+ set$(ctx, "totalSize", state.totalSize);
5069
+ }
4907
5070
  flushItemSizeUpdates(ctx, result);
4908
5071
  }
4909
5072
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -4942,12 +5105,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4942
5105
  }
4943
5106
  let needsRecalculate = !didContainersLayout;
4944
5107
  let shouldMaintainScrollAtEnd = false;
4945
- let minIndexSizeChanged;
5108
+ let positionRecalculationStartIndex;
4946
5109
  const prevSizeKnown = state.sizesKnown.get(itemKey);
4947
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5110
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
4948
5111
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
4949
5112
  if (diff !== 0) {
4950
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5113
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
4951
5114
  const { startBuffered, endBuffered } = state;
4952
5115
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
4953
5116
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -4964,8 +5127,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4964
5127
  size
4965
5128
  });
4966
5129
  }
4967
- if (minIndexSizeChanged !== void 0) {
4968
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5130
+ if (positionRecalculationStartIndex !== void 0) {
5131
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
4969
5132
  }
4970
5133
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
4971
5134
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -4982,7 +5145,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4982
5145
  didMeasureUserScrollAnchorResetItem
4983
5146
  };
4984
5147
  }
4985
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5148
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
4986
5149
  var _a3, _b;
4987
5150
  const state = ctx.state;
4988
5151
  const {
@@ -5005,7 +5168,16 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5005
5168
  fixedItemSize,
5006
5169
  itemType
5007
5170
  } : void 0;
5008
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5171
+ const prevSize = getItemSize(
5172
+ ctx,
5173
+ itemKey,
5174
+ index,
5175
+ itemData,
5176
+ void 0,
5177
+ void 0,
5178
+ notifyTotalSize,
5179
+ resolvedItemSize
5180
+ );
5009
5181
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
5010
5182
  const prevSizeKnown = sizesKnown.get(itemKey);
5011
5183
  const size = Math.round(rawSize) ;
@@ -5027,7 +5199,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5027
5199
  }
5028
5200
  }
5029
5201
  if (!prevSize || Math.abs(prevSize - size) > 0.1) {
5030
- setSize(ctx, itemKey, size);
5202
+ setSize(ctx, itemKey, size, notifyTotalSize);
5031
5203
  return size - prevSize;
5032
5204
  }
5033
5205
  return 0;
@@ -5803,7 +5975,9 @@ function sortDOMElements(container, indexByElement) {
5803
5975
  break;
5804
5976
  }
5805
5977
  }
5806
- if (nextStableElement) {
5978
+ if ("moveBefore" in container && typeof container.moveBefore === "function" && container.isConnected) {
5979
+ container.moveBefore(element, nextStableElement);
5980
+ } else if (nextStableElement) {
5807
5981
  container.insertBefore(element, nextStableElement);
5808
5982
  } else {
5809
5983
  container.appendChild(element);
@@ -5850,13 +6024,18 @@ function findLIS(arr) {
5850
6024
  // src/hooks/useDOMOrder.ts
5851
6025
  function useDOMOrder(ref) {
5852
6026
  const ctx = useStateContext();
5853
- const debounceRef = React3.useRef(void 0);
5854
6027
  React3.useEffect(() => {
6028
+ let timeoutId;
6029
+ let lastUpdateTime = 0;
6030
+ let delay = 500;
5855
6031
  const unsubscribe = listen$(ctx, "lastPositionUpdate", () => {
5856
- if (debounceRef.current !== void 0) {
5857
- clearTimeout(debounceRef.current);
5858
- }
5859
- debounceRef.current = setTimeout(() => {
6032
+ const container = ref.current;
6033
+ const now = Date.now();
6034
+ 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;
6035
+ lastUpdateTime = now;
6036
+ clearTimeout(timeoutId);
6037
+ timeoutId = setTimeout(() => {
6038
+ timeoutId = void 0;
5860
6039
  const parent = ref.current;
5861
6040
  if (parent) {
5862
6041
  const indexByElement = /* @__PURE__ */ new Map();
@@ -5869,14 +6048,11 @@ function useDOMOrder(ref) {
5869
6048
  }
5870
6049
  sortDOMElements(parent, indexByElement);
5871
6050
  }
5872
- debounceRef.current = void 0;
5873
- }, 500);
6051
+ }, delay);
5874
6052
  });
5875
6053
  return () => {
5876
6054
  unsubscribe();
5877
- if (debounceRef.current !== void 0) {
5878
- clearTimeout(debounceRef.current);
5879
- }
6055
+ clearTimeout(timeoutId);
5880
6056
  };
5881
6057
  }, [ctx]);
5882
6058
  }
@@ -6246,6 +6422,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6246
6422
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6247
6423
  },
6248
6424
  getCurrentScrollOffset,
6425
+ getMaxScrollOffset,
6249
6426
  getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6250
6427
  getScrollEventTarget: () => getScrollTarget(),
6251
6428
  getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
@@ -6351,6 +6528,22 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6351
6528
  scrollEventCoalescer.cancel();
6352
6529
  };
6353
6530
  }, [emitScrollEnd, getScrollTarget, handleScroll, scrollEventCoalescer]);
6531
+ React3.useEffect(() => {
6532
+ const target = getScrollTarget();
6533
+ if (!target) return;
6534
+ const onWheel = (nativeEvent) => {
6535
+ const event = nativeEvent;
6536
+ const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6537
+ if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6538
+ const offset = getCurrentScrollOffset();
6539
+ if (interruptMaintainScrollAtEnd(ctx)) {
6540
+ scrollToLocalOffset(offset, false);
6541
+ }
6542
+ }
6543
+ };
6544
+ target.addEventListener("wheel", onWheel, { passive: true });
6545
+ return () => target.removeEventListener("wheel", onWheel);
6546
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6354
6547
  React3.useEffect(() => {
6355
6548
  const doScroll = () => {
6356
6549
  if (contentOffset) {
@@ -6560,8 +6753,11 @@ function ScrollAdjust() {
6560
6753
  window.getComputedStyle(contentNode)[axis.paddingEndProp]
6561
6754
  );
6562
6755
  const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`;
6563
- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd };
6564
6756
  contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd;
6757
+ temporaryPaddingRef.current = {
6758
+ baseline: baselinePaddingEnd,
6759
+ value: contentNode.style[axis.paddingEndProp]
6760
+ };
6565
6761
  void contentNode.offsetHeight;
6566
6762
  scrollBy();
6567
6763
  if (resetPaddingRafRef.current !== void 0) {
@@ -6704,6 +6900,7 @@ var ListComponent = typedMemo(function ListComponent2({
6704
6900
  renderScrollComponent,
6705
6901
  onLayoutFooter,
6706
6902
  onInternalScrollBeginDrag,
6903
+ onInternalScrollEndDrag,
6707
6904
  onInternalScrollEnd,
6708
6905
  scrollAdjustHandler,
6709
6906
  snapToIndices,
@@ -6894,7 +7091,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6894
7091
  for (const key in state.averageSizes) {
6895
7092
  delete state.averageSizes[key];
6896
7093
  }
6897
- state.minIndexSizeChanged = 0;
7094
+ state.positionRecalculationStartIndex = 0;
6898
7095
  state.scrollForNextCalculateItemsInView = void 0;
6899
7096
  }
6900
7097
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -7062,7 +7259,7 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7062
7259
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7063
7260
  }
7064
7261
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
7065
- doMaintainScrollAtEnd(ctx);
7262
+ doMaintainScrollAtEnd(ctx, { immediate: true });
7066
7263
  }
7067
7264
  checkThresholds(ctx);
7068
7265
  if (state) {
@@ -7291,14 +7488,17 @@ function scrollToEnd(ctx, options) {
7291
7488
  if (index === -1) {
7292
7489
  return false;
7293
7490
  }
7294
- const paddingBottom = state.props.stylePaddingBottom || 0;
7491
+ const paddingBottom = getStylePaddingEnd(state.props);
7295
7492
  const footerSize = peek$(ctx, "footerSize") || 0;
7296
7493
  scrollToIndex(ctx, {
7297
7494
  ...options,
7298
7495
  index,
7299
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7496
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
7300
7497
  viewPosition: 1
7301
7498
  });
7499
+ if (state.scrollingTo) {
7500
+ state.scrollingTo.isScrollToEnd = true;
7501
+ }
7302
7502
  return true;
7303
7503
  }
7304
7504
 
@@ -7480,7 +7680,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7480
7680
  for (const key in state.averageSizes) {
7481
7681
  delete state.averageSizes[key];
7482
7682
  }
7483
- state.minIndexSizeChanged = 0;
7683
+ state.positionRecalculationStartIndex = 0;
7484
7684
  state.scrollForNextCalculateItemsInView = void 0;
7485
7685
  state.pendingTotalSize = void 0;
7486
7686
  state.totalSize = 0;
@@ -7874,10 +8074,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7874
8074
  onMetricsChange,
7875
8075
  onLayout: onLayoutProp,
7876
8076
  onLoad,
8077
+ onReady,
7877
8078
  onMomentumScrollEnd,
7878
8079
  onRefresh,
7879
8080
  onScroll: onScrollProp,
7880
8081
  onScrollBeginDrag,
8082
+ onScrollEndDrag,
7881
8083
  onStartReached,
7882
8084
  onStartReachedThreshold = 0.5,
7883
8085
  onStickyHeaderChange,
@@ -7946,17 +8148,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7946
8148
  const initialScrollProp = initialScrollAtEnd ? {
7947
8149
  index: Math.max(0, dataProp.length - 1),
7948
8150
  preserveForBottomPadding: true,
7949
- viewOffset: -stylePaddingEndState,
8151
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
7950
8152
  viewPosition: 1
7951
8153
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7952
8154
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7953
8155
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7954
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
8156
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7955
8157
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7956
8158
  } : {
7957
8159
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7958
8160
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7959
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
8161
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7960
8162
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7961
8163
  } : initialScrollUsesOffsetOnly ? {
7962
8164
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -8016,6 +8218,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8016
8218
  endReachedSnapshot: void 0,
8017
8219
  firstFullyOnScreenIndex: -1,
8018
8220
  freshDataTransitionEpoch: 0,
8221
+ handledDataChangeEpoch: 0,
8222
+ handledFreshDataTransitionEpoch: 0,
8019
8223
  hasHadNonEmptyData: dataProp.length > 0,
8020
8224
  idCache: [],
8021
8225
  idsInView: [],
@@ -8032,11 +8236,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8032
8236
  lastLayout: void 0,
8033
8237
  lastScrollDelta: 0,
8034
8238
  loadStartTime: Date.now(),
8035
- minIndexSizeChanged: 0,
8036
8239
  nativeContentInset: void 0,
8037
8240
  nativeMarginTop: 0,
8038
8241
  pendingDataComparison: void 0,
8039
8242
  pendingNativeMVCPAdjust: void 0,
8243
+ positionRecalculationStartIndex: 0,
8040
8244
  positions: [],
8041
8245
  props: {},
8042
8246
  queuedCalculateItemsInView: 0,
@@ -8075,8 +8279,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8075
8279
  refState.current = ctx.state;
8076
8280
  }
8077
8281
  const state = refState.current;
8078
- const handledDataChangeEpochRef = React3.useRef(state.dataChangeEpoch);
8079
- const handledFreshDataTransitionEpochRef = React3.useRef(state.freshDataTransitionEpoch);
8080
8282
  const isFirstLocal = state.isFirst;
8081
8283
  const previousAdaptiveRender = state.props.adaptiveRender;
8082
8284
  const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
@@ -8141,8 +8343,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8141
8343
  onItemSizeChanged,
8142
8344
  onLoad,
8143
8345
  onMomentumScrollEnd,
8346
+ onReady,
8144
8347
  onScroll: throttleScrollFn,
8145
8348
  onScrollBeginDrag,
8349
+ onScrollEndDrag,
8146
8350
  onStartReached,
8147
8351
  onStartReachedThreshold,
8148
8352
  onStickyHeaderChange,
@@ -8226,10 +8430,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8226
8430
  useDevChecks(props);
8227
8431
  }
8228
8432
  React3.useLayoutEffect(() => {
8229
- const didDataChange = handledDataChangeEpochRef.current !== dataChangeEpoch;
8230
- const didStartFreshData = handledFreshDataTransitionEpochRef.current !== freshDataTransitionEpoch;
8231
- handledDataChangeEpochRef.current = dataChangeEpoch;
8232
- handledFreshDataTransitionEpochRef.current = freshDataTransitionEpoch;
8433
+ const didDataChange = state.handledDataChangeEpoch !== dataChangeEpoch;
8434
+ const didStartFreshData = state.handledFreshDataTransitionEpoch !== freshDataTransitionEpoch;
8435
+ state.handledDataChangeEpoch = dataChangeEpoch;
8436
+ state.handledFreshDataTransitionEpoch = freshDataTransitionEpoch;
8233
8437
  if (didStartFreshData) {
8234
8438
  resetInitialRenderState(ctx, {
8235
8439
  resetInitialScroll: !!initialScrollProp,
@@ -8430,17 +8634,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8430
8634
  onScroll: (event) => onScroll(ctx, event),
8431
8635
  onScrollBeginDrag: (event) => {
8432
8636
  var _a4, _b2;
8433
- const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8434
- if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8435
- cancelImperativeScroll(state);
8436
- }
8437
- if (maintainingScrollAtEnd) {
8438
- finishMaintainScrollAtEnd(ctx);
8439
- }
8637
+ state.isDragging = true;
8638
+ interruptMaintainScrollAtEnd(ctx);
8440
8639
  prepareReachedEdgeForNextUserScroll(ctx);
8441
8640
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8442
8641
  },
8443
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
8642
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
8643
+ onScrollEndDrag: (event) => {
8644
+ var _a4, _b2;
8645
+ state.isDragging = false;
8646
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8647
+ }
8444
8648
  }),
8445
8649
  []
8446
8650
  );
@@ -8464,6 +8668,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8464
8668
  ListHeaderComponent,
8465
8669
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
8466
8670
  onInternalScrollEnd: fns.onScrollEnd,
8671
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
8467
8672
  onLayout,
8468
8673
  onLayoutFooter,
8469
8674
  onMomentumScrollEnd: fns.onMomentumScrollEnd,