@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-native.js CHANGED
@@ -458,6 +458,14 @@ var EDGE_POSITION_EPSILON = 1;
458
458
  var ENABLE_DEVMODE = IS_DEV && false;
459
459
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
460
460
 
461
+ // src/core/endOfContentTarget.ts
462
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
463
+ return -stylePaddingEnd - footerSize;
464
+ }
465
+ function isEndOfContentTarget(target, dataLength) {
466
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
467
+ }
468
+
461
469
  // src/core/cancelImperativeScroll.ts
462
470
  function cancelScrollCompletionChecks({ scheduledWork }) {
463
471
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -642,26 +650,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
642
650
  scrollPosition: context.scrollPosition
643
651
  });
644
652
  };
653
+ let reached = !!wasReached;
645
654
  if (!wasReached) {
646
- if (!within) {
647
- return false;
648
- }
649
- onReached(distance);
650
- updateSnapshot();
651
- return true;
652
- }
653
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
654
- if (reset) {
655
- setSnapshot(void 0);
656
- return false;
657
- }
658
- if (within) {
659
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
660
- if (changed) {
655
+ if (within && onReached(distance)) {
661
656
  updateSnapshot();
657
+ reached = true;
658
+ }
659
+ } else {
660
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
661
+ if (reset) {
662
+ setSnapshot(void 0);
663
+ reached = false;
664
+ } else if (within) {
665
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
666
+ if (changed) {
667
+ updateSnapshot();
668
+ }
662
669
  }
663
670
  }
664
- return true;
671
+ return reached;
665
672
  };
666
673
 
667
674
  // src/utils/edgeReachedGate.ts
@@ -708,7 +715,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
708
715
  return void 0;
709
716
  }
710
717
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
711
- state.edgeReachedGate = "closed";
712
718
  resetEdgeLatch(ctx, allowedEdge);
713
719
  return allowedEdge;
714
720
  }
@@ -749,7 +755,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
749
755
  set$(
750
756
  ctx,
751
757
  "isWithinMaintainScrollAtEndThreshold",
752
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
758
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
753
759
  );
754
760
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
755
761
  if (!shouldSkipThresholdChecks) {
@@ -765,11 +771,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
765
771
  scrollPosition: scroll
766
772
  },
767
773
  (distance) => {
768
- var _a4, _b;
769
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
774
+ let dispatched = false;
775
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
770
776
  markReachedEdge(ctx);
771
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
777
+ state.props.onEndReached({ distanceFromEnd: distance });
778
+ dispatched = true;
772
779
  }
780
+ return dispatched;
773
781
  },
774
782
  (snapshot) => {
775
783
  state.endReachedSnapshot = snapshot;
@@ -802,7 +810,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
802
810
  }
803
811
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
804
812
  set$(ctx, "isNearStart", scroll <= threshold);
805
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
813
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
806
814
  if (!shouldSkipThresholdChecks) {
807
815
  state.isStartReached = checkThreshold(
808
816
  scroll,
@@ -816,11 +824,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
816
824
  scrollPosition: scroll
817
825
  },
818
826
  (distance) => {
819
- var _a3, _b;
820
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
827
+ let dispatched = false;
828
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
821
829
  markReachedEdge(ctx);
822
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
830
+ state.props.onStartReached({ distanceFromStart: distance });
831
+ dispatched = true;
823
832
  }
833
+ return dispatched;
824
834
  },
