@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.mjs CHANGED
@@ -437,6 +437,14 @@ var EDGE_POSITION_EPSILON = 1;
437
437
  var ENABLE_DEVMODE = IS_DEV && false;
438
438
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
439
439
 
440
+ // src/core/endOfContentTarget.ts
441
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
442
+ return -stylePaddingEnd - footerSize;
443
+ }
444
+ function isEndOfContentTarget(target, dataLength) {
445
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
446
+ }
447
+
440
448
  // src/core/cancelImperativeScroll.ts
441
449
  function cancelScrollCompletionChecks({ scheduledWork }) {
442
450
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -621,26 +629,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
621
629
  scrollPosition: context.scrollPosition
622
630
  });
623
631
  };
632
+ let reached = !!wasReached;
624
633
  if (!wasReached) {
625
- if (!within) {
626
- return false;
627
- }
628
- onReached(distance);
629
- updateSnapshot();
630
- return true;
631
- }
632
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
633
- if (reset) {
634
- setSnapshot(void 0);
635
- return false;
636
- }
637
- if (within) {
638
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
639
- if (changed) {
634
+ if (within && onReached(distance)) {
640
635
  updateSnapshot();
636
+ reached = true;
637
+ }
638
+ } else {
639
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
640
+ if (reset) {
641
+ setSnapshot(void 0);
642
+ reached = false;
643
+ } else if (within) {
644
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
645
+ if (changed) {
646
+ updateSnapshot();
647
+ }
641
648
  }
642
649
  }
643
- return true;
650
+ return reached;
644
651
  };
645
652
 
646
653
  // src/utils/edgeReachedGate.ts
@@ -687,7 +694,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
687
694
  return void 0;
688
695
  }
689
696
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
690
- state.edgeReachedGate = "closed";
691
697
  resetEdgeLatch(ctx, allowedEdge);
692
698
  return allowedEdge;
693
699
  }
@@ -728,7 +734,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
728
734
  set$(
729
735
  ctx,
730
736
  "isWithinMaintainScrollAtEndThreshold",
731
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
737
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
732
738
  );
733
739
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
734
740
  if (!shouldSkipThresholdChecks) {
@@ -744,11 +750,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
744
750
  scrollPosition: scroll
745
751
  },
746
752
  (distance) => {
747
- var _a4, _b;
748
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
753
+ let dispatched = false;
754
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
749
755
  markReachedEdge(ctx);
750
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
756
+ state.props.onEndReached({ distanceFromEnd: distance });
757
+ dispatched = true;
751
758
  }
759
+ return dispatched;
752
760
  },
753
761
  (snapshot) => {
754
762
  state.endReachedSnapshot = snapshot;
@@ -781,7 +789,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
781
789
  }
782
790
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
783
791
  set$(ctx, "isNearStart", scroll <= threshold);
784
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
792
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
785
793
  if (!shouldSkipThresholdChecks) {
786
794
  state.isStartReached = checkThreshold(
787
795
  scroll,
@@ -795,11 +803,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
795
803
  scrollPosition: scroll
796
804
  },
797
805
  (distance) => {
798
- var _a3, _b;
799
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
806
+ let dispatched = false;
807
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
800
808
  markReachedEdge(ctx);
801
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
809
+ state.props.onStartReached({ distanceFromStart: distance });
810
+ dispatched = true;
802
811
  }
812
+ return dispatched;
803
813
  },
