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

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.js CHANGED
@@ -847,7 +847,7 @@ var Containers = typedMemo(function Containers2({
847
847
  return !((_a3 = ctx.state) == null ? void 0 : _a3.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
848
848
  }
849
849
  });
850
- const animOpacity = waitForInitialLayout && !IsNewArchitecture ? useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 }) : void 0;
850
+ const animOpacity = waitForInitialLayout ? useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 }) : void 0;
851
851
  const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
852
852
  const containers = [];
853
853
  for (let i = 0; i < numContainers; i++) {
@@ -1130,6 +1130,7 @@ function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize) {
1130
1130
 
1131
1131
  // src/core/calculateOffsetWithOffsetPosition.ts
1132
1132
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1133
+ var _a3;
1133
1134
  const state = ctx.state;
1134
1135
  const { index, viewOffset, viewPosition } = params;
1135
1136
  let offset = offsetParam;
@@ -1143,10 +1144,16 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1143
1144
  }
1144
1145
  }
1145
1146
  if (viewPosition !== void 0 && index !== void 0) {
1146
- const itemSize = getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1147
+ const dataLength = state.props.data.length;
1148
+ if (dataLength === 0) {
1149
+ return offset;
1150
+ }
1151
+ const isOutOfBounds = index < 0 || index >= dataLength;
1152
+ const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
1153
+ const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1147
1154
  const trailingInset = getContentInsetEnd(state);
1148
1155
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1149
- if (index === state.props.data.length - 1) {
1156
+ if (!isOutOfBounds && index === state.props.data.length - 1) {
1150
1157
  const footerSize = peek$(ctx, "footerSize") || 0;
1151
1158
  offset += footerSize;
1152
1159
  }
@@ -1348,7 +1355,9 @@ function finishScrollTo(ctx) {
1348
1355
  const scrollingTo = state.scrollingTo;
1349
1356
  state.scrollHistory.length = 0;
1350
1357
  state.initialScroll = void 0;
1358
+ state.initialScrollUsesOffset = false;
1351
1359
  state.initialAnchor = void 0;
1360
+ state.initialNativeScrollWatchdog = void 0;
1352
1361
  state.scrollingTo = void 0;
1353
1362
  if (state.pendingTotalSize !== void 0) {
1354
1363
  addTotalSize(ctx, null, state.pendingTotalSize);
@@ -1366,21 +1375,21 @@ function finishScrollTo(ctx) {
1366
1375
  }
1367
1376
 
1368
1377
  // src/core/checkFinishedScroll.ts
1378
+ var INITIAL_SCROLL_MIN_TARGET_OFFSET = 1;
1379
+ var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
1380
+ var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
1369
1381
  function checkFinishedScroll(ctx) {
1370
1382
  ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
1371
1383
  }
1372
1384
  function checkFinishedScrollFrame(ctx) {
1385
+ var _a3;
1373
1386
  const scrollingTo = ctx.state.scrollingTo;
1374
1387
  if (scrollingTo) {
1375
1388
  const { state } = ctx;
1376
1389
  state.animFrameCheckFinishedScroll = void 0;
1377
1390
  const scroll = state.scrollPending;
1378
1391
  const adjust = state.scrollAdjustHandler.getAdjust();
1379
- const clampedTargetOffset = clampScrollOffset(
1380
- ctx,
1381
- scrollingTo.offset - (scrollingTo.viewOffset || 0),
1382
- scrollingTo
1383
- );
1392
+ const clampedTargetOffset = (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
1384
1393
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
1385
1394
  const diff1 = Math.abs(scroll - clampedTargetOffset);
1386
1395
  const diff2 = Math.abs(diff1 - adjust);
@@ -1394,17 +1403,33 @@ function checkFinishedScrollFrame(ctx) {
1394
1403
  function checkFinishedScrollFallback(ctx) {
1395
1404
  const state = ctx.state;
1396
1405
  const scrollingTo = state.scrollingTo;
1397
- const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || !state.didContainersLayout;
1406
+ const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1407
+ const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget || !state.didContainersLayout;
1398
1408
  state.timeoutCheckFinishedScrollFallback = setTimeout(
1399
1409
  () => {
1400
1410
  let numChecks = 0;
1401
1411
  const checkHasScrolled = () => {
1412
+ var _a3, _b;
1402
1413
  state.timeoutCheckFinishedScrollFallback = void 0;
1403
1414
  const isStillScrollingTo = state.scrollingTo;
1404
1415
  if (isStillScrollingTo) {
1405
1416
  numChecks++;
1406
- if (state.hasScrolled || numChecks > 5) {
1417
+ const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
1418
+ const maxChecks = isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
1419
+ const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
1420
+ if (shouldFinishZeroTarget || state.hasScrolled || numChecks > maxChecks) {
1407
1421
  finishScrollTo(ctx);
1422
+ } else if (isNativeInitialPending && numChecks <= maxChecks) {
1423
+ const targetOffset = (_b = (_a3 = state.initialNativeScrollWatchdog) == null ? void 0 : _a3.targetOffset) != null ? _b : state.scrollPending;
1424
+ const scroller = state.refScroller.current;
1425
+ if (scroller) {
1426
+ scroller.scrollTo({
1427
+ animated: false,
1428
+ x: state.props.horizontal ? targetOffset : 0,
1429
+ y: state.props.horizontal ? 0 : targetOffset
1430
+ });
1431
+ }
1432
+ state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, 100);
1408
1433
  } else {
1409
1434
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, 100);
1410
1435
  }
@@ -1415,6 +1440,14 @@ function checkFinishedScrollFallback(ctx) {
1415
1440
  slowTimeout ? 500 : 100
1416
1441
  );
1417
1442
  }
1443
+ function isNativeInitialNonZeroTarget(state) {
1444
+ return !state.didFinishInitialScroll && !!state.initialNativeScrollWatchdog && state.initialNativeScrollWatchdog.targetOffset > INITIAL_SCROLL_MIN_TARGET_OFFSET;
1445
+ }
1446
+ function shouldFinishInitialZeroTargetScroll(ctx) {
1447
+ var _a3;
1448
+ const { state } = ctx;
1449
+ 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;
1450
+ }
1418
1451
 
1419
1452
  // src/core/doScrollTo.native.ts
1420
1453
  function doScrollTo(ctx, params) {
@@ -1438,7 +1471,9 @@ function doScrollTo(ctx, params) {
1438
1471
  }
1439
1472
 
1440
1473
  // src/core/scrollTo.ts
1474
+ var WATCHDOG_OFFSET_EPSILON = 1;
1441
1475
  function scrollTo(ctx, params) {
1476
+ var _a3, _b;
1442
1477
  const state = ctx.state;
1443
1478
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
1444
1479
  const { animated, isInitialScroll, offset: scrollTargetOffset, precomputedWithViewOffset } = scrollTarget;
@@ -1455,9 +1490,23 @@ function scrollTo(ctx, params) {
1455
1490
  offset = clampScrollOffset(ctx, offset, scrollTarget);
1456
1491
  state.scrollHistory.length = 0;
1457
1492
  if (!noScrollingTo) {
1458
- state.scrollingTo = scrollTarget;
1493
+ state.scrollingTo = {
1494
+ ...scrollTarget,
1495
+ targetOffset: offset
1496
+ };
1459
1497
  }
1460
1498
  state.scrollPending = offset;
1499
+ const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!state.initialNativeScrollWatchdog) && offset > WATCHDOG_OFFSET_EPSILON;
1500
+ const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!state.initialNativeScrollWatchdog && offset <= WATCHDOG_OFFSET_EPSILON;
1501
+ if (shouldWatchInitialNativeScroll) {
1502
+ state.hasScrolled = false;
1503
+ state.initialNativeScrollWatchdog = {
1504
+ startScroll: (_b = (_a3 = state.initialNativeScrollWatchdog) == null ? void 0 : _a3.startScroll) != null ? _b : state.scroll,
1505
+ targetOffset: offset
1506
+ };
1507
+ } else if (shouldClearInitialNativeScrollWatchdog) {
1508
+ state.initialNativeScrollWatchdog = void 0;
1509
+ }
1461
1510
  if (forceScroll || !isInitialScroll || Platform2.OS === "android") {
1462
1511
  doScrollTo(ctx, { animated, horizontal, offset });
1463
1512
  } else {
@@ -1465,178 +1514,52 @@ function scrollTo(ctx, params) {
1465
1514
  }
1466
1515
  }
1467
1516
 
1468
- // src/platform/flushSync.native.ts
1469
- var flushSync = (fn) => {
1470
- fn();
1471
- };
1472
-
1473
- // src/core/updateScroll.ts
1474
- function updateScroll(ctx, newScroll, forceUpdate) {
1517
+ // src/core/doMaintainScrollAtEnd.ts
1518
+ function doMaintainScrollAtEnd(ctx) {
1475
1519
  const state = ctx.state;
1476
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1477
- const prevScroll = state.scroll;
1478
- state.hasScrolled = true;
1479
- state.lastBatchingAction = Date.now();
1480
- const currentTime = Date.now();
1481
- const adjust = scrollAdjustHandler.getAdjust();
1482
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1483
- if (adjustChanged) {
1484
- scrollHistory.length = 0;
1485
- }
1486
- state.lastScrollAdjustForHistory = adjust;
1487
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1488
- if (!adjustChanged) {
1489
- scrollHistory.push({ scroll: newScroll, time: currentTime });
1490
- }
1491
- }
1492
- if (scrollHistory.length > 5) {
1493
- scrollHistory.shift();
1520
+ const {
1521
+ didContainersLayout,
1522
+ isAtEnd,
1523
+ pendingNativeMVCPAdjust,
1524
+ refScroller,
1525
+ props: { maintainScrollAtEnd }
1526
+ } = state;
1527
+ const shouldMaintainScrollAtEnd = !!(isAtEnd && maintainScrollAtEnd && didContainersLayout);
1528
+ if (pendingNativeMVCPAdjust) {
1529
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1530
+ return false;
1494
1531
  }
1495
- if (ignoreScrollFromMVCP && !scrollingTo) {
1496
- const { lt, gt } = ignoreScrollFromMVCP;
1497
- if (lt && newScroll < lt || gt && newScroll > gt) {
1498
- state.ignoreScrollFromMVCPIgnored = true;
1499
- return;
1532
+ state.pendingMaintainScrollAtEnd = false;
1533
+ if (shouldMaintainScrollAtEnd) {
1534
+ const contentSize = getContentSize(ctx);
1535
+ if (contentSize < state.scrollLength) {
1536
+ state.scroll = 0;
1500
1537
  }
1501
- }
1502
- state.scrollPrev = prevScroll;
1503
- state.scrollPrevTime = state.scrollTime;
1504
- state.scroll = newScroll;
1505
- state.scrollTime = currentTime;
1506
- const scrollDelta = Math.abs(newScroll - prevScroll);
1507
- const scrollLength = state.scrollLength;
1508
- const lastCalculated = state.scrollLastCalculate;
1509
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1510
- const shouldUpdate = useAggressiveItemRecalculation || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1511
- if (shouldUpdate) {
1512
- state.scrollLastCalculate = state.scroll;
1513
- state.ignoreScrollFromMVCPIgnored = false;
1514
- state.lastScrollDelta = scrollDelta;
1515
- const runCalculateItems = () => {
1538
+ requestAnimationFrame(() => {
1516
1539
  var _a3;
1517
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { doMVCP: scrollingTo !== void 0 });
1518
- checkThresholds(ctx);
1519
- };
1520
- if (Platform2.OS === "web" && scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
1521
- flushSync(runCalculateItems);
1522
- } else {
1523
- runCalculateItems();
1524
- }
1525
- state.dataChangeNeedsScrollUpdate = false;
1526
- state.lastScrollDelta = 0;
1527
- }
1528
- }
1529
-
1530
- // src/utils/requestAdjust.ts
1531
- function requestAdjust(ctx, positionDiff, dataChanged) {
1532
- const state = ctx.state;
1533
- if (Math.abs(positionDiff) > 0.1) {
1534
- const needsScrollWorkaround = Platform2.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1535
- const doit = () => {
1536
- if (needsScrollWorkaround) {
1537
- scrollTo(ctx, {
1538
- noScrollingTo: true,
1539
- offset: state.scroll
1540
+ if (state.isAtEnd) {
1541
+ state.maintainingScrollAtEnd = true;
1542
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
1543
+ animated: maintainScrollAtEnd.animated
1540
1544
  });
1541
- } else {
1542
- state.scrollAdjustHandler.requestAdjust(positionDiff);
1543
- if (state.adjustingFromInitialMount) {
1544
- state.adjustingFromInitialMount--;
1545
- }
1546
- }
1547
- };
1548
- state.scroll += positionDiff;
1549
- state.scrollForNextCalculateItemsInView = void 0;
1550
- const readyToRender = peek$(ctx, "readyToRender");
1551
- if (readyToRender) {
1552
- doit();
1553
- if (Platform2.OS !== "web") {
1554
- const threshold = state.scroll - positionDiff / 2;
1555
- if (!state.ignoreScrollFromMVCP) {
1556
- state.ignoreScrollFromMVCP = {};
1557
- }
1558
- if (positionDiff > 0) {
1559
- state.ignoreScrollFromMVCP.lt = threshold;
1560
- } else {
1561
- state.ignoreScrollFromMVCP.gt = threshold;
1562
- }
1563
- if (state.ignoreScrollFromMVCPTimeout) {
1564
- clearTimeout(state.ignoreScrollFromMVCPTimeout);
1565
- }
1566
- const delay = needsScrollWorkaround ? 250 : 100;
1567
- state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
1568
- state.ignoreScrollFromMVCP = void 0;
1569
- const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
1570
- if (shouldForceUpdate) {
1571
- state.ignoreScrollFromMVCPIgnored = false;
1572
- state.scrollPending = state.scroll;
1573
- updateScroll(ctx, state.scroll, true);
1574
- }
1575
- }, delay);
1545
+ setTimeout(
1546
+ () => {
1547
+ state.maintainingScrollAtEnd = false;
1548
+ },
1549
+ maintainScrollAtEnd.animated ? 500 : 0
1550
+ );
1576
1551
  }
1577
- } else {
1578
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1579
- requestAnimationFrame(doit);
1580
- }
1581
- }
1582
- }
1583
-
1584
- // src/core/ensureInitialAnchor.ts
1585
- var INITIAL_ANCHOR_TOLERANCE = 0.5;
1586
- var INITIAL_ANCHOR_MAX_ATTEMPTS = 4;
1587
- var INITIAL_ANCHOR_SETTLED_TICKS = 2;
1588
- function ensureInitialAnchor(ctx) {
1589
- var _a3, _b, _c, _d, _e;
1590
- const state = ctx.state;
1591
- const { initialAnchor, didContainersLayout, scroll, scrollLength } = state;
1592
- const anchor = initialAnchor;
1593
- const item = state.props.data[anchor.index];
1594
- if (!didContainersLayout) {
1595
- return;
1596
- }
1597
- const id = getId(state, anchor.index);
1598
- if (state.positions[anchor.index] === void 0) {
1599
- return;
1600
- }
1601
- const size = getItemSize(ctx, id, anchor.index, item, true, true);
1602
- if (size === void 0) {
1603
- return;
1604
- }
1605
- const availableSpace = Math.max(0, scrollLength - size);
1606
- const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1607
- const desiredOffset = calculateOffsetForIndex(ctx, anchor.index) + topOffsetAdjustment - ((_a3 = anchor.viewOffset) != null ? _a3 : 0) - ((_b = anchor.viewPosition) != null ? _b : 0) * availableSpace;
1608
- const clampedDesiredOffset = clampScrollOffset(ctx, desiredOffset, anchor);
1609
- const delta = clampedDesiredOffset - scroll;
1610
- if (Math.abs(delta) <= INITIAL_ANCHOR_TOLERANCE) {
1611
- const settledTicks = ((_c = anchor.settledTicks) != null ? _c : 0) + 1;
1612
- if (settledTicks >= INITIAL_ANCHOR_SETTLED_TICKS) {
1613
- state.initialAnchor = void 0;
1614
- } else {
1615
- anchor.settledTicks = settledTicks;
1616
- }
1617
- return;
1618
- }
1619
- if (((_d = anchor.attempts) != null ? _d : 0) >= INITIAL_ANCHOR_MAX_ATTEMPTS) {
1620
- state.initialAnchor = void 0;
1621
- return;
1622
- }
1623
- const lastDelta = anchor.lastDelta;
1624
- if (lastDelta !== void 0 && Math.abs(delta) >= Math.abs(lastDelta)) {
1625
- state.initialAnchor = void 0;
1626
- return;
1552
+ });
1553
+ return true;
1627
1554
  }
1628
- Object.assign(anchor, {
1629
- attempts: ((_e = anchor.attempts) != null ? _e : 0) + 1,
1630
- lastDelta: delta,
1631
- settledTicks: 0
1632
- });
1633
- requestAdjust(ctx, delta);
1555
+ return false;
1634
1556
  }
1635
1557
 
1636
1558
  // src/core/mvcp.ts
1637
1559
  var MVCP_POSITION_EPSILON = 0.1;
1638
1560
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
1639
1561
  var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
1562
+ var NATIVE_END_CLAMP_EPSILON = 1;
1640
1563
  function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
1641
1564
  if (!enableMVCPAnchorLock) {
1642
1565
  state.mvcpAnchorLock = void 0;
@@ -1676,6 +1599,100 @@ function updateAnchorLock(state, params) {
1676
1599
  };
1677
1600
  }
1678
1601
  }
1602
+ function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
1603
+ if (!dataChanged || Platform2.OS === "web" || !state.props.maintainVisibleContentPosition.data || scrollTarget !== void 0 || positionDiff >= -MVCP_POSITION_EPSILON) {
1604
+ return false;
1605
+ }
1606
+ const distanceFromEnd = prevTotalSize - prevScroll - state.scrollLength;
1607
+ return distanceFromEnd < Math.abs(positionDiff) - MVCP_POSITION_EPSILON;
1608
+ }
1609
+ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
1610
+ if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
1611
+ return 0;
1612
+ }
1613
+ const maxScroll = Math.max(0, totalSize - state.scrollLength);
1614
+ const clampDelta = maxScroll - state.scroll;
1615
+ if (unresolvedAmount < 0) {
1616
+ return Math.max(unresolvedAmount, Math.min(0, clampDelta));
1617
+ }
1618
+ if (unresolvedAmount > 0) {
1619
+ return Math.min(unresolvedAmount, Math.max(0, clampDelta));
1620
+ }
1621
+ return 0;
1622
+ }
1623
+ function getProgressTowardAmount(targetDelta, nativeDelta) {
1624
+ return targetDelta < 0 ? -nativeDelta : nativeDelta;
1625
+ }
1626
+ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
1627
+ const state = ctx.state;
1628
+ state.pendingNativeMVCPAdjust = void 0;
1629
+ const remaining = remainingAfterManual - nativeDelta;
1630
+ if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
1631
+ requestAdjust(ctx, remaining, true);
1632
+ }
1633
+ }
1634
+ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
1635
+ const state = ctx.state;
1636
+ const pending = state.pendingNativeMVCPAdjust;
1637
+ if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
1638
+ return;
1639
+ }
1640
+ const totalSize = getContentSize(ctx);
1641
+ const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
1642
+ if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
1643
+ return;
1644
+ }
1645
+ const manualDesired = pending.amount - predictedNativeClamp;
1646
+ if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
1647
+ return;
1648
+ }
1649
+ pending.manualApplied = manualDesired;
1650
+ requestAdjust(ctx, manualDesired, true);
1651
+ pending.furthestProgressTowardAmount = 0;
1652
+ }
1653
+ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
1654
+ const state = ctx.state;
1655
+ const pending = state.pendingNativeMVCPAdjust;
1656
+ if (!pending) {
1657
+ return false;
1658
+ }
1659
+ const remainingAfterManual = pending.amount - pending.manualApplied;
1660
+ const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
1661
+ const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
1662
+ const progressTowardAmount = getProgressTowardAmount(remainingAfterManual, nativeDelta);
1663
+ if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
1664
+ state.pendingNativeMVCPAdjust = void 0;
1665
+ return true;
1666
+ }
1667
+ if (isWrongDirection) {
1668
+ state.pendingNativeMVCPAdjust = void 0;
1669
+ return false;
1670
+ }
1671
+ if (progressTowardAmount + MVCP_POSITION_EPSILON >= Math.abs(remainingAfterManual)) {
1672
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
1673
+ return true;
1674
+ }
1675
+ const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
1676
+ const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
1677
+ const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
1678
+ if (isAtExpectedNativeClamp) {
1679
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
1680
+ return true;
1681
+ }
1682
+ if (state.pendingMaintainScrollAtEnd && state.isAtEnd && progressTowardAmount > MVCP_POSITION_EPSILON) {
1683
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
1684
+ return true;
1685
+ }
1686
+ if (progressTowardAmount > pending.furthestProgressTowardAmount + MVCP_POSITION_EPSILON) {
1687
+ pending.furthestProgressTowardAmount = progressTowardAmount;
1688
+ return false;
1689
+ }
1690
+ if (pending.furthestProgressTowardAmount > MVCP_POSITION_EPSILON && progressTowardAmount < pending.furthestProgressTowardAmount - MVCP_POSITION_EPSILON) {
1691
+ state.pendingNativeMVCPAdjust = void 0;
1692
+ return false;
1693
+ }
1694
+ return false;
1695
+ }
1679
1696
  function prepareMVCP(ctx, dataChanged) {
1680
1697
  const state = ctx.state;
1681
1698
  const { idsInView, positions, props } = state;
@@ -1695,7 +1712,13 @@ function prepareMVCP(ctx, dataChanged) {
1695
1712
  const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
1696
1713
  const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
1697
1714
  const indexByKey = state.indexByKey;
1715
+ const prevScroll = state.scroll;
1716
+ const prevTotalSize = getContentSize(ctx);
1698
1717
  if (shouldMVCP) {
1718
+ if (!isWeb && state.pendingNativeMVCPAdjust && scrollTarget === void 0) {
1719
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
1720
+ return void 0;
1721
+ }
1699
1722
  if (anchorLock && scrollTarget === void 0) {
1700
1723
  targetId = anchorLock.id;
1701
1724
  prevPosition = anchorLock.position;
@@ -1786,29 +1809,217 @@ function prepareMVCP(ctx, dataChanged) {
1786
1809
  anchorPositionForLock = newPosition;
1787
1810
  }
1788
1811
  }
1789
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1790
- const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1791
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1792
- if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1793
- const diff = newSize - prevSize;
1794
- if (diff !== 0) {
1795
- positionDiff += diff * scrollingToViewPosition;
1796
- scrollingTo.itemSize = newSize;
1797
- }
1812
+ if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1813
+ const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1814
+ const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1815
+ if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1816
+ const diff = newSize - prevSize;
1817
+ if (diff !== 0) {
1818
+ positionDiff += diff * scrollingToViewPosition;
1819
+ scrollingTo.itemSize = newSize;
1820
+ }
1821
+ }
1822
+ }
1823
+ updateAnchorLock(state, {
1824
+ anchorId: anchorIdForLock,
1825
+ anchorPosition: anchorPositionForLock,
1826
+ dataChanged,
1827
+ now,
1828
+ positionDiff
1829
+ });
1830
+ if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
1831
+ state.pendingNativeMVCPAdjust = {
1832
+ amount: positionDiff,
1833
+ furthestProgressTowardAmount: 0,
1834
+ manualApplied: 0,
1835
+ startScroll: prevScroll
1836
+ };
1837
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
1838
+ return;
1839
+ }
1840
+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1841
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
1842
+ }
1843
+ };
1844
+ }
1845
+ }
1846
+
1847
+ // src/platform/flushSync.native.ts
1848
+ var flushSync = (fn) => {
1849
+ fn();
1850
+ };
1851
+
1852
+ // src/core/updateScroll.ts
1853
+ function updateScroll(ctx, newScroll, forceUpdate) {
1854
+ var _a3;
1855
+ const state = ctx.state;
1856
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1857
+ const prevScroll = state.scroll;
1858
+ state.hasScrolled = true;
1859
+ state.lastBatchingAction = Date.now();
1860
+ const currentTime = Date.now();
1861
+ const adjust = scrollAdjustHandler.getAdjust();
1862
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1863
+ if (adjustChanged) {
1864
+ scrollHistory.length = 0;
1865
+ }
1866
+ state.lastScrollAdjustForHistory = adjust;
1867
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1868
+ if (!adjustChanged) {
1869
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
1870
+ }
1871
+ }
1872
+ if (scrollHistory.length > 5) {
1873
+ scrollHistory.shift();
1874
+ }
1875
+ if (ignoreScrollFromMVCP && !scrollingTo) {
1876
+ const { lt, gt } = ignoreScrollFromMVCP;
1877
+ if (lt && newScroll < lt || gt && newScroll > gt) {
1878
+ state.ignoreScrollFromMVCPIgnored = true;
1879
+ return;
1880
+ }
1881
+ }
1882
+ state.scrollPrev = prevScroll;
1883
+ state.scrollPrevTime = state.scrollTime;
1884
+ state.scroll = newScroll;
1885
+ state.scrollTime = currentTime;
1886
+ const scrollDelta = Math.abs(newScroll - prevScroll);
1887
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
1888
+ const scrollLength = state.scrollLength;
1889
+ const lastCalculated = state.scrollLastCalculate;
1890
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1891
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1892
+ if (shouldUpdate) {
1893
+ state.scrollLastCalculate = state.scroll;
1894
+ state.ignoreScrollFromMVCPIgnored = false;
1895
+ state.lastScrollDelta = scrollDelta;
1896
+ const runCalculateItems = () => {
1897
+ var _a4;
1898
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
1899
+ checkThresholds(ctx);
1900
+ };
1901
+ if (Platform2.OS === "web" && scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
1902
+ flushSync(runCalculateItems);
1903
+ } else {
1904
+ runCalculateItems();
1905
+ }
1906
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
1907
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
1908
+ state.pendingMaintainScrollAtEnd = false;
1909
+ doMaintainScrollAtEnd(ctx);
1910
+ }
1911
+ state.dataChangeNeedsScrollUpdate = false;
1912
+ state.lastScrollDelta = 0;
1913
+ }
1914
+ }
1915
+
1916
+ // src/utils/requestAdjust.ts
1917
+ function requestAdjust(ctx, positionDiff, dataChanged) {
1918
+ const state = ctx.state;
1919
+ if (Math.abs(positionDiff) > 0.1) {
1920
+ const needsScrollWorkaround = Platform2.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
1921
+ const doit = () => {
1922
+ if (needsScrollWorkaround) {
1923
+ scrollTo(ctx, {
1924
+ noScrollingTo: true,
1925
+ offset: state.scroll
1926
+ });
1927
+ } else {
1928
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
1929
+ if (state.adjustingFromInitialMount) {
1930
+ state.adjustingFromInitialMount--;
1931
+ }
1932
+ }
1933
+ };
1934
+ state.scroll += positionDiff;
1935
+ state.scrollForNextCalculateItemsInView = void 0;
1936
+ const readyToRender = peek$(ctx, "readyToRender");
1937
+ if (readyToRender) {
1938
+ doit();
1939
+ if (Platform2.OS !== "web") {
1940
+ const threshold = state.scroll - positionDiff / 2;
1941
+ if (!state.ignoreScrollFromMVCP) {
1942
+ state.ignoreScrollFromMVCP = {};
1798
1943
  }
1944
+ if (positionDiff > 0) {
1945
+ state.ignoreScrollFromMVCP.lt = threshold;
1946
+ } else {
1947
+ state.ignoreScrollFromMVCP.gt = threshold;
1948
+ }
1949
+ if (state.ignoreScrollFromMVCPTimeout) {
1950
+ clearTimeout(state.ignoreScrollFromMVCPTimeout);
1951
+ }
1952
+ const delay = needsScrollWorkaround ? 250 : 100;
1953
+ state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
1954
+ state.ignoreScrollFromMVCP = void 0;
1955
+ const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
1956
+ if (shouldForceUpdate) {
1957
+ state.ignoreScrollFromMVCPIgnored = false;
1958
+ state.scrollPending = state.scroll;
1959
+ updateScroll(ctx, state.scroll, true);
1960
+ }
1961
+ }, delay);
1799
1962
  }
1800
- updateAnchorLock(state, {
1801
- anchorId: anchorIdForLock,
1802
- anchorPosition: anchorPositionForLock,
1803
- dataChanged,
1804
- now,
1805
- positionDiff
1806
- });
1807
- if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
1808
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
1809
- }
1810
- };
1963
+ } else {
1964
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
1965
+ requestAnimationFrame(doit);
1966
+ }
1967
+ }
1968
+ }
1969
+
1970
+ // src/core/ensureInitialAnchor.ts
1971
+ var INITIAL_ANCHOR_TOLERANCE = 0.5;
1972
+ var INITIAL_ANCHOR_MAX_ATTEMPTS = 4;
1973
+ var INITIAL_ANCHOR_SETTLED_TICKS = 2;
1974
+ function ensureInitialAnchor(ctx) {
1975
+ var _a3, _b, _c, _d, _e, _f;
1976
+ const state = ctx.state;
1977
+ const { initialAnchor, didContainersLayout, scroll, scrollLength } = state;
1978
+ const anchor = initialAnchor;
1979
+ if (state.initialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll)) {
1980
+ return;
1981
+ }
1982
+ const item = state.props.data[anchor.index];
1983
+ if (!didContainersLayout) {
1984
+ return;
1985
+ }
1986
+ const id = getId(state, anchor.index);
1987
+ if (state.positions[anchor.index] === void 0) {
1988
+ return;
1989
+ }
1990
+ const size = getItemSize(ctx, id, anchor.index, item, true, true);
1991
+ if (size === void 0) {
1992
+ return;
1993
+ }
1994
+ const availableSpace = Math.max(0, scrollLength - size);
1995
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1996
+ const desiredOffset = calculateOffsetForIndex(ctx, anchor.index) + topOffsetAdjustment - ((_b = anchor.viewOffset) != null ? _b : 0) - ((_c = anchor.viewPosition) != null ? _c : 0) * availableSpace;
1997
+ const clampedDesiredOffset = clampScrollOffset(ctx, desiredOffset, anchor);
1998
+ const delta = clampedDesiredOffset - scroll;
1999
+ if (Math.abs(delta) <= INITIAL_ANCHOR_TOLERANCE) {
2000
+ const settledTicks = ((_d = anchor.settledTicks) != null ? _d : 0) + 1;
2001
+ if (settledTicks >= INITIAL_ANCHOR_SETTLED_TICKS) {
2002
+ state.initialAnchor = void 0;
2003
+ } else {
2004
+ anchor.settledTicks = settledTicks;
2005
+ }
2006
+ return;
2007
+ }
2008
+ if (((_e = anchor.attempts) != null ? _e : 0) >= INITIAL_ANCHOR_MAX_ATTEMPTS) {
2009
+ state.initialAnchor = void 0;
2010
+ return;
2011
+ }
2012
+ const lastDelta = anchor.lastDelta;
2013
+ if (lastDelta !== void 0 && Math.abs(delta) >= Math.abs(lastDelta)) {
2014
+ state.initialAnchor = void 0;
2015
+ return;
1811
2016
  }
