@legendapp/list 3.0.0-beta.40 → 3.0.0-beta.41

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/index.native.mjs CHANGED
@@ -826,7 +826,7 @@ var Containers = typedMemo(function Containers2({
826
826
  return !((_a3 = ctx.state) == null ? void 0 : _a3.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
827
827
  }
828
828
  });
829
- const animOpacity = waitForInitialLayout && !IsNewArchitecture ? useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 }) : void 0;
829
+ const animOpacity = waitForInitialLayout ? useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 }) : void 0;
830
830
  const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
831
831
  const containers = [];
832
832
  for (let i = 0; i < numContainers; i++) {
@@ -1109,6 +1109,7 @@ function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize) {
1109
1109
 
1110
1110
  // src/core/calculateOffsetWithOffsetPosition.ts
1111
1111
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1112
+ var _a3;
1112
1113
  const state = ctx.state;
1113
1114
  const { index, viewOffset, viewPosition } = params;
1114
1115
  let offset = offsetParam;
@@ -1122,10 +1123,16 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1122
1123
  }
1123
1124
  }
1124
1125
  if (viewPosition !== void 0 && index !== void 0) {
1125
- const itemSize = getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1126
+ const dataLength = state.props.data.length;
1127
+ if (dataLength === 0) {
1128
+ return offset;
1129
+ }
1130
+ const isOutOfBounds = index < 0 || index >= dataLength;
1131
+ const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
1132
+ const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1126
1133
  const trailingInset = getContentInsetEnd(state);
1127
1134
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1128
- if (index === state.props.data.length - 1) {
1135
+ if (!isOutOfBounds && index === state.props.data.length - 1) {
1129
1136
  const footerSize = peek$(ctx, "footerSize") || 0;
1130
1137
  offset += footerSize;
1131
1138
  }
@@ -1327,7 +1334,9 @@ function finishScrollTo(ctx) {
1327
1334
  const scrollingTo = state.scrollingTo;
1328
1335
  state.scrollHistory.length = 0;
1329
1336
  state.initialScroll = void 0;
1337
+ state.initialScrollUsesOffset = false;
1330
1338
  state.initialAnchor = void 0;
1339
+ state.initialNativeScrollWatchdog = void 0;
1331
1340
  state.scrollingTo = void 0;
1332
1341
  if (state.pendingTotalSize !== void 0) {
1333
1342
  addTotalSize(ctx, null, state.pendingTotalSize);
@@ -1345,21 +1354,21 @@ function finishScrollTo(ctx) {
1345
1354
  }
1346
1355
 
1347
1356
  // src/core/checkFinishedScroll.ts
1357
+ var INITIAL_SCROLL_MIN_TARGET_OFFSET = 1;
1358
+ var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
1359
+ var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
1348
1360
  function checkFinishedScroll(ctx) {
1349
1361
  ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
1350
1362
  }
1351
1363
  function checkFinishedScrollFrame(ctx) {
1364
+ var _a3;
1352
1365
  const scrollingTo = ctx.state.scrollingTo;
1353
1366
  if (scrollingTo) {
1354
1367
  const { state } = ctx;
1355
1368
  state.animFrameCheckFinishedScroll = void 0;
1356
1369
  const scroll = state.scrollPending;
1357
1370
  const adjust = state.scrollAdjustHandler.getAdjust();
1358
- const clampedTargetOffset = clampScrollOffset(
1359
- ctx,
1360
- scrollingTo.offset - (scrollingTo.viewOffset || 0),
1361
- scrollingTo
1362
- );
1371
+ const clampedTargetOffset = (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
1363
1372
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
1364
1373
  const diff1 = Math.abs(scroll - clampedTargetOffset);
1365
1374
  const diff2 = Math.abs(diff1 - adjust);
@@ -1373,17 +1382,33 @@ function checkFinishedScrollFrame(ctx) {
1373
1382
  function checkFinishedScrollFallback(ctx) {
1374
1383
  const state = ctx.state;
1375
1384
  const scrollingTo = state.scrollingTo;
1376
- const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || !state.didContainersLayout;
1385
+ const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1386
+ const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget || !state.didContainersLayout;
1377
1387
  state.timeoutCheckFinishedScrollFallback = setTimeout(
1378
1388
  () => {
1379
1389
  let numChecks = 0;
1380
1390
  const checkHasScrolled = () => {
1391
+ var _a3, _b;
1381
1392
  state.timeoutCheckFinishedScrollFallback = void 0;
1382
1393
  const isStillScrollingTo = state.scrollingTo;
1383
1394
  if (isStillScrollingTo) {
1384
1395
  numChecks++;
1385
- if (state.hasScrolled || numChecks > 5) {
1396
+ const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
1397
+ const maxChecks = isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
1398
+ const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1399
+ if (shouldFinishZeroTarget || state.hasScrolled || numChecks > maxChecks) {
1386
1400
  finishScrollTo(ctx);
1401
+ } else if (isNativeInitialPending && numChecks <= maxChecks) {
1402
+ const targetOffset = (_b = (_a3 = state.initialNativeScrollWatchdog) == null ? void 0 : _a3.targetOffset) != null ? _b : state.scrollPending;
1403
+ const scroller = state.refScroller.current;
1404
+ if (scroller) {
1405
+ scroller.scrollTo({
1406
+ animated: false,
1407
+ x: state.props.horizontal ? targetOffset : 0,
1408
+ y: state.props.horizontal ? 0 : targetOffset
1409
+ });
1410
+ }
1411
+ state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, 100);
1387
1412
  } else {
1388
1413
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, 100);
1389
1414
  }
@@ -1394,6 +1419,14 @@ function checkFinishedScrollFallback(ctx) {
1394
1419
  slowTimeout ? 500 : 100
1395
1420
  );
1396
1421
  }
1422
+ function isNativeInitialNonZeroTarget(state) {
1423
+ return !state.didFinishInitialScroll && !!state.initialNativeScrollWatchdog && state.initialNativeScrollWatchdog.targetOffset > INITIAL_SCROLL_MIN_TARGET_OFFSET;
1424
+ }
1425
+ function shouldFinishInitialZeroTargetScroll(ctx) {
1426
+ var _a3;
1427
+ const { state } = ctx;
1428
+ 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;
1429
+ }
1397
1430
 
1398
1431
  // src/core/doScrollTo.native.ts
1399
1432
  function doScrollTo(ctx, params) {
@@ -1417,7 +1450,9 @@ function doScrollTo(ctx, params) {
1417
1450
  }
1418
1451
 
1419
1452
  // src/core/scrollTo.ts
1453
+ var WATCHDOG_OFFSET_EPSILON = 1;
1420
1454
  function scrollTo(ctx, params) {
1455
+ var _a3, _b;
1421
1456
  const state = ctx.state;
1422
1457
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
1423
1458
  const { animated, isInitialScroll, offset: scrollTargetOffset, precomputedWithViewOffset } = scrollTarget;
@@ -1434,9 +1469,23 @@ function scrollTo(ctx, params) {
1434
1469
  offset = clampScrollOffset(ctx, offset, scrollTarget);
1435
1470
  state.scrollHistory.length = 0;
1436
1471
  if (!noScrollingTo) {
1437
- state.scrollingTo = scrollTarget;
1472
+ state.scrollingTo = {
1473
+ ...scrollTarget,
1474
+ targetOffset: offset
1475
+ };
1438
1476
  }
1439
1477
  state.scrollPending = offset;
1478
+ const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!state.initialNativeScrollWatchdog) && offset > WATCHDOG_OFFSET_EPSILON;
1479
+ const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!state.initialNativeScrollWatchdog && offset <= WATCHDOG_OFFSET_EPSILON;
1480
+ if (shouldWatchInitialNativeScroll) {
1481
+ state.hasScrolled = false;
1482
+ state.initialNativeScrollWatchdog = {
1483
+ startScroll: (_b = (_a3 = state.initialNativeScrollWatchdog) == null ? void 0 : _a3.startScroll) != null ? _b : state.scroll,
1484
+ targetOffset: offset
1485
+ };
1486
+ } else if (shouldClearInitialNativeScrollWatchdog) {
1487
+ state.initialNativeScrollWatchdog = void 0;
1488
+ }
1440
1489
  if (forceScroll || !isInitialScroll || Platform2.OS === "android") {
1441
1490
  doScrollTo(ctx, { animated, horizontal, offset });
1442
1491
  } else {
@@ -1444,178 +1493,52 @@ function scrollTo(ctx, params) {
1444
1493
  }
1445
1494
  }
1446
1495
 
1447
- // src/platform/flushSync.native.ts
1448
- var flushSync = (fn) => {
1449
- fn();
1450
- };
1451
-
1452
- // src/core/updateScroll.ts
1453
- function updateScroll(ctx, newScroll, forceUpdate) {
1496
+ // src/core/doMaintainScrollAtEnd.ts
1497
+ function doMaintainScrollAtEnd(ctx) {
1454
1498
  const state = ctx.state;
1455
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1456
- const prevScroll = state.scroll;
1457
- state.hasScrolled = true;
1458
- state.lastBatchingAction = Date.now();
1459
- const currentTime = Date.now();
1460
- const adjust = scrollAdjustHandler.getAdjust();
1461
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1462
- if (adjustChanged) {
1463
- scrollHistory.length = 0;
1464
- }
1465
- state.lastScrollAdjustForHistory = adjust;
1466
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1467
- if (!adjustChanged) {
1468
- scrollHistory.push({ scroll: newScroll, time: currentTime });
1469
- }
1470
- }
1471
- if (scrollHistory.length > 5) {
1472
- scrollHistory.shift();
1499
+ const {
1500
+ didContainersLayout,
1501
+ isAtEnd,
1502
+ pendingNativeMVCPAdjust,
1503
+ refScroller,
1504
+ props: { maintainScrollAtEnd }
1505
+ } = state;
1506
+ const shouldMaintainScrollAtEnd = !!(isAtEnd && maintainScrollAtEnd && didContainersLayout);
1507
+ if (pendingNativeMVCPAdjust) {
1508
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1509
+ return false;
1473
1510
  }
1474
- if (ignoreScrollFromMVCP && !scrollingTo) {
1475
- const { lt, gt } = ignoreScrollFromMVCP;
1476
- if (lt && newScroll < lt || gt && newScroll > gt) {
1477
- state.ignoreScrollFromMVCPIgnored = true;
1478
- return;
1511
+ state.pendingMaintainScrollAtEnd = false;
1512
+ if (shouldMaintainScrollAtEnd) {
1513
+ const contentSize = getContentSize(ctx);
1514
+ if (contentSize < state.scrollLength) {
1515
+ state.scroll = 0;
1479
1516
  }
1480
- }
1481
- state.scrollPrev = prevScroll;
1482
- state.scrollPrevTime = state.scrollTime;
1483
- state.scroll = newScroll;
1484
- state.scrollTime = currentTime;
1485
- const scrollDelta = Math.abs(newScroll - prevScroll);
1486
- const scrollLength = state.scrollLength;
1487
- const lastCalculated = state.scrollLastCalculate;
1488
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1489
- const shouldUpdate = useAggressiveItemRecalculation || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1490
- if (shouldUpdate) {
1491
- state.scrollLastCalculate = state.scroll;
1492
- state.ignoreScrollFromMVCPIgnored = false;
1493
- state.lastScrollDelta = scrollDelta;
1494
- const runCalculateItems = () => {
1517
+ requestAnimationFrame(() => {
1495
1518
  var _a3;
1496
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { doMVCP: scrollingTo !== void 0 });
1497
- checkThresholds(ctx);
1498
- };
1499
- if (Platform2.OS === "web" && scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
1500
- flushSync(runCalculateItems);
1501
- } else {
1502
- runCalculateItems();
1503
- }
1504
- state.dataChangeNeedsScrollUpdate = false;
1505
- state.lastScrollDelta = 0;
1506
- }
1507
- }
1508
-
1509
- // src/utils/requestAdjust.ts
1510
- function requestAdjust(ctx, positionDiff, dataChanged) {
1511
- const state = ctx.state;
1512
- if (Math.abs(positionDiff) > 0.1) {
1513
- const needsScrollWorkaround = Platform2.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1514
- const doit = () => {
1515
- if (needsScrollWorkaround) {
1516
- scrollTo(ctx, {
1517
- noScrollingTo: true,
1518
- offset: state.scroll
1519
+ if (state.isAtEnd) {
1520
+ state.maintainingScrollAtEnd = true;
1521
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
1522
+ animated: maintainScrollAtEnd.animated
1519
1523
  });
1520
- } else {
1521
- state.scrollAdjustHandler.requestAdjust(positionDiff);
1522
- if (state.adjustingFromInitialMount) {
1523
- state.adjustingFromInitialMount--;
1524
- }
1525
- }
1526
- };
1527
- state.scroll += positionDiff;
1528
- state.scrollForNextCalculateItemsInView = void 0;
1529
- const readyToRender = peek$(ctx, "readyToRender");
1530
- if (readyToRender) {
1531
- doit();
1532
- if (Platform2.OS !== "web") {
1533
- const threshold = state.scroll - positionDiff / 2;
1534
- if (!state.ignoreScrollFromMVCP) {
1535
- state.ignoreScrollFromMVCP = {};
1536
- }
1537
- if (positionDiff > 0) {
1538
- state.ignoreScrollFromMVCP.lt = threshold;
1539
- } else {
1540
- state.ignoreScrollFromMVCP.gt = threshold;
1541
- }
1542
- if (state.ignoreScrollFromMVCPTimeout) {
1543
- clearTimeout(state.ignoreScrollFromMVCPTimeout);
1544
- }
1545
- const delay = needsScrollWorkaround ? 250 : 100;
1546
- state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
1547
- state.ignoreScrollFromMVCP = void 0;
1548
- const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
1549
- if (shouldForceUpdate) {
1550
- state.ignoreScrollFromMVCPIgnored = false;
1551
- state.scrollPending = state.scroll;
1552
- updateScroll(ctx, state.scroll, true);
1553
- }
1554
- }, delay);
1524
+ setTimeout(
1525
+ () => {
1526
+ state.maintainingScrollAtEnd = false;
1527
+ },
1528
+ maintainScrollAtEnd.animated ? 500 : 0
1529
+ );
1555
1530
  }
1556
- } else {
1557
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1558
- requestAnimationFrame(doit);
1559
- }
1560
- }
1561
- }
1562
-
1563
- // src/core/ensureInitialAnchor.ts
1564
- var INITIAL_ANCHOR_TOLERANCE = 0.5;
1565
- var INITIAL_ANCHOR_MAX_ATTEMPTS = 4;
1566
- var INITIAL_ANCHOR_SETTLED_TICKS = 2;
1567
- function ensureInitialAnchor(ctx) {
1568
- var _a3, _b, _c, _d, _e;
1569
- const state = ctx.state;
1570
- const { initialAnchor, didContainersLayout, scroll, scrollLength } = state;
1571
- const anchor = initialAnchor;
1572
- const item = state.props.data[anchor.index];
1573
- if (!didContainersLayout) {
1574
- return;
1575
- }
1576
- const id = getId(state, anchor.index);
1577
- if (state.positions[anchor.index] === void 0) {
1578
- return;
1579
- }
1580
- const size = getItemSize(ctx, id, anchor.index, item, true, true);
1581
- if (size === void 0) {
1582
- return;
1583
- }
1584
- const availableSpace = Math.max(0, scrollLength - size);
1585
- const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1586
- const desiredOffset = calculateOffsetForIndex(ctx, anchor.index) + topOffsetAdjustment - ((_a3 = anchor.viewOffset) != null ? _a3 : 0) - ((_b = anchor.viewPosition) != null ? _b : 0) * availableSpace;
1587
- const clampedDesiredOffset = clampScrollOffset(ctx, desiredOffset, anchor);
1588
- const delta = clampedDesiredOffset - scroll;
1589
- if (Math.abs(delta) <= INITIAL_ANCHOR_TOLERANCE) {
1590
- const settledTicks = ((_c = anchor.settledTicks) != null ? _c : 0) + 1;
1591
- if (settledTicks >= INITIAL_ANCHOR_SETTLED_TICKS) {
1592
- state.initialAnchor = void 0;
1593
- } else {
1594
- anchor.settledTicks = settledTicks;
1595
- }
1596
- return;
1597
- }
1598
- if (((_d = anchor.attempts) != null ? _d : 0) >= INITIAL_ANCHOR_MAX_ATTEMPTS) {
1599
- state.initialAnchor = void 0;
1600
- return;
1601
- }
1602
- const lastDelta = anchor.lastDelta;
1603
- if (lastDelta !== void 0 && Math.abs(delta) >= Math.abs(lastDelta)) {
1604
- state.initialAnchor = void 0;
1605
- return;
1531
+ });
1532
+ return true;
1606
1533
  }
1607
- Object.assign(anchor, {
1608
- attempts: ((_e = anchor.attempts) != null ? _e : 0) + 1,
1609
- lastDelta: delta,
1610
- settledTicks: 0
1611
- });
1612
- requestAdjust(ctx, delta);
1534
+ return false;
1613
1535
  }
1614
1536
 
1615
1537
  // src/core/mvcp.ts
1616
1538
  var MVCP_POSITION_EPSILON = 0.1;
1617
1539
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
1618
1540
  var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
1541
+ var NATIVE_END_CLAMP_EPSILON = 1;
1619
1542
  function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
1620
1543
  if (!enableMVCPAnchorLock) {
1621
1544
  state.mvcpAnchorLock = void 0;
@@ -1655,6 +1578,84 @@ function updateAnchorLock(state, params) {
1655
1578
  };
1656
1579
  }
1657
1580
  }
1581
+ function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
1582
+ if (!dataChanged || Platform2.OS === "web" || !state.props.maintainVisibleContentPosition.data || scrollTarget !== void 0 || positionDiff >= -MVCP_POSITION_EPSILON) {
1583
+ return false;
1584
+ }
1585
+ const distanceFromEnd = prevTotalSize - prevScroll - state.scrollLength;
1586
+ return distanceFromEnd < Math.abs(positionDiff) - MVCP_POSITION_EPSILON;
1587
+ }
1588
+ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
1589
+ if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
1590
+ return 0;
1591
+ }
1592
+ const maxScroll = Math.max(0, totalSize - state.scrollLength);
1593
+ const clampDelta = maxScroll - state.scroll;
1594
+ if (unresolvedAmount < 0) {
1595
+ return Math.max(unresolvedAmount, Math.min(0, clampDelta));
1596
+ }
1597
+ if (unresolvedAmount > 0) {
1598
+ return Math.min(unresolvedAmount, Math.max(0, clampDelta));
1599
+ }
1600
+ return 0;
1601
+ }
1602
+ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
1603
+ const state = ctx.state;
1604
+ const pending = state.pendingNativeMVCPAdjust;
1605
+ if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
1606
+ return;
1607
+ }
1608
+ const totalSize = getContentSize(ctx);
1609
+ const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
1610
+ if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
1611
+ return;
1612
+ }
1613
+ const manualDesired = pending.amount - predictedNativeClamp;
1614
+ if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
1615
+ return;
1616
+ }
1617
+ pending.manualApplied = manualDesired;
1618
+ requestAdjust(ctx, manualDesired, true);
1619
+ }
1620
+ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1621
+ const state = ctx.state;
1622
+ const pending = state.pendingNativeMVCPAdjust;
1623
+ if (!pending) {
1624
+ return false;
1625
+ }
1626
+ const remainingAfterManual = pending.amount - pending.manualApplied;
1627
+ const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
1628
+ const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
1629
+ if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
1630
+ state.pendingNativeMVCPAdjust = void 0;
1631
+ return true;
1632
+ }
1633
+ if (isWrongDirection) {
1634
+ state.pendingNativeMVCPAdjust = void 0;
1635
+ return false;
1636
+ }
1637
+ const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
1638
+ const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
1639
+ const didApproachClamp = distanceToClamp < pending.closestDistanceToClamp - MVCP_POSITION_EPSILON;
1640
+ const didMoveAwayAfterApproach = pending.hasApproachedClamp && distanceToClamp > pending.closestDistanceToClamp + MVCP_POSITION_EPSILON;
1641
+ if (didApproachClamp) {
1642
+ pending.closestDistanceToClamp = distanceToClamp;
1643
+ pending.hasApproachedClamp = true;
1644
+ } else if (didMoveAwayAfterApproach) {
1645
+ state.pendingNativeMVCPAdjust = void 0;
1646
+ return false;
1647
+ }
1648
+ const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
1649
+ if (!isAtExpectedNativeClamp) {
1650
+ return false;
1651
+ }
1652
+ state.pendingNativeMVCPAdjust = void 0;
1653
+ const remaining = remainingAfterManual - nativeDelta;
1654
+ if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
1655
+ requestAdjust(ctx, remaining, true);
1656
+ }
1657
+ return true;
1658
+ }
1658
1659
  function prepareMVCP(ctx, dataChanged) {
1659
1660
  const state = ctx.state;
1660
1661
  const { idsInView, positions, props } = state;
@@ -1674,7 +1675,13 @@ function prepareMVCP(ctx, dataChanged) {
1674
1675
  const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
1675
1676
  const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
1676
1677
  const indexByKey = state.indexByKey;
1678
+ const prevScroll = state.scroll;
1679
+ const prevTotalSize = getContentSize(ctx);
1677
1680
  if (shouldMVCP) {
1681
+ if (!isWeb && state.pendingNativeMVCPAdjust && scrollTarget === void 0) {
1682
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
1683
+ return void 0;
1684
+ }
1678
1685
  if (anchorLock && scrollTarget === void 0) {
1679
1686
  targetId = anchorLock.id;
1680
1687
  prevPosition = anchorLock.position;
@@ -1765,29 +1772,220 @@ function prepareMVCP(ctx, dataChanged) {
1765
1772
  anchorPositionForLock = newPosition;
1766
1773
  }
1767
1774
  }
1768
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1769
- const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1770
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1771
- if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1772
- const diff = newSize - prevSize;
1773
- if (diff !== 0) {
1774
- positionDiff += diff * scrollingToViewPosition;
1775
- scrollingTo.itemSize = newSize;
1776
- }
1775
+ if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1776
+ const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1777
+ const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1778
+ if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1779
+ const diff = newSize - prevSize;
1780
+ if (diff !== 0) {
1781
+ positionDiff += diff * scrollingToViewPosition;
1782
+ scrollingTo.itemSize = newSize;
1783
+ }
1784
+ }
1785
+ }
1786
+ updateAnchorLock(state, {
1787
+ anchorId: anchorIdForLock,
1788
+ anchorPosition: anchorPositionForLock,
1789
+ dataChanged,
1790
+ now,
1791
+ positionDiff
1792
+ });
1793
+ if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
1794
+ state.pendingNativeMVCPAdjust = {
1795
+ amount: positionDiff,
1796
+ closestDistanceToClamp: Math.abs(
1797
+ prevScroll - Math.max(0, getContentSize(ctx) - state.scrollLength)
1798
+ ),
1799
+ hasApproachedClamp: false,
1800
+ manualApplied: 0,
1801
+ startScroll: prevScroll
1802
+ };
1803
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
1804
+ return;
1805
+ }
1806
+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1807
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
1808
+ }
1809
+ };
1810
+ }
1811
+ }
1812
+
1813
+ // src/platform/flushSync.native.ts
1814
+ var flushSync = (fn) => {
1815
+ fn();
1816
+ };
1817
+
1818
+ // src/core/updateScroll.ts
1819
+ function updateScroll(ctx, newScroll, forceUpdate) {
1820
+ var _a3;
1821
+ const state = ctx.state;
1822
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1823
+ const prevScroll = state.scroll;
1824
+ state.hasScrolled = true;
1825
+ state.lastBatchingAction = Date.now();
1826
+ const currentTime = Date.now();
1827
+ const adjust = scrollAdjustHandler.getAdjust();
1828
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1829
+ if (adjustChanged) {
1830
+ scrollHistory.length = 0;
1831
+ }
1832
+ state.lastScrollAdjustForHistory = adjust;
1833
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1834
+ if (!adjustChanged) {
1835
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
1836
+ }
1837
+ }
1838
+ if (scrollHistory.length > 5) {
1839
+ scrollHistory.shift();
1840
+ }
1841
+ if (ignoreScrollFromMVCP && !scrollingTo) {
1842
+ const { lt, gt } = ignoreScrollFromMVCP;
1843
+ if (lt && newScroll < lt || gt && newScroll > gt) {
1844
+ state.ignoreScrollFromMVCPIgnored = true;
1845
+ return;
1846
+ }
1847
+ }
1848
+ state.scrollPrev = prevScroll;
1849
+ state.scrollPrevTime = state.scrollTime;
1850
+ state.scroll = newScroll;
1851
+ state.scrollTime = currentTime;
1852
+ const scrollDelta = Math.abs(newScroll - prevScroll);
1853
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1854
+ const scrollLength = state.scrollLength;
1855
+ const lastCalculated = state.scrollLastCalculate;
1856
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1857
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1858
+ if (shouldUpdate) {
1859
+ state.scrollLastCalculate = state.scroll;
1860
+ state.ignoreScrollFromMVCPIgnored = false;
1861
+ state.lastScrollDelta = scrollDelta;
1862
+ const runCalculateItems = () => {
1863
+ var _a4;
1864
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
1865
+ checkThresholds(ctx);
1866
+ };
1867
+ if (Platform2.OS === "web" && scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
1868
+ flushSync(runCalculateItems);
1869
+ } else {
1870
+ runCalculateItems();
1871
+ }
1872
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
1873
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
1874
+ state.pendingMaintainScrollAtEnd = false;
1875
+ doMaintainScrollAtEnd(ctx);
1876
+ }
1877
+ state.dataChangeNeedsScrollUpdate = false;
1878
+ state.lastScrollDelta = 0;
1879
+ }
1880
+ }
1881
+
1882
+ // src/utils/requestAdjust.ts
1883
+ function requestAdjust(ctx, positionDiff, dataChanged) {
1884
+ const state = ctx.state;
1885
+ if (Math.abs(positionDiff) > 0.1) {
1886
+ const needsScrollWorkaround = Platform2.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1887
+ const doit = () => {
1888
+ if (needsScrollWorkaround) {
1889
+ scrollTo(ctx, {
1890
+ noScrollingTo: true,
1891
+ offset: state.scroll
1892
+ });
1893
+ } else {
1894
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
1895
+ if (state.adjustingFromInitialMount) {
1896
+ state.adjustingFromInitialMount--;
1897
+ }
1898
+ }
1899
+ };
1900
+ state.scroll += positionDiff;
1901
+ state.scrollForNextCalculateItemsInView = void 0;
1902
+ const readyToRender = peek$(ctx, "readyToRender");
1903
+ if (readyToRender) {
1904
+ doit();
1905
+ if (Platform2.OS !== "web") {
1906
+ const threshold = state.scroll - positionDiff / 2;
1907
+ if (!state.ignoreScrollFromMVCP) {
1908
+ state.ignoreScrollFromMVCP = {};
1777
1909
  }
1910
+ if (positionDiff > 0) {
1911
+ state.ignoreScrollFromMVCP.lt = threshold;
1912
+ } else {
1913
+ state.ignoreScrollFromMVCP.gt = threshold;
1914
+ }
1915
+ if (state.ignoreScrollFromMVCPTimeout) {
1916
+ clearTimeout(state.ignoreScrollFromMVCPTimeout);
1917
+ }
1918
+ const delay = needsScrollWorkaround ? 250 : 100;
1919
+ state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
1920
+ state.ignoreScrollFromMVCP = void 0;
1921
+ const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
1922
+ if (shouldForceUpdate) {
1923
+ state.ignoreScrollFromMVCPIgnored = false;
1924
+ state.scrollPending = state.scroll;
1925
+ updateScroll(ctx, state.scroll, true);
1926
+ }
1927
+ }, delay);
1778
1928
  }
1779
- updateAnchorLock(state, {
1780
- anchorId: anchorIdForLock,
1781
- anchorPosition: anchorPositionForLock,
1782
- dataChanged,
1783
- now,
1784
- positionDiff
1785
- });
1786
- if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1787
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
1788
- }
1789
- };
1929
+ } else {
1930
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1931
+ requestAnimationFrame(doit);
1932
+ }
1933
+ }
1934
+ }
1935
+
1936
+ // src/core/ensureInitialAnchor.ts
1937
+ var INITIAL_ANCHOR_TOLERANCE = 0.5;
1938
+ var INITIAL_ANCHOR_MAX_ATTEMPTS = 4;
1939
+ var INITIAL_ANCHOR_SETTLED_TICKS = 2;
1940
+ function ensureInitialAnchor(ctx) {
1941
+ var _a3, _b, _c, _d, _e, _f;
1942
+ const state = ctx.state;
1943
+ const { initialAnchor, didContainersLayout, scroll, scrollLength } = state;
1944
+ const anchor = initialAnchor;
1945
+ if (state.initialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll)) {
1946
+ return;
1947
+ }
1948
+ const item = state.props.data[anchor.index];
1949
+ if (!didContainersLayout) {
1950
+ return;
1951
+ }
1952
+ const id = getId(state, anchor.index);
1953
+ if (state.positions[anchor.index] === void 0) {
1954
+ return;
1955
+ }
1956
+ const size = getItemSize(ctx, id, anchor.index, item, true, true);
1957
+ if (size === void 0) {
1958
+ return;
1959
+ }
1960
+ const availableSpace = Math.max(0, scrollLength - size);
1961
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1962
+ const desiredOffset = calculateOffsetForIndex(ctx, anchor.index) + topOffsetAdjustment - ((_b = anchor.viewOffset) != null ? _b : 0) - ((_c = anchor.viewPosition) != null ? _c : 0) * availableSpace;
1963
+ const clampedDesiredOffset = clampScrollOffset(ctx, desiredOffset, anchor);
1964
+ const delta = clampedDesiredOffset - scroll;
1965
+ if (Math.abs(delta) <= INITIAL_ANCHOR_TOLERANCE) {
1966
+ const settledTicks = ((_d = anchor.settledTicks) != null ? _d : 0) + 1;
1967
+ if (settledTicks >= INITIAL_ANCHOR_SETTLED_TICKS) {
1968
+ state.initialAnchor = void 0;
1969
+ } else {
1970
+ anchor.settledTicks = settledTicks;
1971
+ }
1972
+ return;
1973
+ }
1974
+ if (((_e = anchor.attempts) != null ? _e : 0) >= INITIAL_ANCHOR_MAX_ATTEMPTS) {
1975
+ state.initialAnchor = void 0;
1976
+ return;
1977
+ }
1978
+ const lastDelta = anchor.lastDelta;
1979
+ if (lastDelta !== void 0 && Math.abs(delta) >= Math.abs(lastDelta)) {
1980
+ state.initialAnchor = void 0;
1981
+ return;
1790
1982
  }