804
814
  (snapshot) => {
805
815
  state.startReachedSnapshot = snapshot;
@@ -918,6 +928,11 @@ function resetInitialRenderState(ctx, {
918
928
  if (resetLayout) {
919
929
  state.didContainersLayout = false;
920
930
  state.queuedInitialLayout = false;
931
+ state.edgeReachedGate = void 0;
932
+ state.isStartReached = null;
933
+ state.isEndReached = null;
934
+ state.startReachedSnapshot = void 0;
935
+ state.endReachedSnapshot = void 0;
921
936
  }
922
937
  if (resetInitialScroll) {
923
938
  state.didFinishInitialScroll = false;
@@ -932,7 +947,7 @@ function setInitialRenderState(ctx, {
932
947
  const { state } = ctx;
933
948
  const {
934
949
  loadStartTime,
935
- props: { onLoad }
950
+ props: { onLoad, onReady }
936
951
  } = state;
937
952
  if (didLayout) {
938
953
  state.didContainersLayout = true;
@@ -953,6 +968,7 @@ function setInitialRenderState(ctx, {
953
968
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
954
969
  }
955
970
  }
971
+ onReady == null ? void 0 : onReady();
956
972
  }
957
973
  }
958
974
 
@@ -993,6 +1009,7 @@ function supersedeInitialScroll(ctx) {
993
1009
  function finishInitialScroll(ctx, options) {
994
1010
  var _a3, _b, _c;
995
1011
  const state = ctx.state;
1012
+ const freshDataTransitionEpoch = state.freshDataTransitionEpoch;
996
1013
  if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
997
1014
  syncInitialScrollOffset(state, options.resolvedOffset);
998
1015
  } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
@@ -1002,9 +1019,13 @@ function finishInitialScroll(ctx, options) {
1002
1019
  }
1003
1020
  }
1004
1021
  const complete = () => {
1005
- var _a4, _b2, _c2, _d, _e;
1006
- const shouldReleaseDeferredPublicOnScroll = Platform.OS === "web" && ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
1007
- const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
1022
+ var _a4, _b2, _c2, _d, _e, _f;
1023
+ if (freshDataTransitionEpoch !== state.freshDataTransitionEpoch) {
1024
+ (_a4 = options == null ? void 0 : options.onFinished) == null ? void 0 : _a4.call(options);
1025
+ return;
1026
+ }
1027
+ const shouldReleaseDeferredPublicOnScroll = Platform.OS === "web" && ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap";
1028
+ const finalScrollOffset = (_e = (_d = (_c2 = options == null ? void 0 : options.resolvedOffset) != null ? _c2 : state.scrollPending) != null ? _d : state.scroll) != null ? _e : 0;
1008
1029
  initialScrollWatchdog.clear(state);
1009
1030
  if ((options == null ? void 0 : options.preserveTarget) && state.initialScroll) {
1010
1031
  state.clearPreservedInitialScrollOnNextFinish = void 0;
@@ -1033,7 +1054,7 @@ function finishInitialScroll(ctx, options) {
1033
1054
  if (shouldReleaseDeferredPublicOnScroll) {
1034
1055
  releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1035
1056
  }
1036
- (_e = options == null ? void 0 : options.onFinished) == null ? void 0 : _e.call(options);
1057
+ (_f = options == null ? void 0 : options.onFinished) == null ? void 0 : _f.call(options);
1037
1058
  };
1038
1059
  if (options == null ? void 0 : options.waitForCompletionFrame) {
1039
1060
  requestAnimationFrame(complete);
@@ -1293,10 +1314,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1293
1314
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1294
1315
  const trailingInset = getContentInsetEnd(ctx);
1295
1316
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1296
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1297
- const footerSize = peek$(ctx, "footerSize") || 0;
1298
- offset += footerSize;
1299
- }
1300
1317
  }
1301
1318
  return offset;
1302
1319
  }
@@ -1317,246 +1334,21 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1317
1334
  return clampedOffset;
1318
1335
  }
1319
1336
 
1320
- // src/core/finishScrollTo.ts
1321
- function finishScrollTo(ctx) {
1322
- var _a3, _b;
1323
- const state = ctx.state;
1324
- if (state == null ? void 0 : state.scrollingTo) {
1325
- cancelScrollCompletionChecks(state);
1326
- const resolvePendingScroll = state.pendingScrollResolve;
1327
- state.pendingScrollResolve = void 0;
1328
- const scrollingTo = state.scrollingTo;
1329
- state.scrollHistory.length = 0;
1330
- state.scrollingTo = void 0;
1331
- state.scrollTargetPinnedRange = void 0;
1332
- if (state.pendingTotalSize !== void 0) {
1333
- addTotalSize(ctx, null, state.pendingTotalSize);
1334
- }
1335
- if (PlatformAdjustBreaksScroll) {
1336
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1337
- }
1338
- if (scrollingTo.isInitialScroll || state.initialScroll) {
1339
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
1340
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
1341
- finishInitialScroll(ctx, {
1342
- onFinished: () => {
1343
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1344
- },
1345
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1346
- recalculateItems: true,
1347
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
1348
- syncObservedOffset: isOffsetSession,
1349
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1350
- });
1351
- return;
1352
- }
1353
- recalculateSettledScroll(ctx);
1354
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1355
- }
1356
- }
1357
-
1358
- // src/core/checkFinishedScroll.ts
1359
- var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
1360
- var INITIAL_SCROLL_COMPLETION_TARGET_EPSILON = 1;
1361
- var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
1362
- var SILENT_INITIAL_SCROLL_RETRY_DELAY_MS = 16;
1363
- var SILENT_INITIAL_SCROLL_TARGET_EPSILON = 1;
1364
- function checkFinishedScroll(ctx, options) {
1365
- const scrollingTo = ctx.state.scrollingTo;
1366
- if (options == null ? void 0 : options.onlyIfAligned) {
1367
- if (!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || scrollingTo.animated) {
1368
- return;
1369
- }
1370
- if (!getResolvedScrollCompletionState(ctx, scrollingTo).isAtResolvedTarget) {
1371
- return;
1372
- }
1373
- }
1374
- ctx.state.scheduledWork.frame(() => checkFinishedScrollFrame(ctx), "checkFinishedScrollFrame");
1375
- }
1376
- function hasScrollCompletionOwnership(state, options) {
1377
- const { clampedTargetOffset, scrollingTo } = options;
1378
- return !scrollingTo.isInitialScroll || state.hasScrolled || clampedTargetOffset <= INITIAL_SCROLL_COMPLETION_TARGET_EPSILON;
1379
- }
1380
- function isSilentInitialDispatch(state, scrollingTo) {
1381
- return !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled;
1382
- }
1383
- function getInitialScrollWatchdogTargetOffset(state) {
1384
- var _a3;
1385
- return (_a3 = initialScrollWatchdog.get(state)) == null ? void 0 : _a3.targetOffset;
1386
- }
1387
- function isNativeInitialNonZeroTarget(state) {
1388
- const targetOffset = getInitialScrollWatchdogTargetOffset(state);
1389
- return !state.didFinishInitialScroll && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
1390
- }
1391
- function shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) {
1392
- var _a3, _b;
1393
- if (!scrollingTo.isInitialScroll || scrollingTo.animated || !state.didContainersLayout) {
1394
- return false;
1395
- }
1396
- if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap") {
1397
- return false;
1398
- }
1399
- const targetOffset = (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset;
1400
- if (initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled) {
1401
- return false;
1402
- }
1403
- if (initialScrollWatchdog.isAtZeroTargetOffset(targetOffset) || Math.abs(state.scroll - targetOffset) > 1 || Math.abs(state.scrollPending - targetOffset) > 1) {
1404
- return false;
1405
- }
1406
- return !!scrollingTo.waitForInitialScrollCompletionFrame || isNativeInitialNonZeroTarget(state);
1407
- }
1408
- function shouldFinishInitialZeroTargetScroll(ctx) {
1409
- var _a3;
1410
- const { state } = ctx;
1411
- 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;
1412
- }
1413
- function isEndAlignedLastItemTarget(ctx, scrollingTo) {
1414
- return scrollingTo.index === ctx.state.props.data.length - 1 && scrollingTo.viewPosition === 1;
1415
- }
1416
- function getCurrentTargetOffset(ctx, scrollingTo) {
1417
- var _a3;
1418
- const index = scrollingTo.index;
1419
- const shouldRecomputeEndTarget = isEndAlignedLastItemTarget(ctx, scrollingTo);
1420
- 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);
1421
- return clampScrollOffset(ctx, requestedTargetOffset, scrollingTo);
1422
- }
1423
- function getResolvedScrollCompletionState(ctx, scrollingTo) {
1424
- const { state } = ctx;
1425
- const scroll = state.scrollPending;
1426
- const adjust = state.scrollAdjustHandler.getAdjust();
1427
- const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
1428
- const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
1429
- const diff1 = Math.abs(scroll - clampedTargetOffset);
1430
- const adjustedTargetOffset = clampedTargetOffset + adjust;
1431
- const diff2 = Math.abs(scroll - adjustedTargetOffset);
1432
- const canUseAdjustedCompletion = !scrollingTo.animated || Platform.OS === "ios";
1433
- return {
1434
- clampedTargetOffset,
1435
- isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || canUseAdjustedCompletion && diff2 < 1)
1436
- };
1437
- }
1438
- function checkFinishedScrollFrame(ctx) {
1439
- const scrollingTo = ctx.state.scrollingTo;
1440
- if (!scrollingTo) {
1441
- return;
1442
- }
1443
- const { state } = ctx;
1444
- const completionState = getResolvedScrollCompletionState(ctx, scrollingTo);
1445
- if (completionState.isAtResolvedTarget && hasScrollCompletionOwnership(state, {
1446
- clampedTargetOffset: completionState.clampedTargetOffset,
1447
- scrollingTo
1448
- })) {
1449
- finishScrollTo(ctx);
1450
- }
1451
- }
1452
- function scrollToFallbackOffset(ctx, offset) {
1453
- var _a3;
1454
- (_a3 = ctx.state.refScroller.current) == null ? void 0 : _a3.scrollTo({
1455
- animated: false,
1456
- x: ctx.state.props.horizontal ? offset : 0,
1457
- y: ctx.state.props.horizontal ? 0 : offset
1458
- });
1459
- }
1460
- function checkFinishedScrollFallback(ctx) {
1461
- const state = ctx.state;
1462
- if (state.scheduledWork.has("checkFinishedScrollFallback")) {
1463
- return;
1464
- }
1465
- const scrollingTo = state.scrollingTo;
1466
- const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1467
- const silentInitialDispatch = isSilentInitialDispatch(state, scrollingTo);
1468
- const canFinishInitialWithoutNativeProgress = scrollingTo !== void 0 ? shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) : false;
1469
- const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget && !canFinishInitialWithoutNativeProgress || !state.didContainersLayout;
1470
- const initialDelay = shouldFinishInitialZeroTarget || canFinishInitialWithoutNativeProgress ? 0 : silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : slowTimeout ? 500 : 100;
1471
- let numChecks = 0;
1472
- const scheduleFallbackCheck = (delay) => {
1473
- state.scheduledWork.timeout(checkHasScrolled, delay, "checkFinishedScrollFallback");
1474
- };
1475
- const checkHasScrolled = () => {
1476
- var _a3, _b, _c, _d;
1477
- const isStillScrollingTo = state.scrollingTo;
1478
- if (isStillScrollingTo) {
1479
- numChecks++;
1480
- const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
1481
- const maxChecks = silentInitialDispatch ? 5 : isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
1482
- const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1483
- const canFinishInitialScrollWithoutNativeProgress = shouldFinishInitialScrollWithoutNativeProgress(
1484
- state,
1485
- isStillScrollingTo
1486
- );
1487
- const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
1488
- const canFinishAfterSilentNativeDispatch = Platform.OS === "android" && silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
1489
- const shouldRetrySilentInitialNativeScroll = Platform.OS === "android" && canFinishAfterSilentNativeDispatch && !initialScrollCompletion.didRetrySilentInitialScroll(state);
1490
- const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
1491
- const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
1492
- const shouldRetryUnalignedEndScroll = Platform.OS === "ios" && !isStillScrollingTo.isInitialScroll && isEndAlignedLastItemTarget(ctx, isStillScrollingTo) && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
1493
- if (shouldRetrySilentInitialNativeScroll) {
1494
- const targetOffset = (_b = (_a3 = getInitialScrollWatchdogTargetOffset(state)) != null ? _a3 : isStillScrollingTo.targetOffset) != null ? _b : 0;
1495
- const jiggleOffset = targetOffset >= SILENT_INITIAL_SCROLL_TARGET_EPSILON ? targetOffset - SILENT_INITIAL_SCROLL_TARGET_EPSILON : targetOffset + SILENT_INITIAL_SCROLL_TARGET_EPSILON;
1496
- initialScrollCompletion.markSilentInitialScrollRetry(state);
1497
- scrollToFallbackOffset(ctx, jiggleOffset);
1498
- state.scheduledWork.frame(
1499
- () => scrollToFallbackOffset(ctx, targetOffset),
1500
- "checkFinishedScrollRetryFrame"
1501
- );
1502
- scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS);
1503
- } else if (shouldRetryUnalignedEndScroll) {
1504
- scrollToFallbackOffset(ctx, completionState.clampedTargetOffset);
1505
- scheduleFallbackCheck(100);
1506
- } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
1507
- finishScrollTo(ctx);
1508
- } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
1509
- const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
1510
- scrollToFallbackOffset(ctx, targetOffset);
1511
- scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
1512
- } else {
1513
- scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
1514
- }
1515
- }
1516
- };
1517
- scheduleFallbackCheck(initialDelay);
1518
- }
1519
-
1520
- // src/core/doScrollTo.native.ts
1521
- function doScrollTo(ctx, params) {
1522
- const state = ctx.state;
1523
- const { animated, horizontal, isInitialScroll, offset } = params;
1524
- const isAnimated = !!animated;
1525
- const { refScroller } = state;
1526
- const scroller = refScroller.current;
1527
- if (!scroller) {
1528
- return;
1529
- }
1530
- const isHorizontal = !!horizontal;
1531
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
1532
- const nativeOffset = toNativeHorizontalOffset(state, offset, contentSize);
1533
- scroller.scrollTo({
1534
- animated: isAnimated,
1535
- x: isHorizontal ? nativeOffset : 0,
1536
- y: isHorizontal ? 0 : offset
1537
- });
1538
- if (isInitialScroll) {
1539
- initialScrollCompletion.markInitialScrollNativeDispatch(state);
1540
- }
1541
- if (isAnimated && Math.abs(state.scroll - offset) <= 1) {
1542
- checkFinishedScroll(ctx);
1543
- }
1544
- if (!isAnimated) {
1545
- state.scroll = offset;
1546
- checkFinishedScrollFallback(ctx);
1547
- }
1548
- }
1549
-
1550
1337
  // src/core/scrollRequestTracker.ts