825
835
  (snapshot) => {
826
836
  state.startReachedSnapshot = snapshot;
@@ -939,6 +949,11 @@ function resetInitialRenderState(ctx, {
939
949
  if (resetLayout) {
940
950
  state.didContainersLayout = false;
941
951
  state.queuedInitialLayout = false;
952
+ state.edgeReachedGate = void 0;
953
+ state.isStartReached = null;
954
+ state.isEndReached = null;
955
+ state.startReachedSnapshot = void 0;
956
+ state.endReachedSnapshot = void 0;
942
957
  }
943
958
  if (resetInitialScroll) {
944
959
  state.didFinishInitialScroll = false;
@@ -953,7 +968,7 @@ function setInitialRenderState(ctx, {
953
968
  const { state } = ctx;
954
969
  const {
955
970
  loadStartTime,
956
- props: { onLoad }
971
+ props: { onLoad, onReady }
957
972
  } = state;
958
973
  if (didLayout) {
959
974
  state.didContainersLayout = true;
@@ -974,6 +989,7 @@ function setInitialRenderState(ctx, {
974
989
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
975
990
  }
976
991
  }
992
+ onReady == null ? void 0 : onReady();
977
993
  }
978
994
  }
979
995
 
@@ -1014,6 +1030,7 @@ function supersedeInitialScroll(ctx) {
1014
1030
  function finishInitialScroll(ctx, options) {
1015
1031
  var _a3, _b, _c;
1016
1032
  const state = ctx.state;
1033
+ const freshDataTransitionEpoch = state.freshDataTransitionEpoch;
1017
1034
  if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
1018
1035
  syncInitialScrollOffset(state, options.resolvedOffset);
1019
1036
  } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
@@ -1023,9 +1040,13 @@ function finishInitialScroll(ctx, options) {
1023
1040
  }
1024
1041
  }
1025
1042
  const complete = () => {
1026
- var _a4, _b2, _c2, _d, _e;
1027
- const shouldReleaseDeferredPublicOnScroll = Platform.OS === "web" && ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
1028
- const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
1043
+ var _a4, _b2, _c2, _d, _e, _f;
1044
+ if (freshDataTransitionEpoch !== state.freshDataTransitionEpoch) {
1045
+ (_a4 = options == null ? void 0 : options.onFinished) == null ? void 0 : _a4.call(options);
1046
+ return;
1047
+ }
1048
+ const shouldReleaseDeferredPublicOnScroll = Platform.OS === "web" && ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap";
1049
+ const finalScrollOffset = (_e = (_d = (_c2 = options == null ? void 0 : options.resolvedOffset) != null ? _c2 : state.scrollPending) != null ? _d : state.scroll) != null ? _e : 0;
1029
1050
  initialScrollWatchdog.clear(state);
1030
1051
  if ((options == null ? void 0 : options.preserveTarget) && state.initialScroll) {
1031
1052
  state.clearPreservedInitialScrollOnNextFinish = void 0;
@@ -1054,7 +1075,7 @@ function finishInitialScroll(ctx, options) {
1054
1075
  if (shouldReleaseDeferredPublicOnScroll) {
1055
1076
  releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1056
1077
  }
1057
- (_e = options == null ? void 0 : options.onFinished) == null ? void 0 : _e.call(options);
1078
+ (_f = options == null ? void 0 : options.onFinished) == null ? void 0 : _f.call(options);
1058
1079
  };
1059
1080
  if (options == null ? void 0 : options.waitForCompletionFrame) {
1060
1081
  requestAnimationFrame(complete);
@@ -1314,10 +1335,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1314
1335
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1315
1336
  const trailingInset = getContentInsetEnd(ctx);
1316
1337
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1317
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1318
- const footerSize = peek$(ctx, "footerSize") || 0;
1319
- offset += footerSize;
1320
- }
1321
1338
  }
1322
1339
  return offset;
1323
1340
  }
@@ -1338,246 +1355,21 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1338
1355
  return clampedOffset;
1339
1356
  }
1340
1357
 
1341
- // src/core/finishScrollTo.ts
1342
- function finishScrollTo(ctx) {
1343
- var _a3, _b;
1344
- const state = ctx.state;
1345
- if (state == null ? void 0 : state.scrollingTo) {
1346
- cancelScrollCompletionChecks(state);
1347
- const resolvePendingScroll = state.pendingScrollResolve;
1348
- state.pendingScrollResolve = void 0;
1349
- const scrollingTo = state.scrollingTo;
1350
- state.scrollHistory.length = 0;
1351
- state.scrollingTo = void 0;
1352
- state.scrollTargetPinnedRange = void 0;
1353
- if (state.pendingTotalSize !== void 0) {
1354
- addTotalSize(ctx, null, state.pendingTotalSize);
1355
- }
1356
- if (PlatformAdjustBreaksScroll) {
1357
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1358
- }
1359
- if (scrollingTo.isInitialScroll || state.initialScroll) {
1360
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
1361
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
1362
- finishInitialScroll(ctx, {
1363
- onFinished: () => {
1364
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1365
- },
1366
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1367
- recalculateItems: true,
1368
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
1369
- syncObservedOffset: isOffsetSession,
1370
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1371
- });
1372
- return;
1373
- }
1374
- recalculateSettledScroll(ctx);
1375
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1376
- }
1377
- }
1378
-
1379
- // src/core/checkFinishedScroll.ts
1380
- var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
1381
- var INITIAL_SCROLL_COMPLETION_TARGET_EPSILON = 1;
1382
- var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
1383
- var SILENT_INITIAL_SCROLL_RETRY_DELAY_MS = 16;
1384
- var SILENT_INITIAL_SCROLL_TARGET_EPSILON = 1;
1385
- function checkFinishedScroll(ctx, options) {
1386
- const scrollingTo = ctx.state.scrollingTo;
1387
- if (options == null ? void 0 : options.onlyIfAligned) {
1388
- if (!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || scrollingTo.animated) {
1389
- return;
1390
- }
1391
- if (!getResolvedScrollCompletionState(ctx, scrollingTo).isAtResolvedTarget) {
1392
- return;
1393
- }
1394
- }
1395
- ctx.state.scheduledWork.frame(() => checkFinishedScrollFrame(ctx), "checkFinishedScrollFrame");
1396
- }
1397
- function hasScrollCompletionOwnership(state, options) {
1398
- const { clampedTargetOffset, scrollingTo } = options;
1399
- return !scrollingTo.isInitialScroll || state.hasScrolled || clampedTargetOffset <= INITIAL_SCROLL_COMPLETION_TARGET_EPSILON;
1400
- }
1401
- function isSilentInitialDispatch(state, scrollingTo) {
1402
- return !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled;
1403
- }
1404
- function getInitialScrollWatchdogTargetOffset(state) {
1405
- var _a3;
1406
- return (_a3 = initialScrollWatchdog.get(state)) == null ? void 0 : _a3.targetOffset;
1407
- }
1408
- function isNativeInitialNonZeroTarget(state) {
1409
- const targetOffset = getInitialScrollWatchdogTargetOffset(state);
1410
- return !state.didFinishInitialScroll && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
1411
- }
1412
- function shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) {
1413
- var _a3, _b;
1414
- if (!scrollingTo.isInitialScroll || scrollingTo.animated || !state.didContainersLayout) {
1415
- return false;
1416
- }
1417
- if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap") {
1418
- return false;
1419
- }
1420
- const targetOffset = (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset;
1421
- if (initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled) {
1422
- return false;
1423
- }
1424
- if (initialScrollWatchdog.isAtZeroTargetOffset(targetOffset) || Math.abs(state.scroll - targetOffset) > 1 || Math.abs(state.scrollPending - targetOffset) > 1) {
1425
- return false;
1426
- }
1427
- return !!scrollingTo.waitForInitialScrollCompletionFrame || isNativeInitialNonZeroTarget(state);
1428
- }
1429
- function shouldFinishInitialZeroTargetScroll(ctx) {
1430
- var _a3;
1431
- const { state } = ctx;
1432
- return !!((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) && state.props.data.length > 0 && getContentSize(ctx) <= state.scrollLength && state.scrollPending <= INITIAL_SCROLL_ZERO_TARGET_EPSILON;
1433
- }
1434
- function isEndAlignedLastItemTarget(ctx, scrollingTo) {
1435
- return scrollingTo.index === ctx.state.props.data.length - 1 && scrollingTo.viewPosition === 1;
1436
- }
1437
- function getCurrentTargetOffset(ctx, scrollingTo) {
1438
- var _a3;
1439
- const index = scrollingTo.index;
1440
- const shouldRecomputeEndTarget = isEndAlignedLastItemTarget(ctx, scrollingTo);
1441
- const requestedTargetOffset = shouldRecomputeEndTarget && index !== void 0 ? calculateOffsetWithOffsetPosition(ctx, calculateOffsetForIndex(ctx, index), scrollingTo) : (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
1442
- return clampScrollOffset(ctx, requestedTargetOffset, scrollingTo);
1443
- }
1444
- function getResolvedScrollCompletionState(ctx, scrollingTo) {
1445
- const { state } = ctx;
1446
- const scroll = state.scrollPending;
1447
- const adjust = state.scrollAdjustHandler.getAdjust();
1448
- const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
1449
- const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
1450
- const diff1 = Math.abs(scroll - clampedTargetOffset);
1451
- const adjustedTargetOffset = clampedTargetOffset + adjust;
1452
- const diff2 = Math.abs(scroll - adjustedTargetOffset);
1453
- const canUseAdjustedCompletion = !scrollingTo.animated || Platform.OS === "ios";
1454
- return {
1455
- clampedTargetOffset,
1456
- isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || canUseAdjustedCompletion && diff2 < 1)
1457
- };
1458
- }
1459
- function checkFinishedScrollFrame(ctx) {
1460
- const scrollingTo = ctx.state.scrollingTo;
1461
- if (!scrollingTo) {
1462
- return;
1463
- }
1464
- const { state } = ctx;
1465
- const completionState = getResolvedScrollCompletionState(ctx, scrollingTo);
1466
- if (completionState.isAtResolvedTarget && hasScrollCompletionOwnership(state, {
1467
- clampedTargetOffset: completionState.clampedTargetOffset,
1468
- scrollingTo
1469
- })) {
1470
- finishScrollTo(ctx);
1471
- }
1472
- }
1473
- function scrollToFallbackOffset(ctx, offset) {
1474
- var _a3;
1475
- (_a3 = ctx.state.refScroller.current) == null ? void 0 : _a3.scrollTo({
1476
- animated: false,
1477
- x: ctx.state.props.horizontal ? offset : 0,
1478
- y: ctx.state.props.horizontal ? 0 : offset
1479
- });
1480
- }
1481
- function checkFinishedScrollFallback(ctx) {
1482
- const state = ctx.state;
1483
- if (state.scheduledWork.has("checkFinishedScrollFallback")) {
1484
- return;
1485
- }
1486
- const scrollingTo = state.scrollingTo;
1487
- const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1488
- const silentInitialDispatch = isSilentInitialDispatch(state, scrollingTo);
1489
- const canFinishInitialWithoutNativeProgress = scrollingTo !== void 0 ? shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) : false;
1490
- const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget && !canFinishInitialWithoutNativeProgress || !state.didContainersLayout;
1491
- const initialDelay = shouldFinishInitialZeroTarget || canFinishInitialWithoutNativeProgress ? 0 : silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : slowTimeout ? 500 : 100;
1492
- let numChecks = 0;
1493
- const scheduleFallbackCheck = (delay) => {
1494
- state.scheduledWork.timeout(checkHasScrolled, delay, "checkFinishedScrollFallback");
1495
- };
1496
- const checkHasScrolled = () => {
1497
- var _a3, _b, _c, _d;
1498
- const isStillScrollingTo = state.scrollingTo;
1499
- if (isStillScrollingTo) {
1500
- numChecks++;
1501
- const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
1502
- const maxChecks = silentInitialDispatch ? 5 : isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
1503
- const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1504
- const canFinishInitialScrollWithoutNativeProgress = shouldFinishInitialScrollWithoutNativeProgress(
1505
- state,
1506
- isStillScrollingTo
1507
- );
1508
- const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
1509
- const canFinishAfterSilentNativeDispatch = Platform.OS === "android" && silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
1510
- const shouldRetrySilentInitialNativeScroll = Platform.OS === "android" && canFinishAfterSilentNativeDispatch && !initialScrollCompletion.didRetrySilentInitialScroll(state);
1511
- const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
1512
- const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
1513
- const shouldRetryUnalignedEndScroll = Platform.OS === "ios" && !isStillScrollingTo.isInitialScroll && isEndAlignedLastItemTarget(ctx, isStillScrollingTo) && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
1514
- if (shouldRetrySilentInitialNativeScroll) {
1515
- const targetOffset = (_b = (_a3 = getInitialScrollWatchdogTargetOffset(state)) != null ? _a3 : isStillScrollingTo.targetOffset) != null ? _b : 0;
1516
- const jiggleOffset = targetOffset >= SILENT_INITIAL_SCROLL_TARGET_EPSILON ? targetOffset - SILENT_INITIAL_SCROLL_TARGET_EPSILON : targetOffset + SILENT_INITIAL_SCROLL_TARGET_EPSILON;
1517
- initialScrollCompletion.markSilentInitialScrollRetry(state);
1518
- scrollToFallbackOffset(ctx, jiggleOffset);
1519
- state.scheduledWork.frame(
1520
- () => scrollToFallbackOffset(ctx, targetOffset),
1521
- "checkFinishedScrollRetryFrame"
1522
- );
1523
- scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS);
1524
- } else if (shouldRetryUnalignedEndScroll) {
1525
- scrollToFallbackOffset(ctx, completionState.clampedTargetOffset);
1526
- scheduleFallbackCheck(100);
1527
- } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
1528
- finishScrollTo(ctx);
1529
- } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
1530
- const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
1531
- scrollToFallbackOffset(ctx, targetOffset);
1532
- scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
1533
- } else {
1534
- scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
1535
- }
1536
- }
1537
- };
1538
- scheduleFallbackCheck(initialDelay);
1539
- }
1540
-
1541
- // src/core/doScrollTo.native.ts
1542
- function doScrollTo(ctx, params) {
1543
- const state = ctx.state;
1544
- const { animated, horizontal, isInitialScroll, offset } = params;
1545
- const isAnimated = !!animated;
1546
- const { refScroller } = state;
1547
- const scroller = refScroller.current;
1548
- if (!scroller) {
1549
- return;
1550
- }
1551
- const isHorizontal = !!horizontal;
1552
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
1553
- const nativeOffset = toNativeHorizontalOffset(state, offset, contentSize);
1554
- scroller.scrollTo({
1555
- animated: isAnimated,
1556
- x: isHorizontal ? nativeOffset : 0,
1557
- y: isHorizontal ? 0 : offset
1558
- });
1559
- if (isInitialScroll) {
1560
- initialScrollCompletion.markInitialScrollNativeDispatch(state);
1561
- }
1562
- if (isAnimated && Math.abs(state.scroll - offset) <= 1) {
1563
- checkFinishedScroll(ctx);
1564
- }
1565
- if (!isAnimated) {
1566
- state.scroll = offset;
1567
- checkFinishedScrollFallback(ctx);
1568
- }
1569
- }
1570
-
1571
1358
  // src/core/scrollRequestTracker.ts