1983
+ Object.assign(anchor, {
1984
+ attempts: ((_f = anchor.attempts) != null ? _f : 0) + 1,
1985
+ lastDelta: delta,
1986
+ settledTicks: 0
1987
+ });
1988
+ requestAdjust(ctx, delta);
1791
1989
  }
1792
1990
 
1793
1991
  // src/core/prepareColumnStartState.ts
@@ -2443,7 +2641,14 @@ function comparatorByDistance(a, b) {
2443
2641
  }
2444
2642
 
2445
2643
  // src/core/scrollToIndex.ts
2446
- function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPosition }) {
2644
+ function scrollToIndex(ctx, {
2645
+ index,
2646
+ viewOffset = 0,
2647
+ animated = true,
2648
+ forceScroll,
2649
+ isInitialScroll,
2650
+ viewPosition
2651
+ }) {
2447
2652
  const state = ctx.state;
2448
2653
  const { data } = state.props;
2449
2654
  if (index >= data.length) {
@@ -2461,7 +2666,9 @@ function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPositi
2461
2666
  const itemSize = getItemSize(ctx, targetId, index, state.props.data[index]);
2462
2667
  scrollTo(ctx, {
2463
2668
  animated,
2669
+ forceScroll,
2464
2670
  index,
2671
+ isInitialScroll,
2465
2672
  itemSize,
2466
2673
  offset: firstIndexOffset,
2467
2674
  viewOffset,
@@ -2469,15 +2676,58 @@ function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPositi
2469
2676
  });
2470
2677
  }
2471
2678
 
2679
+ // src/utils/performInitialScroll.ts
2680
+ function performInitialScroll(ctx, params) {
2681
+ var _a3;
2682
+ const { forceScroll, initialScrollUsesOffset, resolvedOffset, target } = params;
2683
+ if (initialScrollUsesOffset || resolvedOffset !== void 0) {
2684
+ scrollTo(ctx, {
2685
+ animated: false,
2686
+ forceScroll,
2687
+ index: initialScrollUsesOffset ? void 0 : target.index,
2688
+ isInitialScroll: true,
2689
+ offset: (_a3 = resolvedOffset != null ? resolvedOffset : target.contentOffset) != null ? _a3 : 0,
2690
+ precomputedWithViewOffset: resolvedOffset !== void 0
2691
+ });
2692
+ return;
2693
+ }
2694
+ if (target.index === void 0) {
2695
+ return;
2696
+ }
2697
+ scrollToIndex(ctx, {
2698
+ ...target,
2699
+ animated: false,
2700
+ forceScroll,
2701
+ isInitialScroll: true
2702
+ });
2703
+ }
2704
+
2472
2705
  // src/utils/setDidLayout.ts
2473
2706
  function setDidLayout(ctx) {
2474
2707
  const state = ctx.state;
2475
2708
  const { initialScroll } = state;
2476
2709
  state.queuedInitialLayout = true;
2477
2710
  checkAtBottom(ctx);
2478
- if ((initialScroll == null ? void 0 : initialScroll.index) !== void 0) {
2479
- const target = initialScroll;
2480
- const runScroll = () => scrollToIndex(ctx, { ...target, animated: false });
2711
+ if (initialScroll) {
2712
+ const runScroll = () => {
2713
+ var _a3, _b;
2714
+ const target = state.initialScroll;
2715
+ if (!target) {
2716
+ return;
2717
+ }
2718
+ const activeInitialTargetOffset = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? (_b = state.scrollingTo.targetOffset) != null ? _b : state.scrollingTo.offset : void 0;
2719
+ const desiredInitialTargetOffset = state.initialScrollUsesOffset ? target.contentOffset : activeInitialTargetOffset;
2720
+ const isAlreadyAtDesiredInitialTarget = desiredInitialTargetOffset !== void 0 && Math.abs(state.scroll - desiredInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - desiredInitialTargetOffset) <= 1;
2721
+ if (!isAlreadyAtDesiredInitialTarget) {
2722
+ performInitialScroll(ctx, {
2723
+ forceScroll: true,
2724
+ initialScrollUsesOffset: state.initialScrollUsesOffset,
2725
+ // Offset-based initial scrolls do not need item lookup, so they can run even before data exists.
2726
+ // Re-run on the next frame to pick up measured viewport size without waiting for index resolution.
2727
+ target
2728
+ });
2729
+ }
2730
+ };
2481
2731
  runScroll();
2482
2732
  requestAnimationFrame(runScroll);
2483
2733
  }
@@ -2555,7 +2805,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
2555
2805
  function calculateItemsInView(ctx, params = {}) {
2556
2806
  const state = ctx.state;
2557
2807
  batchedUpdates(() => {
2558
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2808
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
2559
2809
  const {
2560
2810
  columns,
2561
2811
  columnSpans,
@@ -2594,7 +2844,7 @@ function calculateItemsInView(ctx, params = {}) {
2594
2844
  }
2595
2845
  return;
2596
2846
  }
2597
- const totalSize = getContentSize(ctx);
2847
+ let totalSize = getContentSize(ctx);
2598
2848
  const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
2599
2849
  const numColumns = peek$(ctx, "numColumns");
2600
2850
  const speed = getScrollVelocity(state);
@@ -2602,14 +2852,14 @@ function calculateItemsInView(ctx, params = {}) {
2602
2852
  const { queuedInitialLayout } = state;
2603
2853
  let { scroll: scrollState } = state;
2604
2854
  if (!queuedInitialLayout && initialScroll) {
2605
- const updatedOffset = calculateOffsetWithOffsetPosition(
2855
+ const updatedOffset = state.initialScrollUsesOffset ? (_a3 = initialScroll.contentOffset) != null ? _a3 : 0 : calculateOffsetWithOffsetPosition(
2606
2856
  ctx,
2607
2857
  calculateOffsetForIndex(ctx, initialScroll.index),
2608
2858
  initialScroll
2609
2859
  );
2610
2860
  scrollState = updatedOffset;
2611
2861
  }
2612
- const scrollAdjustPending = (_a3 = peek$(ctx, "scrollAdjustPending")) != null ? _a3 : 0;
2862
+ const scrollAdjustPending = (_b = peek$(ctx, "scrollAdjustPending")) != null ? _b : 0;
2613
2863
  const scrollAdjustPad = scrollAdjustPending - topPad;
2614
2864
  let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
2615
2865
  if (scroll + scrollLength > totalSize) {
@@ -2654,13 +2904,14 @@ function calculateItemsInView(ctx, params = {}) {
2654
2904
  columns.length = 0;
2655
2905
  columnSpans.length = 0;
2656
2906
  }
2657
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_b = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _b : 0;
2907
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
2658
2908
  updateItemPositions(ctx, dataChanged, {
2659
2909
  doMVCP,
2660
2910
  forceFullUpdate: !!forceFullItemPositions,
2661
2911
  scrollBottomBuffered,
2662
2912
  startIndex
2663
2913
  });
2914
+ totalSize = getContentSize(ctx);
2664
2915
  if (minIndexSizeChanged !== void 0) {
2665
2916
  state.minIndexSizeChanged = void 0;
2666
2917
  }
@@ -2672,9 +2923,9 @@ function calculateItemsInView(ctx, params = {}) {
2672
2923
  let endBuffered = null;
2673
2924
  let loopStart = !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
2674
2925
  for (let i = loopStart; i >= 0; i--) {
2675
- const id = (_c = idCache[i]) != null ? _c : getId(state, i);
2926
+ const id = (_d = idCache[i]) != null ? _d : getId(state, i);
2676
2927
  const top = positions[i];
2677
- const size = (_d = sizes.get(id)) != null ? _d : getItemSize(ctx, id, i, data[i]);
2928
+ const size = (_e = sizes.get(id)) != null ? _e : getItemSize(ctx, id, i, data[i]);
2678
2929
  const bottom = top + size;
2679
2930
  if (bottom > scroll - scrollBufferTop) {
2680
2931
  loopStart = i;
@@ -2705,14 +2956,14 @@ function calculateItemsInView(ctx, params = {}) {
2705
2956
  let firstFullyOnScreenIndex;
2706
2957
  const dataLength = data.length;
2707
2958
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
2708
- const id = (_e = idCache[i]) != null ? _e : getId(state, i);
2709
- const size = (_f = sizes.get(id)) != null ? _f : getItemSize(ctx, id, i, data[i]);
2959
+ const id = (_f = idCache[i]) != null ? _f : getId(state, i);
2960
+ const size = (_g = sizes.get(id)) != null ? _g : getItemSize(ctx, id, i, data[i]);
2710
2961
  const top = positions[i];
2711
2962
  if (!foundEnd) {
2712
2963
  if (startNoBuffer === null && top + size > scroll) {
2713
2964
  startNoBuffer = i;
2714
2965
  }
2715
- if (firstFullyOnScreenIndex === void 0 && top >= scroll - 10) {
2966
+ if (firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
2716
2967
  firstFullyOnScreenIndex = i;
2717
2968
  }
2718
2969
  if (startBuffered === null && top + size > scrollTopBuffered) {
@@ -2742,9 +2993,12 @@ function calculateItemsInView(ctx, params = {}) {
2742
2993
  }
2743
2994
  }
2744
2995
  const idsInView = [];
2745
- for (let i = firstFullyOnScreenIndex; i <= endNoBuffer; i++) {
2746
- const id = (_g = idCache[i]) != null ? _g : getId(state, i);
2747
- idsInView.push(id);
2996
+ const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
2997
+ if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
2998
+ for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
2999
+ const id = (_h = idCache[i]) != null ? _h : getId(state, i);
3000
+ idsInView.push(id);
3001
+ }
2748
3002
  }
2749
3003
  Object.assign(state, {
2750
3004
  endBuffered,
@@ -2775,7 +3029,7 @@ function calculateItemsInView(ctx, params = {}) {
2775
3029
  const needNewContainers = [];
2776
3030
  const needNewContainersSet = /* @__PURE__ */ new Set();
2777
3031
  for (let i = startBuffered; i <= endBuffered; i++) {
2778
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
3032
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
2779
3033
  if (!containerItemKeys.has(id)) {
2780
3034
  needNewContainersSet.add(i);
2781
3035
  needNewContainers.push(i);
@@ -2784,7 +3038,7 @@ function calculateItemsInView(ctx, params = {}) {
2784
3038
  if (alwaysRenderArr.length > 0) {
2785
3039
  for (const index of alwaysRenderArr) {
2786
3040
  if (index < 0 || index >= dataLength) continue;
2787
- const id = (_i = idCache[index]) != null ? _i : getId(state, index);
3041
+ const id = (_j = idCache[index]) != null ? _j : getId(state, index);
2788
3042
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
2789
3043
  needNewContainersSet.add(index);
2790
3044
  needNewContainers.push(index);
@@ -2822,7 +3076,7 @@ function calculateItemsInView(ctx, params = {}) {
2822
3076
  for (let idx = 0; idx < needNewContainers.length; idx++) {
2823
3077
  const i = needNewContainers[idx];
2824
3078
  const containerIndex = availableContainers[idx];
2825
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
3079
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
2826
3080
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
2827
3081
  if (oldKey && oldKey !== id) {
2828
3082
  containerItemKeys.delete(oldKey);
@@ -2863,7 +3117,7 @@ function calculateItemsInView(ctx, params = {}) {
2863
3117
  if (alwaysRenderArr.length > 0) {
2864
3118
  for (const index of alwaysRenderArr) {
2865
3119
  if (index < 0 || index >= dataLength) continue;
2866
- const id = (_k = idCache[index]) != null ? _k : getId(state, index);
3120
+ const id = (_l = idCache[index]) != null ? _l : getId(state, index);
2867
3121
  const containerIndex = containerItemKeys.get(id);
2868
3122
  if (containerIndex !== void 0) {
2869
3123
  state.stickyContainerPool.add(containerIndex);
@@ -2974,40 +3228,6 @@ function checkActualChange(state, dataProp, previousData) {
2974
3228
  return false;
2975
3229
  }
2976
3230
 
2977
- // src/core/doMaintainScrollAtEnd.ts
2978
- function doMaintainScrollAtEnd(ctx, animated) {
2979
- const state = ctx.state;
2980
- const {
2981
- didContainersLayout,
2982
- isAtEnd,
2983
- refScroller,
2984
- props: { maintainScrollAtEnd }
2985
- } = state;
2986
- if (isAtEnd && maintainScrollAtEnd && didContainersLayout) {
2987
- const contentSize = getContentSize(ctx);
2988
- if (contentSize < state.scrollLength) {
2989
- state.scroll = 0;
2990
- }
2991
- requestAnimationFrame(() => {
2992
- var _a3;
2993
- if (state.isAtEnd) {
2994
- state.maintainingScrollAtEnd = true;
2995
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
2996
- animated
2997
- });
2998
- setTimeout(
2999
- () => {
3000
- state.maintainingScrollAtEnd = false;
3001
- },
3002
- 0
3003
- );
3004
- }
3005
- });
3006
- return true;
3007
- }
3008
- return false;
3009
- }
3010
-
3011
3231
  // src/utils/updateAveragesOnDataChange.ts
3012
3232
  function updateAveragesOnDataChange(state, oldData, newData) {
3013
3233
  var _a3;
@@ -3069,8 +3289,8 @@ function checkResetContainers(ctx, dataProp) {
3069
3289
  }
3070
3290
  const { maintainScrollAtEnd } = state.props;
3071
3291
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
3072
- const shouldMaintainScrollAtEnd = maintainScrollAtEnd === true || maintainScrollAtEnd.onDataChange;
3073
- const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx, false);
3292
+ const shouldMaintainScrollAtEnd = maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onDataChange;
3293
+ const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx);
3074
3294
  if (!didMaintainScrollAtEnd && previousData && dataProp.length > previousData.length) {
3075
3295
  state.isEndReached = false;
3076
3296
  }
@@ -3174,8 +3394,8 @@ function handleLayout(ctx, layoutParam, setCanRender) {
3174
3394
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
3175
3395
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
3176
3396
  }
3177
- if (maintainScrollAtEnd === true || maintainScrollAtEnd.onLayout) {
3178
- doMaintainScrollAtEnd(ctx, false);
3397
+ if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
3398
+ doMaintainScrollAtEnd(ctx);
3179
3399
  }
3180
3400
  checkThresholds(ctx);
3181
3401
  if (state) {
@@ -3192,6 +3412,12 @@ function handleLayout(ctx, layoutParam, setCanRender) {
3192
3412
  }
3193
3413
 
3194
3414
  // src/core/onScroll.ts
3415
+ var INITIAL_SCROLL_PROGRESS_EPSILON = 1;
3416
+ function didObserveInitialScrollProgress(newScroll, watchdog) {
3417
+ const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
3418
+ const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
3419
+ return nextDistance <= INITIAL_SCROLL_PROGRESS_EPSILON || nextDistance + INITIAL_SCROLL_PROGRESS_EPSILON < previousDistance;
3420
+ }
3195
3421
  function onScroll(ctx, event) {
3196
3422
  var _a3, _b, _c, _d;
3197
3423
  const state = ctx.state;
@@ -3229,7 +3455,16 @@ function onScroll(ctx, event) {
3229
3455
  }
3230
3456
  }
3231
3457
  state.scrollPending = newScroll;
3458
+ const initialNativeScrollWatchdog = state.initialNativeScrollWatchdog;
3459
+ const didInitialScrollProgress = !!initialNativeScrollWatchdog && didObserveInitialScrollProgress(newScroll, initialNativeScrollWatchdog);
3460
+ if (didInitialScrollProgress) {
3461
+ state.initialNativeScrollWatchdog = void 0;
3462
+ }
3232
3463
  updateScroll(ctx, newScroll, insetChanged);
3464
+ if (initialNativeScrollWatchdog && !didInitialScrollProgress) {
3465
+ state.hasScrolled = false;
3466
+ state.initialNativeScrollWatchdog = initialNativeScrollWatchdog;
3467
+ }
3233
3468
  if (state.scrollingTo) {
3234
3469
  checkFinishedScroll(ctx);
3235
3470
  }
@@ -3396,8 +3631,8 @@ function updateItemSize(ctx, itemKey, sizeObj) {
3396
3631
  runOrScheduleMVCPRecalculate(ctx);
3397
3632
  }
3398
3633
  if (shouldMaintainScrollAtEnd) {
3399
- if (maintainScrollAtEnd === true || maintainScrollAtEnd.onItemLayout) {
3400
- doMaintainScrollAtEnd(ctx, false);
3634
+ if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onItemLayout) {
3635
+ doMaintainScrollAtEnd(ctx);
3401
3636
  }
3402
3637
  }
3403
3638
  }
@@ -3773,6 +4008,34 @@ function getRenderedItem(ctx, key) {
3773
4008
  return { index, item: data[index], renderedItem };
3774
4009
  }
3775
4010
 
4011
+ // src/utils/normalizeMaintainScrollAtEnd.ts
4012
+ function normalizeMaintainScrollAtEndOn(on, hasExplicitOn) {
4013
+ var _a3, _b, _c;
4014
+ return {
4015
+ animated: false,
4016
+ onDataChange: hasExplicitOn ? (_a3 = on == null ? void 0 : on.dataChange) != null ? _a3 : false : true,
4017
+ onItemLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.itemLayout) != null ? _b : false : true,
4018
+ onLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.layout) != null ? _c : false : true
4019
+ };
4020
+ }
4021
+ function normalizeMaintainScrollAtEnd(value) {
4022
+ var _a3;
4023
+ if (!value) {
4024
+ return void 0;
4025
+ }
4026
+ if (value === true) {
4027
+ return {
4028
+ ...normalizeMaintainScrollAtEndOn(void 0, false),
4029
+ animated: false
4030
+ };
4031
+ }
4032
+ const normalizedTriggers = normalizeMaintainScrollAtEndOn(value.on, "on" in value);
4033
+ return {
4034
+ ...normalizedTriggers,
4035
+ animated: (_a3 = value.animated) != null ? _a3 : false
4036
+ };
4037
+ }
4038
+
3776
4039
  // src/utils/normalizeMaintainVisibleContentPosition.ts
3777
4040
  function normalizeMaintainVisibleContentPosition(value) {
3778
4041
  var _a3, _b;
@@ -3874,7 +4137,7 @@ var LegendList = typedMemo2(
3874
4137
  })
3875
4138
  );
3876
4139
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
3877
- var _a3, _b, _c, _d, _e;
4140
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
3878
4141
  const {
3879
4142
  alignItemsAtEnd = false,
3880
4143
  alwaysRender,
@@ -3960,16 +4223,24 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3960
4223
  const style = { ...StyleSheet.flatten(styleProp) };
3961
4224
  const stylePaddingTopState = extractPadding(style, contentContainerStyle, "Top");
3962
4225
  const stylePaddingBottomState = extractPadding(style, contentContainerStyle, "Bottom");
4226
+ const maintainScrollAtEndConfig = normalizeMaintainScrollAtEnd(maintainScrollAtEnd);
3963
4227
  const maintainVisibleContentPositionConfig = normalizeMaintainVisibleContentPosition(
3964
4228
  maintainVisibleContentPositionProp
3965
4229
  );
3966
- const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? {
3967
- index: initialScrollIndexProp.index || 0,
3968
- viewOffset: initialScrollIndexProp.viewOffset || (initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0),
3969
- viewPosition: initialScrollIndexProp.viewPosition || 0
4230
+ const hasInitialScrollIndex = initialScrollIndexProp !== void 0 && initialScrollIndexProp !== null;
4231
+ const hasInitialScrollOffset = initialScrollOffsetProp !== void 0 && initialScrollOffsetProp !== null;
4232
+ const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && hasInitialScrollOffset;
4233
+ const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
4234
+ index: (_a3 = initialScrollIndexProp.index) != null ? _a3 : 0,
4235
+ viewOffset: (_b = initialScrollIndexProp.viewOffset) != null ? _b : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0,
4236
+ viewPosition: (_c = initialScrollIndexProp.viewPosition) != null ? _c : 0
3970
4237
  } : {
3971
- index: initialScrollIndexProp || 0,
3972
- viewOffset: initialScrollOffsetProp || 0
4238
+ index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
4239
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
4240
+ } : initialScrollUsesOffsetOnly ? {
4241
+ contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
4242
+ index: 0,
4243
+ viewOffset: 0
3973
4244
  } : void 0;
3974
4245
  const [canRender, setCanRender] = React2.useState(!IsNewArchitecture);
3975
4246
  const ctx = useStateContext();
@@ -3984,8 +4255,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3984
4255
  }, [
3985
4256
  alwaysRender == null ? void 0 : alwaysRender.top,
3986
4257
  alwaysRender == null ? void 0 : alwaysRender.bottom,
3987
- (_a3 = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _a3.join(","),
3988
- (_b = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _b.join(","),
4258
+ (_d = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _d.join(","),
4259
+ (_e = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _e.join(","),
3989
4260
  dataProp,
3990
4261
  dataVersion,
3991
4262
  keyExtractor
@@ -4029,14 +4300,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4029
4300
  idCache: [],
4030
4301
  idsInView: [],
4031
4302
  indexByKey: /* @__PURE__ */ new Map(),
4032
- initialAnchor: (initialScrollProp == null ? void 0 : initialScrollProp.index) !== void 0 && (initialScrollProp == null ? void 0 : initialScrollProp.viewPosition) !== void 0 ? {
4303
+ initialAnchor: !initialScrollUsesOffsetOnly && (initialScrollProp == null ? void 0 : initialScrollProp.index) !== void 0 && (initialScrollProp == null ? void 0 : initialScrollProp.viewPosition) !== void 0 ? {
4033
4304
  attempts: 0,
4034
4305
  index: initialScrollProp.index,
4035
4306
  settledTicks: 0,
4036
- viewOffset: (_c = initialScrollProp.viewOffset) != null ? _c : 0,
4307
+ viewOffset: (_f = initialScrollProp.viewOffset) != null ? _f : 0,
4037
4308
  viewPosition: initialScrollProp.viewPosition
4038
4309
  } : void 0,
4310
+ initialNativeScrollWatchdog: void 0,
4039
4311
  initialScroll: initialScrollProp,
4312
+ initialScrollLastDidFinish: false,
4313
+ initialScrollLastTarget: initialScrollProp,
4314
+ initialScrollLastTargetUsesOffset: initialScrollUsesOffsetOnly,
4315
+ initialScrollPreviousDataLength: dataProp.length,
4316
+ initialScrollRetryLastLength: void 0,
4317
+ initialScrollRetryWindowUntil: 0,
4318
+ initialScrollUsesOffset: initialScrollUsesOffsetOnly,
4040
4319
  isAtEnd: false,
4041
4320
  isAtStart: false,
4042
4321
  isEndReached: null,
@@ -4049,6 +4328,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4049
4328
  minIndexSizeChanged: 0,
4050
4329
  nativeContentInset: void 0,
4051
4330
  nativeMarginTop: 0,
4331
+ pendingNativeMVCPAdjust: void 0,
4052
4332
  positions: [],
4053
4333
  props: {},
4054
4334
  queuedCalculateItemsInView: 0,
@@ -4086,7 +4366,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4086
4366
  const state = refState.current;
4087
4367
  const isFirstLocal = state.isFirst;
4088
4368
  state.didColumnsChange = numColumnsProp !== state.props.numColumns;
4089
- const didDataChangeLocal = state.props.dataVersion !== dataVersion || state.props.data !== dataProp && checkActualChange(state, dataProp, state.props.data);
4369
+ const didDataReferenceChangeLocal = state.props.data !== dataProp;
4370
+ const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
4371
+ const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkActualChange(state, dataProp, state.props.data);
4090
4372
  if (didDataChangeLocal) {
4091
4373
  state.dataChangeEpoch += 1;
4092
4374
  state.dataChangeNeedsScrollUpdate = true;
@@ -4112,7 +4394,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4112
4394
  initialContainerPoolRatio,
4113
4395
  itemsAreEqual,
4114
4396
  keyExtractor: useWrapIfItem(keyExtractor),
4115
- maintainScrollAtEnd,
4397
+ maintainScrollAtEnd: maintainScrollAtEndConfig,
4116
4398
  maintainScrollAtEndThreshold,
4117
4399
  maintainVisibleContentPosition: maintainVisibleContentPositionConfig,
4118
4400
  numColumns: numColumnsProp,
@@ -4168,16 +4450,83 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4168
4450
  );
4169
4451
  }
4170
4452
  const resolveInitialScrollOffset = useCallback((initialScroll) => {
4453
+ var _a4;
4454
+ if (state.initialScrollUsesOffset) {
4455
+ return clampScrollOffset(ctx, (_a4 = initialScroll.contentOffset) != null ? _a4 : 0);
4456
+ }
4171
4457
  const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4172
4458
  const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4173
4459
  return clampScrollOffset(ctx, resolvedOffset, initialScroll);
4174
4460
  }, []);
4461
+ const finishInitialScrollWithoutScroll = useCallback(() => {
4462
+ refState.current.initialAnchor = void 0;
4463
+ refState.current.initialScroll = void 0;
4464
+ state.initialAnchor = void 0;
4465
+ state.initialScroll = void 0;
4466
+ state.initialScrollUsesOffset = false;
4467
+ state.initialScrollLastTarget = void 0;
4468
+ state.initialScrollLastTargetUsesOffset = false;
4469
+ setInitialRenderState(ctx, { didInitialScroll: true });
4470
+ }, []);
4471
+ const setActiveInitialScrollTarget = useCallback(
4472
+ (target, options) => {
4473
+ var _a4;
4474
+ const usesOffset = !!(options == null ? void 0 : options.usesOffset);
4475
+ state.initialScrollUsesOffset = usesOffset;
4476
+ state.initialScrollLastTarget = target;
4477
+ state.initialScrollLastTargetUsesOffset = usesOffset;
4478
+ refState.current.initialScroll = target;
4479
+ state.initialScroll = target;
4480
+ if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
4481
+ state.didFinishInitialScroll = false;
4482
+ }
4483
+ if (!(options == null ? void 0 : options.syncAnchor)) {
4484
+ return;
4485
+ }
4486
+ if (!IsNewArchitecture && !usesOffset && target.index !== void 0 && target.viewPosition !== void 0) {
4487
+ state.initialAnchor = {
4488
+ attempts: 0,
4489
+ index: target.index,
4490
+ settledTicks: 0,
4491
+ viewOffset: (_a4 = target.viewOffset) != null ? _a4 : 0,
4492
+ viewPosition: target.viewPosition
4493
+ };
4494
+ }
4495
+ },
4496
+ []
4497
+ );
4498
+ const shouldFinishInitialScrollAtOrigin = useCallback(
4499
+ (initialScroll, offset) => {
4500
+ var _a4, _b2, _c2;
4501
+ if (offset !== 0 || initialScrollAtEnd) {
4502
+ return false;
4503
+ }
4504
+ if (state.initialScrollUsesOffset) {
4505
+ return Math.abs((_a4 = initialScroll.contentOffset) != null ? _a4 : 0) <= 1;
4506
+ }
4507
+ return initialScroll.index === 0 && ((_b2 = initialScroll.viewPosition) != null ? _b2 : 0) === 0 && Math.abs((_c2 = initialScroll.viewOffset) != null ? _c2 : 0) <= 1;
4508
+ },
4509
+ [initialScrollAtEnd]
4510
+ );
4511
+ const shouldFinishEmptyInitialScrollAtEnd = useCallback(
4512
+ (initialScroll, offset) => {
4513
+ return dataProp.length === 0 && initialScrollAtEnd && offset === 0 && initialScroll.viewPosition === 1;
4514
+ },
4515
+ [dataProp.length, initialScrollAtEnd]
4516
+ );
4517
+ const shouldRearmFinishedEmptyInitialScrollAtEnd = useCallback(
4518
+ (initialScroll) => {
4519
+ var _a4;
4520
+ return !!(state.didFinishInitialScroll && dataProp.length > 0 && initialScroll && !state.initialScrollUsesOffset && initialScroll.index === 0 && initialScroll.viewPosition === 1 && ((_a4 = initialScroll.contentOffset) != null ? _a4 : 0) === 0);
4521
+ },
4522
+ [dataProp.length]
4523
+ );
4175
4524
  const initialContentOffset = useMemo(() => {
4176
4525
  var _a4;
4177
4526
  let value;
4178
4527
  const { initialScroll, initialAnchor } = refState.current;
4179
4528
  if (initialScroll) {
4180
- if (!IsNewArchitecture && initialScroll.index !== void 0 && (!initialAnchor || (initialAnchor == null ? void 0 : initialAnchor.index) !== initialScroll.index)) {
4529
+ if (!state.initialScrollUsesOffset && !IsNewArchitecture && initialScroll.index !== void 0 && (!initialAnchor || (initialAnchor == null ? void 0 : initialAnchor.index) !== initialScroll.index)) {
4181
4530
  refState.current.initialAnchor = {
4182
4531
  attempts: 0,
4183
4532
  index: initialScroll.index,
@@ -4191,16 +4540,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4191
4540
  } else {
4192
4541
  const clampedOffset = resolveInitialScrollOffset(initialScroll);
4193
4542
  const updatedInitialScroll = { ...initialScroll, contentOffset: clampedOffset };
4194
- refState.current.initialScroll = updatedInitialScroll;
4195
- state.initialScroll = updatedInitialScroll;
4543
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4544
+ usesOffset: state.initialScrollUsesOffset
4545
+ });
4196
4546
  value = clampedOffset;
4197
4547
  }
4198
4548
  } else {
4199
4549
  refState.current.initialAnchor = void 0;
4200
4550
  value = 0;
4201
4551
  }
4202
- if (!value) {
4203
- setInitialRenderState(ctx, { didInitialScroll: true });
4552
+ const hasPendingDataDependentInitialScroll = !!initialScroll && dataProp.length === 0 && !shouldFinishInitialScrollAtOrigin(initialScroll, value) && !shouldFinishEmptyInitialScrollAtEnd(initialScroll, value);
4553
+ if (!value && !hasPendingDataDependentInitialScroll) {
4554
+ if (initialScroll && shouldFinishInitialScrollAtOrigin(initialScroll, value)) {
4555
+ finishInitialScrollWithoutScroll();
4556
+ } else {
4557
+ setInitialRenderState(ctx, { didInitialScroll: true });
4558
+ }
4204
4559
  }
4205
4560
  return value;
4206
4561
  }, []);
@@ -4217,24 +4572,131 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4217
4572
  set$(ctx, "totalSize", 0);
4218
4573
  }
4219
4574
  }
4220
- const doInitialScroll = useCallback(() => {
4221
- const { initialScroll, didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4222
- if (initialScroll && !queuedInitialLayout && !didFinishInitialScroll && !scrollingTo) {
4223
- const offset = resolveInitialScrollOffset(initialScroll);
4575
+ const doInitialScroll = useCallback((options) => {
4576
+ var _a4, _b2;
4577
+ const allowPostFinishRetry = !!(options == null ? void 0 : options.allowPostFinishRetry);
4578
+ const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4579
+ const initialScroll = (_a4 = state.initialScroll) != null ? _a4 : allowPostFinishRetry ? state.initialScrollLastTarget : void 0;
4580
+ const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
4581
+ const needsContainerLayoutForInitialScroll = !state.initialScrollUsesOffset;
4582
+ const shouldWaitForInitialLayout = waitForInitialLayout && needsContainerLayoutForInitialScroll && !queuedInitialLayout && !allowPostFinishRetry && !isInitialScrollInProgress;
4583
+ if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll && !allowPostFinishRetry || scrollingTo && !isInitialScrollInProgress) {
4584
+ return;
4585
+ }
4586
+ if (allowPostFinishRetry && state.initialScrollLastTargetUsesOffset) {
4587
+ return;
4588
+ }
4589
+ const didMoveAwayFromInitialTarget = allowPostFinishRetry && initialScroll.contentOffset !== void 0 && Math.abs(state.scroll - initialScroll.contentOffset) > 1;
4590
+ if (didMoveAwayFromInitialTarget) {
4591
+ state.initialScrollRetryWindowUntil = 0;
4592
+ return;
4593
+ }
4594
+ const offset = resolveInitialScrollOffset(initialScroll);
4595
+ const activeInitialTargetOffset = isInitialScrollInProgress ? (_b2 = scrollingTo.targetOffset) != null ? _b2 : scrollingTo.offset : void 0;
4596
+ const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - offset) > 1;
4597
+ const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - offset) > 1;
4598
+ if (!didOffsetChange && (allowPostFinishRetry || isInitialScrollInProgress && !didActiveInitialTargetChange)) {
4599
+ return;
4600
+ }
4601
+ if (didOffsetChange) {
4224
4602
  const updatedInitialScroll = { ...initialScroll, contentOffset: offset };
4225
- refState.current.initialScroll = updatedInitialScroll;
4226
- state.initialScroll = updatedInitialScroll;
4227
- scrollTo(ctx, {
4228
- animated: false,
4229
- index: initialScroll.index,
4230
- isInitialScroll: true,
4231
- offset,
4232
- precomputedWithViewOffset: true
4233
- });
4603
+ if (!state.initialScrollUsesOffset) {
4604
+ state.initialScrollLastTarget = updatedInitialScroll;
4605
+ state.initialScrollLastTargetUsesOffset = false;
4606
+ if (state.initialScroll) {
4607
+ refState.current.initialScroll = updatedInitialScroll;
4608
+ state.initialScroll = updatedInitialScroll;
4609
+ }
4610
+ }
4234
4611
  }
4612
+ const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
4613
+ const shouldForceNativeInitialScroll = state.initialScrollUsesOffset && hasMeasuredScrollLayout || allowPostFinishRetry || !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
4614
+ performInitialScroll(ctx, {
4615
+ forceScroll: shouldForceNativeInitialScroll,
4616
+ initialScrollUsesOffset: state.initialScrollUsesOffset,
4617
+ resolvedOffset: offset,
4618
+ target: initialScroll
4619
+ });
4235
4620
  }, []);
4621
+ useLayoutEffect(() => {
4622
+ var _a4;
4623
+ const previousDataLength = state.initialScrollPreviousDataLength;
4624
+ state.initialScrollPreviousDataLength = dataProp.length;
4625
+ if (previousDataLength !== 0 || dataProp.length === 0 || !state.initialScroll || !state.queuedInitialLayout) {
4626
+ return;
4627
+ }
4628
+ if (initialScrollAtEnd) {
4629
+ const lastIndex = Math.max(0, dataProp.length - 1);
4630
+ const initialScroll = state.initialScroll;
4631
+ const shouldRearm = shouldRearmFinishedEmptyInitialScrollAtEnd(initialScroll);
4632
+ if (state.didFinishInitialScroll && !shouldRearm) {
4633
+ return;
4634
+ }
4635
+ if (initialScroll && !state.initialScrollUsesOffset && initialScroll.index === lastIndex && initialScroll.viewPosition === 1 && !shouldRearm) {
4636
+ return;
4637
+ }
4638
+ const updatedInitialScroll = {
4639
+ contentOffset: void 0,
4640
+ index: lastIndex,
4641
+ viewOffset: (_a4 = initialScroll == null ? void 0 : initialScroll.viewOffset) != null ? _a4 : -stylePaddingBottomState,
4642
+ viewPosition: 1
4643
+ };
4644
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4645
+ resetDidFinish: shouldRearm,
4646
+ syncAnchor: true
4647
+ });
4648
+ doInitialScroll();
4649
+ return;
4650
+ }
4651
+ if (state.didFinishInitialScroll) {
4652
+ return;
4653
+ }
4654
+ doInitialScroll();
4655
+ }, [
4656
+ dataProp.length,
4657
+ doInitialScroll,
4658
+ initialScrollAtEnd,
4659
+ shouldRearmFinishedEmptyInitialScrollAtEnd,
4660
+ stylePaddingBottomState
4661
+ ]);
4662
+ useLayoutEffect(() => {
4663
+ var _a4;
4664
+ if (!initialScrollAtEnd) {
4665
+ return;
4666
+ }
4667
+ const lastIndex = Math.max(0, dataProp.length - 1);
4668
+ const initialScroll = state.initialScroll;
4669
+ const shouldRearm = shouldRearmFinishedEmptyInitialScrollAtEnd(initialScroll);
4670
+ if (state.didFinishInitialScroll && !shouldRearm) {
4671
+ return;
4672
+ }
4673
+ if (shouldRearm) {
4674
+ state.didFinishInitialScroll = false;
4675
+ }
4676
+ if (initialScroll && !state.initialScrollUsesOffset && initialScroll.index === lastIndex && initialScroll.viewPosition === 1 && !shouldRearm) {
4677
+ return;
4678
+ }
4679
+ const updatedInitialScroll = {
4680
+ contentOffset: void 0,
4681
+ index: lastIndex,
4682
+ viewOffset: (_a4 = initialScroll == null ? void 0 : initialScroll.viewOffset) != null ? _a4 : -stylePaddingBottomState,
4683
+ viewPosition: 1
4684
+ };
4685
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4686
+ resetDidFinish: shouldRearm,
4687
+ syncAnchor: true
4688
+ });
4689
+ doInitialScroll();
4690
+ }, [
4691
+ dataProp.length,
4692
+ doInitialScroll,
4693
+ initialScrollAtEnd,
4694
+ shouldRearmFinishedEmptyInitialScrollAtEnd,
4695
+ stylePaddingBottomState
4696
+ ]);
4236
4697
  const onLayoutFooter = useCallback(
4237
4698
  (layout) => {
4699
+ var _a4;
4238
4700
  if (!initialScrollAtEnd) {
4239
4701
  return;
4240
4702
  }
@@ -4249,16 +4711,48 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4249
4711
  const footerSize = layout[horizontal ? "width" : "height"];
4250
4712
  const viewOffset = -stylePaddingBottomState - footerSize;
4251
4713
  if (initialScroll.viewOffset !== viewOffset) {
4714
+ const previousTargetOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(initialScroll);
4715
+ const didMoveAwayFromFinishedInitialTarget = state.didFinishInitialScroll && Math.abs(state.scroll - previousTargetOffset) > 1;
4716
+ if (didMoveAwayFromFinishedInitialTarget) {
4717
+ return;
4718
+ }
4252
4719
  const updatedInitialScroll = { ...initialScroll, viewOffset };
4253
- refState.current.initialScroll = updatedInitialScroll;
4254
- state.initialScroll = updatedInitialScroll;
4720
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4721
+ resetDidFinish: true
4722
+ });
4723
+ doInitialScroll();
4255
4724
  }
4256
4725
  },
4257
- [dataProp.length, horizontal, initialScrollAtEnd, stylePaddingBottomState]
4726
+ [
4727
+ dataProp.length,
4728
+ doInitialScroll,
4729
+ horizontal,
4730
+ initialScrollAtEnd,
4731
+ resolveInitialScrollOffset,
4732
+ stylePaddingBottomState
4733
+ ]
4258
4734
  );
4259
4735
  const onLayoutChange = useCallback((layout) => {
4260
- doInitialScroll();
4736
+ var _a4;
4261
4737
  handleLayout(ctx, layout, setCanRender);
4738
+ const SCROLL_LENGTH_RETRY_WINDOW_MS = 600;
4739
+ const now = Date.now();
4740
+ const didFinishInitialScroll = !!state.didFinishInitialScroll;
4741
+ if (didFinishInitialScroll && !state.initialScrollLastDidFinish) {
4742
+ state.initialScrollRetryWindowUntil = now + SCROLL_LENGTH_RETRY_WINDOW_MS;
4743
+ }
4744
+ state.initialScrollLastDidFinish = didFinishInitialScroll;
4745
+ const previousScrollLength = state.initialScrollRetryLastLength;
4746
+ const currentScrollLength = state.scrollLength;
4747
+ const didScrollLengthChange = previousScrollLength === void 0 || Math.abs(currentScrollLength - previousScrollLength) > 1;
4748
+ if (didScrollLengthChange) {
4749
+ state.initialScrollRetryLastLength = currentScrollLength;
4750
+ }
4751
+ if (didFinishInitialScroll && didScrollLengthChange && now <= state.initialScrollRetryWindowUntil && !state.initialScrollLastTargetUsesOffset && ((_a4 = state.initialScrollLastTarget) == null ? void 0 : _a4.index) !== void 0) {
4752
+ doInitialScroll({ allowPostFinishRetry: true });
4753
+ return;
4754
+ }
4755
+ doInitialScroll();
4262
4756
  }, []);
4263
4757
  const { onLayout } = useOnLayoutSync({
4264
4758
  onLayoutChange,
@@ -4375,7 +4869,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4375
4869
  onScroll: onScrollHandler,
4376
4870
  recycleItems,
4377
4871
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2.cloneElement(refreshControlElement, {
4378
- progressViewOffset: ((_d = refreshControlElement.props.progressViewOffset) != null ? _d : 0) + stylePaddingTopState
4872
+ progressViewOffset: ((_g = refreshControlElement.props.progressViewOffset) != null ? _g : 0) + stylePaddingTopState
4379
4873
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React2.createElement(
4380
4874
  RefreshControl,
4381
4875
  {
@@ -4386,7 +4880,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4386
4880
  ),
4387
4881
  refScrollView: combinedRef,
4388
4882
  renderScrollComponent,
4389
- scrollAdjustHandler: (_e = refState.current) == null ? void 0 : _e.scrollAdjustHandler,
4883
+ scrollAdjustHandler: (_h = refState.current) == null ? void 0 : _h.scrollAdjustHandler,
4390
4884
  scrollEventThrottle: 0,
4391
4885
  snapToIndices,
4392
4886
  stickyHeaderIndices,