1551
1338
  function getScrollRequestTracker(ctx) {
1552
1339
  if (!ctx.scrollRequestTracker) {
1553
1340
  let currentToken = 0;
1554
- const start = (resolve) => {
1341
+ let waitingToken;
1342
+ const start = (resolve, isInternal = false) => {
1555
1343
  const state = ctx.state;
1344
+ if (!isInternal && (state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd)) {
1345
+ finishMaintainScrollAtEnd(ctx);
1346
+ }
1556
1347
  state.scheduledWork.cancel("imperativeScrollReady");
1557
1348
  const token = ++currentToken;
1558
1349
  settlePendingImperativeScroll(state);
1559
1350
  state.pendingScrollResolve = resolve;
1351
+ waitingToken = token;
1560
1352
  return token;
1561
1353
  };
1562
1354
  const runNow = (token, resolve, run) => {
@@ -1564,6 +1356,7 @@ function getScrollRequestTracker(ctx) {
1564
1356
  if (token !== currentToken) {
1565
1357
  return;
1566
1358
  }
1359
+ waitingToken = void 0;
1567
1360
  const didStartScroll = run();
1568
1361
  if (!didStartScroll || !state.scrollingTo) {
1569
1362
  if (state.pendingScrollResolve === resolve) {
@@ -1574,6 +1367,7 @@ function getScrollRequestTracker(ctx) {
1574
1367
  };
1575
1368
  ctx.scrollRequestTracker = {
1576
1369
  isCurrent: (token) => token === currentToken,
1370
+ isWaitingToRun: () => waitingToken === currentToken && !!ctx.state.pendingScrollResolve,
1577
1371
  runNow,
1578
1372
  runNowIfIdle: (run) => {
1579
1373
  const state = ctx.state;
@@ -1581,7 +1375,7 @@ function getScrollRequestTracker(ctx) {
1581
1375
  return Promise.resolve();
1582
1376
  }
1583
1377
  return new Promise((resolve) => {
1584
- const token = start(resolve);
1378
+ const token = start(resolve, true);
1585
1379
  runNow(token, resolve, run);
1586
1380
  });
1587
1381
  },
@@ -1645,87 +1439,6 @@ function requestAdjust(ctx, positionDiff, source) {
1645
1439
  }
1646
1440
  }
1647
1441
 
1648
- // src/core/doMaintainScrollAtEnd.ts
1649
- function finishMaintainScrollAtEnd(ctx) {
1650
- var _a3;
1651
- const { state } = ctx;
1652
- const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1653
- const nextPadding = getAlignItemsAtEndPadding(ctx);
1654
- state.maintainingScrollAtEnd = void 0;
1655
- state.pendingMaintainScrollAtEnd = false;
1656
- if (currentPadding !== nextPadding) {
1657
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1658
- state.scrollForNextCalculateItemsInView = void 0;
1659
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1660
- const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1661
- requestAdjust(ctx, nextScroll - state.scroll);
1662
- }
1663
- }
1664
- function doMaintainScrollAtEnd(ctx) {
1665
- const state = ctx.state;
1666
- const {
1667
- didContainersLayout,
1668
- didFinishInitialScroll,
1669
- pendingNativeMVCPAdjust,
1670
- props: { maintainScrollAtEnd }
1671
- } = state;
1672
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1673
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1674
- const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1675
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1676
- state.pendingMaintainScrollAtEnd = true;
1677
- return false;
1678
- }
1679
- if (pendingNativeMVCPAdjust) {
1680
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1681
- return false;
1682
- }
1683
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1684
- state.pendingMaintainScrollAtEnd = false;
1685
- const contentSize = getContentSize(ctx);
1686
- if (contentSize < state.scrollLength) {
1687
- state.scroll = 0;
1688
- }
1689
- if (!state.maintainingScrollAtEnd) {
1690
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1691
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1692
- const scrollAtRequest = state.scroll;
1693
- state.maintainingScrollAtEnd = pendingState;
1694
- requestAnimationFrame(() => {
1695
- if (state.maintainingScrollAtEnd !== pendingState) {
1696
- return;
1697
- }
1698
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1699
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1700
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1701
- state.maintainingScrollAtEnd = activeState;
1702
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1703
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1704
- );
1705
- void scrollPromise.then(() => {
1706
- if (state.maintainingScrollAtEnd !== activeState) {
1707
- return;
1708
- }
1709
- if (state.pendingMaintainScrollAtEnd) {
1710
- state.maintainingScrollAtEnd = void 0;
1711
- doMaintainScrollAtEnd(ctx);
1712
- } else {
1713
- finishMaintainScrollAtEnd(ctx);
1714
- }
1715
- });
1716
- } else if (state.maintainingScrollAtEnd === pendingState) {
1717
- finishMaintainScrollAtEnd(ctx);
1718
- }
1719
- });
1720
- } else {
1721
- state.pendingMaintainScrollAtEnd = true;
1722
- }
1723
- return true;
1724
- }
1725
- finishMaintainScrollAtEnd(ctx);
1726
- return false;
1727
- }
1728
-
1729
1442
  // src/core/mvcp.ts
1730
1443
  var MVCP_POSITION_EPSILON = 0.1;
1731
1444
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2043,125 +1756,489 @@ var getScrollVelocity = (state) => {
2043
1756
  if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2044
1757
  return 0;
2045
1758
  }
2046
- let direction = 0;
2047
- let weightedVelocity = 0;
2048
- let totalWeight = 0;
2049
- for (let i = newestIndex; i > 0; i--) {
2050
- const current = scrollHistory[i];
2051
- const previous = scrollHistory[i - 1];
2052
- const scrollDiff = current.scroll - previous.scroll;
2053
- const timeDiff = current.time - previous.time;
2054
- const deltaSign = Math.sign(scrollDiff);
2055
- if (deltaSign !== 0) {
2056
- if (direction === 0) {
2057
- direction = deltaSign;
2058
- } else if (deltaSign !== direction) {
2059
- break;
2060
- }
2061
- }
2062
- if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
2063
- break;
2064
- }
2065
- if (scrollDiff === 0 || timeDiff <= 0) {
2066
- continue;
2067
- }
2068
- const age = newest.time - current.time;
2069
- const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2070
- weightedVelocity += scrollDiff / timeDiff * weight;
2071
- totalWeight += weight;
1759
+ let direction = 0;
1760
+ let weightedVelocity = 0;
1761
+ let totalWeight = 0;
1762
+ for (let i = newestIndex; i > 0; i--) {
1763
+ const current = scrollHistory[i];
1764
+ const previous = scrollHistory[i - 1];
1765
+ const scrollDiff = current.scroll - previous.scroll;
1766
+ const timeDiff = current.time - previous.time;
1767
+ const deltaSign = Math.sign(scrollDiff);
1768
+ if (deltaSign !== 0) {
1769
+ if (direction === 0) {
1770
+ direction = deltaSign;
1771
+ } else if (deltaSign !== direction) {
1772
+ break;
1773
+ }
1774
+ }
1775
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1776
+ break;
1777
+ }
1778
+ if (scrollDiff === 0 || timeDiff <= 0) {
1779
+ continue;
1780
+ }
1781
+ const age = newest.time - current.time;
1782
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
1783
+ weightedVelocity += scrollDiff / timeDiff * weight;
1784
+ totalWeight += weight;
1785
+ }
1786
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
1787
+ };
1788
+
1789
+ // src/utils/isInMVCPActiveMode.native.ts
1790
+ function isInMVCPActiveMode(state) {
1791
+ return state.dataChangeNeedsScrollUpdate;
1792
+ }
1793
+
1794
+ // src/core/updateScroll.ts
1795
+ function updateScroll(ctx, newScroll, forceUpdate, options) {
1796
+ var _a3;
1797
+ const state = ctx.state;
1798
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1799
+ const prevScroll = state.scroll;
1800
+ if ((options == null ? void 0 : options.markHasScrolled) !== false) {
1801
+ state.hasScrolled = true;
1802
+ }
1803
+ const currentTime = Date.now();
1804
+ state.lastBatchingAction = currentTime;
1805
+ const adjust = scrollAdjustHandler.getAdjust();
1806
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1807
+ if (adjustChanged) {
1808
+ scrollHistory.length = 0;
1809
+ }
1810
+ state.lastScrollAdjustForHistory = adjust;
1811
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1812
+ if (!adjustChanged) {
1813
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
1814
+ }
1815
+ }
1816
+ if (scrollHistory.length > 5) {
1817
+ scrollHistory.shift();
1818
+ }
1819
+ if (ignoreScrollFromMVCP && !scrollingTo) {
1820
+ const { lt, gt } = ignoreScrollFromMVCP;
1821
+ if (lt && newScroll < lt || gt && newScroll > gt) {
1822
+ state.ignoreScrollFromMVCPIgnored = true;
1823
+ return;
1824
+ }
1825
+ }
1826
+ state.scrollPrev = prevScroll;
1827
+ state.scrollPrevTime = state.scrollTime;
1828
+ state.scroll = newScroll;
1829
+ state.scrollTime = currentTime;
1830
+ const scrollDelta = Math.abs(newScroll - prevScroll);
1831
+ const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1832
+ const didCancelMaintainScrollAtEnd = Platform.OS === "web" && isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1833
+ if (didCancelMaintainScrollAtEnd) {
1834
+ finishMaintainScrollAtEnd(ctx);
1835
+ }
1836
+ const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
1837
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1838
+ const scrollLength = state.scrollLength;
1839
+ const isLargeUserScrollJump = scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust;
1840
+ const scrollVelocity = getScrollVelocity(state);
1841
+ updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
1842
+ const lastCalculated = state.scrollLastCalculate;
1843
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1844
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1845
+ if (shouldUpdate) {
1846
+ state.scrollLastCalculate = state.scroll;
1847
+ state.ignoreScrollFromMVCPIgnored = false;
1848
+ state.lastScrollDelta = scrollDelta;
1849
+ const runCalculateItems = () => {
1850
+ var _a4;
1851
+ const calculateItemsParams = {
1852
+ doMVCP: scrollingTo !== void 0,
1853
+ scrollVelocity
1854
+ };
1855
+ if (isLargeUserScrollJump) {
1856
+ calculateItemsParams.drawDistanceMode = "visible-first";
1857
+ }
1858
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, calculateItemsParams);
1859
+ checkThresholds(ctx, allowedEdge);
1860
+ };
1861
+ if (isLargeUserScrollJump) {
1862
+ state.mvcpAnchorLock = void 0;
1863
+ state.pendingNativeMVCPAdjust = void 0;
1864
+ state.userScrollAnchorReset = { keys: /* @__PURE__ */ new Set() };
1865
+ state.scheduledWork.cancel("mvcpRecalculate");
1866
+ flushSync(runCalculateItems);
1867
+ scheduleFullDrawDistancePrewarm(ctx);
1868
+ } else {
1869
+ runCalculateItems();
1870
+ }
1871
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
1872
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
1873
+ state.pendingMaintainScrollAtEnd = false;
1874
+ doMaintainScrollAtEnd(ctx);
1875
+ }
1876
+ state.dataChangeNeedsScrollUpdate = false;
1877
+ state.lastScrollDelta = 0;
1878
+ }
1879
+ }
1880
+
1881
+ // src/core/doMaintainScrollAtEnd.ts
1882
+ function interruptMaintainScrollAtEnd(ctx) {
1883
+ var _a3;
1884
+ const { state } = ctx;
1885
+ const maintaining = state.maintainingScrollAtEnd;
1886
+ const cancelActiveScroll = !!(maintaining === "animated" || maintaining === "instant" || maintaining && ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isScrollToEnd));
1887
+ if (cancelActiveScroll) {
1888
+ cancelImperativeScroll(state);
1889
+ }
1890
+ if (maintaining || state.pendingMaintainScrollAtEnd) {
1891
+ finishMaintainScrollAtEnd(ctx);
1892
+ }
1893
+ return cancelActiveScroll;
1894
+ }
1895
+ function finishMaintainScrollAtEnd(ctx) {
1896
+ var _a3;
1897
+ const { state } = ctx;
1898
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1899
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1900
+ state.maintainingScrollAtEnd = void 0;
1901
+ state.pendingMaintainScrollAtEnd = false;
1902
+ if (currentPadding !== nextPadding) {
1903
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1904
+ state.scrollForNextCalculateItemsInView = void 0;
1905
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1906
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1907
+ requestAdjust(ctx, nextScroll - state.scroll);
1908
+ }
1909
+ }
1910
+ function doMaintainScrollAtEnd(ctx, options) {
1911
+ var _a3, _b, _c, _d, _e, _f;
1912
+ const state = ctx.state;
1913
+ let didMaintain = false;
1914
+ if (state.isDragging) {
1915
+ finishMaintainScrollAtEnd(ctx);
1916
+ } else {
1917
+ const {
1918
+ didContainersLayout,
1919
+ didFinishInitialScroll,
1920
+ pendingNativeMVCPAdjust,
1921
+ props: { maintainScrollAtEnd }
1922
+ } = state;
1923
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1924
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1925
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1926
+ 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);
1927
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1928
+ const scroller = state.refScroller.current;
1929
+ const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1930
+ const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1931
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1932
+ state.pendingMaintainScrollAtEnd = true;
1933
+ state.scrollPending = observed;
1934
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1935
+ }
1936
+ }
1937
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1938
+ state.pendingMaintainScrollAtEnd = true;
1939
+ } else if (pendingNativeMVCPAdjust) {
1940
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1941
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1942
+ if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1943
+ state.pendingMaintainScrollAtEnd = false;
1944
+ ctx.scrollToEnd({ animated: false });
1945
+ didMaintain = true;
1946
+ } else {
1947
+ state.pendingMaintainScrollAtEnd = true;
1948
+ (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1949
+ }
1950
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1951
+ state.pendingMaintainScrollAtEnd = false;
1952
+ const contentSize = getContentSize(ctx);
1953
+ if (contentSize < state.scrollLength) {
1954
+ state.scroll = 0;
1955
+ }
1956
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1957
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1958
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1959
+ const scrollAtRequest = state.scroll;
1960
+ state.maintainingScrollAtEnd = pendingState;
1961
+ const run = () => {
1962
+ if (state.maintainingScrollAtEnd === pendingState) {
1963
+ if (state.isDragging) {
1964
+ finishMaintainScrollAtEnd(ctx);
1965
+ } else {
1966
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1967
+ const didScrollSinceRequest = Platform.OS === "web" && state.scroll !== scrollAtRequest;
1968
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1969
+ if (state.scrollingTo || state.pendingScrollResolve) {
1970
+ state.pendingMaintainScrollAtEnd = true;
1971
+ } else {
1972
+ state.maintainingScrollAtEnd = activeState;
1973
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1974
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1975
+ );
1976
+ void scrollPromise.then(() => {
1977
+ if (state.maintainingScrollAtEnd === activeState) {
1978
+ if (state.pendingMaintainScrollAtEnd) {
1979
+ state.maintainingScrollAtEnd = void 0;
1980
+ doMaintainScrollAtEnd(ctx);
1981
+ } else {
1982
+ finishMaintainScrollAtEnd(ctx);
1983
+ }
1984
+ }
1985
+ });
1986
+ }
1987
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1988
+ finishMaintainScrollAtEnd(ctx);
1989
+ }
1990
+ }
1991
+ }
1992
+ };
1993
+ if (immediate) {
1994
+ run();
1995
+ } else {
1996
+ requestAnimationFrame(run);
1997
+ }
1998
+ } else {
1999
+ state.pendingMaintainScrollAtEnd = true;
2000
+ }
2001
+ didMaintain = true;
2002
+ } else {
2003
+ finishMaintainScrollAtEnd(ctx);
2004
+ }
2005
+ }
2006
+ return didMaintain;
2007
+ }
2008
+
2009
+ // src/core/finishScrollTo.ts
2010
+ function finishScrollTo(ctx) {
2011
+ var _a3, _b, _c;
2012
+ const state = ctx.state;
2013
+ if (state == null ? void 0 : state.scrollingTo) {
2014
+ cancelScrollCompletionChecks(state);
2015
+ const resolvePendingScroll = ((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) ? void 0 : state.pendingScrollResolve;
2016
+ if (resolvePendingScroll) {
2017
+ state.pendingScrollResolve = void 0;
2018
+ }
2019
+ const scrollingTo = state.scrollingTo;
2020
+ state.scrollHistory.length = 0;
2021
+ state.scrollingTo = void 0;
2022
+ state.scrollTargetPinnedRange = void 0;
2023
+ if (state.pendingTotalSize !== void 0) {
2024
+ addTotalSize(ctx, null, state.pendingTotalSize);
2025
+ }
2026
+ if (PlatformAdjustBreaksScroll) {
2027
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
2028
+ }
2029
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
2030
+ const isOffsetSession = ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) === "offset";
2031
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_c = state.initialScroll) == null ? void 0 : _c.viewPosition) === 1;
2032
+ finishInitialScroll(ctx, {
2033
+ onFinished: () => {
2034
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2035
+ },
2036
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
2037
+ recalculateItems: true,
2038
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
2039
+ syncObservedOffset: isOffsetSession,
2040
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
2041
+ });
2042
+ return;
2043
+ }
2044
+ recalculateSettledScroll(ctx);
2045
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2046
+ if (state.pendingMaintainScrollAtEnd) {
2047
+ state.maintainingScrollAtEnd = void 0;
2048
+ doMaintainScrollAtEnd(ctx);
2049
+ }
2050
+ }
2051
+ }
2052
+
2053
+ // src/core/checkFinishedScroll.ts
2054
+ var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
2055
+ var INITIAL_SCROLL_COMPLETION_TARGET_EPSILON = 1;
2056
+ var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
2057
+ var SILENT_INITIAL_SCROLL_RETRY_DELAY_MS = 16;
2058
+ var SILENT_INITIAL_SCROLL_TARGET_EPSILON = 1;
2059
+ function checkFinishedScroll(ctx, options) {
2060
+ const scrollingTo = ctx.state.scrollingTo;
2061
+ if (options == null ? void 0 : options.onlyIfAligned) {
2062
+ if (!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || scrollingTo.animated) {
2063
+ return;
2064
+ }
2065
+ if (!getResolvedScrollCompletionState(ctx, scrollingTo).isAtResolvedTarget) {
2066
+ return;
2067
+ }
2068
+ }
2069
+ ctx.state.scheduledWork.frame(() => checkFinishedScrollFrame(ctx), "checkFinishedScrollFrame");
2070
+ }
2071
+ function hasScrollCompletionOwnership(state, options) {
2072
+ const { clampedTargetOffset, scrollingTo } = options;
2073
+ return !scrollingTo.isInitialScroll || state.hasScrolled || clampedTargetOffset <= INITIAL_SCROLL_COMPLETION_TARGET_EPSILON;
2074
+ }
2075
+ function isSilentInitialDispatch(state, scrollingTo) {
2076
+ return !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled;
2077
+ }
2078
+ function getInitialScrollWatchdogTargetOffset(state) {
2079
+ var _a3;
2080
+ return (_a3 = initialScrollWatchdog.get(state)) == null ? void 0 : _a3.targetOffset;
2081
+ }
2082
+ function isNativeInitialNonZeroTarget(state) {
2083
+ const targetOffset = getInitialScrollWatchdogTargetOffset(state);
2084
+ return !state.didFinishInitialScroll && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
2085
+ }
2086
+ function shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) {
2087
+ var _a3, _b;
2088
+ if (!scrollingTo.isInitialScroll || scrollingTo.animated || !state.didContainersLayout) {
2089
+ return false;
2090
+ }
2091
+ if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap") {
2092
+ return false;
2093
+ }
2094
+ const targetOffset = (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset;
2095
+ if (initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled) {
2096
+ return false;
2097
+ }
2098
+ if (initialScrollWatchdog.isAtZeroTargetOffset(targetOffset) || Math.abs(state.scroll - targetOffset) > 1 || Math.abs(state.scrollPending - targetOffset) > 1) {
2099
+ return false;
2100
+ }
2101
+ return !!scrollingTo.waitForInitialScrollCompletionFrame || isNativeInitialNonZeroTarget(state);
2102
+ }
2103
+ function shouldFinishInitialZeroTargetScroll(ctx) {
2104
+ var _a3;
2105
+ const { state } = ctx;
2106
+ 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;
2107
+ }
2108
+ function isEndAlignedLastItemTarget(ctx, scrollingTo) {
2109
+ return scrollingTo.index === ctx.state.props.data.length - 1 && scrollingTo.viewPosition === 1;
2110
+ }
2111
+ function getCurrentTargetOffset(ctx, scrollingTo) {
2112
+ var _a3;
2113
+ const index = scrollingTo.index;
2114
+ const shouldRecomputeEndTarget = isEndAlignedLastItemTarget(ctx, scrollingTo);
2115
+ 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);
2116
+ return clampScrollOffset(ctx, requestedTargetOffset, scrollingTo);
2117
+ }
2118
+ function getResolvedScrollCompletionState(ctx, scrollingTo) {
2119
+ const { state } = ctx;
2120
+ const scroll = state.scrollPending;
2121
+ const adjust = state.scrollAdjustHandler.getAdjust();
2122
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
2123
+ const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
2124
+ const diff1 = Math.abs(scroll - clampedTargetOffset);
2125
+ const adjustedTargetOffset = clampedTargetOffset + adjust;
2126
+ const diff2 = Math.abs(scroll - adjustedTargetOffset);
2127
+ const canUseAdjustedCompletion = !scrollingTo.animated || Platform.OS === "ios";
2128
+ return {
2129
+ clampedTargetOffset,
2130
+ isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || canUseAdjustedCompletion && diff2 < 1)
2131
+ };
2132
+ }
2133
+ function checkFinishedScrollFrame(ctx) {
2134
+ const scrollingTo = ctx.state.scrollingTo;
2135
+ if (!scrollingTo) {
2136
+ return;
2137
+ }
2138
+ const { state } = ctx;
2139
+ const completionState = getResolvedScrollCompletionState(ctx, scrollingTo);
2140
+ if (completionState.isAtResolvedTarget && hasScrollCompletionOwnership(state, {
2141
+ clampedTargetOffset: completionState.clampedTargetOffset,
2142
+ scrollingTo
2143
+ })) {
2144
+ finishScrollTo(ctx);
2072
2145
  }
2073
- return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
2074
- };
2075
-
2076
- // src/utils/isInMVCPActiveMode.native.ts
2077
- function isInMVCPActiveMode(state) {
2078
- return state.dataChangeNeedsScrollUpdate;
2079
2146
  }