1572
1359
  function getScrollRequestTracker(ctx) {
1573
1360
  if (!ctx.scrollRequestTracker) {
1574
1361
  let currentToken = 0;
1575
- const start = (resolve) => {
1362
+ let waitingToken;
1363
+ const start = (resolve, isInternal = false) => {
1576
1364
  const state = ctx.state;
1365
+ if (!isInternal && (state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd)) {
1366
+ finishMaintainScrollAtEnd(ctx);
1367
+ }
1577
1368
  state.scheduledWork.cancel("imperativeScrollReady");
1578
1369
  const token = ++currentToken;
1579
1370
  settlePendingImperativeScroll(state);
1580
1371
  state.pendingScrollResolve = resolve;
1372
+ waitingToken = token;
1581
1373
  return token;
1582
1374
  };
1583
1375
  const runNow = (token, resolve, run) => {
@@ -1585,6 +1377,7 @@ function getScrollRequestTracker(ctx) {
1585
1377
  if (token !== currentToken) {
1586
1378
  return;
1587
1379
  }
1380
+ waitingToken = void 0;
1588
1381
  const didStartScroll = run();
1589
1382
  if (!didStartScroll || !state.scrollingTo) {
1590
1383
  if (state.pendingScrollResolve === resolve) {
@@ -1595,6 +1388,7 @@ function getScrollRequestTracker(ctx) {
1595
1388
  };
1596
1389
  ctx.scrollRequestTracker = {
1597
1390
  isCurrent: (token) => token === currentToken,
1391
+ isWaitingToRun: () => waitingToken === currentToken && !!ctx.state.pendingScrollResolve,
1598
1392
  runNow,
1599
1393
  runNowIfIdle: (run) => {
1600
1394
  const state = ctx.state;
@@ -1602,7 +1396,7 @@ function getScrollRequestTracker(ctx) {
1602
1396
  return Promise.resolve();
1603
1397
  }
1604
1398
  return new Promise((resolve) => {
1605
- const token = start(resolve);
1399
+ const token = start(resolve, true);
1606
1400
  runNow(token, resolve, run);
1607
1401
  });
1608
1402
  },
@@ -1666,87 +1460,6 @@ function requestAdjust(ctx, positionDiff, source) {
1666
1460
  }
1667
1461
  }
1668
1462
 
1669
- // src/core/doMaintainScrollAtEnd.ts
1670
- function finishMaintainScrollAtEnd(ctx) {
1671
- var _a3;
1672
- const { state } = ctx;
1673
- const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1674
- const nextPadding = getAlignItemsAtEndPadding(ctx);
1675
- state.maintainingScrollAtEnd = void 0;
1676
- state.pendingMaintainScrollAtEnd = false;
1677
- if (currentPadding !== nextPadding) {
1678
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1679
- state.scrollForNextCalculateItemsInView = void 0;
1680
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1681
- const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1682
- requestAdjust(ctx, nextScroll - state.scroll);
1683
- }
1684
- }
1685
- function doMaintainScrollAtEnd(ctx) {
1686
- const state = ctx.state;
1687
- const {
1688
- didContainersLayout,
1689
- didFinishInitialScroll,
1690
- pendingNativeMVCPAdjust,
1691
- props: { maintainScrollAtEnd }
1692
- } = state;
1693
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1694
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1695
- const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1696
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1697
- state.pendingMaintainScrollAtEnd = true;
1698
- return false;
1699
- }
1700
- if (pendingNativeMVCPAdjust) {
1701
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1702
- return false;
1703
- }
1704
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1705
- state.pendingMaintainScrollAtEnd = false;
1706
- const contentSize = getContentSize(ctx);
1707
- if (contentSize < state.scrollLength) {
1708
- state.scroll = 0;
1709
- }
1710
- if (!state.maintainingScrollAtEnd) {
1711
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1712
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1713
- const scrollAtRequest = state.scroll;
1714
- state.maintainingScrollAtEnd = pendingState;
1715
- requestAnimationFrame(() => {
1716
- if (state.maintainingScrollAtEnd !== pendingState) {
1717
- return;
1718
- }
1719
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1720
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1721
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1722
- state.maintainingScrollAtEnd = activeState;
1723
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1724
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1725
- );
1726
- void scrollPromise.then(() => {
1727
- if (state.maintainingScrollAtEnd !== activeState) {
1728
- return;
1729
- }
1730
- if (state.pendingMaintainScrollAtEnd) {
1731
- state.maintainingScrollAtEnd = void 0;
1732
- doMaintainScrollAtEnd(ctx);
1733
- } else {
1734
- finishMaintainScrollAtEnd(ctx);
1735
- }
1736
- });
1737
- } else if (state.maintainingScrollAtEnd === pendingState) {
1738
- finishMaintainScrollAtEnd(ctx);
1739
- }
1740
- });
1741
- } else {
1742
- state.pendingMaintainScrollAtEnd = true;
1743
- }
1744
- return true;
1745
- }
1746
- finishMaintainScrollAtEnd(ctx);
1747
- return false;
1748
- }
1749
-
1750
1463
  // src/core/mvcp.ts
1751
1464
  var MVCP_POSITION_EPSILON = 0.1;
1752
1465
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2064,125 +1777,489 @@ var getScrollVelocity = (state) => {
2064
1777
  if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2065
1778
  return 0;
2066
1779
  }
2067
- let direction = 0;
2068
- let weightedVelocity = 0;
2069
- let totalWeight = 0;
2070
- for (let i = newestIndex; i > 0; i--) {
2071
- const current = scrollHistory[i];
2072
- const previous = scrollHistory[i - 1];
2073
- const scrollDiff = current.scroll - previous.scroll;
2074
- const timeDiff = current.time - previous.time;
2075
- const deltaSign = Math.sign(scrollDiff);
2076
- if (deltaSign !== 0) {
2077
- if (direction === 0) {
2078
- direction = deltaSign;
2079
- } else if (deltaSign !== direction) {
2080
- break;
2081
- }
2082
- }
2083
- if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2084
- break;
2085
- }
2086
- if (scrollDiff === 0 || timeDiff <= 0) {
2087
- continue;
2088
- }
2089
- const age = newest.time - current.time;
2090
- const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2091
- weightedVelocity += scrollDiff / timeDiff * weight;
2092
- totalWeight += weight;
1780
+ let direction = 0;
1781
+ let weightedVelocity = 0;
1782
+ let totalWeight = 0;
1783
+ for (let i = newestIndex; i > 0; i--) {
1784
+ const current = scrollHistory[i];
1785
+ const previous = scrollHistory[i - 1];
1786
+ const scrollDiff = current.scroll - previous.scroll;
1787
+ const timeDiff = current.time - previous.time;
1788
+ const deltaSign = Math.sign(scrollDiff);
1789
+ if (deltaSign !== 0) {
1790
+ if (direction === 0) {
1791
+ direction = deltaSign;
1792
+ } else if (deltaSign !== direction) {
1793
+ break;
1794
+ }
1795
+ }
1796
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1797
+ break;
1798
+ }
1799
+ if (scrollDiff === 0 || timeDiff <= 0) {
1800
+ continue;
1801
+ }
1802
+ const age = newest.time - current.time;
1803
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
1804
+ weightedVelocity += scrollDiff / timeDiff * weight;
1805
+ totalWeight += weight;
1806
+ }
1807
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
1808
+ };
1809
+
1810
+ // src/utils/isInMVCPActiveMode.native.ts
1811
+ function isInMVCPActiveMode(state) {
1812
+ return state.dataChangeNeedsScrollUpdate;
1813
+ }
1814
+
1815
+ // src/core/updateScroll.ts
1816
+ function updateScroll(ctx, newScroll, forceUpdate, options) {
1817
+ var _a3;
1818
+ const state = ctx.state;
1819
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1820
+ const prevScroll = state.scroll;
1821
+ if ((options == null ? void 0 : options.markHasScrolled) !== false) {
1822
+ state.hasScrolled = true;
1823
+ }
1824
+ const currentTime = Date.now();
1825
+ state.lastBatchingAction = currentTime;
1826
+ const adjust = scrollAdjustHandler.getAdjust();
1827
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1828
+ if (adjustChanged) {
1829
+ scrollHistory.length = 0;
1830
+ }
1831
+ state.lastScrollAdjustForHistory = adjust;
1832
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1833
+ if (!adjustChanged) {
1834
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
1835
+ }
1836
+ }
1837
+ if (scrollHistory.length > 5) {
1838
+ scrollHistory.shift();
1839
+ }
1840
+ if (ignoreScrollFromMVCP && !scrollingTo) {
1841
+ const { lt, gt } = ignoreScrollFromMVCP;
1842
+ if (lt && newScroll < lt || gt && newScroll > gt) {
1843
+ state.ignoreScrollFromMVCPIgnored = true;
1844
+ return;
1845
+ }
1846
+ }
1847
+ state.scrollPrev = prevScroll;
1848
+ state.scrollPrevTime = state.scrollTime;
1849
+ state.scroll = newScroll;
1850
+ state.scrollTime = currentTime;
1851
+ const scrollDelta = Math.abs(newScroll - prevScroll);
1852
+ const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1853
+ const didCancelMaintainScrollAtEnd = Platform.OS === "web" && isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1854
+ if (didCancelMaintainScrollAtEnd) {
1855
+ finishMaintainScrollAtEnd(ctx);
1856
+ }
1857
+ const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
1858
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1859
+ const scrollLength = state.scrollLength;
1860
+ const isLargeUserScrollJump = scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust;
1861
+ const scrollVelocity = getScrollVelocity(state);
1862
+ updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
1863
+ const lastCalculated = state.scrollLastCalculate;
1864
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1865
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1866
+ if (shouldUpdate) {
1867
+ state.scrollLastCalculate = state.scroll;
1868
+ state.ignoreScrollFromMVCPIgnored = false;
1869
+ state.lastScrollDelta = scrollDelta;
1870
+ const runCalculateItems = () => {
1871
+ var _a4;
1872
+ const calculateItemsParams = {
1873
+ doMVCP: scrollingTo !== void 0,
1874
+ scrollVelocity
1875
+ };
1876
+ if (isLargeUserScrollJump) {
1877
+ calculateItemsParams.drawDistanceMode = "visible-first";
1878
+ }
1879
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, calculateItemsParams);
1880
+ checkThresholds(ctx, allowedEdge);
1881
+ };
1882
+ if (isLargeUserScrollJump) {
1883
+ state.mvcpAnchorLock = void 0;
1884
+ state.pendingNativeMVCPAdjust = void 0;
1885
+ state.userScrollAnchorReset = { keys: /* @__PURE__ */ new Set() };
1886
+ state.scheduledWork.cancel("mvcpRecalculate");
1887
+ flushSync(runCalculateItems);
1888
+ scheduleFullDrawDistancePrewarm(ctx);
1889
+ } else {
1890
+ runCalculateItems();
1891
+ }
1892
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
1893
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
1894
+ state.pendingMaintainScrollAtEnd = false;
1895
+ doMaintainScrollAtEnd(ctx);
1896
+ }
1897
+ state.dataChangeNeedsScrollUpdate = false;
1898
+ state.lastScrollDelta = 0;
1899
+ }
1900
+ }
1901
+
1902
+ // src/core/doMaintainScrollAtEnd.ts
1903
+ function interruptMaintainScrollAtEnd(ctx) {
1904
+ var _a3;
1905
+ const { state } = ctx;
1906
+ const maintaining = state.maintainingScrollAtEnd;
1907
+ const cancelActiveScroll = !!(maintaining === "animated" || maintaining === "instant" || maintaining && ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isScrollToEnd));
1908
+ if (cancelActiveScroll) {
1909
+ cancelImperativeScroll(state);
1910
+ }
1911
+ if (maintaining || state.pendingMaintainScrollAtEnd) {
1912
+ finishMaintainScrollAtEnd(ctx);
1913
+ }
1914
+ return cancelActiveScroll;
1915
+ }
1916
+ function finishMaintainScrollAtEnd(ctx) {
1917
+ var _a3;
1918
+ const { state } = ctx;
1919
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1920
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1921
+ state.maintainingScrollAtEnd = void 0;
1922
+ state.pendingMaintainScrollAtEnd = false;
1923
+ if (currentPadding !== nextPadding) {
1924
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1925
+ state.scrollForNextCalculateItemsInView = void 0;
1926
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1927
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1928
+ requestAdjust(ctx, nextScroll - state.scroll);
1929
+ }
1930
+ }
1931
+ function doMaintainScrollAtEnd(ctx, options) {
1932
+ var _a3, _b, _c, _d, _e, _f;
1933
+ const state = ctx.state;
1934
+ let didMaintain = false;
1935
+ if (state.isDragging) {
1936
+ finishMaintainScrollAtEnd(ctx);
1937
+ } else {
1938
+ const {
1939
+ didContainersLayout,
1940
+ didFinishInitialScroll,
1941
+ pendingNativeMVCPAdjust,
1942
+ props: { maintainScrollAtEnd }
1943
+ } = state;
1944
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1945
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1946
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1947
+ 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);
1948
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1949
+ const scroller = state.refScroller.current;
1950
+ const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1951
+ const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1952
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1953
+ state.pendingMaintainScrollAtEnd = true;
1954
+ state.scrollPending = observed;
1955
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1956
+ }
1957
+ }
1958
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1959
+ state.pendingMaintainScrollAtEnd = true;
1960
+ } else if (pendingNativeMVCPAdjust) {
1961
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1962
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1963
+ if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1964
+ state.pendingMaintainScrollAtEnd = false;
1965
+ ctx.scrollToEnd({ animated: false });
1966
+ didMaintain = true;
1967
+ } else {
1968
+ state.pendingMaintainScrollAtEnd = true;
1969
+ (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1970
+ }
1971
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1972
+ state.pendingMaintainScrollAtEnd = false;
1973
+ const contentSize = getContentSize(ctx);
1974
+ if (contentSize < state.scrollLength) {
1975
+ state.scroll = 0;
1976
+ }
1977
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1978
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1979
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1980
+ const scrollAtRequest = state.scroll;
1981
+ state.maintainingScrollAtEnd = pendingState;
1982
+ const run = () => {
1983
+ if (state.maintainingScrollAtEnd === pendingState) {
1984
+ if (state.isDragging) {
1985
+ finishMaintainScrollAtEnd(ctx);
1986
+ } else {
1987
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1988
+ const didScrollSinceRequest = Platform.OS === "web" && state.scroll !== scrollAtRequest;
1989
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1990
+ if (state.scrollingTo || state.pendingScrollResolve) {
1991
+ state.pendingMaintainScrollAtEnd = true;
1992
+ } else {
1993
+ state.maintainingScrollAtEnd = activeState;
1994
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1995
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1996
+ );
1997
+ void scrollPromise.then(() => {
1998
+ if (state.maintainingScrollAtEnd === activeState) {
1999
+ if (state.pendingMaintainScrollAtEnd) {
2000
+ state.maintainingScrollAtEnd = void 0;
2001
+ doMaintainScrollAtEnd(ctx);
2002
+ } else {
2003
+ finishMaintainScrollAtEnd(ctx);
2004
+ }
2005
+ }
2006
+ });
2007
+ }
2008
+ } else if (state.maintainingScrollAtEnd === pendingState) {
2009
+ finishMaintainScrollAtEnd(ctx);
2010
+ }
2011
+ }
2012
+ }
2013
+ };
2014
+ if (immediate) {
2015
+ run();
2016
+ } else {
2017
+ requestAnimationFrame(run);
2018
+ }
2019
+ } else {
2020
+ state.pendingMaintainScrollAtEnd = true;
2021
+ }
2022
+ didMaintain = true;
2023
+ } else {
2024
+ finishMaintainScrollAtEnd(ctx);
2025
+ }
2026
+ }
2027
+ return didMaintain;
2028
+ }
2029
+
2030
+ // src/core/finishScrollTo.ts
2031
+ function finishScrollTo(ctx) {
2032
+ var _a3, _b, _c;
2033
+ const state = ctx.state;
2034
+ if (state == null ? void 0 : state.scrollingTo) {
2035
+ cancelScrollCompletionChecks(state);
2036
+ const resolvePendingScroll = ((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) ? void 0 : state.pendingScrollResolve;
2037
+ if (resolvePendingScroll) {
2038
+ state.pendingScrollResolve = void 0;
2039
+ }
2040
+ const scrollingTo = state.scrollingTo;
2041
+ state.scrollHistory.length = 0;
2042
+ state.scrollingTo = void 0;
2043
+ state.scrollTargetPinnedRange = void 0;
2044
+ if (state.pendingTotalSize !== void 0) {
2045
+ addTotalSize(ctx, null, state.pendingTotalSize);
2046
+ }
2047
+ if (PlatformAdjustBreaksScroll) {
2048
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
2049
+ }
2050
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
2051
+ const isOffsetSession = ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) === "offset";
2052
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_c = state.initialScroll) == null ? void 0 : _c.viewPosition) === 1;
2053
+ finishInitialScroll(ctx, {
2054
+ onFinished: () => {
2055
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2056
+ },
2057
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
2058
+ recalculateItems: true,
2059
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
2060
+ syncObservedOffset: isOffsetSession,
2061
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
2062
+ });
2063
+ return;
2064
+ }
2065
+ recalculateSettledScroll(ctx);
2066
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2067
+ if (state.pendingMaintainScrollAtEnd) {
2068
+ state.maintainingScrollAtEnd = void 0;
2069
+ doMaintainScrollAtEnd(ctx);
2070
+ }
2071
+ }
2072
+ }
2073
+
2074
+ // src/core/checkFinishedScroll.ts
2075
+ var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
2076
+ var INITIAL_SCROLL_COMPLETION_TARGET_EPSILON = 1;
2077
+ var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
2078
+ var SILENT_INITIAL_SCROLL_RETRY_DELAY_MS = 16;
2079
+ var SILENT_INITIAL_SCROLL_TARGET_EPSILON = 1;
2080
+ function checkFinishedScroll(ctx, options) {
2081
+ const scrollingTo = ctx.state.scrollingTo;
2082
+ if (options == null ? void 0 : options.onlyIfAligned) {
2083
+ if (!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || scrollingTo.animated) {
2084
+ return;
2085
+ }
2086
+ if (!getResolvedScrollCompletionState(ctx, scrollingTo).isAtResolvedTarget) {
2087
+ return;
2088
+ }
2089
+ }
2090
+ ctx.state.scheduledWork.frame(() => checkFinishedScrollFrame(ctx), "checkFinishedScrollFrame");
2091
+ }
2092
+ function hasScrollCompletionOwnership(state, options) {
2093
+ const { clampedTargetOffset, scrollingTo } = options;
2094
+ return !scrollingTo.isInitialScroll || state.hasScrolled || clampedTargetOffset <= INITIAL_SCROLL_COMPLETION_TARGET_EPSILON;
2095
+ }
2096
+ function isSilentInitialDispatch(state, scrollingTo) {
2097
+ return !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled;
2098
+ }
2099
+ function getInitialScrollWatchdogTargetOffset(state) {
2100
+ var _a3;
2101
+ return (_a3 = initialScrollWatchdog.get(state)) == null ? void 0 : _a3.targetOffset;
2102
+ }
2103
+ function isNativeInitialNonZeroTarget(state) {
2104
+ const targetOffset = getInitialScrollWatchdogTargetOffset(state);
2105
+ return !state.didFinishInitialScroll && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
2106
+ }
2107
+ function shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) {
2108
+ var _a3, _b;
2109
+ if (!scrollingTo.isInitialScroll || scrollingTo.animated || !state.didContainersLayout) {
2110
+ return false;
2111
+ }
2112
+ if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap") {
2113
+ return false;
2114
+ }
2115
+ const targetOffset = (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset;
2116
+ if (initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled) {
2117
+ return false;
2118
+ }
2119
+ if (initialScrollWatchdog.isAtZeroTargetOffset(targetOffset) || Math.abs(state.scroll - targetOffset) > 1 || Math.abs(state.scrollPending - targetOffset) > 1) {
2120
+ return false;
2121
+ }
2122
+ return !!scrollingTo.waitForInitialScrollCompletionFrame || isNativeInitialNonZeroTarget(state);
2123
+ }
2124
+ function shouldFinishInitialZeroTargetScroll(ctx) {
2125
+ var _a3;
2126
+ const { state } = ctx;
2127
+ return !!((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) && state.props.data.length > 0 && getContentSize(ctx) <= state.scrollLength && state.scrollPending <= INITIAL_SCROLL_ZERO_TARGET_EPSILON;
2128
+ }
2129
+ function isEndAlignedLastItemTarget(ctx, scrollingTo) {
2130
+ return scrollingTo.index === ctx.state.props.data.length - 1 && scrollingTo.viewPosition === 1;
2131
+ }
2132
+ function getCurrentTargetOffset(ctx, scrollingTo) {
2133
+ var _a3;
2134
+ const index = scrollingTo.index;
2135
+ const shouldRecomputeEndTarget = isEndAlignedLastItemTarget(ctx, scrollingTo);
2136
+ const requestedTargetOffset = shouldRecomputeEndTarget && index !== void 0 ? calculateOffsetWithOffsetPosition(ctx, calculateOffsetForIndex(ctx, index), scrollingTo) : (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
2137
+ return clampScrollOffset(ctx, requestedTargetOffset, scrollingTo);
2138
+ }
2139
+ function getResolvedScrollCompletionState(ctx, scrollingTo) {
2140
+ const { state } = ctx;
2141
+ const scroll = state.scrollPending;
2142
+ const adjust = state.scrollAdjustHandler.getAdjust();
2143
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
2144
+ const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
2145
+ const diff1 = Math.abs(scroll - clampedTargetOffset);
2146
+ const adjustedTargetOffset = clampedTargetOffset + adjust;
2147
+ const diff2 = Math.abs(scroll - adjustedTargetOffset);
2148
+ const canUseAdjustedCompletion = !scrollingTo.animated || Platform.OS === "ios";
2149
+ return {
2150
+ clampedTargetOffset,
2151
+ isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || canUseAdjustedCompletion && diff2 < 1)
2152
+ };
2153
+ }
2154
+ function checkFinishedScrollFrame(ctx) {
2155
+ const scrollingTo = ctx.state.scrollingTo;
2156
+ if (!scrollingTo) {
2157
+ return;
2158
+ }
2159
+ const { state } = ctx;
2160
+ const completionState = getResolvedScrollCompletionState(ctx, scrollingTo);
2161
+ if (completionState.isAtResolvedTarget && hasScrollCompletionOwnership(state, {
2162
+ clampedTargetOffset: completionState.clampedTargetOffset,
2163
+ scrollingTo
2164
+ })) {
2165
+ finishScrollTo(ctx);
2093
2166
  }
2094
- return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
2095
- };
2096
-
2097
- // src/utils/isInMVCPActiveMode.native.ts
2098
- function isInMVCPActiveMode(state) {
2099
- return state.dataChangeNeedsScrollUpdate;
2100
2167
  }
2101
-
2102
- // src/core/updateScroll.ts
2103
- function updateScroll(ctx, newScroll, forceUpdate, options) {
2168
+ function scrollToFallbackOffset(ctx, offset) {
2104
2169
  var _a3;
2170
+ (_a3 = ctx.state.refScroller.current) == null ? void 0 : _a3.scrollTo({
2171
+ animated: false,
2172
+ x: ctx.state.props.horizontal ? offset : 0,
2173
+ y: ctx.state.props.horizontal ? 0 : offset
2174
+ });
2175
+ }
2176
+ function checkFinishedScrollFallback(ctx) {
2105
2177
  const state = ctx.state;
2106
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
2107
- const prevScroll = state.scroll;
2108
- if ((options == null ? void 0 : options.markHasScrolled) !== false) {
2109
- state.hasScrolled = true;
2110
- }
2111
- const currentTime = Date.now();
2112
- state.lastBatchingAction = currentTime;
2113
- const adjust = scrollAdjustHandler.getAdjust();
2114
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
2115
- if (adjustChanged) {
2116
- scrollHistory.length = 0;
2178
+ if (state.scheduledWork.has("checkFinishedScrollFallback")) {
2179
+ return;
2117
2180
  }
2118
- state.lastScrollAdjustForHistory = adjust;
2119
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
2120
- if (!adjustChanged) {
2121
- scrollHistory.push({ scroll: newScroll, time: currentTime });
2181
+ const scrollingTo = state.scrollingTo;
2182
+ const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
2183
+ const silentInitialDispatch = isSilentInitialDispatch(state, scrollingTo);
2184
+ const canFinishInitialWithoutNativeProgress = scrollingTo !== void 0 ? shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) : false;
2185
+ const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget && !canFinishInitialWithoutNativeProgress || !state.didContainersLayout;
2186
+ const initialDelay = shouldFinishInitialZeroTarget || canFinishInitialWithoutNativeProgress ? 0 : silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : slowTimeout ? 500 : 100;
2187
+ let numChecks = 0;
2188
+ const scheduleFallbackCheck = (delay) => {
2189
+ state.scheduledWork.timeout(checkHasScrolled, delay, "checkFinishedScrollFallback");
2190
+ };
2191
+ const checkHasScrolled = () => {
2192
+ var _a3, _b, _c, _d;
2193
+ const isStillScrollingTo = state.scrollingTo;
2194
+ if (isStillScrollingTo) {
2195
+ numChecks++;
2196
+ const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
2197
+ const maxChecks = silentInitialDispatch ? 5 : isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
2198
+ const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
2199
+ const canFinishInitialScrollWithoutNativeProgress = shouldFinishInitialScrollWithoutNativeProgress(
2200
+ state,
2201
+ isStillScrollingTo
2202
+ );
2203
+ const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
2204
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android" && silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
2205
+ const shouldRetrySilentInitialNativeScroll = Platform.OS === "android" && canFinishAfterSilentNativeDispatch && !initialScrollCompletion.didRetrySilentInitialScroll(state);
2206
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
2207
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
2208
+ const shouldRetryUnalignedEndScroll = Platform.OS !== "web" && !isStillScrollingTo.isInitialScroll && isEndAlignedLastItemTarget(ctx, isStillScrollingTo) && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
2209
+ if (shouldRetrySilentInitialNativeScroll) {
2210
+ const targetOffset = (_b = (_a3 = getInitialScrollWatchdogTargetOffset(state)) != null ? _a3 : isStillScrollingTo.targetOffset) != null ? _b : 0;
2211
+ const jiggleOffset = targetOffset >= SILENT_INITIAL_SCROLL_TARGET_EPSILON ? targetOffset - SILENT_INITIAL_SCROLL_TARGET_EPSILON : targetOffset + SILENT_INITIAL_SCROLL_TARGET_EPSILON;
2212
+ initialScrollCompletion.markSilentInitialScrollRetry(state);
2213
+ scrollToFallbackOffset(ctx, jiggleOffset);
2214
+ state.scheduledWork.frame(
2215
+ () => scrollToFallbackOffset(ctx, targetOffset),
2216
+ "checkFinishedScrollRetryFrame"
2217
+ );
2218
+ scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS);
2219
+ } else if (shouldRetryUnalignedEndScroll) {
2220
+ scrollToFallbackOffset(ctx, completionState.clampedTargetOffset);
2221
+ scheduleFallbackCheck(100);
2222
+ } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
2223
+ finishScrollTo(ctx);
2224
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
2225
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
2226
+ scrollToFallbackOffset(ctx, targetOffset);
2227
+ scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
2228
+ } else {
2229
+ scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
2230
+ }
2122
2231
  }
2232
+ };
2233
+ scheduleFallbackCheck(initialDelay);
2234
+ }
2235
+
2236
+ // src/core/doScrollTo.native.ts
2237
+ function doScrollTo(ctx, params) {
2238
+ const state = ctx.state;
2239
+ const { animated, horizontal, isInitialScroll, offset } = params;
2240
+ const isAnimated = !!animated;
2241
+ const { refScroller } = state;
2242
+ const scroller = refScroller.current;
2243
+ if (!scroller) {
2244
+ return;
2123
2245
  }
2124
- if (scrollHistory.length > 5) {
2125
- scrollHistory.shift();
2126
- }
2127
- if (ignoreScrollFromMVCP && !scrollingTo) {
2128
- const { lt, gt } = ignoreScrollFromMVCP;
2129
- if (lt && newScroll < lt || gt && newScroll > gt) {
2130
- state.ignoreScrollFromMVCPIgnored = true;
2131
- return;
2132
- }
2246
+ const isHorizontal = !!horizontal;
2247
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2248
+ const nativeOffset = toNativeHorizontalOffset(state, offset, contentSize);
2249
+ scroller.scrollTo({
2250
+ animated: isAnimated,
2251
+ x: isHorizontal ? nativeOffset : 0,
2252
+ y: isHorizontal ? 0 : offset
2253
+ });
2254
+ if (isInitialScroll) {
2255
+ initialScrollCompletion.markInitialScrollNativeDispatch(state);
2133
2256
  }
2134
- state.scrollPrev = prevScroll;
2135
- state.scrollPrevTime = state.scrollTime;
2136
- state.scroll = newScroll;
2137
- state.scrollTime = currentTime;
2138
- const scrollDelta = Math.abs(newScroll - prevScroll);
2139
- const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
2140
- const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
2141
- if (didCancelMaintainScrollAtEnd) {
2142
- finishMaintainScrollAtEnd(ctx);
2257
+ if (isAnimated && Math.abs(state.scroll - offset) <= 1) {
2258
+ checkFinishedScroll(ctx);
2143
2259
  }
2144
- const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
2145
- const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2146
- const scrollLength = state.scrollLength;
2147
- const isLargeUserScrollJump = scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust;
2148
- const scrollVelocity = getScrollVelocity(state);
2149
- updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2150
- const lastCalculated = state.scrollLastCalculate;
2151
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2152
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2153
- if (shouldUpdate) {
2154
- state.scrollLastCalculate = state.scroll;
2155
- state.ignoreScrollFromMVCPIgnored = false;
2156
- state.lastScrollDelta = scrollDelta;
2157
- const runCalculateItems = () => {
2158
- var _a4;
2159
- const calculateItemsParams = {
2160
- doMVCP: scrollingTo !== void 0,
2161
- scrollVelocity
2162
- };
2163
- if (isLargeUserScrollJump) {
2164
- calculateItemsParams.drawDistanceMode = "visible-first";
2165
- }
2166
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, calculateItemsParams);
2167
- checkThresholds(ctx, allowedEdge);
2168
- };
2169
- if (isLargeUserScrollJump) {
2170
- state.mvcpAnchorLock = void 0;
2171
- state.pendingNativeMVCPAdjust = void 0;
2172
- state.userScrollAnchorReset = { keys: /* @__PURE__ */ new Set() };
2173
- state.scheduledWork.cancel("mvcpRecalculate");
2174
- flushSync(runCalculateItems);
2175
- scheduleFullDrawDistancePrewarm(ctx);
2176
- } else {
2177
- runCalculateItems();
2178
- }
2179
- const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
2180
- if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
2181
- state.pendingMaintainScrollAtEnd = false;
2182
- doMaintainScrollAtEnd(ctx);
2183
- }
2184
- state.dataChangeNeedsScrollUpdate = false;
2185
- state.lastScrollDelta = 0;
2260
+ if (!isAnimated) {
2261
+ state.scroll = offset;
2262
+ checkFinishedScrollFallback(ctx);
2186
2263
  }
2187
2264
  }
2188
2265
 
@@ -2336,7 +2413,9 @@ function scrollTo(ctx, params) {
2336
2413
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2337
2414
  }
2338
2415
  }
2339
- state.scrollPending = targetOffset;
2416
+ if (!animated || isInitialScroll) {
2417
+ state.scrollPending = targetOffset;
2418
+ }
2340
2419
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2341
2420
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2342
2421
  if (animated) {
@@ -2713,13 +2792,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2713
2792
  queueBootstrapInitialScrollReevaluation(ctx.state);
2714
2793
  }
2715
2794
  function createInitialScrollAtEndTarget(options) {
2716
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2795
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2717
2796
  return {
2718
2797
  contentOffset: void 0,
2719
- index: Math.max(0, dataLength - 1),
2798
+ index: index != null ? index : Math.max(0, dataLength - 1),
2720
2799
  preserveForBottomPadding: true,
2721
2800
  preserveForFooterLayout,
2722
- viewOffset: -stylePaddingEnd - footerSize,
2801
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2723
2802
  viewPosition: 1
2724
2803
  };
2725
2804
  }
@@ -2735,13 +2814,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2735
2814
  function createRetargetedBottomAlignedInitialScroll(options) {
2736
2815
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2737
2816
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2817
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2738
2818
  return {
2739
2819
  ...target,
2740
2820
  contentOffset: void 0,
2741
2821
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2742
2822
  preserveForBottomPadding: true,
2743
2823
  preserveForFooterLayout,
2744
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2824
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2745
2825
  viewPosition: 1
2746
2826
  };
2747
2827
  }
@@ -2755,7 +2835,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2755
2835
  }