2017
+ Object.assign(anchor, {
2018
+ attempts: ((_f = anchor.attempts) != null ? _f : 0) + 1,
2019
+ lastDelta: delta,
2020
+ settledTicks: 0
2021
+ });
2022
+ requestAdjust(ctx, delta);
1812
2023
  }
1813
2024
 
1814
2025
  // src/core/prepareColumnStartState.ts
@@ -2464,7 +2675,14 @@ function comparatorByDistance(a, b) {
2464
2675
  }
2465
2676
 
2466
2677
  // src/core/scrollToIndex.ts
2467
- function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPosition }) {
2678
+ function scrollToIndex(ctx, {
2679
+ index,
2680
+ viewOffset = 0,
2681
+ animated = true,
2682
+ forceScroll,
2683
+ isInitialScroll,
2684
+ viewPosition
2685
+ }) {
2468
2686
  const state = ctx.state;
2469
2687
  const { data } = state.props;
2470
2688
  if (index >= data.length) {
@@ -2482,7 +2700,9 @@ function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPositi
2482
2700
  const itemSize = getItemSize(ctx, targetId, index, state.props.data[index]);
2483
2701
  scrollTo(ctx, {
2484
2702
  animated,
2703
+ forceScroll,
2485
2704
  index,
2705
+ isInitialScroll,
2486
2706
  itemSize,
2487
2707
  offset: firstIndexOffset,
2488
2708
  viewOffset,
@@ -2490,15 +2710,58 @@ function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPositi
2490
2710
  });
2491
2711
  }
2492
2712
 
2713
+ // src/utils/performInitialScroll.ts
2714
+ function performInitialScroll(ctx, params) {
2715
+ var _a3;
2716
+ const { forceScroll, initialScrollUsesOffset, resolvedOffset, target } = params;
2717
+ if (initialScrollUsesOffset || resolvedOffset !== void 0) {
2718
+ scrollTo(ctx, {
2719
+ animated: false,
2720
+ forceScroll,
2721
+ index: initialScrollUsesOffset ? void 0 : target.index,
2722
+ isInitialScroll: true,
2723
+ offset: (_a3 = resolvedOffset != null ? resolvedOffset : target.contentOffset) != null ? _a3 : 0,
2724
+ precomputedWithViewOffset: resolvedOffset !== void 0
2725
+ });
2726
+ return;
2727
+ }
2728
+ if (target.index === void 0) {
2729
+ return;
2730
+ }
2731
+ scrollToIndex(ctx, {
2732
+ ...target,
2733
+ animated: false,
2734
+ forceScroll,
2735
+ isInitialScroll: true
2736
+ });
2737
+ }
2738
+
2493
2739
  // src/utils/setDidLayout.ts
2494
2740
  function setDidLayout(ctx) {
2495
2741
  const state = ctx.state;
2496
2742
  const { initialScroll } = state;
2497
2743
  state.queuedInitialLayout = true;
2498
2744
  checkAtBottom(ctx);
2499
- if ((initialScroll == null ? void 0 : initialScroll.index) !== void 0) {
2500
- const target = initialScroll;
2501
- const runScroll = () => scrollToIndex(ctx, { ...target, animated: false });
2745
+ if (initialScroll) {
2746
+ const runScroll = () => {
2747
+ var _a3, _b;
2748
+ const target = state.initialScroll;
2749
+ if (!target) {
2750
+ return;
2751
+ }
2752
+ const activeInitialTargetOffset = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? (_b = state.scrollingTo.targetOffset) != null ? _b : state.scrollingTo.offset : void 0;
2753
+ const desiredInitialTargetOffset = state.initialScrollUsesOffset ? target.contentOffset : activeInitialTargetOffset;
2754
+ const isAlreadyAtDesiredInitialTarget = desiredInitialTargetOffset !== void 0 && Math.abs(state.scroll - desiredInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - desiredInitialTargetOffset) <= 1;
2755
+ if (!isAlreadyAtDesiredInitialTarget) {
2756
+ performInitialScroll(ctx, {
2757
+ forceScroll: true,
2758
+ initialScrollUsesOffset: state.initialScrollUsesOffset,
2759
+ // Offset-based initial scrolls do not need item lookup, so they can run even before data exists.
2760
+ // Re-run on the next frame to pick up measured viewport size without waiting for index resolution.
2761
+ target
2762
+ });
2763
+ }
2764
+ };
2502
2765
  runScroll();
2503
2766
  requestAnimationFrame(runScroll);
2504
2767
  }
@@ -2576,7 +2839,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
2576
2839
  function calculateItemsInView(ctx, params = {}) {
2577
2840
  const state = ctx.state;
2578
2841
  batchedUpdates(() => {
2579
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2842
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
2580
2843
  const {
2581
2844
  columns,
2582
2845
  columnSpans,
@@ -2615,7 +2878,7 @@ function calculateItemsInView(ctx, params = {}) {
2615
2878
  }
2616
2879
  return;
2617
2880
  }
2618
- const totalSize = getContentSize(ctx);
2881
+ let totalSize = getContentSize(ctx);
2619
2882
  const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
2620
2883
  const numColumns = peek$(ctx, "numColumns");
2621
2884
  const speed = getScrollVelocity(state);
@@ -2623,14 +2886,14 @@ function calculateItemsInView(ctx, params = {}) {
2623
2886
  const { queuedInitialLayout } = state;
2624
2887
  let { scroll: scrollState } = state;
2625
2888
  if (!queuedInitialLayout && initialScroll) {
2626
- const updatedOffset = calculateOffsetWithOffsetPosition(
2889
+ const updatedOffset = state.initialScrollUsesOffset ? (_a3 = initialScroll.contentOffset) != null ? _a3 : 0 : calculateOffsetWithOffsetPosition(
2627
2890
  ctx,
2628
2891
  calculateOffsetForIndex(ctx, initialScroll.index),
2629
2892
  initialScroll
2630
2893
  );
2631
2894
  scrollState = updatedOffset;
2632
2895
  }
2633
- const scrollAdjustPending = (_a3 = peek$(ctx, "scrollAdjustPending")) != null ? _a3 : 0;
2896
+ const scrollAdjustPending = (_b = peek$(ctx, "scrollAdjustPending")) != null ? _b : 0;
2634
2897
  const scrollAdjustPad = scrollAdjustPending - topPad;
2635
2898
  let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
2636
2899
  if (scroll + scrollLength > totalSize) {
@@ -2675,13 +2938,14 @@ function calculateItemsInView(ctx, params = {}) {
2675
2938
  columns.length = 0;
2676
2939
  columnSpans.length = 0;
2677
2940
  }
2678
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_b = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _b : 0;
2941
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
2679
2942
  updateItemPositions(ctx, dataChanged, {
2680
2943
  doMVCP,
2681
2944
  forceFullUpdate: !!forceFullItemPositions,
2682
2945
  scrollBottomBuffered,
2683
2946
  startIndex
2684
2947
  });
2948
+ totalSize = getContentSize(ctx);
2685
2949
  if (minIndexSizeChanged !== void 0) {
2686
2950
  state.minIndexSizeChanged = void 0;
2687
2951
  }
@@ -2693,9 +2957,9 @@ function calculateItemsInView(ctx, params = {}) {
2693
2957
  let endBuffered = null;
2694
2958
  let loopStart = !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
2695
2959
  for (let i = loopStart; i >= 0; i--) {
2696
- const id = (_c = idCache[i]) != null ? _c : getId(state, i);
2960
+ const id = (_d = idCache[i]) != null ? _d : getId(state, i);
2697
2961
  const top = positions[i];
2698
- const size = (_d = sizes.get(id)) != null ? _d : getItemSize(ctx, id, i, data[i]);
2962
+ const size = (_e = sizes.get(id)) != null ? _e : getItemSize(ctx, id, i, data[i]);
2699
2963
  const bottom = top + size;
2700
2964
  if (bottom > scroll - scrollBufferTop) {
2701
2965
  loopStart = i;
@@ -2726,14 +2990,14 @@ function calculateItemsInView(ctx, params = {}) {
2726
2990
  let firstFullyOnScreenIndex;
2727
2991
  const dataLength = data.length;
2728
2992
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
2729
- const id = (_e = idCache[i]) != null ? _e : getId(state, i);
2730
- const size = (_f = sizes.get(id)) != null ? _f : getItemSize(ctx, id, i, data[i]);
2993
+ const id = (_f = idCache[i]) != null ? _f : getId(state, i);
2994
+ const size = (_g = sizes.get(id)) != null ? _g : getItemSize(ctx, id, i, data[i]);
2731
2995
  const top = positions[i];
2732
2996
  if (!foundEnd) {
2733
2997
  if (startNoBuffer === null && top + size > scroll) {
2734
2998
  startNoBuffer = i;
2735
2999
  }
2736
- if (firstFullyOnScreenIndex === void 0 && top >= scroll - 10) {
3000
+ if (firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
2737
3001
  firstFullyOnScreenIndex = i;
2738
3002
  }
2739
3003
  if (startBuffered === null && top + size > scrollTopBuffered) {
@@ -2763,9 +3027,12 @@ function calculateItemsInView(ctx, params = {}) {
2763
3027
  }
2764
3028
  }
2765
3029
  const idsInView = [];
2766
- for (let i = firstFullyOnScreenIndex; i <= endNoBuffer; i++) {
2767
- const id = (_g = idCache[i]) != null ? _g : getId(state, i);
2768
- idsInView.push(id);
3030
+ const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
3031
+ if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
3032
+ for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
3033
+ const id = (_h = idCache[i]) != null ? _h : getId(state, i);
3034
+ idsInView.push(id);
3035
+ }
2769
3036
  }
2770
3037
  Object.assign(state, {
2771
3038
  endBuffered,
@@ -2796,7 +3063,7 @@ function calculateItemsInView(ctx, params = {}) {
2796
3063
  const needNewContainers = [];
2797
3064
  const needNewContainersSet = /* @__PURE__ */ new Set();
2798
3065
  for (let i = startBuffered; i <= endBuffered; i++) {
2799
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
3066
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
2800
3067
  if (!containerItemKeys.has(id)) {
2801
3068
  needNewContainersSet.add(i);
2802
3069
  needNewContainers.push(i);
@@ -2805,7 +3072,7 @@ function calculateItemsInView(ctx, params = {}) {
2805
3072
  if (alwaysRenderArr.length > 0) {
2806
3073
  for (const index of alwaysRenderArr) {
2807
3074
  if (index < 0 || index >= dataLength) continue;
2808
- const id = (_i = idCache[index]) != null ? _i : getId(state, index);
3075
+ const id = (_j = idCache[index]) != null ? _j : getId(state, index);
2809
3076
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
2810
3077
  needNewContainersSet.add(index);
2811
3078
  needNewContainers.push(index);
@@ -2843,7 +3110,7 @@ function calculateItemsInView(ctx, params = {}) {
2843
3110
  for (let idx = 0; idx < needNewContainers.length; idx++) {
2844
3111
  const i = needNewContainers[idx];
2845
3112
  const containerIndex = availableContainers[idx];
2846
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
3113
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
2847
3114
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
2848
3115
  if (oldKey && oldKey !== id) {
2849
3116
  containerItemKeys.delete(oldKey);
@@ -2884,7 +3151,7 @@ function calculateItemsInView(ctx, params = {}) {
2884
3151
  if (alwaysRenderArr.length > 0) {
2885
3152
  for (const index of alwaysRenderArr) {
2886
3153
  if (index < 0 || index >= dataLength) continue;
2887
- const id = (_k = idCache[index]) != null ? _k : getId(state, index);
3154
+ const id = (_l = idCache[index]) != null ? _l : getId(state, index);
2888
3155
  const containerIndex = containerItemKeys.get(id);
2889
3156
  if (containerIndex !== void 0) {
2890
3157
  state.stickyContainerPool.add(containerIndex);
@@ -2995,40 +3262,6 @@ function checkActualChange(state, dataProp, previousData) {
2995
3262
  return false;
2996
3263
  }
2997
3264
 
2998
- // src/core/doMaintainScrollAtEnd.ts
2999
- function doMaintainScrollAtEnd(ctx, animated) {
3000
- const state = ctx.state;
3001
- const {
3002
- didContainersLayout,
3003
- isAtEnd,
3004
- refScroller,
3005
- props: { maintainScrollAtEnd }
3006
- } = state;
3007
- if (isAtEnd && maintainScrollAtEnd && didContainersLayout) {
3008
- const contentSize = getContentSize(ctx);
3009
- if (contentSize < state.scrollLength) {
3010
- state.scroll = 0;
3011
- }
3012
- requestAnimationFrame(() => {
3013
- var _a3;
3014
- if (state.isAtEnd) {
3015
- state.maintainingScrollAtEnd = true;
3016
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
3017
- animated
3018
- });
3019
- setTimeout(
3020
- () => {
3021
- state.maintainingScrollAtEnd = false;
3022
- },
3023
- 0
3024
- );
3025
- }
3026
- });
3027
- return true;
3028
- }
3029
- return false;
3030
- }
3031
-
3032
3265
  // src/utils/updateAveragesOnDataChange.ts
3033
3266
  function updateAveragesOnDataChange(state, oldData, newData) {
3034
3267
  var _a3;
@@ -3090,8 +3323,8 @@ function checkResetContainers(ctx, dataProp) {
3090
3323
  }
3091
3324
  const { maintainScrollAtEnd } = state.props;
3092
3325
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
3093
- const shouldMaintainScrollAtEnd = maintainScrollAtEnd === true || maintainScrollAtEnd.onDataChange;
3094
- const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx, false);
3326
+ const shouldMaintainScrollAtEnd = maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onDataChange;
3327
+ const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx);
3095
3328
  if (!didMaintainScrollAtEnd && previousData && dataProp.length > previousData.length) {
3096
3329
  state.isEndReached = false;
3097
3330
  }
@@ -3195,8 +3428,8 @@ function handleLayout(ctx, layoutParam, setCanRender) {
3195
3428
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
3196
3429
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
3197
3430
  }
3198
- if (maintainScrollAtEnd === true || maintainScrollAtEnd.onLayout) {
3199
- doMaintainScrollAtEnd(ctx, false);
3431
+ if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
3432
+ doMaintainScrollAtEnd(ctx);
3200
3433
  }
3201
3434
  checkThresholds(ctx);
3202
3435
  if (state) {
@@ -3213,6 +3446,12 @@ function handleLayout(ctx, layoutParam, setCanRender) {
3213
3446
  }
3214
3447
 
3215
3448
  // src/core/onScroll.ts
3449
+ var INITIAL_SCROLL_PROGRESS_EPSILON = 1;
3450
+ function didObserveInitialScrollProgress(newScroll, watchdog) {
3451
+ const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
3452
+ const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
3453
+ return nextDistance <= INITIAL_SCROLL_PROGRESS_EPSILON || nextDistance + INITIAL_SCROLL_PROGRESS_EPSILON < previousDistance;
3454
+ }
3216
3455
  function onScroll(ctx, event) {
3217
3456
  var _a3, _b, _c, _d;
3218
3457
  const state = ctx.state;
@@ -3250,7 +3489,16 @@ function onScroll(ctx, event) {
3250
3489
  }
3251
3490
  }
3252
3491
  state.scrollPending = newScroll;
3492
+ const initialNativeScrollWatchdog = state.initialNativeScrollWatchdog;
3493
+ const didInitialScrollProgress = !!initialNativeScrollWatchdog && didObserveInitialScrollProgress(newScroll, initialNativeScrollWatchdog);
3494
+ if (didInitialScrollProgress) {
3495
+ state.initialNativeScrollWatchdog = void 0;
3496
+ }
3253
3497
  updateScroll(ctx, newScroll, insetChanged);
3498
+ if (initialNativeScrollWatchdog && !didInitialScrollProgress) {
3499
+ state.hasScrolled = false;
3500
+ state.initialNativeScrollWatchdog = initialNativeScrollWatchdog;
3501
+ }
3254
3502
  if (state.scrollingTo) {
3255
3503
  checkFinishedScroll(ctx);
3256
3504
  }
@@ -3417,8 +3665,8 @@ function updateItemSize(ctx, itemKey, sizeObj) {
3417
3665
  runOrScheduleMVCPRecalculate(ctx);
3418
3666
  }
3419
3667
  if (shouldMaintainScrollAtEnd) {
3420
- if (maintainScrollAtEnd === true || maintainScrollAtEnd.onItemLayout) {
3421
- doMaintainScrollAtEnd(ctx, false);
3668
+ if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onItemLayout) {
3669
+ doMaintainScrollAtEnd(ctx);
3422
3670
  }
3423
3671
  }
3424
3672
  }
@@ -3794,6 +4042,34 @@ function getRenderedItem(ctx, key) {
3794
4042
  return { index, item: data[index], renderedItem };
3795
4043
  }
3796
4044
 
4045
+ // src/utils/normalizeMaintainScrollAtEnd.ts
4046
+ function normalizeMaintainScrollAtEndOn(on, hasExplicitOn) {
4047
+ var _a3, _b, _c;
4048
+ return {
4049
+ animated: false,
4050
+ onDataChange: hasExplicitOn ? (_a3 = on == null ? void 0 : on.dataChange) != null ? _a3 : false : true,
4051
+ onItemLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.itemLayout) != null ? _b : false : true,
4052
+ onLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.layout) != null ? _c : false : true
4053
+ };
4054
+ }
4055
+ function normalizeMaintainScrollAtEnd(value) {
4056
+ var _a3;
4057
+ if (!value) {
4058
+ return void 0;
4059
+ }
4060
+ if (value === true) {
4061
+ return {
4062
+ ...normalizeMaintainScrollAtEndOn(void 0, false),
4063
+ animated: false
4064
+ };
4065
+ }
4066
+ const normalizedTriggers = normalizeMaintainScrollAtEndOn(value.on, "on" in value);
4067
+ return {
4068
+ ...normalizedTriggers,
4069
+ animated: (_a3 = value.animated) != null ? _a3 : false
4070
+ };
4071
+ }
4072
+
3797
4073
  // src/utils/normalizeMaintainVisibleContentPosition.ts
3798
4074
  function normalizeMaintainVisibleContentPosition(value) {
3799
4075
  var _a3, _b;
@@ -3895,7 +4171,7 @@ var LegendList = typedMemo2(
3895
4171
  })
3896
4172
  );
3897
4173
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
3898
- var _a3, _b, _c, _d, _e;
4174
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
3899
4175
  const {
3900
4176
  alignItemsAtEnd = false,
3901
4177
  alwaysRender,
@@ -3981,16 +4257,24 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3981
4257
  const style = { ...StyleSheet.flatten(styleProp) };
3982
4258
  const stylePaddingTopState = extractPadding(style, contentContainerStyle, "Top");
3983
4259
  const stylePaddingBottomState = extractPadding(style, contentContainerStyle, "Bottom");
4260
+ const maintainScrollAtEndConfig = normalizeMaintainScrollAtEnd(maintainScrollAtEnd);
3984
4261
  const maintainVisibleContentPositionConfig = normalizeMaintainVisibleContentPosition(
3985
4262
  maintainVisibleContentPositionProp
3986
4263
  );
3987
- const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? {
3988
- index: initialScrollIndexProp.index || 0,
3989
- viewOffset: initialScrollIndexProp.viewOffset || (initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0),
3990
- viewPosition: initialScrollIndexProp.viewPosition || 0
4264
+ const hasInitialScrollIndex = initialScrollIndexProp !== void 0 && initialScrollIndexProp !== null;
4265
+ const hasInitialScrollOffset = initialScrollOffsetProp !== void 0 && initialScrollOffsetProp !== null;
4266
+ const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && hasInitialScrollOffset;
4267
+ const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
4268
+ index: (_a3 = initialScrollIndexProp.index) != null ? _a3 : 0,
4269
+ viewOffset: (_b = initialScrollIndexProp.viewOffset) != null ? _b : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0,
4270
+ viewPosition: (_c = initialScrollIndexProp.viewPosition) != null ? _c : 0
3991
4271
  } : {
3992
- index: initialScrollIndexProp || 0,
3993
- viewOffset: initialScrollOffsetProp || 0
4272
+ index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
4273
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
4274
+ } : initialScrollUsesOffsetOnly ? {
4275
+ contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
4276
+ index: 0,
4277
+ viewOffset: 0
3994
4278
  } : void 0;
3995
4279
  const [canRender, setCanRender] = React2__namespace.useState(!IsNewArchitecture);
3996
4280
  const ctx = useStateContext();
@@ -4005,8 +4289,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4005
4289
  }, [
4006
4290
  alwaysRender == null ? void 0 : alwaysRender.top,
4007
4291
  alwaysRender == null ? void 0 : alwaysRender.bottom,
4008
- (_a3 = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _a3.join(","),
4009
- (_b = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _b.join(","),
4292
+ (_d = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _d.join(","),
4293
+ (_e = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _e.join(","),
4010
4294
  dataProp,
4011
4295
  dataVersion,
4012
4296
  keyExtractor
@@ -4050,14 +4334,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4050
4334
  idCache: [],
4051
4335
  idsInView: [],
4052
4336
  indexByKey: /* @__PURE__ */ new Map(),
4053
- initialAnchor: (initialScrollProp == null ? void 0 : initialScrollProp.index) !== void 0 && (initialScrollProp == null ? void 0 : initialScrollProp.viewPosition) !== void 0 ? {
4337
+ initialAnchor: !initialScrollUsesOffsetOnly && (initialScrollProp == null ? void 0 : initialScrollProp.index) !== void 0 && (initialScrollProp == null ? void 0 : initialScrollProp.viewPosition) !== void 0 ? {
4054
4338
  attempts: 0,
4055
4339
  index: initialScrollProp.index,
4056
4340
  settledTicks: 0,
4057
- viewOffset: (_c = initialScrollProp.viewOffset) != null ? _c : 0,
4341
+ viewOffset: (_f = initialScrollProp.viewOffset) != null ? _f : 0,
4058
4342
  viewPosition: initialScrollProp.viewPosition
4059
4343
  } : void 0,
4344
+ initialNativeScrollWatchdog: void 0,
4060
4345
  initialScroll: initialScrollProp,
4346
+ initialScrollLastDidFinish: false,
4347
+ initialScrollLastTarget: initialScrollProp,
4348
+ initialScrollLastTargetUsesOffset: initialScrollUsesOffsetOnly,
4349
+ initialScrollPreviousDataLength: dataProp.length,
4350
+ initialScrollRetryLastLength: void 0,
4351
+ initialScrollRetryWindowUntil: 0,
4352
+ initialScrollUsesOffset: initialScrollUsesOffsetOnly,
4061
4353
  isAtEnd: false,
4062
4354
  isAtStart: false,
4063
4355
  isEndReached: null,
@@ -4070,6 +4362,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4070
4362
  minIndexSizeChanged: 0,
4071
4363
  nativeContentInset: void 0,
4072
4364
  nativeMarginTop: 0,
4365
+ pendingNativeMVCPAdjust: void 0,
4073
4366
  positions: [],
4074
4367
  props: {},
4075
4368
  queuedCalculateItemsInView: 0,
@@ -4107,7 +4400,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4107
4400
  const state = refState.current;
4108
4401
  const isFirstLocal = state.isFirst;
4109
4402
  state.didColumnsChange = numColumnsProp !== state.props.numColumns;
4110
- const didDataChangeLocal = state.props.dataVersion !== dataVersion || state.props.data !== dataProp && checkActualChange(state, dataProp, state.props.data);
4403
+ const didDataReferenceChangeLocal = state.props.data !== dataProp;
4404
+ const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
4405
+ const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkActualChange(state, dataProp, state.props.data);
4111
4406
  if (didDataChangeLocal) {
4112
4407
  state.dataChangeEpoch += 1;
4113
4408
  state.dataChangeNeedsScrollUpdate = true;
@@ -4133,7 +4428,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4133
4428
  initialContainerPoolRatio,
4134
4429
  itemsAreEqual,
4135
4430
  keyExtractor: useWrapIfItem(keyExtractor),
4136
- maintainScrollAtEnd,
4431
+ maintainScrollAtEnd: maintainScrollAtEndConfig,
4137
4432
  maintainScrollAtEndThreshold,
4138
4433
  maintainVisibleContentPosition: maintainVisibleContentPositionConfig,
4139
4434
  numColumns: numColumnsProp,
@@ -4189,16 +4484,83 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4189
4484
  );
4190
4485
  }
4191
4486
  const resolveInitialScrollOffset = React2.useCallback((initialScroll) => {
4487
+ var _a4;
4488
+ if (state.initialScrollUsesOffset) {
4489
+ return clampScrollOffset(ctx, (_a4 = initialScroll.contentOffset) != null ? _a4 : 0);
4490
+ }
4192
4491
  const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4193
4492
  const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4194
4493
  return clampScrollOffset(ctx, resolvedOffset, initialScroll);
4195
4494
  }, []);
4495
+ const finishInitialScrollWithoutScroll = React2.useCallback(() => {
4496
+ refState.current.initialAnchor = void 0;
4497
+ refState.current.initialScroll = void 0;
4498
+ state.initialAnchor = void 0;
4499
+ state.initialScroll = void 0;
4500
+ state.initialScrollUsesOffset = false;
4501
+ state.initialScrollLastTarget = void 0;
4502
+ state.initialScrollLastTargetUsesOffset = false;
4503
+ setInitialRenderState(ctx, { didInitialScroll: true });
4504
+ }, []);
4505
+ const setActiveInitialScrollTarget = React2.useCallback(
4506
+ (target, options) => {
4507
+ var _a4;
4508
+ const usesOffset = !!(options == null ? void 0 : options.usesOffset);
4509
+ state.initialScrollUsesOffset = usesOffset;
4510
+ state.initialScrollLastTarget = target;
4511
+ state.initialScrollLastTargetUsesOffset = usesOffset;
4512
+ refState.current.initialScroll = target;
4513
+ state.initialScroll = target;
4514
+ if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
4515
+ state.didFinishInitialScroll = false;
4516
+ }
4517
+ if (!(options == null ? void 0 : options.syncAnchor)) {
4518
+ return;
4519
+ }
4520
+ if (!IsNewArchitecture && !usesOffset && target.index !== void 0 && target.viewPosition !== void 0) {
4521
+ state.initialAnchor = {
4522
+ attempts: 0,
4523
+ index: target.index,
4524
+ settledTicks: 0,
4525
+ viewOffset: (_a4 = target.viewOffset) != null ? _a4 : 0,
4526
+ viewPosition: target.viewPosition
4527
+ };
4528
+ }
4529
+ },
4530
+ []
4531
+ );
4532
+ const shouldFinishInitialScrollAtOrigin = React2.useCallback(
4533
+ (initialScroll, offset) => {
4534
+ var _a4, _b2, _c2;
4535
+ if (offset !== 0 || initialScrollAtEnd) {
4536
+ return false;
4537
+ }
4538
+ if (state.initialScrollUsesOffset) {
4539
+ return Math.abs((_a4 = initialScroll.contentOffset) != null ? _a4 : 0) <= 1;
4540
+ }
4541
+ return initialScroll.index === 0 && ((_b2 = initialScroll.viewPosition) != null ? _b2 : 0) === 0 && Math.abs((_c2 = initialScroll.viewOffset) != null ? _c2 : 0) <= 1;
4542
+ },
4543
+ [initialScrollAtEnd]
4544
+ );
4545
+ const shouldFinishEmptyInitialScrollAtEnd = React2.useCallback(
4546
+ (initialScroll, offset) => {
4547
+ return dataProp.length === 0 && initialScrollAtEnd && offset === 0 && initialScroll.viewPosition === 1;
4548
+ },
4549
+ [dataProp.length, initialScrollAtEnd]
4550
+ );
4551
+ const shouldRearmFinishedEmptyInitialScrollAtEnd = React2.useCallback(
4552
+ (initialScroll) => {
4553
+ var _a4;
4554
+ return !!(state.didFinishInitialScroll && dataProp.length > 0 && initialScroll && !state.initialScrollUsesOffset && initialScroll.index === 0 && initialScroll.viewPosition === 1 && ((_a4 = initialScroll.contentOffset) != null ? _a4 : 0) === 0);
4555
+ },
4556
+ [dataProp.length]
4557
+ );
4196
4558
  const initialContentOffset = React2.useMemo(() => {
4197
4559
  var _a4;
4198
4560
  let value;
4199
4561
  const { initialScroll, initialAnchor } = refState.current;
4200
4562
  if (initialScroll) {
4201
- if (!IsNewArchitecture && initialScroll.index !== void 0 && (!initialAnchor || (initialAnchor == null ? void 0 : initialAnchor.index) !== initialScroll.index)) {
4563
+ if (!state.initialScrollUsesOffset && !IsNewArchitecture && initialScroll.index !== void 0 && (!initialAnchor || (initialAnchor == null ? void 0 : initialAnchor.index) !== initialScroll.index)) {
4202
4564
  refState.current.initialAnchor = {
4203
4565
  attempts: 0,
4204
4566
  index: initialScroll.index,
@@ -4212,16 +4574,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4212
4574
  } else {
4213
4575
  const clampedOffset = resolveInitialScrollOffset(initialScroll);
4214
4576
  const updatedInitialScroll = { ...initialScroll, contentOffset: clampedOffset };
4215
- refState.current.initialScroll = updatedInitialScroll;
4216
- state.initialScroll = updatedInitialScroll;
4577
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4578
+ usesOffset: state.initialScrollUsesOffset
4579
+ });
4217
4580
  value = clampedOffset;
4218
4581
  }
4219
4582
  } else {
4220
4583
  refState.current.initialAnchor = void 0;
4221
4584
  value = 0;
4222
4585
  }
4223
- if (!value) {
4224
- setInitialRenderState(ctx, { didInitialScroll: true });
4586
+ const hasPendingDataDependentInitialScroll = !!initialScroll && dataProp.length === 0 && !shouldFinishInitialScrollAtOrigin(initialScroll, value) && !shouldFinishEmptyInitialScrollAtEnd(initialScroll, value);
4587
+ if (!value && !hasPendingDataDependentInitialScroll) {
4588
+ if (initialScroll && shouldFinishInitialScrollAtOrigin(initialScroll, value)) {
4589
+ finishInitialScrollWithoutScroll();
4590
+ } else {
4591
+ setInitialRenderState(ctx, { didInitialScroll: true });
4592
+ }
4225
4593
  }
4226
4594
  return value;
4227
4595
  }, []);
@@ -4238,24 +4606,131 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4238
4606
  set$(ctx, "totalSize", 0);
4239
4607
  }
4240
4608
  }
4241
- const doInitialScroll = React2.useCallback(() => {
4242
- const { initialScroll, didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4243
- if (initialScroll && !queuedInitialLayout && !didFinishInitialScroll && !scrollingTo) {
4244
- const offset = resolveInitialScrollOffset(initialScroll);
4609
+ const doInitialScroll = React2.useCallback((options) => {
4610
+ var _a4, _b2;
4611
+ const allowPostFinishRetry = !!(options == null ? void 0 : options.allowPostFinishRetry);
4612
+ const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4613
+ const initialScroll = (_a4 = state.initialScroll) != null ? _a4 : allowPostFinishRetry ? state.initialScrollLastTarget : void 0;
4614
+ const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
4615
+ const needsContainerLayoutForInitialScroll = !state.initialScrollUsesOffset;
4616
+ const shouldWaitForInitialLayout = waitForInitialLayout && needsContainerLayoutForInitialScroll && !queuedInitialLayout && !allowPostFinishRetry && !isInitialScrollInProgress;
4617
+ if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll && !allowPostFinishRetry || scrollingTo && !isInitialScrollInProgress) {
4618
+ return;
4619
+ }
4620
+ if (allowPostFinishRetry && state.initialScrollLastTargetUsesOffset) {
4621
+ return;
4622
+ }
4623
+ const didMoveAwayFromInitialTarget = allowPostFinishRetry && initialScroll.contentOffset !== void 0 && Math.abs(state.scroll - initialScroll.contentOffset) > 1;
4624
+ if (didMoveAwayFromInitialTarget) {
4625
+ state.initialScrollRetryWindowUntil = 0;
4626
+ return;
4627
+ }
4628
+ const offset = resolveInitialScrollOffset(initialScroll);
4629
+ const activeInitialTargetOffset = isInitialScrollInProgress ? (_b2 = scrollingTo.targetOffset) != null ? _b2 : scrollingTo.offset : void 0;
4630
+ const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - offset) > 1;
4631
+ const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - offset) > 1;
4632
+ if (!didOffsetChange && (allowPostFinishRetry || isInitialScrollInProgress && !didActiveInitialTargetChange)) {
4633
+ return;
4634
+ }
4635
+ if (didOffsetChange) {
4245
4636
  const updatedInitialScroll = { ...initialScroll, contentOffset: offset };
4246
- refState.current.initialScroll = updatedInitialScroll;
4247
- state.initialScroll = updatedInitialScroll;
4248
- scrollTo(ctx, {
4249
- animated: false,
4250
- index: initialScroll.index,
4251
- isInitialScroll: true,
4252
- offset,
4253
- precomputedWithViewOffset: true
4254
- });
4637
+ if (!state.initialScrollUsesOffset) {
4638
+ state.initialScrollLastTarget = updatedInitialScroll;
4639
+ state.initialScrollLastTargetUsesOffset = false;
4640
+ if (state.initialScroll) {
4641
+ refState.current.initialScroll = updatedInitialScroll;
4642
+ state.initialScroll = updatedInitialScroll;
4643
+ }
4644
+ }
4255
4645
  }
4646
+ const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
4647
+ const shouldForceNativeInitialScroll = state.initialScrollUsesOffset && hasMeasuredScrollLayout || allowPostFinishRetry || !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
4648
+ performInitialScroll(ctx, {
4649
+ forceScroll: shouldForceNativeInitialScroll,
4650
+ initialScrollUsesOffset: state.initialScrollUsesOffset,
4651
+ resolvedOffset: offset,
4652
+ target: initialScroll
4653
+ });
4256
4654
  }, []);
4655
+ React2.useLayoutEffect(() => {
4656
+ var _a4;
4657
+ const previousDataLength = state.initialScrollPreviousDataLength;
4658
+ state.initialScrollPreviousDataLength = dataProp.length;
4659
+ if (previousDataLength !== 0 || dataProp.length === 0 || !state.initialScroll || !state.queuedInitialLayout) {
4660
+ return;
4661
+ }
4662
+ if (initialScrollAtEnd) {
4663
+ const lastIndex = Math.max(0, dataProp.length - 1);
4664
+ const initialScroll = state.initialScroll;
4665
+ const shouldRearm = shouldRearmFinishedEmptyInitialScrollAtEnd(initialScroll);
4666
+ if (state.didFinishInitialScroll && !shouldRearm) {
4667
+ return;
4668
+ }
4669
+ if (initialScroll && !state.initialScrollUsesOffset && initialScroll.index === lastIndex && initialScroll.viewPosition === 1 && !shouldRearm) {
4670
+ return;
4671
+ }
4672
+ const updatedInitialScroll = {
4673
+ contentOffset: void 0,
4674
+ index: lastIndex,
4675
+ viewOffset: (_a4 = initialScroll == null ? void 0 : initialScroll.viewOffset) != null ? _a4 : -stylePaddingBottomState,
4676
+ viewPosition: 1
4677
+ };
4678
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4679
+ resetDidFinish: shouldRearm,
4680
+ syncAnchor: true
4681
+ });
4682
+ doInitialScroll();
4683
+ return;
4684
+ }
4685
+ if (state.didFinishInitialScroll) {
4686
+ return;
4687
+ }
4688
+ doInitialScroll();
4689
+ }, [
4690
+ dataProp.length,
4691
+ doInitialScroll,
4692
+ initialScrollAtEnd,
4693
+ shouldRearmFinishedEmptyInitialScrollAtEnd,
4694
+ stylePaddingBottomState
4695
+ ]);
4696
+ React2.useLayoutEffect(() => {
4697
+ var _a4;
4698
+ if (!initialScrollAtEnd) {
4699
+ return;
4700
+ }
4701
+ const lastIndex = Math.max(0, dataProp.length - 1);
4702
+ const initialScroll = state.initialScroll;
4703
+ const shouldRearm = shouldRearmFinishedEmptyInitialScrollAtEnd(initialScroll);
4704
+ if (state.didFinishInitialScroll && !shouldRearm) {
4705
+ return;
4706
+ }
4707
+ if (shouldRearm) {
4708
+ state.didFinishInitialScroll = false;
4709
+ }
4710
+ if (initialScroll && !state.initialScrollUsesOffset && initialScroll.index === lastIndex && initialScroll.viewPosition === 1 && !shouldRearm) {
4711
+ return;
4712
+ }
4713
+ const updatedInitialScroll = {
4714
+ contentOffset: void 0,
4715
+ index: lastIndex,
4716
+ viewOffset: (_a4 = initialScroll == null ? void 0 : initialScroll.viewOffset) != null ? _a4 : -stylePaddingBottomState,
4717
+ viewPosition: 1
4718
+ };
4719
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4720
+ resetDidFinish: shouldRearm,
4721
+ syncAnchor: true
4722
+ });
4723
+ doInitialScroll();
4724
+ }, [
4725
+ dataProp.length,
4726
+ doInitialScroll,
4727
+ initialScrollAtEnd,
4728
+ shouldRearmFinishedEmptyInitialScrollAtEnd,
4729
+ stylePaddingBottomState
4730
+ ]);
4257
4731
  const onLayoutFooter = React2.useCallback(
4258
4732
  (layout) => {
4733
+ var _a4;
4259
4734
  if (!initialScrollAtEnd) {
4260
4735
  return;
4261
4736
  }
@@ -4270,16 +4745,48 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4270
4745
  const footerSize = layout[horizontal ? "width" : "height"];
4271
4746
  const viewOffset = -stylePaddingBottomState - footerSize;
4272
4747
  if (initialScroll.viewOffset !== viewOffset) {
4748
+ const previousTargetOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(initialScroll);
4749
+ const didMoveAwayFromFinishedInitialTarget = state.didFinishInitialScroll && Math.abs(state.scroll - previousTargetOffset) > 1;
4750
+ if (didMoveAwayFromFinishedInitialTarget) {
4751
+ return;
4752
+ }
4273
4753
  const updatedInitialScroll = { ...initialScroll, viewOffset };
4274
- refState.current.initialScroll = updatedInitialScroll;
4275
- state.initialScroll = updatedInitialScroll;
4754
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4755
+ resetDidFinish: true
4756
+ });
4757
+ doInitialScroll();
4276
4758
  }
4277
4759
  },
4278
- [dataProp.length, horizontal, initialScrollAtEnd, stylePaddingBottomState]
4760
+ [
4761
+ dataProp.length,
4762
+ doInitialScroll,
4763
+ horizontal,
4764
+ initialScrollAtEnd,
4765
+ resolveInitialScrollOffset,
4766
+ stylePaddingBottomState
4767
+ ]
4279
4768
  );
4280
4769
  const onLayoutChange = React2.useCallback((layout) => {
4281
- doInitialScroll();
4770
+ var _a4;
4282
4771
  handleLayout(ctx, layout, setCanRender);
4772
+ const SCROLL_LENGTH_RETRY_WINDOW_MS = 600;
4773
+ const now = Date.now();
4774
+ const didFinishInitialScroll = !!state.didFinishInitialScroll;
4775
+ if (didFinishInitialScroll && !state.initialScrollLastDidFinish) {
4776
+ state.initialScrollRetryWindowUntil = now + SCROLL_LENGTH_RETRY_WINDOW_MS;
4777
+ }
4778
+ state.initialScrollLastDidFinish = didFinishInitialScroll;
4779
+ const previousScrollLength = state.initialScrollRetryLastLength;
4780
+ const currentScrollLength = state.scrollLength;
4781
+ const didScrollLengthChange = previousScrollLength === void 0 || Math.abs(currentScrollLength - previousScrollLength) > 1;
4782
+ if (didScrollLengthChange) {
4783
+ state.initialScrollRetryLastLength = currentScrollLength;
4784
+ }
4785
+ if (didFinishInitialScroll && didScrollLengthChange && now <= state.initialScrollRetryWindowUntil && !state.initialScrollLastTargetUsesOffset && ((_a4 = state.initialScrollLastTarget) == null ? void 0 : _a4.index) !== void 0) {
4786
+ doInitialScroll({ allowPostFinishRetry: true });
4787
+ return;
4788
+ }
4789
+ doInitialScroll();
4283
4790
  }, []);
4284
4791
  const { onLayout } = useOnLayoutSync({
4285
4792
  onLayoutChange,
@@ -4396,7 +4903,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4396
4903
  onScroll: onScrollHandler,
4397
4904
  recycleItems,
4398
4905
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2__namespace.cloneElement(refreshControlElement, {
4399
- progressViewOffset: ((_d = refreshControlElement.props.progressViewOffset) != null ? _d : 0) + stylePaddingTopState
4906
+ progressViewOffset: ((_g = refreshControlElement.props.progressViewOffset) != null ? _g : 0) + stylePaddingTopState
4400
4907
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React2__namespace.createElement(
4401
4908
  ReactNative.RefreshControl,
4402
4909
  {
@@ -4407,7 +4914,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4407
4914
  ),
4408
4915
  refScrollView: combinedRef,
4409
4916
  renderScrollComponent,
4410
- scrollAdjustHandler: (_e = refState.current) == null ? void 0 : _e.scrollAdjustHandler,
4917
+ scrollAdjustHandler: (_h = refState.current) == null ? void 0 : _h.scrollAdjustHandler,
4411
4918
  scrollEventThrottle: 0,
4412
4919
  snapToIndices,
4413
4920
  stickyHeaderIndices,