2080
-
2081
- // src/core/updateScroll.ts
2082
- function updateScroll(ctx, newScroll, forceUpdate, options) {
2147
+ function scrollToFallbackOffset(ctx, offset) {
2083
2148
  var _a3;
2149
+ (_a3 = ctx.state.refScroller.current) == null ? void 0 : _a3.scrollTo({
2150
+ animated: false,
2151
+ x: ctx.state.props.horizontal ? offset : 0,
2152
+ y: ctx.state.props.horizontal ? 0 : offset
2153
+ });
2154
+ }
2155
+ function checkFinishedScrollFallback(ctx) {
2084
2156
  const state = ctx.state;
2085
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
2086
- const prevScroll = state.scroll;
2087
- if ((options == null ? void 0 : options.markHasScrolled) !== false) {
2088
- state.hasScrolled = true;
2089
- }
2090
- const currentTime = Date.now();
2091
- state.lastBatchingAction = currentTime;
2092
- const adjust = scrollAdjustHandler.getAdjust();
2093
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
2094
- if (adjustChanged) {
2095
- scrollHistory.length = 0;
2157
+ if (state.scheduledWork.has("checkFinishedScrollFallback")) {
2158
+ return;
2096
2159
  }
2097
- state.lastScrollAdjustForHistory = adjust;
2098
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
2099
- if (!adjustChanged) {
2100
- scrollHistory.push({ scroll: newScroll, time: currentTime });
2160
+ const scrollingTo = state.scrollingTo;
2161
+ const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
2162
+ const silentInitialDispatch = isSilentInitialDispatch(state, scrollingTo);
2163
+ const canFinishInitialWithoutNativeProgress = scrollingTo !== void 0 ? shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) : false;
2164
+ const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget && !canFinishInitialWithoutNativeProgress || !state.didContainersLayout;
2165
+ const initialDelay = shouldFinishInitialZeroTarget || canFinishInitialWithoutNativeProgress ? 0 : silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : slowTimeout ? 500 : 100;
2166
+ let numChecks = 0;
2167
+ const scheduleFallbackCheck = (delay) => {
2168
+ state.scheduledWork.timeout(checkHasScrolled, delay, "checkFinishedScrollFallback");
2169
+ };
2170
+ const checkHasScrolled = () => {
2171
+ var _a3, _b, _c, _d;
2172
+ const isStillScrollingTo = state.scrollingTo;
2173
+ if (isStillScrollingTo) {
2174
+ numChecks++;
2175
+ const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
2176
+ const maxChecks = silentInitialDispatch ? 5 : isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
2177
+ const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
2178
+ const canFinishInitialScrollWithoutNativeProgress = shouldFinishInitialScrollWithoutNativeProgress(
2179
+ state,
2180
+ isStillScrollingTo
2181
+ );
2182
+ const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
2183
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android" && silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
2184
+ const shouldRetrySilentInitialNativeScroll = Platform.OS === "android" && canFinishAfterSilentNativeDispatch && !initialScrollCompletion.didRetrySilentInitialScroll(state);
2185
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
2186
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
2187
+ const shouldRetryUnalignedEndScroll = Platform.OS !== "web" && !isStillScrollingTo.isInitialScroll && isEndAlignedLastItemTarget(ctx, isStillScrollingTo) && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
2188
+ if (shouldRetrySilentInitialNativeScroll) {
2189
+ const targetOffset = (_b = (_a3 = getInitialScrollWatchdogTargetOffset(state)) != null ? _a3 : isStillScrollingTo.targetOffset) != null ? _b : 0;
2190
+ const jiggleOffset = targetOffset >= SILENT_INITIAL_SCROLL_TARGET_EPSILON ? targetOffset - SILENT_INITIAL_SCROLL_TARGET_EPSILON : targetOffset + SILENT_INITIAL_SCROLL_TARGET_EPSILON;
2191
+ initialScrollCompletion.markSilentInitialScrollRetry(state);
2192
+ scrollToFallbackOffset(ctx, jiggleOffset);
2193
+ state.scheduledWork.frame(
2194
+ () => scrollToFallbackOffset(ctx, targetOffset),
2195
+ "checkFinishedScrollRetryFrame"
2196
+ );
2197
+ scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS);
2198
+ } else if (shouldRetryUnalignedEndScroll) {
2199
+ scrollToFallbackOffset(ctx, completionState.clampedTargetOffset);
2200
+ scheduleFallbackCheck(100);
2201
+ } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
2202
+ finishScrollTo(ctx);
2203
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
2204
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
2205
+ scrollToFallbackOffset(ctx, targetOffset);
2206
+ scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
2207
+ } else {
2208
+ scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
2209
+ }
2101
2210
  }
2211
+ };
2212
+ scheduleFallbackCheck(initialDelay);
2213
+ }
2214
+
2215
+ // src/core/doScrollTo.native.ts
2216
+ function doScrollTo(ctx, params) {
2217
+ const state = ctx.state;
2218
+ const { animated, horizontal, isInitialScroll, offset } = params;
2219
+ const isAnimated = !!animated;
2220
+ const { refScroller } = state;
2221
+ const scroller = refScroller.current;
2222
+ if (!scroller) {
2223
+ return;
2102
2224
  }
2103
- if (scrollHistory.length > 5) {
2104
- scrollHistory.shift();
2105
- }
2106
- if (ignoreScrollFromMVCP && !scrollingTo) {
2107
- const { lt, gt } = ignoreScrollFromMVCP;
2108
- if (lt && newScroll < lt || gt && newScroll > gt) {
2109
- state.ignoreScrollFromMVCPIgnored = true;
2110
- return;
2111
- }
2225
+ const isHorizontal = !!horizontal;
2226
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2227
+ const nativeOffset = toNativeHorizontalOffset(state, offset, contentSize);
2228
+ scroller.scrollTo({
2229
+ animated: isAnimated,
2230
+ x: isHorizontal ? nativeOffset : 0,
2231
+ y: isHorizontal ? 0 : offset
2232
+ });
2233
+ if (isInitialScroll) {
2234
+ initialScrollCompletion.markInitialScrollNativeDispatch(state);
2112
2235
  }
2113
- state.scrollPrev = prevScroll;
2114
- state.scrollPrevTime = state.scrollTime;
2115
- state.scroll = newScroll;
2116
- state.scrollTime = currentTime;
2117
- const scrollDelta = Math.abs(newScroll - prevScroll);
2118
- const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
2119
- const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
2120
- if (didCancelMaintainScrollAtEnd) {
2121
- finishMaintainScrollAtEnd(ctx);
2236
+ if (isAnimated && Math.abs(state.scroll - offset) <= 1) {
2237
+ checkFinishedScroll(ctx);
2122
2238
  }
2123
- const allowedEdge = isUserScrollEvent ? beginReachedEdgeUserScroll(ctx, newScroll - prevScroll) : void 0;
2124
- const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2125
- const scrollLength = state.scrollLength;
2126
- const isLargeUserScrollJump = scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust;
2127
- const scrollVelocity = getScrollVelocity(state);
2128
- updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2129
- const lastCalculated = state.scrollLastCalculate;
2130
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2131
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || didCancelMaintainScrollAtEnd || allowedEdge !== void 0 || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2132
- if (shouldUpdate) {
2133
- state.scrollLastCalculate = state.scroll;
2134
- state.ignoreScrollFromMVCPIgnored = false;
2135
- state.lastScrollDelta = scrollDelta;
2136
- const runCalculateItems = () => {
2137
- var _a4;
2138
- const calculateItemsParams = {
2139
- doMVCP: scrollingTo !== void 0,
2140
- scrollVelocity
2141
- };
2142
- if (isLargeUserScrollJump) {
2143
- calculateItemsParams.drawDistanceMode = "visible-first";
2144
- }
2145
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, calculateItemsParams);
2146
- checkThresholds(ctx, allowedEdge);
2147
- };
2148
- if (isLargeUserScrollJump) {
2149
- state.mvcpAnchorLock = void 0;
2150
- state.pendingNativeMVCPAdjust = void 0;
2151
- state.userScrollAnchorReset = { keys: /* @__PURE__ */ new Set() };
2152
- state.scheduledWork.cancel("mvcpRecalculate");
2153
- flushSync(runCalculateItems);
2154
- scheduleFullDrawDistancePrewarm(ctx);
2155
- } else {
2156
- runCalculateItems();
2157
- }
2158
- const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
2159
- if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
2160
- state.pendingMaintainScrollAtEnd = false;
2161
- doMaintainScrollAtEnd(ctx);
2162
- }
2163
- state.dataChangeNeedsScrollUpdate = false;
2164
- state.lastScrollDelta = 0;
2239
+ if (!isAnimated) {
2240
+ state.scroll = offset;
2241
+ checkFinishedScrollFallback(ctx);
2165
2242
  }
2166
2243
  }
2167
2244
 
@@ -2315,7 +2392,9 @@ function scrollTo(ctx, params) {
2315
2392
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2316
2393
  }
2317
2394
  }