2756
2836
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2757
2837
  dataLength,
2758
- footerSize: 0,
2838
+ footerSize: peek$(ctx, "footerSize") || 0,
2759
2839
  preserveForFooterLayout: void 0,
2760
2840
  stylePaddingEnd
2761
2841
  });
@@ -2778,13 +2858,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2778
2858
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2779
2859
  }
2780
2860
  function getPreservedEndAnchorOffsetDiff(ctx) {
2781
- var _a3;
2782
2861
  const state = ctx.state;
2783
2862
  const initialScroll = state.initialScroll;
2784
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2863
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2785
2864
  return;
2786
2865
  }
2787
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2866
+ const currentOffset = Platform.OS !== "web" && typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2788
2867
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2789
2868
  }
2790
2869
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2812,6 +2891,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2812
2891
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2813
2892
  if (hasObservedNativeScrollAfterRequest) {
2814
2893
  activeCorrection.lastRequestTime = Date.now();
2894
+ if (Platform.OS === "web") {
2895
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2896
+ }
2815
2897
  requestAdjust(ctx, offsetDiff);
2816
2898
  }
2817
2899
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2934,10 +3016,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2934
3016
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2935
3017
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2936
3018
  const state = ctx.state;
2937
- if (!initialScrollAtEnd) {
3019
+ const initialScroll = state.initialScroll;
3020
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2938
3021
  return;
2939
3022
  }
2940
- const initialScroll = state.initialScroll;
2941
3023
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2942
3024
  return;
2943
3025
  }
@@ -2955,6 +3037,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2955
3037
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2956
3038
  dataLength,
2957
3039
  footerSize,
3040
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
2958
3041
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2959
3042
  stylePaddingEnd
2960
3043
  });
@@ -3191,6 +3274,11 @@ function handleInitialScrollDataChange(ctx, options) {
3191
3274
  const state = ctx.state;
3192
3275
  const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3193
3276
  const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3277
+ if (didStartFreshData) {
3278
+ cancelImperativeScroll(state);
3279
+ state.maintainingScrollAtEnd = void 0;
3280
+ state.pendingMaintainScrollAtEnd = false;
3281
+ }
3194
3282
  if (dataLength > 0) {
3195
3283
  state.hasHadNonEmptyData = true;
3196
3284
  }
@@ -3909,7 +3997,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
3909
3997
  }
3910
3998
 
3911
3999
  // src/utils/findAvailableContainers.ts
3912
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
4000
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
3913
4001
  var _a3;
3914
4002
  const numNeeded = needNewContainers.length;
3915
4003
  if (numNeeded === 0) {
@@ -3918,16 +4006,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3918
4006
  const numContainers = peek$(ctx, "numContainers");
3919
4007
  const state = ctx.state;
3920
4008
  const { containerItemMetadata, stickyContainerPool } = state;
4009
+ const { data, getItemType } = state.props;
4010
+ const hasItemTypes = !!getItemType;
3921
4011
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
3922
4012
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
3923
- const requests = needNewContainers.map((itemIndex, order) => ({
3924
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
3925
- itemIndex,
3926
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
3927
- order
3928
- }));
3929
- const normalRequests = requests.filter((request) => !request.isSticky);
3930
- const stickyRequests = requests.filter((request) => request.isSticky);
4013
+ const requests = new Array(numNeeded);
4014
+ const normalRequests = [];
4015
+ const stickyRequests = [];
4016
+ for (let order = 0; order < numNeeded; order++) {
4017
+ const itemIndex = needNewContainers[order];
4018
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
4019
+ const request = {
4020
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4021
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4022
+ itemIndex,
4023
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4024
+ order,
4025
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4026
+ };
4027
+ requests[order] = request;
4028
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4029
+ }
3931
4030
  const normalCandidates = [];
3932
4031
  const stickyCandidates = [];
3933
4032
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -3983,7 +4082,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3983
4082
  }