2318
- state.scrollPending = targetOffset;
2395
+ if (!animated || isInitialScroll) {
2396
+ state.scrollPending = targetOffset;
2397
+ }
2319
2398
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2320
2399
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2321
2400
  if (animated) {
@@ -2692,13 +2771,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2692
2771
  queueBootstrapInitialScrollReevaluation(ctx.state);
2693
2772
  }
2694
2773
  function createInitialScrollAtEndTarget(options) {
2695
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2774
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2696
2775
  return {
2697
2776
  contentOffset: void 0,
2698
- index: Math.max(0, dataLength - 1),
2777
+ index: index != null ? index : Math.max(0, dataLength - 1),
2699
2778
  preserveForBottomPadding: true,
2700
2779
  preserveForFooterLayout,
2701
- viewOffset: -stylePaddingEnd - footerSize,
2780
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2702
2781
  viewPosition: 1
2703
2782
  };
2704
2783
  }
@@ -2714,13 +2793,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2714
2793
  function createRetargetedBottomAlignedInitialScroll(options) {
2715
2794
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2716
2795
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2796
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2717
2797
  return {
2718
2798
  ...target,
2719
2799
  contentOffset: void 0,
2720
2800
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2721
2801
  preserveForBottomPadding: true,
2722
2802
  preserveForFooterLayout,
2723
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2803
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2724
2804
  viewPosition: 1
2725
2805
  };
2726
2806
  }