3984
4083
  };
3985
4084
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
3986
- if (getRequiredItemType) {
4085
+ if (hasItemTypes) {
3987
4086
  assignMatching(
3988
4087
  pendingRequests,
3989
4088
  candidates,
@@ -4007,6 +4106,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4007
4106
  }
4008
4107
  assign(request, containerIndex);
4009
4108
  }
4109
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4110
+ const containerIndices = [];
4111
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4112
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4113
+ }
4010
4114
  if (pendingRemovalChanged) {
4011
4115
  pendingRemoval.length = 0;
4012
4116
  if (pendingRemovalSet) {
@@ -4036,12 +4140,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4036
4140
  function comparatorByDistance(a, b) {
4037
4141
  return b.distance - a.distance;
4038
4142
  }
4143
+ function comparatorByRenderPriority(a, b) {
4144
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4145
+ }
4146
+ function comparatorByIndex(a, b) {
4147
+ return a - b;
4148
+ }
4149
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4150
+ if (requests.length >= 2) {
4151
+ if (groupByType) {
4152
+ const groups = /* @__PURE__ */ new Map();
4153
+ for (const request of requests) {
4154
+ const group = groups.get(request.itemType);
4155
+ if (group) {
4156
+ group.push(request);
4157
+ } else {
4158
+ groups.set(request.itemType, [request]);
4159
+ }
4160
+ }
4161
+ for (const group of groups.values()) {
4162
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4163
+ }
4164
+ } else {
4165
+ containerIndices.length = 0;
4166
+ let indicesSorted = true;
4167
+ for (let i = 0; i < requests.length; i++) {
4168
+ const containerIndex = allocations[requests[i].order].containerIndex;
4169
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4170
+ indicesSorted = false;
4171
+ }
4172
+ containerIndices.push(containerIndex);
4173
+ }
4174
+ if (!indicesSorted) {
4175
+ containerIndices.sort(comparatorByIndex);
4176
+ }
4177
+ requests.sort(comparatorByRenderPriority);
4178
+ for (let i = 0; i < requests.length; i++) {
4179
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4180
+ }
4181
+ }
4182
+ }
4183
+ }
4039
4184
 