@@ -2734,7 +2814,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2734
2814
  }
2735
2815
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2736
2816
  dataLength,
2737
- footerSize: 0,
2817
+ footerSize: peek$(ctx, "footerSize") || 0,
2738
2818
  preserveForFooterLayout: void 0,
2739
2819
  stylePaddingEnd
2740
2820
  });
@@ -2757,13 +2837,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2757
2837
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2758
2838
  }
2759
2839
  function getPreservedEndAnchorOffsetDiff(ctx) {
2760
- var _a3;
2761
2840
  const state = ctx.state;
2762
2841
  const initialScroll = state.initialScroll;
2763
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2842
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2764
2843
  return;
2765
2844
  }
2766
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2845
+ const currentOffset = Platform.OS !== "web" && typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2767
2846
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2768
2847
  }
2769
2848
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2791,6 +2870,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2791
2870
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2792
2871
  if (hasObservedNativeScrollAfterRequest) {
2793
2872
  activeCorrection.lastRequestTime = Date.now();
2873
+ if (Platform.OS === "web") {
2874
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2875
+ }
2794
2876
  requestAdjust(ctx, offsetDiff);
2795
2877
  }
2796
2878
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2913,10 +2995,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2913
2995
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2914
2996
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2915
2997
  const state = ctx.state;
2916
- if (!initialScrollAtEnd) {
2998
+ const initialScroll = state.initialScroll;
2999
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2917
3000
  return;
2918
3001
  }
2919
- const initialScroll = state.initialScroll;
2920
3002
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2921
3003
  return;
2922
3004
  }
@@ -2934,6 +3016,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2934
3016
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2935
3017
  dataLength,
2936
3018
  footerSize,
3019
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
2937
3020
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2938
3021
  stylePaddingEnd
2939
3022
  });
@@ -3170,6 +3253,11 @@ function handleInitialScrollDataChange(ctx, options) {
3170
3253
  const state = ctx.state;
3171
3254
  const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3172
3255
  const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3256
+ if (didStartFreshData) {
3257
+ cancelImperativeScroll(state);
3258
+ state.maintainingScrollAtEnd = void 0;
3259
+ state.pendingMaintainScrollAtEnd = false;
3260
+ }
3173
3261
  if (dataLength > 0) {
3174
3262
  state.hasHadNonEmptyData = true;
3175
3263
  }
@@ -3888,7 +3976,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
3888
3976
  }
3889
3977
 
3890
3978
  // src/utils/findAvailableContainers.ts
3891
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
3979
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
3892
3980
  var _a3;
3893
3981
  const numNeeded = needNewContainers.length;
3894
3982
  if (numNeeded === 0) {
@@ -3897,16 +3985,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3897
3985
  const numContainers = peek$(ctx, "numContainers");
3898
3986
  const state = ctx.state;
3899
3987
  const { containerItemMetadata, stickyContainerPool } = state;
3988
+ const { data, getItemType } = state.props;
3989
+ const hasItemTypes = !!getItemType;
3900
3990
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
3901
3991
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
3902
- const requests = needNewContainers.map((itemIndex, order) => ({
3903
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
3904
- itemIndex,
3905
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
3906
- order
3907
- }));
3908
- const normalRequests = requests.filter((request) => !request.isSticky);
3909
- const stickyRequests = requests.filter((request) => request.isSticky);
3992
+ const requests = new Array(numNeeded);
3993
+ const normalRequests = [];
3994
+ const stickyRequests = [];
3995
+ for (let order = 0; order < numNeeded; order++) {
3996
+ const itemIndex = needNewContainers[order];
3997
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
3998
+ const request = {
3999
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4000
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4001
+ itemIndex,
4002
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4003
+ order,
4004
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4005
+ };
4006
+ requests[order] = request;
4007
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4008
+ }
3910
4009
  const normalCandidates = [];
3911
4010
  const stickyCandidates = [];
3912
4011
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -3962,7 +4061,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3962
4061
  }
3963
4062
  };
3964
4063
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
3965
- if (getRequiredItemType) {
4064
+ if (hasItemTypes) {
3966
4065
  assignMatching(
3967
4066
  pendingRequests,
3968
4067
  candidates,
@@ -3986,6 +4085,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3986
4085
  }
3987
4086
  assign(request, containerIndex);
3988
4087
  }
4088
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4089
+ const containerIndices = [];
4090
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4091
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4092
+ }
3989
4093
  if (pendingRemovalChanged) {
3990
4094
  pendingRemoval.length = 0;
3991
4095
  if (pendingRemovalSet) {
@@ -4015,12 +4119,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4015
4119
  function comparatorByDistance(a, b) {
4016
4120
  return b.distance - a.distance;
4017
4121
  }
4122
+ function comparatorByRenderPriority(a, b) {
4123
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4124
+ }
4125
+ function comparatorByIndex(a, b) {
4126
+ return a - b;
4127
+ }
4128
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4129
+ if (requests.length >= 2) {
4130
+ if (groupByType) {
4131
+ const groups = /* @__PURE__ */ new Map();
4132
+ for (const request of requests) {
4133
+ const group = groups.get(request.itemType);
4134
+ if (group) {
4135
+ group.push(request);
4136
+ } else {
4137
+ groups.set(request.itemType, [request]);
4138
+ }
4139
+ }
4140
+ for (const group of groups.values()) {
4141
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4142
+ }
4143
+ } else {
4144
+ containerIndices.length = 0;
4145
+ let indicesSorted = true;
4146
+ for (let i = 0; i < requests.length; i++) {
4147
+ const containerIndex = allocations[requests[i].order].containerIndex;
4148
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4149
+ indicesSorted = false;
4150
+ }
4151
+ containerIndices.push(containerIndex);
4152
+ }
4153
+ if (!indicesSorted) {
4154
+ containerIndices.sort(comparatorByIndex);
4155
+ }
4156
+ requests.sort(comparatorByRenderPriority);
4157
+ for (let i = 0; i < requests.length; i++) {
4158
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4159
+ }
4160
+ }
4161
+ }
4162
+ }
4018
4163
 