4040
4185
  // src/utils/setDidLayout.ts
4041
4186
  function setDidLayout(ctx) {
4042
4187
  const state = ctx.state;
4043
4188
  state.queuedInitialLayout = true;
4044
- checkAtBottom(ctx);
4189
+ checkThresholds(ctx);
4045
4190
  setInitialRenderState(ctx, { didLayout: true });
4046
4191
  if (state.pendingMaintainScrollAtEnd) {
4047
4192
  doMaintainScrollAtEnd(ctx);
@@ -4269,9 +4414,9 @@ function calculateItemsInView(ctx, params = {}) {
4269
4414
  enableScrollForNextCalculateItemsInView,
4270
4415
  idCache,
4271
4416
  indexByKey,
4272
- minIndexSizeChanged,
4417
+ positionRecalculationStartIndex,
4273
4418
  positions,
4274
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4419
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4275
4420
  scrollForNextCalculateItemsInView,
4276
4421
  scrollLength,
4277
4422
  sizes,
@@ -4387,21 +4532,21 @@ function calculateItemsInView(ctx, params = {}) {
4387
4532
  if (dataChanged) {
4388
4533
  resetLayoutCachesForDataChange(state);
4389
4534
  }
4390
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4391
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4535
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4536
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4392
4537
  updateItemPositions(ctx, dataChanged, {
4393
4538
  doMVCP,
4394
4539
  // A changed size shifts every following position. The scrolling early-exit
4395
4540
  // would leave the untouched suffix in the old coordinate space.
4396
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4541
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4397
4542
  optimizeForVisibleWindow,
4398
4543
  scrollBottomBuffered,
4399
4544
  scrollVelocity: speed,
4400
4545
  startIndex
4401
4546
  });
4402
4547
  totalSize = getContentSize(ctx);
4403
- if (minIndexSizeChanged !== void 0) {
4404
- state.minIndexSizeChanged = void 0;
4548
+ if (positionRecalculationStartIndex !== void 0) {
4549
+ state.positionRecalculationStartIndex = void 0;
4405
4550
  }
4406
4551
  let protectedContainerKeys;
4407
4552
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4577,18 +4722,18 @@ function calculateItemsInView(ctx, params = {}) {
4577
4722
  set$(ctx, "activeStickyIndex", -1);
4578
4723
  }
4579
4724
  if (needNewContainers.length > 0) {
4580
- const getRequiredItemType = getItemType ? (i) => {
4581
- const itemType = getItemType(data[i], i);
4582
- return itemType !== void 0 ? String(itemType) : "";
4583
- } : void 0;
4584
4725
  const availableContainerAllocations = findAvailableContainers(
4585
4726
  ctx,
4586
4727
  needNewContainers,
4587
4728
  startBuffered,
4588
4729
  endBuffered,
4589
4730
  pendingRemoval,
4590
- getRequiredItemType,
4591
- protectedContainerKeys
4731
+ protectedContainerKeys,
4732
+ // Initial end alignment has no scroll velocity yet. Keep its
4733
+ // target-side rows first until the initial scroll settles.
4734
+ // After a pause velocity can be zero on the first movement;
4735
+ // use its displacement without treating initial insets as motion.
4736
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4592
4737
  );
4593
4738
  for (const allocation of availableContainerAllocations) {
4594
4739
  const i = allocation.itemIndex;
@@ -4735,6 +4880,7 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4735
4880
  const nextAnchorIndex = anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex;
4736
4881
  let nextAnchorKey;
4737
4882
  let isReady = true;
4883
+ let canUpdateSize = true;
4738
4884
  let nextSize = 0;
4739
4885
  if (anchoredEndSpace) {
4740
4886
  const { anchorIndex, anchorMaxSize, anchorOffset = 0 } = anchoredEndSpace;
@@ -4756,25 +4902,30 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4756
4902
  contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap);
4757
4903
  contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props);
4758
4904
  isReady = !hasUnknownTailSize;
4759
- nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4905
+ const knownSizeBound = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4906
+ nextSize = hasUnknownTailSize ? Math.min(previousSize || 0, knownSizeBound) : knownSizeBound;
4760
4907
  } else if (anchorIndex >= 0) {
4761
4908
  isReady = false;
4909
+ canUpdateSize = false;
4762
4910
  }
4763
4911
  }
4764
4912
  const didSizeChange = previousSize !== nextSize && (previousSize !== void 0 || anchoredEndSpace !== void 0);
4765
4913
  const didEffectiveSizeChange = (previousSize || 0) !== nextSize;
4914
+ const canApplySizeChange = canUpdateSize && (isReady || previousSize !== void 0);
4766
4915
  const didReadyAnchorChange = previousReadyAnchorIndex !== nextAnchorIndex || previousReadyAnchorKey !== nextAnchorKey;
4767
- if (isReady && (didSizeChange || didReadyAnchorChange)) {
4916
+ const didBecomeReady = isReady && state.anchoredEndSpacePendingReady;
4917
+ state.anchoredEndSpacePendingReady = !isReady;
4918
+ if (canApplySizeChange && didSizeChange) {
4919
+ set$(ctx, "anchoredEndSpaceSize", nextSize);
4920
+ (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4921
+ }
4922
+ if (canApplySizeChange && didEffectiveSizeChange) {
4923
+ updateContentMetricsState(ctx);
4924
+ updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4925
+ }
4926
+ if (isReady && (didSizeChange || didReadyAnchorChange || didBecomeReady)) {
4768
4927
  state.anchoredEndSpaceReadyAnchorIndex = nextAnchorIndex;
4769
4928
  state.anchoredEndSpaceReadyAnchorKey = nextAnchorKey;
4770
- if (didSizeChange) {
4771
- set$(ctx, "anchoredEndSpaceSize", nextSize);
4772
- (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4773
- }
4774
- if (didEffectiveSizeChange) {
4775
- updateContentMetricsState(ctx);
4776
- updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4777
- }
4778
4929
  (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize });
4779
4930
  }
4780
4931
  return nextSize;
@@ -4878,6 +5029,7 @@ function updateItemSizesBatch(ctx, measurements) {
4878
5029
  var _a3;
4879
5030
  const state = ctx.state;
4880
5031
  const result = {};
5032
+ const previousTotalSize = state.totalSize;
4881
5033
  for (const measurement of measurements) {
4882
5034
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4883
5035
  if (ownsMeasuredItem) {
@@ -4888,6 +5040,9 @@ function updateItemSizesBatch(ctx, measurements) {
4888
5040
  mergeItemSizeUpdateResult(result, nextResult);
4889
5041
  }
4890
5042
  }
5043
+ if (state.totalSize !== previousTotalSize) {
5044
+ set$(ctx, "totalSize", state.totalSize);
5045
+ }
4891
5046
  flushItemSizeUpdates(ctx, result);
4892
5047
  }
4893
5048
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -4926,12 +5081,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4926
5081
  }
4927
5082
  let needsRecalculate = !didContainersLayout;
4928
5083
  let shouldMaintainScrollAtEnd = false;
4929
- let minIndexSizeChanged;
5084
+ let positionRecalculationStartIndex;
4930
5085
  const prevSizeKnown = state.sizesKnown.get(itemKey);
4931
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5086
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
4932
5087
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
4933
5088
  if (diff !== 0) {
4934
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5089
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
4935
5090
  const { startBuffered, endBuffered } = state;
4936
5091
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
4937
5092
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -4948,8 +5103,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4948
5103
  size
4949
5104
  });
4950
5105
  }
4951
- if (minIndexSizeChanged !== void 0) {
4952
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5106
+ if (positionRecalculationStartIndex !== void 0) {
5107
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
4953
5108
  }
4954
5109
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
4955
5110
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -4966,7 +5121,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4966
5121
  didMeasureUserScrollAnchorResetItem
4967
5122
  };
4968
5123
  }
4969
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5124
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
4970
5125
  var _a3, _b;
4971
5126
  const state = ctx.state;
4972
5127
  const {
@@ -4989,7 +5144,16 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4989
5144
  fixedItemSize,
4990
5145
  itemType
4991
5146
  } : void 0;
4992
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5147
+ const prevSize = getItemSize(
5148
+ ctx,
5149
+ itemKey,
5150
+ index,
5151
+ itemData,
5152
+ void 0,
5153
+ void 0,
5154
+ notifyTotalSize,
5155
+ resolvedItemSize
5156
+ );
4993
5157
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
4994
5158
  const prevSizeKnown = sizesKnown.get(itemKey);
4995
5159
  if (Platform.OS !== "web" && prevSizeKnown !== void 0 && isNativeLayoutNoise(rawSize - prevSizeKnown)) {
@@ -5014,7 +5178,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5014
5178
  }
5015
5179
  }
5016
5180
  if (!prevSize || Math.abs(prevSize - size) > 0.1) {
5017
- setSize(ctx, itemKey, size);
5181
+ setSize(ctx, itemKey, size, notifyTotalSize);
5018
5182
  return size - prevSize;
5019
5183
  }
5020
5184
  return 0;
@@ -5165,10 +5329,16 @@ function useValue$(key, params) {
5165
5329
  var _a3;
5166
5330
  return (_a3 = getValue ? getValue(peek$(ctx, key)) : peek$(ctx, key)) != null ? _a3 : 0;
5167
5331
  };
5168
- const animValue = useAnimatedValue(getNewValue());
5332
+ const initialValue = getNewValue();
5333
+ const animValue = useAnimatedValue(initialValue);
5334
+ const lastValue = React2.useRef(initialValue);
5169
5335
  React2.useLayoutEffect(() => {
5170
5336
  const syncCurrentValue = () => {
5171
- animValue.setValue(getNewValue());
5337
+ const nextValue = getNewValue();
5338
+ if (lastValue.current !== nextValue) {
5339
+ lastValue.current = nextValue;
5340
+ animValue.setValue(nextValue);
5341
+ }
5172
5342
  };
5173
5343
  const unsubscribe = listen$(ctx, key, syncCurrentValue);
5174
5344
  syncCurrentValue();
@@ -6083,6 +6253,7 @@ var ListComponent = typedMemo(function ListComponent2({
6083
6253
  renderScrollComponent,
6084
6254
  onLayoutFooter,
6085
6255
  onInternalScrollBeginDrag,
6256
+ onInternalScrollEndDrag,
6086
6257
  onInternalScrollEnd,
6087
6258
  scrollAdjustHandler,
6088
6259
  snapToIndices,
@@ -6154,7 +6325,7 @@ var ListComponent = typedMemo(function ListComponent2({
6154
6325
  SnapOrScroll,
6155
6326
  {
6156
6327
  ...rest,
6157
- ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag },
6328
+ ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag, onScrollEndDrag: onInternalScrollEndDrag },
6158
6329
  contentContainerStyle: [
6159
6330
  horizontal ? { height: "100%" } : {},
6160
6331
  contentContainerStyle,
@@ -6273,7 +6444,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6273
6444
  for (const key in state.averageSizes) {
6274
6445
  delete state.averageSizes[key];
6275
6446
  }
6276
- state.minIndexSizeChanged = 0;
6447
+ state.positionRecalculationStartIndex = 0;
6277
6448
  state.scrollForNextCalculateItemsInView = void 0;
6278
6449
  }
6279
6450
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -6437,7 +6608,7 @@ function handleLayout(ctx, layoutParam, setCanRender) {
6437
6608
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
6438
6609
  }
6439
6610
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
6440
- doMaintainScrollAtEnd(ctx);
6611
+ doMaintainScrollAtEnd(ctx, { immediate: true });
6441
6612
  }
6442
6613
  checkThresholds(ctx);
6443
6614
  if (state) {
@@ -6670,14 +6841,17 @@ function scrollToEnd(ctx, options) {
6670
6841
  if (index === -1) {
6671
6842
  return false;
6672
6843
  }
6673
- const paddingBottom = state.props.stylePaddingBottom || 0;
6844
+ const paddingBottom = getStylePaddingEnd(state.props);
6674
6845
  const footerSize = peek$(ctx, "footerSize") || 0;
6675
6846
  scrollToIndex(ctx, {
6676
6847
  ...options,
6677
6848
  index,
6678
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6849
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
6679
6850
  viewPosition: 1
6680
6851
  });
6852
+ if (state.scrollingTo) {
6853
+ state.scrollingTo.isScrollToEnd = true;
6854
+ }
6681
6855
  return true;
6682
6856
  }
6683
6857
 
@@ -6875,7 +7049,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6875
7049
  for (const key in state.averageSizes) {
6876
7050
  delete state.averageSizes[key];
6877
7051
  }
6878
- state.minIndexSizeChanged = 0;
7052
+ state.positionRecalculationStartIndex = 0;
6879
7053
  state.scrollForNextCalculateItemsInView = void 0;
6880
7054
  state.pendingTotalSize = void 0;
6881
7055
  state.totalSize = 0;
@@ -7269,10 +7443,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7269
7443
  onMetricsChange,
7270
7444
  onLayout: onLayoutProp,
7271
7445
  onLoad,
7446
+ onReady,
7272
7447
  onMomentumScrollEnd,
7273
7448
  onRefresh,
7274
7449
  onScroll: onScrollProp,
7275
7450
  onScrollBeginDrag,
7451
+ onScrollEndDrag,
7276
7452
  onStartReached,
7277
7453
  onStartReachedThreshold = 0.5,
7278
7454
  onStickyHeaderChange,
@@ -7341,17 +7517,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7341
7517
  const initialScrollProp = initialScrollAtEnd ? {
7342
7518
  index: Math.max(0, dataProp.length - 1),
7343
7519
  preserveForBottomPadding: true,
7344
- viewOffset: -stylePaddingEndState,
7520
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
7345
7521
  viewPosition: 1
7346
7522
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7347
7523
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7348
7524
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7349
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
7525
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7350
7526
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7351
7527
  } : {
7352
7528
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7353
7529
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7354
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7530
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7355
7531
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7356
7532
  } : initialScrollUsesOffsetOnly ? {
7357
7533
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -7411,6 +7587,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7411
7587
  endReachedSnapshot: void 0,
7412
7588
  firstFullyOnScreenIndex: -1,
7413
7589
  freshDataTransitionEpoch: 0,
7590
+ handledDataChangeEpoch: 0,
7591
+ handledFreshDataTransitionEpoch: 0,
7414
7592
  hasHadNonEmptyData: dataProp.length > 0,
7415
7593
  idCache: [],
7416
7594
  idsInView: [],
@@ -7427,11 +7605,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7427
7605
  lastLayout: void 0,
7428
7606
  lastScrollDelta: 0,
7429
7607
  loadStartTime: Date.now(),
7430
- minIndexSizeChanged: 0,
7431
7608
  nativeContentInset: void 0,
7432
7609
  nativeMarginTop: 0,
7433
7610
  pendingDataComparison: void 0,
7434
7611
  pendingNativeMVCPAdjust: void 0,
7612
+ positionRecalculationStartIndex: 0,
7435
7613
  positions: [],
7436
7614
  props: {},
7437
7615
  queuedCalculateItemsInView: 0,
@@ -7470,8 +7648,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7470
7648
  refState.current = ctx.state;
7471
7649
  }
7472
7650
  const state = refState.current;
7473
- const handledDataChangeEpochRef = React2.useRef(state.dataChangeEpoch);
7474
- const handledFreshDataTransitionEpochRef = React2.useRef(state.freshDataTransitionEpoch);
7475
7651
  const isFirstLocal = state.isFirst;
7476
7652
  const previousAdaptiveRender = state.props.adaptiveRender;
7477
7653
  const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
@@ -7536,8 +7712,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7536
7712
  onItemSizeChanged,
7537
7713
  onLoad,
7538
7714
  onMomentumScrollEnd,
7715
+ onReady,
7539
7716
  onScroll: throttleScrollFn,
7540
7717
  onScrollBeginDrag,
7718
+ onScrollEndDrag,
7541
7719
  onStartReached,
7542
7720
  onStartReachedThreshold,
7543
7721
  onStickyHeaderChange,
@@ -7626,10 +7804,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7626
7804
  useDevChecks(props);
7627
7805
  }
7628
7806
  React2.useLayoutEffect(() => {
7629
- const didDataChange = handledDataChangeEpochRef.current !== dataChangeEpoch;
7630
- const didStartFreshData = handledFreshDataTransitionEpochRef.current !== freshDataTransitionEpoch;
7631
- handledDataChangeEpochRef.current = dataChangeEpoch;
7632
- handledFreshDataTransitionEpochRef.current = freshDataTransitionEpoch;
7807
+ const didDataChange = state.handledDataChangeEpoch !== dataChangeEpoch;
7808
+ const didStartFreshData = state.handledFreshDataTransitionEpoch !== freshDataTransitionEpoch;
7809
+ state.handledDataChangeEpoch = dataChangeEpoch;
7810
+ state.handledFreshDataTransitionEpoch = freshDataTransitionEpoch;
7633
7811
  if (didStartFreshData) {
7634
7812
  resetInitialRenderState(ctx, {
7635
7813
  resetInitialScroll: !!initialScrollProp,
@@ -7833,17 +8011,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7833
8011
  onScroll: (event) => onScroll(ctx, event),
7834
8012
  onScrollBeginDrag: (event) => {
7835
8013
  var _a4, _b2;
7836
- const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
7837
- if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
7838
- cancelImperativeScroll(state);
7839
- }
7840
- if (maintainingScrollAtEnd) {
7841
- finishMaintainScrollAtEnd(ctx);
7842
- }
8014
+ state.isDragging = true;
8015
+ interruptMaintainScrollAtEnd(ctx);
7843
8016
  prepareReachedEdgeForNextUserScroll(ctx);
7844
8017
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7845
8018
  },
7846
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
8019
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
8020
+ onScrollEndDrag: (event) => {
8021
+ var _a4, _b2;
8022
+ state.isDragging = false;
8023
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8024
+ }
7847
8025
  }),
7848
8026
  []
7849
8027
  );
@@ -7867,6 +8045,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7867
8045
  ListHeaderComponent,
7868
8046
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
7869
8047
  onInternalScrollEnd: fns.onScrollEnd,
8048
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
7870
8049
  onLayout,
7871
8050
  onLayoutFooter,
7872
8051
  onMomentumScrollEnd: fns.onMomentumScrollEnd,