4019
4164
  // src/utils/setDidLayout.ts
4020
4165
  function setDidLayout(ctx) {
4021
4166
  const state = ctx.state;
4022
4167
  state.queuedInitialLayout = true;
4023
- checkAtBottom(ctx);
4168
+ checkThresholds(ctx);
4024
4169
  setInitialRenderState(ctx, { didLayout: true });
4025
4170
  if (state.pendingMaintainScrollAtEnd) {
4026
4171
  doMaintainScrollAtEnd(ctx);
@@ -4248,9 +4393,9 @@ function calculateItemsInView(ctx, params = {}) {
4248
4393
  enableScrollForNextCalculateItemsInView,
4249
4394
  idCache,
4250
4395
  indexByKey,
4251
- minIndexSizeChanged,
4396
+ positionRecalculationStartIndex,
4252
4397
  positions,
4253
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4398
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4254
4399
  scrollForNextCalculateItemsInView,
4255
4400
  scrollLength,
4256
4401
  sizes,
@@ -4366,21 +4511,21 @@ function calculateItemsInView(ctx, params = {}) {
4366
4511
  if (dataChanged) {
4367
4512
  resetLayoutCachesForDataChange(state);
4368
4513
  }
4369
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4370
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4514
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4515
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4371
4516
  updateItemPositions(ctx, dataChanged, {
4372
4517
  doMVCP,
4373
4518
  // A changed size shifts every following position. The scrolling early-exit
4374
4519
  // would leave the untouched suffix in the old coordinate space.
4375
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4520
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4376
4521
  optimizeForVisibleWindow,
4377
4522
  scrollBottomBuffered,
4378
4523
  scrollVelocity: speed,
4379
4524
  startIndex
4380
4525
  });
4381
4526
  totalSize = getContentSize(ctx);
4382
- if (minIndexSizeChanged !== void 0) {
4383
- state.minIndexSizeChanged = void 0;
4527
+ if (positionRecalculationStartIndex !== void 0) {
4528
+ state.positionRecalculationStartIndex = void 0;
4384
4529
  }
4385
4530
  let protectedContainerKeys;
4386
4531
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4556,18 +4701,18 @@ function calculateItemsInView(ctx, params = {}) {
4556
4701
  set$(ctx, "activeStickyIndex", -1);
4557
4702
  }
4558
4703
  if (needNewContainers.length > 0) {
4559
- const getRequiredItemType = getItemType ? (i) => {
4560
- const itemType = getItemType(data[i], i);
4561
- return itemType !== void 0 ? String(itemType) : "";
4562
- } : void 0;
4563
4704
  const availableContainerAllocations = findAvailableContainers(
4564
4705
  ctx,
4565
4706
  needNewContainers,
4566
4707
  startBuffered,
4567
4708
  endBuffered,
4568
4709
  pendingRemoval,
4569
- getRequiredItemType,
4570
- protectedContainerKeys
4710
+ protectedContainerKeys,
4711
+ // Initial end alignment has no scroll velocity yet. Keep its
4712
+ // target-side rows first until the initial scroll settles.
4713
+ // After a pause velocity can be zero on the first movement;
4714
+ // use its displacement without treating initial insets as motion.
4715
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4571
4716
  );
4572
4717
  for (const allocation of availableContainerAllocations) {
4573
4718
  const i = allocation.itemIndex;
@@ -4714,6 +4859,7 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4714
4859
  const nextAnchorIndex = anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex;
4715
4860
  let nextAnchorKey;
4716
4861
  let isReady = true;
4862
+ let canUpdateSize = true;
4717
4863
  let nextSize = 0;
4718
4864
  if (anchoredEndSpace) {
4719
4865
  const { anchorIndex, anchorMaxSize, anchorOffset = 0 } = anchoredEndSpace;
@@ -4735,25 +4881,30 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4735
4881
  contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap);
4736
4882
  contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props);
4737
4883
  isReady = !hasUnknownTailSize;
4738
- nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4884
+ const knownSizeBound = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4885
+ nextSize = hasUnknownTailSize ? Math.min(previousSize || 0, knownSizeBound) : knownSizeBound;
4739
4886
  } else if (anchorIndex >= 0) {
4740
4887
  isReady = false;
4888
+ canUpdateSize = false;
4741
4889
  }
4742
4890
  }
4743
4891
  const didSizeChange = previousSize !== nextSize && (previousSize !== void 0 || anchoredEndSpace !== void 0);
4744
4892
  const didEffectiveSizeChange = (previousSize || 0) !== nextSize;
4893
+ const canApplySizeChange = canUpdateSize && (isReady || previousSize !== void 0);
4745
4894
  const didReadyAnchorChange = previousReadyAnchorIndex !== nextAnchorIndex || previousReadyAnchorKey !== nextAnchorKey;
4746
- if (isReady && (didSizeChange || didReadyAnchorChange)) {
4895
+ const didBecomeReady = isReady && state.anchoredEndSpacePendingReady;
4896
+ state.anchoredEndSpacePendingReady = !isReady;
4897
+ if (canApplySizeChange && didSizeChange) {
4898
+ set$(ctx, "anchoredEndSpaceSize", nextSize);
4899
+ (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4900
+ }
4901
+ if (canApplySizeChange && didEffectiveSizeChange) {
4902
+ updateContentMetricsState(ctx);
4903
+ updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4904
+ }
4905
+ if (isReady && (didSizeChange || didReadyAnchorChange || didBecomeReady)) {
4747
4906
  state.anchoredEndSpaceReadyAnchorIndex = nextAnchorIndex;
4748
4907
  state.anchoredEndSpaceReadyAnchorKey = nextAnchorKey;
4749
- if (didSizeChange) {
4750
- set$(ctx, "anchoredEndSpaceSize", nextSize);
4751
- (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4752
- }
4753
- if (didEffectiveSizeChange) {
4754
- updateContentMetricsState(ctx);
4755
- updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4756
- }
4757
4908
  (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize });
4758
4909
  }
4759
4910
  return nextSize;
@@ -4857,6 +5008,7 @@ function updateItemSizesBatch(ctx, measurements) {
4857
5008
  var _a3;
4858
5009
  const state = ctx.state;
4859
5010
  const result = {};
5011
+ const previousTotalSize = state.totalSize;
4860
5012
  for (const measurement of measurements) {
4861
5013
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4862
5014
  if (ownsMeasuredItem) {
@@ -4867,6 +5019,9 @@ function updateItemSizesBatch(ctx, measurements) {
4867
5019
  mergeItemSizeUpdateResult(result, nextResult);
4868
5020
  }
4869
5021
  }
5022
+ if (state.totalSize !== previousTotalSize) {
5023
+ set$(ctx, "totalSize", state.totalSize);
5024
+ }
4870
5025
  flushItemSizeUpdates(ctx, result);
4871
5026
  }
4872
5027
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -4905,12 +5060,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4905
5060
  }
4906
5061
  let needsRecalculate = !didContainersLayout;
4907
5062
  let shouldMaintainScrollAtEnd = false;
4908
- let minIndexSizeChanged;
5063
+ let positionRecalculationStartIndex;
4909
5064
  const prevSizeKnown = state.sizesKnown.get(itemKey);
4910
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5065
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
4911
5066
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
4912
5067
  if (diff !== 0) {
4913
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5068
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
4914
5069
  const { startBuffered, endBuffered } = state;
4915
5070
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
4916
5071
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -4927,8 +5082,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4927
5082
  size
4928
5083
  });
4929
5084
  }
4930
- if (minIndexSizeChanged !== void 0) {
4931
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5085
+ if (positionRecalculationStartIndex !== void 0) {
5086
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
4932
5087
  }
4933
5088
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
4934
5089
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -4945,7 +5100,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4945
5100
  didMeasureUserScrollAnchorResetItem
4946
5101
  };
4947
5102
  }
4948
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5103
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
4949
5104
  var _a3, _b;
4950
5105
  const state = ctx.state;
4951
5106
  const {
@@ -4968,7 +5123,16 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4968
5123
  fixedItemSize,
4969
5124
  itemType
4970
5125
  } : void 0;
4971
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5126
+ const prevSize = getItemSize(
5127
+ ctx,
5128
+ itemKey,
5129
+ index,
5130
+ itemData,
5131
+ void 0,
5132
+ void 0,
5133
+ notifyTotalSize,
5134
+ resolvedItemSize
5135
+ );
4972
5136
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
4973
5137
  const prevSizeKnown = sizesKnown.get(itemKey);
4974
5138
  if (Platform.OS !== "web" && prevSizeKnown !== void 0 && isNativeLayoutNoise(rawSize - prevSizeKnown)) {
@@ -4993,7 +5157,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4993
5157
  }
4994
5158
  }
4995
5159
  if (!prevSize || Math.abs(prevSize - size) > 0.1) {
4996
- setSize(ctx, itemKey, size);
5160
+ setSize(ctx, itemKey, size, notifyTotalSize);
4997
5161
  return size - prevSize;
4998
5162
  }
4999
5163
  return 0;
@@ -5144,10 +5308,16 @@ function useValue$(key, params) {
5144
5308
  var _a3;
5145
5309
  return (_a3 = getValue ? getValue(peek$(ctx, key)) : peek$(ctx, key)) != null ? _a3 : 0;
5146
5310
  };
5147
- const animValue = useAnimatedValue(getNewValue());
5311
+ const initialValue = getNewValue();
5312
+ const animValue = useAnimatedValue(initialValue);
5313
+ const lastValue = useRef(initialValue);
5148
5314
  useLayoutEffect(() => {
5149
5315
  const syncCurrentValue = () => {
5150
- animValue.setValue(getNewValue());
5316
+ const nextValue = getNewValue();
5317
+ if (lastValue.current !== nextValue) {
5318
+ lastValue.current = nextValue;
5319
+ animValue.setValue(nextValue);
5320
+ }
5151
5321
  };
5152
5322
  const unsubscribe = listen$(ctx, key, syncCurrentValue);
5153
5323
  syncCurrentValue();
@@ -6062,6 +6232,7 @@ var ListComponent = typedMemo(function ListComponent2({
6062
6232
  renderScrollComponent,
6063
6233
  onLayoutFooter,
6064
6234
  onInternalScrollBeginDrag,
6235
+ onInternalScrollEndDrag,
6065
6236
  onInternalScrollEnd,
6066
6237
  scrollAdjustHandler,
6067
6238
  snapToIndices,
@@ -6133,7 +6304,7 @@ var ListComponent = typedMemo(function ListComponent2({
6133
6304
  SnapOrScroll,
6134
6305
  {
6135
6306
  ...rest,
6136
- ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag },
6307
+ ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag, onScrollEndDrag: onInternalScrollEndDrag },
6137
6308
  contentContainerStyle: [
6138
6309
  horizontal ? { height: "100%" } : {},
6139
6310
  contentContainerStyle,
@@ -6252,7 +6423,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6252
6423
  for (const key in state.averageSizes) {
6253
6424
  delete state.averageSizes[key];
6254
6425
  }
6255
- state.minIndexSizeChanged = 0;
6426
+ state.positionRecalculationStartIndex = 0;
6256
6427
  state.scrollForNextCalculateItemsInView = void 0;
6257
6428
  }
6258
6429
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -6416,7 +6587,7 @@ function handleLayout(ctx, layoutParam, setCanRender) {
6416
6587
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
6417
6588
  }
6418
6589
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
6419
- doMaintainScrollAtEnd(ctx);
6590
+ doMaintainScrollAtEnd(ctx, { immediate: true });
6420
6591
  }
6421
6592
  checkThresholds(ctx);
6422
6593
  if (state) {
@@ -6649,14 +6820,17 @@ function scrollToEnd(ctx, options) {
6649
6820
  if (index === -1) {
6650
6821
  return false;
6651
6822
  }
6652
- const paddingBottom = state.props.stylePaddingBottom || 0;
6823
+ const paddingBottom = getStylePaddingEnd(state.props);
6653
6824
  const footerSize = peek$(ctx, "footerSize") || 0;
6654
6825
  scrollToIndex(ctx, {
6655
6826
  ...options,
6656
6827
  index,
6657
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6828
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
6658
6829
  viewPosition: 1
6659
6830
  });
6831
+ if (state.scrollingTo) {
6832
+ state.scrollingTo.isScrollToEnd = true;
6833
+ }
6660
6834
  return true;
6661
6835
  }
6662
6836
 
@@ -6854,7 +7028,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6854
7028
  for (const key in state.averageSizes) {
6855
7029
  delete state.averageSizes[key];
6856
7030
  }
6857
- state.minIndexSizeChanged = 0;
7031
+ state.positionRecalculationStartIndex = 0;
6858
7032
  state.scrollForNextCalculateItemsInView = void 0;
6859
7033
  state.pendingTotalSize = void 0;
6860
7034
  state.totalSize = 0;
@@ -7248,10 +7422,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7248
7422
  onMetricsChange,
7249
7423
  onLayout: onLayoutProp,
7250
7424
  onLoad,
7425
+ onReady,
7251
7426
  onMomentumScrollEnd,
7252
7427
  onRefresh,
7253
7428
  onScroll: onScrollProp,
7254
7429
  onScrollBeginDrag,
7430
+ onScrollEndDrag,
7255
7431
  onStartReached,
7256
7432
  onStartReachedThreshold = 0.5,
7257
7433
  onStickyHeaderChange,
@@ -7320,17 +7496,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7320
7496
  const initialScrollProp = initialScrollAtEnd ? {
7321
7497
  index: Math.max(0, dataProp.length - 1),
7322
7498
  preserveForBottomPadding: true,
7323
- viewOffset: -stylePaddingEndState,
7499
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
7324
7500
  viewPosition: 1
7325
7501
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7326
7502
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7327
7503
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7328
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
7504
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7329
7505
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7330
7506
  } : {
7331
7507
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7332
7508
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7333
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7509
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7334
7510
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7335
7511
  } : initialScrollUsesOffsetOnly ? {
7336
7512
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -7390,6 +7566,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7390
7566
  endReachedSnapshot: void 0,
7391
7567
  firstFullyOnScreenIndex: -1,
7392
7568
  freshDataTransitionEpoch: 0,
7569
+ handledDataChangeEpoch: 0,
7570
+ handledFreshDataTransitionEpoch: 0,
7393
7571
  hasHadNonEmptyData: dataProp.length > 0,
7394
7572
  idCache: [],
7395
7573
  idsInView: [],
@@ -7406,11 +7584,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7406
7584
  lastLayout: void 0,
7407
7585
  lastScrollDelta: 0,
7408
7586
  loadStartTime: Date.now(),
7409
- minIndexSizeChanged: 0,
7410
7587
  nativeContentInset: void 0,
7411
7588
  nativeMarginTop: 0,
7412
7589
  pendingDataComparison: void 0,
7413
7590
  pendingNativeMVCPAdjust: void 0,
7591
+ positionRecalculationStartIndex: 0,
7414
7592
  positions: [],
7415
7593
  props: {},
7416
7594
  queuedCalculateItemsInView: 0,
@@ -7449,8 +7627,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7449
7627
  refState.current = ctx.state;
7450
7628
  }
7451
7629
  const state = refState.current;
7452
- const handledDataChangeEpochRef = useRef(state.dataChangeEpoch);
7453
- const handledFreshDataTransitionEpochRef = useRef(state.freshDataTransitionEpoch);
7454
7630
  const isFirstLocal = state.isFirst;
7455
7631
  const previousAdaptiveRender = state.props.adaptiveRender;
7456
7632
  const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
@@ -7515,8 +7691,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7515
7691
  onItemSizeChanged,
7516
7692
  onLoad,
7517
7693
  onMomentumScrollEnd,
7694
+ onReady,
7518
7695
  onScroll: throttleScrollFn,
7519
7696
  onScrollBeginDrag,
7697
+ onScrollEndDrag,
7520
7698
  onStartReached,
7521
7699
  onStartReachedThreshold,
7522
7700
  onStickyHeaderChange,
@@ -7605,10 +7783,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7605
7783
  useDevChecks(props);
7606
7784
  }
7607
7785
  useLayoutEffect(() => {
7608
- const didDataChange = handledDataChangeEpochRef.current !== dataChangeEpoch;
7609
- const didStartFreshData = handledFreshDataTransitionEpochRef.current !== freshDataTransitionEpoch;
7610
- handledDataChangeEpochRef.current = dataChangeEpoch;
7611
- handledFreshDataTransitionEpochRef.current = freshDataTransitionEpoch;
7786
+ const didDataChange = state.handledDataChangeEpoch !== dataChangeEpoch;
7787
+ const didStartFreshData = state.handledFreshDataTransitionEpoch !== freshDataTransitionEpoch;
7788
+ state.handledDataChangeEpoch = dataChangeEpoch;
7789
+ state.handledFreshDataTransitionEpoch = freshDataTransitionEpoch;
7612
7790
  if (didStartFreshData) {
7613
7791
  resetInitialRenderState(ctx, {
7614
7792
  resetInitialScroll: !!initialScrollProp,
@@ -7812,17 +7990,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7812
7990
  onScroll: (event) => onScroll(ctx, event),
7813
7991
  onScrollBeginDrag: (event) => {
7814
7992
  var _a4, _b2;
7815
- const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
7816
- if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
7817
- cancelImperativeScroll(state);
7818
- }
7819
- if (maintainingScrollAtEnd) {
7820
- finishMaintainScrollAtEnd(ctx);
7821
- }
7993
+ state.isDragging = true;
7994
+ interruptMaintainScrollAtEnd(ctx);
7822
7995
  prepareReachedEdgeForNextUserScroll(ctx);
7823
7996
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7824
7997
  },
7825
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
7998
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
7999
+ onScrollEndDrag: (event) => {
8000
+ var _a4, _b2;
8001
+ state.isDragging = false;
8002
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8003
+ }
7826
8004
  }),
7827
8005
  []
7828
8006
  );
@@ -7846,6 +8024,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7846
8024
  ListHeaderComponent,
7847
8025
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
7848
8026
  onInternalScrollEnd: fns.onScrollEnd,
8027
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
7849
8028
  onLayout,
7850
8029
  onLayoutFooter,
7851
8030
  onMomentumScrollEnd: fns.onMomentumScrollEnd,