@legendapp/list 3.0.0 → 3.0.2

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.
@@ -2669,1383 +2669,1526 @@ function listenForScrollEnd(ctx, params) {
2669
2669
  }
2670
2670
  }
2671
2671
 
2672
- // src/core/scrollTo.ts
2673
- function getAverageSizeSnapshot(state) {
2674
- if (Object.keys(state.averageSizes).length === 0) {
2675
- return void 0;
2672
+ // src/core/doMaintainScrollAtEnd.ts
2673
+ function doMaintainScrollAtEnd(ctx) {
2674
+ const state = ctx.state;
2675
+ const {
2676
+ didContainersLayout,
2677
+ pendingNativeMVCPAdjust,
2678
+ refScroller,
2679
+ props: { maintainScrollAtEnd }
2680
+ } = state;
2681
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2682
+ const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
2683
+ if (pendingNativeMVCPAdjust) {
2684
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
2685
+ return false;
2676
2686
  }
2677
- const snapshot = {};
2678
- for (const itemType in state.averageSizes) {
2679
- const averages = state.averageSizes[itemType];
2680
- snapshot[itemType] = averages.avg;
2687
+ state.pendingMaintainScrollAtEnd = false;
2688
+ if (shouldMaintainScrollAtEnd) {
2689
+ const contentSize = getContentSize(ctx);
2690
+ if (contentSize < state.scrollLength) {
2691
+ state.scroll = 0;
2692
+ }
2693
+ if (!state.maintainingScrollAtEnd) {
2694
+ state.maintainingScrollAtEnd = true;
2695
+ requestAnimationFrame(() => {
2696
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
2697
+ const scroller = refScroller.current;
2698
+ if (state.props.horizontal && isHorizontalRTL(state)) {
2699
+ const currentContentSize = getContentSize(ctx);
2700
+ const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
2701
+ const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
2702
+ scroller == null ? void 0 : scroller.scrollTo({
2703
+ animated: maintainScrollAtEnd.animated,
2704
+ x: nativeOffset,
2705
+ y: 0
2706
+ });
2707
+ } else {
2708
+ scroller == null ? void 0 : scroller.scrollToEnd({
2709
+ animated: maintainScrollAtEnd.animated
2710
+ });
2711
+ }
2712
+ setTimeout(
2713
+ () => {
2714
+ state.maintainingScrollAtEnd = false;
2715
+ },
2716
+ maintainScrollAtEnd.animated ? 500 : 0
2717
+ );
2718
+ } else {
2719
+ state.maintainingScrollAtEnd = false;
2720
+ }
2721
+ });
2722
+ }
2723
+ return true;
2681
2724
  }
2682
- return snapshot;
2725
+ return false;
2683
2726
  }
2684
- function syncInitialScrollNativeWatchdog(state, options) {
2685
- var _a3;
2686
- const { isInitialScroll, requestedOffset, targetOffset } = options;
2687
- const existingWatchdog = initialScrollWatchdog.get(state);
2688
- const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!existingWatchdog) && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
2689
- const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!existingWatchdog && initialScrollWatchdog.isAtZeroTargetOffset(requestedOffset);
2690
- if (shouldWatchInitialNativeScroll) {
2691
- state.hasScrolled = false;
2692
- initialScrollWatchdog.set(state, {
2693
- startScroll: (_a3 = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _a3 : state.scroll,
2694
- targetOffset
2695
- });
2696
- return;
2697
- }
2698
- if (shouldClearInitialNativeScrollWatchdog) {
2699
- initialScrollWatchdog.clear(state);
2727
+
2728
+ // src/utils/requestAdjust.ts
2729
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2730
+ const state = ctx.state;
2731
+ if (Math.abs(positionDiff) > 0.1) {
2732
+ const doit = () => {
2733
+ {
2734
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2735
+ if (state.adjustingFromInitialMount) {
2736
+ state.adjustingFromInitialMount--;
2737
+ }
2738
+ }
2739
+ };
2740
+ state.scroll += positionDiff;
2741
+ state.scrollForNextCalculateItemsInView = void 0;
2742
+ const readyToRender = peek$(ctx, "readyToRender");
2743
+ if (readyToRender) {
2744
+ doit();
2745
+ } else {
2746
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2747
+ requestAnimationFrame(doit);
2748
+ }
2700
2749
  }
2701
2750
  }
2702
- function scrollTo(ctx, params) {
2703
- var _a3;
2704
- const state = ctx.state;
2705
- const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2706
- const {
2707
- animated,
2708
- isInitialScroll,
2709
- offset: scrollTargetOffset,
2710
- precomputedWithViewOffset,
2711
- waitForInitialScrollCompletionFrame
2712
- } = scrollTarget;
2713
- const {
2714
- props: { horizontal }
2715
- } = state;
2716
- if (state.animFrameCheckFinishedScroll) {
2717
- cancelAnimationFrame(ctx.state.animFrameCheckFinishedScroll);
2751
+
2752
+ // src/core/mvcp.ts
2753
+ var MVCP_POSITION_EPSILON = 0.1;
2754
+ var MVCP_ANCHOR_LOCK_TTL_MS = 300;
2755
+ var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
2756
+ var NATIVE_END_CLAMP_EPSILON = 1;
2757
+ function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
2758
+ if (!enableMVCPAnchorLock) {
2759
+ state.mvcpAnchorLock = void 0;
2760
+ return void 0;
2718
2761
  }
2719
- if (state.timeoutCheckFinishedScrollFallback) {
2720
- clearTimeout(ctx.state.timeoutCheckFinishedScrollFallback);
2762
+ const lock = state.mvcpAnchorLock;
2763
+ if (!lock) {
2764
+ return void 0;
2721
2765
  }
2722
- const requestedOffset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
2723
- const shouldPreserveRawInitialOffsetRequest = !!isInitialScroll && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2724
- const targetOffset = clampScrollOffset(ctx, requestedOffset, scrollTarget);
2725
- const offset = shouldPreserveRawInitialOffsetRequest ? requestedOffset : targetOffset;
2726
- state.scrollHistory.length = 0;
2727
- if (!noScrollingTo) {
2728
- if (isInitialScroll) {
2729
- initialScrollCompletion.resetFlags(state);
2766
+ const isExpired = now > lock.expiresAt;
2767
+ const isMissing = state.indexByKey.get(lock.id) === void 0;
2768
+ if (isExpired || isMissing || !mvcpData) {
2769
+ state.mvcpAnchorLock = void 0;
2770
+ return void 0;
2771
+ }
2772
+ return lock;
2773
+ }
2774
+ function updateAnchorLock(state, params) {
2775
+ {
2776
+ const { anchorId, anchorPosition, dataChanged, now, positionDiff } = params;
2777
+ const enableMVCPAnchorLock = !!dataChanged || !!state.mvcpAnchorLock;
2778
+ const mvcpData = state.props.maintainVisibleContentPosition.data;
2779
+ if (!enableMVCPAnchorLock || !mvcpData || state.scrollingTo || !anchorId || anchorPosition === void 0) {
2780
+ return;
2730
2781
  }
2731
- const averageSizeSnapshot = getAverageSizeSnapshot(state);
2732
- state.scrollingTo = {
2733
- ...scrollTarget,
2734
- ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
2735
- targetOffset,
2736
- waitForInitialScrollCompletionFrame
2782
+ const existingLock = state.mvcpAnchorLock;
2783
+ const quietPasses = !dataChanged && Math.abs(positionDiff) <= MVCP_POSITION_EPSILON && (existingLock == null ? void 0 : existingLock.id) === anchorId ? existingLock.quietPasses + 1 : 0;
2784
+ if (!dataChanged && quietPasses >= MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE) {
2785
+ state.mvcpAnchorLock = void 0;
2786
+ return;
2787
+ }
2788
+ state.mvcpAnchorLock = {
2789
+ expiresAt: now + MVCP_ANCHOR_LOCK_TTL_MS,
2790
+ id: anchorId,
2791
+ position: anchorPosition,
2792
+ quietPasses
2737
2793
  };
2738
2794
  }
2739
- state.scrollPending = targetOffset;
2740
- syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2741
- if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2742
- doScrollTo(ctx, { animated, horizontal, offset });
2743
- } else {
2744
- state.scroll = offset;
2795
+ }
2796
+ function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
2797
+ {
2798
+ return false;
2745
2799
  }
2746
2800
  }
2747
-
2748
- // src/core/scrollToIndex.ts
2749
- function clampScrollIndex(index, dataLength) {
2750
- if (dataLength <= 0) {
2751
- return -1;
2801
+ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
2802
+ if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
2803
+ return 0;
2752
2804
  }
2753
- if (index >= dataLength) {
2754
- return dataLength - 1;
2805
+ const maxScroll = Math.max(0, totalSize - state.scrollLength);
2806
+ const clampDelta = maxScroll - state.scroll;
2807
+ if (unresolvedAmount < 0) {
2808
+ return Math.max(unresolvedAmount, Math.min(0, clampDelta));
2755
2809
  }
2756
- if (index < 0) {
2757
- return 0;
2810
+ if (unresolvedAmount > 0) {
2811
+ return Math.min(unresolvedAmount, Math.max(0, clampDelta));
2758
2812
  }
2759
- return index;
2813
+ return 0;
2760
2814
  }
2761
- function scrollToIndex(ctx, {
2762
- index,
2763
- viewOffset = 0,
2764
- animated = true,
2765
- forceScroll,
2766
- isInitialScroll,
2767
- viewPosition
2768
- }) {
2815
+ function getProgressTowardAmount(targetDelta, nativeDelta) {
2816
+ return targetDelta < 0 ? -nativeDelta : nativeDelta;
2817
+ }
2818
+ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
2769
2819
  const state = ctx.state;
2770
- const { data } = state.props;
2771
- index = clampScrollIndex(index, data.length);
2772
- const itemSize = getItemSizeAtIndex(ctx, index);
2773
- const firstIndexOffset = calculateOffsetForIndex(ctx, index);
2774
- const isLast = index === data.length - 1;
2775
- if (isLast && viewPosition === void 0) {
2776
- viewPosition = 1;
2820
+ state.pendingNativeMVCPAdjust = void 0;
2821
+ const remaining = remainingAfterManual - nativeDelta;
2822
+ if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
2823
+ requestAdjust(ctx, remaining);
2777
2824
  }
2778
- state.scrollForNextCalculateItemsInView = void 0;
2779
- scrollTo(ctx, {
2780
- animated,
2781
- forceScroll,
2782
- index,
2783
- isInitialScroll,
2784
- itemSize,
2785
- offset: firstIndexOffset,
2786
- viewOffset,
2787
- viewPosition: viewPosition != null ? viewPosition : 0
2788
- });
2789
- }
2790
-
2791
- // src/core/initialScroll.ts
2792
- function dispatchInitialScroll(ctx, params) {
2793
- const { forceScroll, resolvedOffset, target, waitForCompletionFrame } = params;
2794
- const requestedIndex = target.index;
2795
- const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0;
2796
- const itemSize = getItemSizeAtIndex(ctx, index);
2797
- scrollTo(ctx, {
2798
- animated: false,
2799
- forceScroll,
2800
- index: index !== void 0 && index >= 0 ? index : void 0,
2801
- isInitialScroll: true,
2802
- itemSize,
2803
- offset: resolvedOffset,
2804
- precomputedWithViewOffset: true,
2805
- viewOffset: target.viewOffset,
2806
- viewPosition: target.viewPosition,
2807
- waitForInitialScrollCompletionFrame: waitForCompletionFrame
2808
- });
2809
2825
  }
2810
- function setInitialScrollTarget(state, target, options) {
2811
- var _a3;
2812
- state.clearPreservedInitialScrollOnNextFinish = void 0;
2813
- if (state.timeoutPreservedInitialScrollClear !== void 0) {
2814
- clearTimeout(state.timeoutPreservedInitialScrollClear);
2815
- state.timeoutPreservedInitialScrollClear = void 0;
2816
- }
2817
- state.initialScroll = target;
2818
- if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
2819
- state.didFinishInitialScroll = false;
2820
- }
2821
- setInitialScrollSession(state, {
2822
- kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
2823
- });
2824
- }
2825
- function resolveInitialScrollOffset(ctx, initialScroll) {
2826
- var _a3, _b;
2826
+ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
2827
2827
  const state = ctx.state;
2828
- if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
2829
- return (_b = initialScroll.contentOffset) != null ? _b : 0;
2828
+ const pending = state.pendingNativeMVCPAdjust;
2829
+ if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
2830
+ return;
2830
2831
  }
2831
- const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
2832
- const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
2833
- return clampScrollOffset(ctx, resolvedOffset, initialScroll);
2834
- }
2835
- function getAdvanceableInitialScrollState(state, options) {
2836
- const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
2837
- const initialScroll = state.initialScroll;
2838
- const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
2839
- const shouldWaitForInitialLayout = !!(options == null ? void 0 : options.requiresMeasuredLayout) && !queuedInitialLayout && !isInitialScrollInProgress;
2840
- if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll || scrollingTo && !isInitialScrollInProgress) {
2841
- return void 0;
2832
+ const totalSize = getContentSize(ctx);
2833
+ const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
2834
+ if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
2835
+ return;
2842
2836
  }
2843
- return {
2844
- initialScroll,
2845
- isInitialScrollInProgress,
2846
- queuedInitialLayout,
2847
- scrollingTo
2848
- };
2837
+ const manualDesired = pending.amount - predictedNativeClamp;
2838
+ if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
2839
+ return;
2840
+ }
2841
+ pending.manualApplied = manualDesired;
2842
+ requestAdjust(ctx, manualDesired);
2843
+ pending.furthestProgressTowardAmount = 0;
2849
2844
  }
2850
- function advanceMeasuredInitialScroll(ctx, options) {
2851
- var _a3, _b, _c;
2845
+ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
2852
2846
  const state = ctx.state;
2853
- const advanceableState = getAdvanceableInitialScrollState(state, {
2854
- requiresMeasuredLayout: true
2855
- });
2856
- if (!advanceableState) {
2847
+ const pending = state.pendingNativeMVCPAdjust;
2848
+ if (!pending) {
2857
2849
  return false;
2858
2850
  }
2859
- const { initialScroll, isInitialScrollInProgress, queuedInitialLayout } = advanceableState;
2860
- const scrollingTo = isInitialScrollInProgress ? advanceableState.scrollingTo : void 0;
2861
- const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
2862
- const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2863
- const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2864
- const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2865
- const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2866
- if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2867
- return false;
2851
+ const remainingAfterManual = pending.amount - pending.manualApplied;
2852
+ const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
2853
+ const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
2854
+ const progressTowardAmount = getProgressTowardAmount(remainingAfterManual, nativeDelta);
2855
+ if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
2856
+ state.pendingNativeMVCPAdjust = void 0;
2857
+ return true;
2868
2858
  }
2869
- if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2859
+ if (isWrongDirection) {
2860
+ state.pendingNativeMVCPAdjust = void 0;
2870
2861
  return false;
2871
2862
  }
2872
- if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2873
- setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2863
+ if (progressTowardAmount + MVCP_POSITION_EPSILON >= Math.abs(remainingAfterManual)) {
2864
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2865
+ return true;
2874
2866
  }
2875
- const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
2876
- dispatchInitialScroll(ctx, {
2877
- forceScroll,
2878
- resolvedOffset,
2879
- target: initialScroll
2880
- });
2881
- return true;
2882
- }
2883
- function advanceOffsetInitialScroll(ctx, options) {
2884
- var _a3, _b;
2885
- const state = ctx.state;
2886
- const advanceableState = getAdvanceableInitialScrollState(state);
2887
- if (!advanceableState) {
2888
- return false;
2867
+ const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
2868
+ const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
2869
+ const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
2870
+ if (isAtExpectedNativeClamp) {
2871
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2872
+ return true;
2889
2873
  }
2890
- const { initialScroll, queuedInitialLayout } = advanceableState;
2891
- const resolvedOffset = (_a3 = initialScroll.contentOffset) != null ? _a3 : 0;
2892
- const isAlreadyAtDesiredInitialTarget = Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2893
- if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2874
+ if (state.pendingMaintainScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold") && progressTowardAmount > MVCP_POSITION_EPSILON) {
2875
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2876
+ return true;
2877
+ }
2878
+ if (progressTowardAmount > pending.furthestProgressTowardAmount + MVCP_POSITION_EPSILON) {
2879
+ pending.furthestProgressTowardAmount = progressTowardAmount;
2894
2880
  return false;
2895
2881
  }
2896
- const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
2897
- const forceScroll = (_b = options == null ? void 0 : options.forceScroll) != null ? _b : hasMeasuredScrollLayout || !!queuedInitialLayout;
2898
- dispatchInitialScroll(ctx, {
2899
- forceScroll,
2900
- resolvedOffset,
2901
- target: initialScroll
2902
- });
2903
- return true;
2904
- }
2905
- function advanceCurrentInitialScrollSession(ctx, options) {
2906
- var _a3;
2907
- return ((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? advanceOffsetInitialScroll(ctx, {
2908
- forceScroll: options == null ? void 0 : options.forceScroll
2909
- }) : advanceMeasuredInitialScroll(ctx, {
2910
- forceScroll: options == null ? void 0 : options.forceScroll
2911
- });
2912
- }
2913
-
2914
- // src/utils/checkAllSizesKnown.ts
2915
- function isNullOrUndefined2(value) {
2916
- return value === null || value === void 0;
2917
- }
2918
- function getMountedIndicesInRange(state, start, end) {
2919
- if (!isNullOrUndefined2(end) && !isNullOrUndefined2(start) && start >= 0 && end >= 0) {
2920
- return Array.from(state.containerItemKeys.keys()).map((key) => state.indexByKey.get(key)).filter((index) => index !== void 0 && index >= start && index <= end).sort((a, b) => a - b);
2882
+ if (pending.furthestProgressTowardAmount > MVCP_POSITION_EPSILON && progressTowardAmount < pending.furthestProgressTowardAmount - MVCP_POSITION_EPSILON) {
2883
+ state.pendingNativeMVCPAdjust = void 0;
2884
+ return false;
2921
2885
  }
2922
- return [];
2923
- }
2924
- function getMountedBufferedIndices(state) {
2925
- return getMountedIndicesInRange(state, state.startBuffered, state.endBuffered);
2926
- }
2927
- function getMountedNoBufferIndices(state) {
2928
- return getMountedIndicesInRange(state, state.startNoBuffer, state.endNoBuffer);
2929
- }
2930
- function checkAllSizesKnown(state, indices) {
2931
- return indices.length > 0 && indices.every((index) => {
2932
- const key = getId(state, index);
2933
- return key !== void 0 && state.sizesKnown.has(key);
2934
- });
2886
+ return false;
2935
2887
  }
2936
-
2937
- // src/utils/requestAdjust.ts
2938
- function requestAdjust(ctx, positionDiff, dataChanged) {
2888
+ function prepareMVCP(ctx, dataChanged) {
2939
2889
  const state = ctx.state;
2940
- if (Math.abs(positionDiff) > 0.1) {
2941
- const doit = () => {
2942
- {
2943
- state.scrollAdjustHandler.requestAdjust(positionDiff);
2944
- if (state.adjustingFromInitialMount) {
2945
- state.adjustingFromInitialMount--;
2890
+ const { idsInView, positions, props } = state;
2891
+ const {
2892
+ maintainVisibleContentPosition: { data: mvcpData, size: mvcpScroll, shouldRestorePosition }
2893
+ } = props;
2894
+ const now = Date.now();
2895
+ const enableMVCPAnchorLock = (!!dataChanged || !!state.mvcpAnchorLock);
2896
+ const scrollingTo = state.scrollingTo;
2897
+ const anchorLock = resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) ;
2898
+ let prevPosition;
2899
+ let targetId;
2900
+ const idsInViewWithPositions = [];
2901
+ const scrollTarget = scrollingTo == null ? void 0 : scrollingTo.index;
2902
+ const scrollingToViewPosition = scrollingTo == null ? void 0 : scrollingTo.viewPosition;
2903
+ const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
2904
+ const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
2905
+ const indexByKey = state.indexByKey;
2906
+ const prevScroll = state.scroll;
2907
+ getContentSize(ctx);
2908
+ if (shouldMVCP) {
2909
+ if (anchorLock && scrollTarget === void 0) {
2910
+ targetId = anchorLock.id;
2911
+ prevPosition = anchorLock.position;
2912
+ } else if (scrollTarget !== void 0) {
2913
+ targetId = getId(state, scrollTarget);
2914
+ } else if (idsInView.length > 0 && state.didContainersLayout && !dataChanged) {
2915
+ targetId = idsInView.find((id) => indexByKey.get(id) !== void 0);
2916
+ }
2917
+ if (dataChanged && idsInView.length > 0 && state.didContainersLayout) {
2918
+ for (let i = 0; i < idsInView.length; i++) {
2919
+ const id = idsInView[i];
2920
+ const index = indexByKey.get(id);
2921
+ if (index !== void 0) {
2922
+ const position = positions[index];
2923
+ if (position !== void 0) {
2924
+ idsInViewWithPositions.push({ id, position });
2925
+ }
2946
2926
  }
2947
2927
  }
2948
- };
2949
- state.scroll += positionDiff;
2950
- state.scrollForNextCalculateItemsInView = void 0;
2951
- const readyToRender = peek$(ctx, "readyToRender");
2952
- if (readyToRender) {
2953
- doit();
2954
- } else {
2955
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2956
- requestAnimationFrame(doit);
2957
- }
2958
- }
2959
- }
2960
-
2961
- // src/core/bootstrapInitialScroll.ts
2962
- var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2963
- var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
2964
- var DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES = 24;
2965
- var BOOTSTRAP_REVEAL_ABORT_WARNING = "LegendList bootstrap initial scroll aborted after exceeding convergence bounds.";
2966
- function getBootstrapInitialScrollSession(state) {
2967
- var _a3;
2968
- return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
2969
- }
2970
- function isOffsetInitialScrollSession(state) {
2971
- var _a3;
2972
- return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2973
- }
2974
- function doVisibleIndicesMatch(previous, next) {
2975
- if (!previous || previous.length !== next.length) {
2976
- return false;
2977
- }
2978
- for (let i = 0; i < previous.length; i++) {
2979
- if (previous[i] !== next[i]) {
2980
- return false;
2981
2928
  }
2982
- }
2983
- return true;
2984
- }
2985
- function getBootstrapRevealVisibleIndices(options) {
2986
- const { dataLength, getSize, offset, positions, scrollLength, startIndex: requestedStartIndex } = options;
2987
- const endOffset = offset + scrollLength;
2988
- const visibleIndices = [];
2989
- let index = requestedStartIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, requestedStartIndex)) : 0;
2990
- while (index > 0) {
2991
- const previousIndex = index - 1;
2992
- const previousPosition = positions[previousIndex];
2993
- if (previousPosition === void 0) {
2994
- index = previousIndex;
2995
- continue;
2929
+ if (targetId !== void 0 && prevPosition === void 0) {
2930
+ const targetIndex = indexByKey.get(targetId);
2931
+ if (targetIndex !== void 0) {
2932
+ prevPosition = positions[targetIndex];
2933
+ }
2996
2934
  }
2997
- const previousSize = getSize(previousIndex);
2998
- if (previousSize === void 0) {
2999
- index = previousIndex;
3000
- continue;
3001
- }
3002
- if (previousPosition + previousSize <= offset) {
3003
- break;
2935
+ return () => {
2936
+ let positionDiff = 0;
2937
+ let anchorIdForLock = anchorLock == null ? void 0 : anchorLock.id;
2938
+ let anchorPositionForLock;
2939
+ let skipTargetAnchor = false;
2940
+ const data = state.props.data;
2941
+ const shouldValidateLockedAnchor = dataChanged && mvcpData && scrollTarget === void 0 && targetId !== void 0 && (anchorLock == null ? void 0 : anchorLock.id) === targetId && shouldRestorePosition !== void 0;
2942
+ if (shouldValidateLockedAnchor && targetId !== void 0) {
2943
+ const index = indexByKey.get(targetId);
2944
+ if (index !== void 0) {
2945
+ const item = data[index];
2946
+ skipTargetAnchor = item === void 0 || !shouldRestorePosition(item, index, data);
2947
+ if (skipTargetAnchor && (anchorLock == null ? void 0 : anchorLock.id) === targetId) {
2948
+ state.mvcpAnchorLock = void 0;
2949
+ }
2950
+ }
2951
+ }
2952
+ const shouldUseFallbackVisibleAnchor = dataChanged && mvcpData && scrollTarget === void 0 && (() => {
2953
+ if (targetId === void 0 || skipTargetAnchor) {
2954
+ return true;
2955
+ }
2956
+ const targetIndex = indexByKey.get(targetId);
2957
+ return targetIndex === void 0 || positions[targetIndex] === void 0;
2958
+ })();
2959
+ if (shouldUseFallbackVisibleAnchor) {
2960
+ for (let i = 0; i < idsInViewWithPositions.length; i++) {
2961
+ const { id, position } = idsInViewWithPositions[i];
2962
+ const index = indexByKey.get(id);
2963
+ if (index !== void 0 && shouldRestorePosition) {
2964
+ const item = data[index];
2965
+ if (item === void 0 || !shouldRestorePosition(item, index, data)) {
2966
+ continue;
2967
+ }
2968
+ }
2969
+ const newPosition = index !== void 0 ? positions[index] : void 0;
2970
+ if (newPosition !== void 0) {
2971
+ positionDiff = newPosition - position;
2972
+ anchorIdForLock = id;
2973
+ anchorPositionForLock = newPosition;
2974
+ break;
2975
+ }
2976
+ }
2977
+ }
2978
+ if (!skipTargetAnchor && targetId !== void 0 && prevPosition !== void 0) {
2979
+ const targetIndex = indexByKey.get(targetId);
2980
+ const newPosition = targetIndex !== void 0 ? positions[targetIndex] : void 0;
2981
+ if (newPosition !== void 0) {
2982
+ const totalSize = getContentSize(ctx);
2983
+ let diff = newPosition - prevPosition;
2984
+ if (diff !== 0 && isEndAnchoredScrollTarget && state.scroll + state.scrollLength > totalSize) {
2985
+ if (diff > 0) {
2986
+ diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
2987
+ } else {
2988
+ diff = 0;
2989
+ }
2990
+ }
2991
+ positionDiff = diff;
2992
+ anchorIdForLock = targetId;
2993
+ anchorPositionForLock = newPosition;
2994
+ }
2995
+ }
2996
+ if (scrollingToViewPosition && scrollingToViewPosition > 0) {
2997
+ const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
2998
+ const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
2999
+ if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
3000
+ const diff = newSize - prevSize;
3001
+ if (diff !== 0) {
3002
+ positionDiff += diff * scrollingToViewPosition;
3003
+ scrollingTo.itemSize = newSize;
3004
+ }
3005
+ }
3006
+ }
3007
+ updateAnchorLock(state, {
3008
+ anchorId: anchorIdForLock,
3009
+ anchorPosition: anchorPositionForLock,
3010
+ dataChanged,
3011
+ now,
3012
+ positionDiff
3013
+ });
3014
+ if (shouldQueueNativeMVCPAdjust()) {
3015
+ state.pendingNativeMVCPAdjust = {
3016
+ amount: positionDiff,
3017
+ furthestProgressTowardAmount: 0,
3018
+ manualApplied: 0,
3019
+ startScroll: prevScroll
3020
+ };
3021
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
3022
+ return;
3023
+ }
3024
+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3025
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3026
+ if (!shouldSkipAdjustForMaintainedEnd) {
3027
+ requestAdjust(ctx, positionDiff);
3028
+ }
3029
+ }
3030
+ };
3031
+ }
3032
+ }
3033
+
3034
+ // src/core/updateScroll.ts
3035
+ function updateScroll(ctx, newScroll, forceUpdate, options) {
3036
+ var _a3;
3037
+ const state = ctx.state;
3038
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
3039
+ const prevScroll = state.scroll;
3040
+ if ((options == null ? void 0 : options.markHasScrolled) !== false) {
3041
+ state.hasScrolled = true;
3042
+ }
3043
+ state.lastBatchingAction = Date.now();
3044
+ const currentTime = Date.now();
3045
+ const adjust = scrollAdjustHandler.getAdjust();
3046
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
3047
+ if (adjustChanged) {
3048
+ scrollHistory.length = 0;
3049
+ }
3050
+ state.lastScrollAdjustForHistory = adjust;
3051
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
3052
+ if (!adjustChanged) {
3053
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
3004
3054
  }
3005
- index = previousIndex;
3006
3055
  }
3007
- for (; index < dataLength; index++) {
3008
- const position = positions[index];
3009
- if (position === void 0) {
3010
- continue;
3056
+ if (scrollHistory.length > 5) {
3057
+ scrollHistory.shift();
3058
+ }
3059
+ if (ignoreScrollFromMVCP && !scrollingTo) {
3060
+ const { lt, gt } = ignoreScrollFromMVCP;
3061
+ if (lt && newScroll < lt || gt && newScroll > gt) {
3062
+ state.ignoreScrollFromMVCPIgnored = true;
3063
+ return;
3011
3064
  }
3012
- const size = getSize(index);
3013
- if (size === void 0) {
3014
- continue;
3065
+ }
3066
+ state.scrollPrev = prevScroll;
3067
+ state.scrollPrevTime = state.scrollTime;
3068
+ state.scroll = newScroll;
3069
+ state.scrollTime = currentTime;
3070
+ const scrollDelta = Math.abs(newScroll - prevScroll);
3071
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
3072
+ const scrollLength = state.scrollLength;
3073
+ const lastCalculated = state.scrollLastCalculate;
3074
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
3075
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
3076
+ if (shouldUpdate) {
3077
+ state.scrollLastCalculate = state.scroll;
3078
+ state.ignoreScrollFromMVCPIgnored = false;
3079
+ state.lastScrollDelta = scrollDelta;
3080
+ const runCalculateItems = () => {
3081
+ var _a4;
3082
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
3083
+ checkThresholds(ctx);
3084
+ };
3085
+ if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
3086
+ state.mvcpAnchorLock = void 0;
3087
+ state.pendingNativeMVCPAdjust = void 0;
3088
+ state.userScrollAnchorResetKeys = /* @__PURE__ */ new Set();
3089
+ if (state.queuedMVCPRecalculate !== void 0) {
3090
+ cancelAnimationFrame(state.queuedMVCPRecalculate);
3091
+ state.queuedMVCPRecalculate = void 0;
3092
+ }
3093
+ flushSync(runCalculateItems);
3094
+ } else {
3095
+ runCalculateItems();
3015
3096
  }
3016
- if (position < endOffset && position + size > offset) {
3017
- visibleIndices.push(index);
3018
- } else if (visibleIndices.length > 0 && position >= endOffset) {
3019
- break;
3097
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
3098
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
3099
+ state.pendingMaintainScrollAtEnd = false;
3100
+ doMaintainScrollAtEnd(ctx);
3020
3101
  }
3102
+ state.dataChangeNeedsScrollUpdate = false;
3103
+ state.lastScrollDelta = 0;
3021
3104
  }
3022
- return visibleIndices;
3023
- }
3024
- function shouldAbortBootstrapReveal(options) {
3025
- const {
3026
- mountFrameCount,
3027
- maxFrames = DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES,
3028
- maxPasses = DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES,
3029
- passCount
3030
- } = options;
3031
- return mountFrameCount >= maxFrames || passCount >= maxPasses;
3032
3105
  }
3033
- function abortBootstrapRevealIfNeeded(ctx, options) {
3034
- if (!shouldAbortBootstrapReveal(options)) {
3035
- return false;
3106
+
3107
+ // src/core/scrollTo.ts
3108
+ function getAverageSizeSnapshot(state) {
3109
+ if (Object.keys(state.averageSizes).length === 0) {
3110
+ return void 0;
3036
3111
  }
3037
- if (IS_DEV) {
3038
- console.warn(BOOTSTRAP_REVEAL_ABORT_WARNING);
3112
+ const snapshot = {};
3113
+ for (const itemType in state.averageSizes) {
3114
+ const averages = state.averageSizes[itemType];
3115
+ snapshot[itemType] = averages.avg;
3039
3116
  }
3040
- abortBootstrapInitialScroll(ctx);
3041
- return true;
3117
+ return snapshot;
3042
3118
  }
3043
- function clearBootstrapInitialScrollSession(state) {
3119
+ function syncInitialScrollNativeWatchdog(state, options) {
3044
3120
  var _a3;
3045
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3046
- const frameHandle = bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle;
3047
- if (frameHandle !== void 0 && typeof cancelAnimationFrame === "function") {
3048
- cancelAnimationFrame(frameHandle);
3121
+ const { isInitialScroll, requestedOffset, targetOffset } = options;
3122
+ const existingWatchdog = initialScrollWatchdog.get(state);
3123
+ const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!existingWatchdog) && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
3124
+ const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!existingWatchdog && initialScrollWatchdog.isAtZeroTargetOffset(requestedOffset);
3125
+ if (shouldWatchInitialNativeScroll) {
3126
+ state.hasScrolled = false;
3127
+ initialScrollWatchdog.set(state, {
3128
+ startScroll: (_a3 = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _a3 : state.scroll,
3129
+ targetOffset
3130
+ });
3131
+ return;
3049
3132
  }
3050
- if (bootstrapInitialScroll) {
3051
- bootstrapInitialScroll.frameHandle = void 0;
3133
+ if (shouldClearInitialNativeScrollWatchdog) {
3134
+ initialScrollWatchdog.clear(state);
3052
3135
  }
3053
- setInitialScrollSession(state, {
3054
- bootstrap: null,
3055
- kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
3056
- });
3057
- }
3058
- function startBootstrapInitialScrollSession(state, options) {
3059
- var _a3, _b, _c;
3060
- const previousBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3061
- setInitialScrollSession(state, {
3062
- bootstrap: {
3063
- frameHandle: previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.frameHandle,
3064
- // Re-arming during the initial mount should spend from the same watchdog budget.
3065
- mountFrameCount: (_a3 = previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.mountFrameCount) != null ? _a3 : 0,
3066
- passCount: 0,
3067
- previousResolvedOffset: void 0,
3068
- scroll: options.scroll,
3069
- seedContentOffset: (_c = (_b = options.seedContentOffset) != null ? _b : previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.seedContentOffset) != null ? _c : options.scroll,
3070
- targetIndexSeed: options.targetIndexSeed,
3071
- visibleIndices: void 0
3072
- },
3073
- kind: "bootstrap"
3074
- });
3075
3136
  }
3076
- function resetBootstrapInitialScrollSession(state, options) {
3077
- var _a3, _b, _c;
3078
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3079
- if (!bootstrapInitialScroll) {
3080
- if ((options == null ? void 0 : options.scroll) !== void 0) {
3081
- startBootstrapInitialScrollSession(state, {
3082
- scroll: options.scroll,
3083
- seedContentOffset: options.seedContentOffset,
3084
- targetIndexSeed: options.targetIndexSeed
3085
- });
3137
+ function scrollTo(ctx, params) {
3138
+ var _a3;
3139
+ const state = ctx.state;
3140
+ const { noScrollingTo, forceScroll, ...scrollTarget } = params;
3141
+ const {
3142
+ animated,
3143
+ isInitialScroll,
3144
+ offset: scrollTargetOffset,
3145
+ precomputedWithViewOffset,
3146
+ waitForInitialScrollCompletionFrame
3147
+ } = scrollTarget;
3148
+ const {
3149
+ props: { horizontal }
3150
+ } = state;
3151
+ if (state.animFrameCheckFinishedScroll) {
3152
+ cancelAnimationFrame(ctx.state.animFrameCheckFinishedScroll);
3153
+ }
3154
+ if (state.timeoutCheckFinishedScrollFallback) {
3155
+ clearTimeout(ctx.state.timeoutCheckFinishedScrollFallback);
3156
+ }
3157
+ const requestedOffset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
3158
+ const shouldPreserveRawInitialOffsetRequest = !!isInitialScroll && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
3159
+ const targetOffset = clampScrollOffset(ctx, requestedOffset, scrollTarget);
3160
+ const offset = shouldPreserveRawInitialOffsetRequest ? requestedOffset : targetOffset;
3161
+ state.scrollHistory.length = 0;
3162
+ if (!noScrollingTo) {
3163
+ if (isInitialScroll) {
3164
+ initialScrollCompletion.resetFlags(state);
3086
3165
  }
3166
+ const averageSizeSnapshot = getAverageSizeSnapshot(state);
3167
+ state.scrollingTo = {
3168
+ ...scrollTarget,
3169
+ ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
3170
+ targetOffset,
3171
+ waitForInitialScrollCompletionFrame
3172
+ };
3173
+ }
3174
+ state.scrollPending = targetOffset;
3175
+ syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
3176
+ if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
3177
+ updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
3178
+ }
3179
+ if (forceScroll || !isInitialScroll || Platform.OS === "android") {
3180
+ doScrollTo(ctx, { animated, horizontal, offset });
3087
3181
  } else {
3088
- bootstrapInitialScroll.passCount = 0;
3089
- bootstrapInitialScroll.previousResolvedOffset = void 0;
3090
- bootstrapInitialScroll.scroll = (_a3 = options == null ? void 0 : options.scroll) != null ? _a3 : bootstrapInitialScroll.scroll;
3091
- bootstrapInitialScroll.seedContentOffset = (_b = options == null ? void 0 : options.seedContentOffset) != null ? _b : bootstrapInitialScroll.seedContentOffset;
3092
- bootstrapInitialScroll.targetIndexSeed = (_c = options == null ? void 0 : options.targetIndexSeed) != null ? _c : bootstrapInitialScroll.targetIndexSeed;
3093
- bootstrapInitialScroll.visibleIndices = void 0;
3094
- setInitialScrollSession(state, {
3095
- bootstrap: bootstrapInitialScroll,
3096
- kind: "bootstrap"
3097
- });
3182
+ state.scroll = offset;
3098
3183
  }
3099
3184
  }
3100
- function queueBootstrapInitialScrollReevaluation(state) {
3101
- requestAnimationFrame(() => {
3102
- var _a3;
3103
- if (getBootstrapInitialScrollSession(state)) {
3104
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
3105
- }
3106
- });
3185
+
3186
+ // src/core/scrollToIndex.ts
3187
+ function clampScrollIndex(index, dataLength) {
3188
+ if (dataLength <= 0) {
3189
+ return -1;
3190
+ }
3191
+ if (index >= dataLength) {
3192
+ return dataLength - 1;
3193
+ }
3194
+ if (index < 0) {
3195
+ return 0;
3196
+ }
3197
+ return index;
3107
3198
  }
3108
- function ensureBootstrapInitialScrollFrameTicker(ctx) {
3199
+ function scrollToIndex(ctx, {
3200
+ index,
3201
+ viewOffset = 0,
3202
+ animated = true,
3203
+ forceScroll,
3204
+ isInitialScroll,
3205
+ viewPosition
3206
+ }) {
3109
3207
  const state = ctx.state;
3110
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3111
- if (!bootstrapInitialScroll || bootstrapInitialScroll.frameHandle !== void 0) {
3112
- return;
3208
+ const { data } = state.props;
3209
+ index = clampScrollIndex(index, data.length);
3210
+ const itemSize = getItemSizeAtIndex(ctx, index);
3211
+ const firstIndexOffset = calculateOffsetForIndex(ctx, index);
3212
+ const isLast = index === data.length - 1;
3213
+ if (isLast && viewPosition === void 0) {
3214
+ viewPosition = 1;
3113
3215
  }
3114
- const tick = () => {
3115
- const activeBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3116
- if (!activeBootstrapInitialScroll) {
3117
- return;
3118
- }
3119
- activeBootstrapInitialScroll.frameHandle = void 0;
3120
- activeBootstrapInitialScroll.mountFrameCount += 1;
3121
- if (abortBootstrapRevealIfNeeded(ctx, {
3122
- mountFrameCount: activeBootstrapInitialScroll.mountFrameCount,
3123
- passCount: activeBootstrapInitialScroll.passCount
3124
- })) {
3125
- return;
3126
- }
3127
- ensureBootstrapInitialScrollFrameTicker(ctx);
3128
- };
3129
- bootstrapInitialScroll.frameHandle = requestAnimationFrame(tick);
3130
- }
3131
- function rearmBootstrapInitialScroll(ctx, options) {
3132
- resetBootstrapInitialScrollSession(ctx.state, options);
3133
- ensureBootstrapInitialScrollFrameTicker(ctx);
3134
- queueBootstrapInitialScrollReevaluation(ctx.state);
3135
- }
3136
- function createInitialScrollAtEndTarget(options) {
3137
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingBottom } = options;
3138
- return {
3139
- contentOffset: void 0,
3140
- index: Math.max(0, dataLength - 1),
3141
- preserveForBottomPadding: true,
3142
- preserveForFooterLayout,
3143
- viewOffset: -stylePaddingBottom - footerSize,
3144
- viewPosition: 1
3145
- };
3146
- }
3147
- function shouldPreserveInitialScrollForBottomPadding(target) {
3148
- return !!(target == null ? void 0 : target.preserveForBottomPadding);
3149
- }
3150
- function shouldPreserveInitialScrollForFooterLayout(target) {
3151
- return !!(target == null ? void 0 : target.preserveForFooterLayout);
3152
- }
3153
- function isRetargetableBottomAlignedInitialScrollTarget(target) {
3154
- return !!(target && target.viewPosition === 1 && (shouldPreserveInitialScrollForBottomPadding(target) || shouldPreserveInitialScrollForFooterLayout(target)));
3155
- }
3156
- function createRetargetedBottomAlignedInitialScroll(options) {
3157
- const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom, target } = options;
3158
- const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
3159
- return {
3160
- ...target,
3161
- contentOffset: void 0,
3162
- index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
3163
- preserveForBottomPadding: true,
3164
- preserveForFooterLayout,
3165
- viewOffset: -stylePaddingBottom - (preserveForFooterLayout ? footerSize : 0),
3166
- viewPosition: 1
3167
- };
3216
+ state.scrollForNextCalculateItemsInView = void 0;
3217
+ scrollTo(ctx, {
3218
+ animated,
3219
+ forceScroll,
3220
+ index,
3221
+ isInitialScroll,
3222
+ itemSize,
3223
+ offset: firstIndexOffset,
3224
+ viewOffset,
3225
+ viewPosition: viewPosition != null ? viewPosition : 0
3226
+ });
3168
3227
  }
3169
- function areEquivalentBootstrapInitialScrollTargets(current, next) {
3170
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
3228
+
3229
+ // src/core/initialScroll.ts
3230
+ function dispatchInitialScroll(ctx, params) {
3231
+ const { forceScroll, resolvedOffset, target, waitForCompletionFrame } = params;
3232
+ const requestedIndex = target.index;
3233
+ const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0;
3234
+ const itemSize = getItemSizeAtIndex(ctx, index);
3235
+ scrollTo(ctx, {
3236
+ animated: false,
3237
+ forceScroll,
3238
+ index: index !== void 0 && index >= 0 ? index : void 0,
3239
+ isInitialScroll: true,
3240
+ itemSize,
3241
+ offset: resolvedOffset,
3242
+ precomputedWithViewOffset: true,
3243
+ viewOffset: target.viewOffset,
3244
+ viewPosition: target.viewPosition,
3245
+ waitForInitialScrollCompletionFrame: waitForCompletionFrame
3246
+ });
3171
3247
  }
3172
- function clearPendingInitialScrollFooterLayout(ctx, options) {
3173
- const { dataLength, stylePaddingBottom, target } = options;
3174
- const state = ctx.state;
3175
- if (!shouldPreserveInitialScrollForFooterLayout(target)) {
3176
- return;
3248
+ function setInitialScrollTarget(state, target, options) {
3249
+ var _a3;
3250
+ state.clearPreservedInitialScrollOnNextFinish = void 0;
3251
+ if (state.timeoutPreservedInitialScrollClear !== void 0) {
3252
+ clearTimeout(state.timeoutPreservedInitialScrollClear);
3253
+ state.timeoutPreservedInitialScrollClear = void 0;
3177
3254
  }
3178
- const clearedFooterTarget = createInitialScrollAtEndTarget({
3179
- dataLength,
3180
- footerSize: 0,
3181
- preserveForFooterLayout: void 0,
3182
- stylePaddingBottom
3255
+ state.initialScroll = target;
3256
+ if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
3257
+ state.didFinishInitialScroll = false;
3258
+ }
3259
+ setInitialScrollSession(state, {
3260
+ kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
3183
3261
  });
3184
- setInitialScrollTarget(state, clearedFooterTarget);
3185
- }
3186
- function clearFinishedViewportRetargetableInitialScroll(state) {
3187
- clearPreservedInitialScrollTarget(state);
3188
3262
  }
3189
- function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3263
+ function resolveInitialScrollOffset(ctx, initialScroll) {
3264
+ var _a3, _b;
3190
3265
  const state = ctx.state;
3191
- if (!state.didFinishInitialScroll) {
3192
- return false;
3266
+ if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
3267
+ return (_b = initialScroll.contentOffset) != null ? _b : 0;
3193
3268
  }
3194
- const currentOffset = getObservedBootstrapInitialScrollOffset(state);
3195
- return Math.abs(currentOffset - resolveInitialScrollOffset(ctx, target)) > epsilon;
3196
- }
3197
- function getObservedBootstrapInitialScrollOffset(state) {
3198
- var _a3, _b, _c, _d;
3199
- const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3200
- return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3269
+ const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
3270
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
3271
+ return clampScrollOffset(ctx, resolvedOffset, initialScroll);
3201
3272
  }
3202
- function getPreservedEndAnchorOffsetDiff(ctx) {
3203
- var _a3;
3204
- const state = ctx.state;
3273
+ function getAdvanceableInitialScrollState(state, options) {
3274
+ const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
3205
3275
  const initialScroll = state.initialScroll;
3206
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3207
- return;
3276
+ const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
3277
+ const shouldWaitForInitialLayout = !!(options == null ? void 0 : options.requiresMeasuredLayout) && !queuedInitialLayout && !isInitialScrollInProgress;
3278
+ if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll || scrollingTo && !isInitialScrollInProgress) {
3279
+ return void 0;
3208
3280
  }
3209
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3210
- return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3281
+ return {
3282
+ initialScroll,
3283
+ isInitialScrollInProgress,
3284
+ queuedInitialLayout,
3285
+ scrollingTo
3286
+ };
3211
3287
  }
3212
- function schedulePreservedEndAnchorCorrection(ctx) {
3213
- if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3288
+ function advanceMeasuredInitialScroll(ctx, options) {
3289
+ var _a3, _b, _c;
3290
+ const state = ctx.state;
3291
+ const advanceableState = getAdvanceableInitialScrollState(state, {
3292
+ requiresMeasuredLayout: true
3293
+ });
3294
+ if (!advanceableState) {
3214
3295
  return false;
3215
3296
  }
3216
- const correction = {};
3217
- schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3218
- return true;
3219
- }
3220
- function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3221
- const state = ctx.state;
3222
- state.preservedEndAnchorCorrection = correction;
3223
- requestAnimationFrame(() => {
3224
- var _a3;
3225
- const activeCorrection = state.preservedEndAnchorCorrection;
3226
- if (activeCorrection !== correction) {
3227
- return;
3228
- }
3229
- const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3230
- if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3231
- state.preservedEndAnchorCorrection = void 0;
3232
- return;
3233
- }
3234
- const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3235
- if (hasObservedNativeScrollAfterRequest) {
3236
- activeCorrection.lastRequestTime = Date.now();
3237
- requestAdjust(ctx, offsetDiff);
3238
- }
3239
- schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3297
+ const { initialScroll, isInitialScrollInProgress, queuedInitialLayout } = advanceableState;
3298
+ const scrollingTo = isInitialScrollInProgress ? advanceableState.scrollingTo : void 0;
3299
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3300
+ const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
3301
+ const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
3302
+ const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
3303
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
3304
+ if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
3305
+ return false;
3306
+ }
3307
+ if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
3308
+ return false;
3309
+ }
3310
+ if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
3311
+ setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
3312
+ }
3313
+ const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
3314
+ dispatchInitialScroll(ctx, {
3315
+ forceScroll,
3316
+ resolvedOffset,
3317
+ target: initialScroll
3240
3318
  });
3319
+ return true;
3241
3320
  }
3242
- function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3321
+ function advanceOffsetInitialScroll(ctx, options) {
3243
3322
  var _a3, _b;
3244
3323
  const state = ctx.state;
3245
- const initialScroll = state.initialScroll;
3246
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3247
- return;
3324
+ const advanceableState = getAdvanceableInitialScrollState(state);
3325
+ if (!advanceableState) {
3326
+ return false;
3248
3327
  }
3249
- if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3250
- const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3251
- if (!shouldKeepEndTargetAlive) {
3252
- if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3253
- clearPendingInitialScrollFooterLayout(ctx, {
3254
- dataLength: state.props.data.length,
3255
- stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3256
- target: initialScroll
3257
- });
3258
- } else {
3259
- clearFinishedViewportRetargetableInitialScroll(state);
3260
- }
3261
- }
3328
+ const { initialScroll, queuedInitialLayout } = advanceableState;
3329
+ const resolvedOffset = (_a3 = initialScroll.contentOffset) != null ? _a3 : 0;
3330
+ const isAlreadyAtDesiredInitialTarget = Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
3331
+ if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
3332
+ return false;
3262
3333
  }
3334
+ const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
3335
+ const forceScroll = (_b = options == null ? void 0 : options.forceScroll) != null ? _b : hasMeasuredScrollLayout || !!queuedInitialLayout;
3336
+ dispatchInitialScroll(ctx, {
3337
+ forceScroll,
3338
+ resolvedOffset,
3339
+ target: initialScroll
3340
+ });
3341
+ return true;
3263
3342
  }
3264
- function startBootstrapInitialScrollOnMount(ctx, options) {
3265
- var _a3, _b, _c;
3266
- const { initialScrollAtEnd, target } = options;
3267
- const state = ctx.state;
3268
- const offset = resolveInitialScrollOffset(ctx, target);
3269
- const shouldFinishAtOrigin = offset === 0 && !initialScrollAtEnd && (isOffsetInitialScrollSession(state) ? Math.abs((_a3 = target.contentOffset) != null ? _a3 : 0) <= 1 : target.index === 0 && ((_b = target.viewPosition) != null ? _b : 0) === 0 && Math.abs((_c = target.viewOffset) != null ? _c : 0) <= 1);
3270
- const shouldFinishWithPreservedTarget = state.props.data.length === 0 && target.index !== void 0;
3271
- if (shouldFinishAtOrigin) {
3272
- clearBootstrapInitialScrollSession(state);
3273
- finishInitialScroll(ctx, {
3274
- resolvedOffset: offset
3275
- });
3276
- } else if (shouldFinishWithPreservedTarget) {
3277
- clearBootstrapInitialScrollSession(state);
3278
- finishInitialScroll(ctx, {
3279
- preserveTarget: true,
3280
- resolvedOffset: offset
3281
- });
3282
- } else {
3283
- startBootstrapInitialScrollSession(state, {
3284
- scroll: offset,
3285
- seedContentOffset: 0 ,
3286
- targetIndexSeed: target.index
3287
- });
3288
- ensureBootstrapInitialScrollFrameTicker(ctx);
3289
- }
3343
+ function advanceCurrentInitialScrollSession(ctx, options) {
3344
+ var _a3;
3345
+ return ((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? advanceOffsetInitialScroll(ctx, {
3346
+ forceScroll: options == null ? void 0 : options.forceScroll
3347
+ }) : advanceMeasuredInitialScroll(ctx, {
3348
+ forceScroll: options == null ? void 0 : options.forceScroll
3349
+ });
3290
3350
  }
3291
- function handleBootstrapInitialScrollDataChange(ctx, options) {
3292
- const { dataLength, didDataChange, initialScrollAtEnd, previousDataLength, stylePaddingBottom } = options;
3293
- const state = ctx.state;
3294
- const initialScroll = state.initialScroll;
3295
- if (isOffsetInitialScrollSession(state) || !initialScroll) {
3296
- return;
3351
+
3352
+ // src/utils/checkAllSizesKnown.ts
3353
+ function isNullOrUndefined2(value) {
3354
+ return value === null || value === void 0;
3355
+ }
3356
+ function getMountedIndicesInRange(state, start, end) {
3357
+ if (!isNullOrUndefined2(end) && !isNullOrUndefined2(start) && start >= 0 && end >= 0) {
3358
+ return Array.from(state.containerItemKeys.keys()).map((key) => state.indexByKey.get(key)).filter((index) => index !== void 0 && index >= start && index <= end).sort((a, b) => a - b);
3297
3359
  }
3298
- const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
3299
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3300
- const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3301
- if (shouldClearFinishedResizePreservation) {
3302
- clearPreservedInitialScrollTarget(state);
3303
- return;
3360
+ return [];
3361
+ }
3362
+ function getMountedBufferedIndices(state) {
3363
+ return getMountedIndicesInRange(state, state.startBuffered, state.endBuffered);
3364
+ }
3365
+ function getMountedNoBufferIndices(state) {
3366
+ return getMountedIndicesInRange(state, state.startNoBuffer, state.endNoBuffer);
3367
+ }
3368
+ function checkAllSizesKnown(state, indices) {
3369
+ return indices.length > 0 && indices.every((index) => {
3370
+ const key = getId(state, index);
3371
+ return key !== void 0 && state.sizesKnown.has(key);
3372
+ });
3373
+ }
3374
+
3375
+ // src/core/bootstrapInitialScroll.ts
3376
+ var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
3377
+ var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
3378
+ var DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES = 24;
3379
+ var BOOTSTRAP_REVEAL_ABORT_WARNING = "LegendList bootstrap initial scroll aborted after exceeding convergence bounds.";
3380
+ function getBootstrapInitialScrollSession(state) {
3381
+ var _a3;
3382
+ return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
3383
+ }
3384
+ function isOffsetInitialScrollSession(state) {
3385
+ var _a3;
3386
+ return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
3387
+ }
3388
+ function doVisibleIndicesMatch(previous, next) {
3389
+ if (!previous || previous.length !== next.length) {
3390
+ return false;
3304
3391
  }
3305
- const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
3306
- if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
3307
- return;
3392
+ for (let i = 0; i < previous.length; i++) {
3393
+ if (previous[i] !== next[i]) {
3394
+ return false;
3395
+ }
3308
3396
  }
3309
- if (shouldRetargetBottomAligned) {
3310
- const updatedInitialScroll = initialScrollAtEnd ? createInitialScrollAtEndTarget({
3311
- dataLength,
3312
- footerSize: peek$(ctx, "footerSize") || 0,
3313
- preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3314
- stylePaddingBottom
3315
- }) : createRetargetedBottomAlignedInitialScroll({
3316
- dataLength,
3317
- footerSize: peek$(ctx, "footerSize") || 0,
3318
- initialScrollAtEnd,
3319
- stylePaddingBottom,
3320
- target: initialScroll
3321
- });
3322
- if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3323
- clearPendingInitialScrollFooterLayout(ctx, {
3324
- dataLength,
3325
- stylePaddingBottom,
3326
- target: initialScroll
3327
- });
3328
- return;
3397
+ return true;
3398
+ }
3399
+ function getBootstrapRevealVisibleIndices(options) {
3400
+ const { dataLength, getSize, offset, positions, scrollLength, startIndex: requestedStartIndex } = options;
3401
+ const endOffset = offset + scrollLength;
3402
+ const visibleIndices = [];
3403
+ let index = requestedStartIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, requestedStartIndex)) : 0;
3404
+ while (index > 0) {
3405
+ const previousIndex = index - 1;
3406
+ const previousPosition = positions[previousIndex];
3407
+ if (previousPosition === void 0) {
3408
+ index = previousIndex;
3409
+ continue;
3329
3410
  }
3330
- if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
3331
- setInitialScrollTarget(state, updatedInitialScroll, {
3332
- resetDidFinish: shouldResetDidFinish
3333
- });
3334
- rearmBootstrapInitialScroll(ctx, {
3335
- scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3336
- seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3337
- targetIndexSeed: updatedInitialScroll.index
3338
- });
3339
- return;
3411
+ const previousSize = getSize(previousIndex);
3412
+ if (previousSize === void 0) {
3413
+ index = previousIndex;
3414
+ continue;
3340
3415
  }
3416
+ if (previousPosition + previousSize <= offset) {
3417
+ break;
3418
+ }
3419
+ index = previousIndex;
3341
3420
  }
3342
- if (!didDataChange) {
3343
- return;
3344
- }
3345
- if (bootstrapInitialScroll || shouldResetDidFinish) {
3346
- setInitialScrollTarget(state, initialScroll, {
3347
- resetDidFinish: shouldResetDidFinish
3348
- });
3349
- rearmBootstrapInitialScroll(ctx, {
3350
- scroll: resolveInitialScrollOffset(ctx, initialScroll),
3351
- seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3352
- targetIndexSeed: initialScroll.index
3353
- });
3421
+ for (; index < dataLength; index++) {
3422
+ const position = positions[index];
3423
+ if (position === void 0) {
3424
+ continue;
3425
+ }
3426
+ const size = getSize(index);
3427
+ if (size === void 0) {
3428
+ continue;
3429
+ }
3430
+ if (position < endOffset && position + size > offset) {
3431
+ visibleIndices.push(index);
3432
+ } else if (visibleIndices.length > 0 && position >= endOffset) {
3433
+ break;
3434
+ }
3354
3435
  }
3436
+ return visibleIndices;
3355
3437
  }
3356
- function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3357
- const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom } = options;
3358
- const state = ctx.state;
3359
- if (!initialScrollAtEnd) {
3360
- return;
3361
- }
3362
- const initialScroll = state.initialScroll;
3363
- if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
3364
- return;
3365
- }
3366
- const shouldProcessFooterLayout = !!getBootstrapInitialScrollSession(state) || shouldPreserveInitialScrollForFooterLayout(initialScroll);
3367
- if (!shouldProcessFooterLayout) {
3368
- return;
3369
- }
3370
- if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3371
- clearPendingInitialScrollFooterLayout(ctx, {
3372
- dataLength,
3373
- stylePaddingBottom,
3374
- target: initialScroll
3375
- });
3376
- } else {
3377
- const updatedInitialScroll = createInitialScrollAtEndTarget({
3378
- dataLength,
3379
- footerSize,
3380
- preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3381
- stylePaddingBottom
3382
- });
3383
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3384
- if (!didTargetChange) {
3385
- clearPendingInitialScrollFooterLayout(ctx, {
3386
- dataLength,
3387
- stylePaddingBottom,
3388
- target: initialScroll
3389
- });
3390
- } else {
3391
- const didFinishInitialScroll = !!state.didFinishInitialScroll;
3392
- setInitialScrollTarget(state, updatedInitialScroll, {
3393
- resetDidFinish: didFinishInitialScroll
3394
- });
3395
- rearmBootstrapInitialScroll(ctx, {
3396
- scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3397
- targetIndexSeed: updatedInitialScroll.index
3398
- });
3399
- }
3400
- }
3438
+ function shouldAbortBootstrapReveal(options) {
3439
+ const {
3440
+ mountFrameCount,
3441
+ maxFrames = DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES,
3442
+ maxPasses = DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES,
3443
+ passCount
3444
+ } = options;
3445
+ return mountFrameCount >= maxFrames || passCount >= maxPasses;
3401
3446
  }
3402
- function handleBootstrapInitialScrollLayoutChange(ctx) {
3403
- var _a3, _b, _c;
3404
- const state = ctx.state;
3405
- const initialScroll = state.initialScroll;
3406
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3407
- if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3408
- const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3409
- const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
3410
- if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
3411
- const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3412
- const offsetDiff = resolvedOffset - currentOffset;
3413
- if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3414
- if (state.didFinishInitialScroll) {
3415
- schedulePreservedEndAnchorCorrection(ctx);
3416
- } else if (scrollingTo) {
3417
- const existingWatchdog = initialScrollWatchdog.get(state);
3418
- scrollingTo.offset = resolvedOffset;
3419
- scrollingTo.targetOffset = resolvedOffset;
3420
- state.initialScroll = {
3421
- ...initialScroll,
3422
- contentOffset: resolvedOffset
3423
- };
3424
- state.hasScrolled = false;
3425
- initialScrollWatchdog.set(state, {
3426
- startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3427
- targetOffset: resolvedOffset
3428
- });
3429
- requestAdjust(ctx, offsetDiff);
3430
- }
3431
- }
3432
- } else {
3433
- rearmBootstrapInitialScroll(ctx, {
3434
- scroll: resolvedOffset,
3435
- targetIndexSeed: initialScroll.index
3436
- });
3437
- }
3447
+ function abortBootstrapRevealIfNeeded(ctx, options) {
3448
+ if (!shouldAbortBootstrapReveal(options)) {
3449
+ return false;
3438
3450
  }
3451
+ if (IS_DEV) {
3452
+ console.warn(BOOTSTRAP_REVEAL_ABORT_WARNING);
3453
+ }
3454
+ abortBootstrapInitialScroll(ctx);
3455
+ return true;
3439
3456
  }
3440
- function evaluateBootstrapInitialScroll(ctx) {
3441
- var _a3, _b;
3442
- const state = ctx.state;
3457
+ function clearBootstrapInitialScrollSession(state) {
3458
+ var _a3;
3443
3459
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3444
- const initialScroll = state.initialScroll;
3445
- if (!bootstrapInitialScroll || !initialScroll || isOffsetInitialScrollSession(state) || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll)) {
3446
- return;
3447
- }
3448
- bootstrapInitialScroll.passCount += 1;
3449
- if (abortBootstrapRevealIfNeeded(ctx, {
3450
- mountFrameCount: bootstrapInitialScroll.mountFrameCount,
3451
- passCount: bootstrapInitialScroll.passCount
3452
- })) {
3453
- return;
3460
+ const frameHandle = bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle;
3461
+ if (frameHandle !== void 0 && typeof cancelAnimationFrame === "function") {
3462
+ cancelAnimationFrame(frameHandle);
3454
3463
  }
3455
- if (initialScroll.index !== void 0 && state.startBuffered >= 0 && state.endBuffered >= 0 && initialScroll.index >= state.startBuffered && initialScroll.index <= state.endBuffered) {
3456
- bootstrapInitialScroll.targetIndexSeed = void 0;
3464
+ if (bootstrapInitialScroll) {
3465
+ bootstrapInitialScroll.frameHandle = void 0;
3457
3466
  }
3458
- const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3459
- const mountedBufferedIndices = getMountedBufferedIndices(state);
3460
- const areMountedBufferedIndicesMeasured = checkAllSizesKnown(state, mountedBufferedIndices);
3461
- const didResolvedOffsetChange = Math.abs(bootstrapInitialScroll.scroll - resolvedOffset) > 1;
3462
- const { data } = state.props;
3463
- const visibleIndices = getBootstrapRevealVisibleIndices({
3464
- dataLength: data.length,
3465
- getSize: (index) => {
3466
- var _a4, _b2;
3467
- const id = (_a4 = state.idCache[index]) != null ? _a4 : getId(state, index);
3468
- return (_b2 = state.sizes.get(id)) != null ? _b2 : getItemSize(ctx, id, index, data[index]);
3469
- },
3470
- offset: resolvedOffset,
3471
- positions: state.positions,
3472
- scrollLength: state.scrollLength,
3473
- startIndex: (_b = bootstrapInitialScroll.targetIndexSeed) != null ? _b : state.startBuffered >= 0 ? state.startBuffered : void 0
3467
+ setInitialScrollSession(state, {
3468
+ bootstrap: null,
3469
+ kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
3474
3470
  });
3475
- const areVisibleIndicesMeasured = visibleIndices.length > 0 && visibleIndices.every((index) => {
3476
- var _a4;
3477
- const id = (_a4 = state.idCache[index]) != null ? _a4 : getId(state, index);
3478
- return state.sizesKnown.has(id);
3471
+ }
3472
+ function startBootstrapInitialScrollSession(state, options) {
3473
+ var _a3, _b, _c;
3474
+ const previousBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3475
+ setInitialScrollSession(state, {
3476
+ bootstrap: {
3477
+ frameHandle: previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.frameHandle,
3478
+ // Re-arming during the initial mount should spend from the same watchdog budget.
3479
+ mountFrameCount: (_a3 = previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.mountFrameCount) != null ? _a3 : 0,
3480
+ passCount: 0,
3481
+ previousResolvedOffset: void 0,
3482
+ scroll: options.scroll,
3483
+ seedContentOffset: (_c = (_b = options.seedContentOffset) != null ? _b : previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.seedContentOffset) != null ? _c : options.scroll,
3484
+ targetIndexSeed: options.targetIndexSeed,
3485
+ visibleIndices: void 0
3486
+ },
3487
+ kind: "bootstrap"
3479
3488
  });
3480
- const previousResolvedOffset = bootstrapInitialScroll.previousResolvedOffset;
3481
- const previousVisibleIndices = bootstrapInitialScroll.visibleIndices;
3482
- bootstrapInitialScroll.previousResolvedOffset = resolvedOffset;
3483
- bootstrapInitialScroll.visibleIndices = visibleIndices;
3484
- if (didResolvedOffsetChange) {
3485
- bootstrapInitialScroll.scroll = resolvedOffset;
3486
- queueBootstrapInitialScrollReevaluation(state);
3487
- return;
3488
- }
3489
- if (!areMountedBufferedIndicesMeasured || !areVisibleIndicesMeasured) {
3490
- return;
3491
- }
3492
- const didRevealSettle = previousResolvedOffset !== void 0 && Math.abs(previousResolvedOffset - resolvedOffset) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON && doVisibleIndicesMatch(previousVisibleIndices, visibleIndices);
3493
- if (!didRevealSettle) {
3494
- queueBootstrapInitialScrollReevaluation(state);
3495
- return;
3496
- }
3497
- {
3498
- clearBootstrapInitialScrollSession(state);
3499
- dispatchInitialScroll(ctx, {
3500
- forceScroll: true,
3501
- resolvedOffset,
3502
- target: initialScroll,
3503
- waitForCompletionFrame: Platform.OS === "web"
3489
+ }
3490
+ function resetBootstrapInitialScrollSession(state, options) {
3491
+ var _a3, _b, _c;
3492
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3493
+ if (!bootstrapInitialScroll) {
3494
+ if ((options == null ? void 0 : options.scroll) !== void 0) {
3495
+ startBootstrapInitialScrollSession(state, {
3496
+ scroll: options.scroll,
3497
+ seedContentOffset: options.seedContentOffset,
3498
+ targetIndexSeed: options.targetIndexSeed
3499
+ });
3500
+ }
3501
+ } else {
3502
+ bootstrapInitialScroll.passCount = 0;
3503
+ bootstrapInitialScroll.previousResolvedOffset = void 0;
3504
+ bootstrapInitialScroll.scroll = (_a3 = options == null ? void 0 : options.scroll) != null ? _a3 : bootstrapInitialScroll.scroll;
3505
+ bootstrapInitialScroll.seedContentOffset = (_b = options == null ? void 0 : options.seedContentOffset) != null ? _b : bootstrapInitialScroll.seedContentOffset;
3506
+ bootstrapInitialScroll.targetIndexSeed = (_c = options == null ? void 0 : options.targetIndexSeed) != null ? _c : bootstrapInitialScroll.targetIndexSeed;
3507
+ bootstrapInitialScroll.visibleIndices = void 0;
3508
+ setInitialScrollSession(state, {
3509
+ bootstrap: bootstrapInitialScroll,
3510
+ kind: "bootstrap"
3504
3511
  });
3505
3512
  }
3506
3513
  }
3507
- function finishBootstrapInitialScrollWithoutScroll(ctx, resolvedOffset) {
3508
- var _a3;
3509
- const state = ctx.state;
3510
- clearBootstrapInitialScrollSession(state);
3511
- const shouldPreserveResizeTarget = !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_a3 = state.initialScroll) == null ? void 0 : _a3.viewPosition) === 1;
3512
- finishInitialScroll(ctx, {
3513
- preserveTarget: shouldPreserveResizeTarget,
3514
- recalculateItems: true,
3515
- resolvedOffset,
3516
- schedulePreservedTargetClear: shouldPreserveResizeTarget
3514
+ function queueBootstrapInitialScrollReevaluation(state) {
3515
+ requestAnimationFrame(() => {
3516
+ var _a3;
3517
+ if (getBootstrapInitialScrollSession(state)) {
3518
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
3519
+ }
3517
3520
  });
3518
3521
  }
3519
- function abortBootstrapInitialScroll(ctx) {
3520
- var _a3, _b, _c, _d;
3522
+ function ensureBootstrapInitialScrollFrameTicker(ctx) {
3521
3523
  const state = ctx.state;
3522
3524
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3523
- const initialScroll = state.initialScroll;
3524
- if (bootstrapInitialScroll && initialScroll && !isOffsetInitialScrollSession(state) && state.refScroller.current) {
3525
- clearBootstrapInitialScrollSession(state);
3526
- dispatchInitialScroll(ctx, {
3527
- forceScroll: true,
3528
- resolvedOffset: bootstrapInitialScroll.scroll,
3529
- target: initialScroll,
3530
- waitForCompletionFrame: Platform.OS === "web"
3531
- });
3532
- } else {
3533
- finishBootstrapInitialScrollWithoutScroll(
3534
- ctx,
3535
- (_d = (_c = (_b = (_a3 = getBootstrapInitialScrollSession(state)) == null ? void 0 : _a3.scroll) != null ? _b : state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0
3536
- );
3525
+ if (!bootstrapInitialScroll || bootstrapInitialScroll.frameHandle !== void 0) {
3526
+ return;
3537
3527
  }
3538
- }
3539
-
3540
- // src/core/checkFinishedScroll.ts
3541
- var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
3542
- var INITIAL_SCROLL_COMPLETION_TARGET_EPSILON = 1;
3543
- var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
3544
- var SILENT_INITIAL_SCROLL_RETRY_DELAY_MS = 16;
3545
- function checkFinishedScroll(ctx, options) {
3546
- const scrollingTo = ctx.state.scrollingTo;
3547
- if (options == null ? void 0 : options.onlyIfAligned) {
3548
- if (!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || scrollingTo.animated) {
3528
+ const tick = () => {
3529
+ const activeBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3530
+ if (!activeBootstrapInitialScroll) {
3549
3531
  return;
3550
3532
  }
3551
- if (!getResolvedScrollCompletionState(ctx, scrollingTo).isAtResolvedTarget) {
3533
+ activeBootstrapInitialScroll.frameHandle = void 0;
3534
+ activeBootstrapInitialScroll.mountFrameCount += 1;
3535
+ if (abortBootstrapRevealIfNeeded(ctx, {
3536
+ mountFrameCount: activeBootstrapInitialScroll.mountFrameCount,
3537
+ passCount: activeBootstrapInitialScroll.passCount
3538
+ })) {
3552
3539
  return;
3553
3540
  }
3554
- }
3555
- ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3541
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3542
+ };
3543
+ bootstrapInitialScroll.frameHandle = requestAnimationFrame(tick);
3556
3544
  }
3557
- function hasScrollCompletionOwnership(state, options) {
3558
- const { clampedTargetOffset, scrollingTo } = options;
3559
- return !scrollingTo.isInitialScroll || state.hasScrolled || clampedTargetOffset <= INITIAL_SCROLL_COMPLETION_TARGET_EPSILON;
3560
- }
3561
- function isSilentInitialDispatch(state, scrollingTo) {
3562
- return !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled;
3545
+ function rearmBootstrapInitialScroll(ctx, options) {
3546
+ resetBootstrapInitialScrollSession(ctx.state, options);
3547
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3548
+ queueBootstrapInitialScrollReevaluation(ctx.state);
3563
3549
  }
3564
- function getInitialScrollWatchdogTargetOffset(state) {
3565
- var _a3;
3566
- return (_a3 = initialScrollWatchdog.get(state)) == null ? void 0 : _a3.targetOffset;
3550
+ function createInitialScrollAtEndTarget(options) {
3551
+ const { dataLength, footerSize, preserveForFooterLayout, stylePaddingBottom } = options;
3552
+ return {
3553
+ contentOffset: void 0,
3554
+ index: Math.max(0, dataLength - 1),
3555
+ preserveForBottomPadding: true,
3556
+ preserveForFooterLayout,
3557
+ viewOffset: -stylePaddingBottom - footerSize,
3558
+ viewPosition: 1
3559
+ };
3567
3560
  }
3568
- function isNativeInitialNonZeroTarget(state) {
3569
- const targetOffset = getInitialScrollWatchdogTargetOffset(state);
3570
- return !state.didFinishInitialScroll && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
3561
+ function shouldPreserveInitialScrollForBottomPadding(target) {
3562
+ return !!(target == null ? void 0 : target.preserveForBottomPadding);
3571
3563
  }
3572
- function shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) {
3573
- var _a3, _b;
3574
- if (!scrollingTo.isInitialScroll || scrollingTo.animated || !state.didContainersLayout) {
3575
- return false;
3576
- }
3577
- if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap") {
3578
- return false;
3579
- }
3580
- const targetOffset = (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset;
3581
- if (initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled) {
3582
- return false;
3583
- }
3584
- if (initialScrollWatchdog.isAtZeroTargetOffset(targetOffset) || Math.abs(state.scroll - targetOffset) > 1 || Math.abs(state.scrollPending - targetOffset) > 1) {
3585
- return false;
3586
- }
3587
- return !!scrollingTo.waitForInitialScrollCompletionFrame || isNativeInitialNonZeroTarget(state);
3564
+ function shouldPreserveInitialScrollForFooterLayout(target) {
3565
+ return !!(target == null ? void 0 : target.preserveForFooterLayout);
3588
3566
  }
3589
- function shouldFinishInitialZeroTargetScroll(ctx) {
3590
- var _a3;
3591
- const { state } = ctx;
3592
- 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;
3567
+ function isRetargetableBottomAlignedInitialScrollTarget(target) {
3568
+ return !!(target && target.viewPosition === 1 && (shouldPreserveInitialScrollForBottomPadding(target) || shouldPreserveInitialScrollForFooterLayout(target)));
3593
3569
  }
3594
- function getResolvedScrollCompletionState(ctx, scrollingTo) {
3595
- var _a3;
3596
- const { state } = ctx;
3597
- const scroll = state.scrollPending;
3598
- const adjust = state.scrollAdjustHandler.getAdjust();
3599
- const clampedTargetOffset = (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
3600
- const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
3601
- const diff1 = Math.abs(scroll - clampedTargetOffset);
3602
- const diff2 = Math.abs(diff1 - adjust);
3570
+ function createRetargetedBottomAlignedInitialScroll(options) {
3571
+ const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom, target } = options;
3572
+ const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
3603
3573
  return {
3604
- clampedTargetOffset,
3605
- isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || !scrollingTo.animated && diff2 < 1)
3574
+ ...target,
3575
+ contentOffset: void 0,
3576
+ index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
3577
+ preserveForBottomPadding: true,
3578
+ preserveForFooterLayout,
3579
+ viewOffset: -stylePaddingBottom - (preserveForFooterLayout ? footerSize : 0),
3580
+ viewPosition: 1
3606
3581
  };
3607
3582
  }
3608
- function checkFinishedScrollFrame(ctx) {
3609
- const scrollingTo = ctx.state.scrollingTo;
3610
- if (!scrollingTo) {
3583
+ function areEquivalentBootstrapInitialScrollTargets(current, next) {
3584
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
3585
+ }
3586
+ function clearPendingInitialScrollFooterLayout(ctx, options) {
3587
+ const { dataLength, stylePaddingBottom, target } = options;
3588
+ const state = ctx.state;
3589
+ if (!shouldPreserveInitialScrollForFooterLayout(target)) {
3611
3590
  return;
3612
3591
  }
3613
- const { state } = ctx;
3614
- state.animFrameCheckFinishedScroll = void 0;
3615
- const completionState = getResolvedScrollCompletionState(ctx, scrollingTo);
3616
- if (completionState.isAtResolvedTarget && hasScrollCompletionOwnership(state, {
3617
- clampedTargetOffset: completionState.clampedTargetOffset,
3618
- scrollingTo
3619
- })) {
3620
- finishScrollTo(ctx);
3621
- }
3622
- }
3623
- function scrollToFallbackOffset(ctx, offset) {
3624
- var _a3;
3625
- (_a3 = ctx.state.refScroller.current) == null ? void 0 : _a3.scrollTo({
3626
- animated: false,
3627
- x: ctx.state.props.horizontal ? offset : 0,
3628
- y: ctx.state.props.horizontal ? 0 : offset
3592
+ const clearedFooterTarget = createInitialScrollAtEndTarget({
3593
+ dataLength,
3594
+ footerSize: 0,
3595
+ preserveForFooterLayout: void 0,
3596
+ stylePaddingBottom
3629
3597
  });
3598
+ setInitialScrollTarget(state, clearedFooterTarget);
3630
3599
  }
3631
- function checkFinishedScrollFallback(ctx) {
3632
- const state = ctx.state;
3633
- const scrollingTo = state.scrollingTo;
3634
- const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
3635
- const silentInitialDispatch = isSilentInitialDispatch(state, scrollingTo);
3636
- const canFinishInitialWithoutNativeProgress = scrollingTo !== void 0 ? shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) : false;
3637
- const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget && !canFinishInitialWithoutNativeProgress || !state.didContainersLayout;
3638
- const initialDelay = shouldFinishInitialZeroTarget || canFinishInitialWithoutNativeProgress ? 0 : silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : slowTimeout ? 500 : 100;
3639
- state.timeoutCheckFinishedScrollFallback = setTimeout(() => {
3640
- let numChecks = 0;
3641
- const scheduleFallbackCheck = (delay) => {
3642
- state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, delay);
3643
- };
3644
- const checkHasScrolled = () => {
3645
- var _c, _d;
3646
- state.timeoutCheckFinishedScrollFallback = void 0;
3647
- const isStillScrollingTo = state.scrollingTo;
3648
- if (isStillScrollingTo) {
3649
- numChecks++;
3650
- const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
3651
- const maxChecks = silentInitialDispatch ? 5 : isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
3652
- const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
3653
- const canFinishInitialScrollWithoutNativeProgress = shouldFinishInitialScrollWithoutNativeProgress(
3654
- state,
3655
- isStillScrollingTo
3656
- );
3657
- const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
3658
- const canFinishAfterSilentNativeDispatch = Platform.OS === "android";
3659
- const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
3660
- const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
3661
- if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3662
- finishScrollTo(ctx);
3663
- } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
3664
- const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
3665
- scrollToFallbackOffset(ctx, targetOffset);
3666
- scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
3667
- } else {
3668
- scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
3669
- }
3670
- }
3671
- };
3672
- checkHasScrolled();
3673
- }, initialDelay);
3600
+ function clearFinishedViewportRetargetableInitialScroll(state) {
3601
+ clearPreservedInitialScrollTarget(state);
3674
3602
  }
3675
-
3676
- // src/core/initialScrollLifecycle.ts
3677
- function retargetActiveInitialScrollAtEnd(ctx) {
3678
- var _a3;
3603
+ function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3679
3604
  const state = ctx.state;
3680
- const initialScroll = state.initialScroll;
3681
- if (state.didFinishInitialScroll) {
3682
- return schedulePreservedEndAnchorCorrection(ctx);
3683
- }
3684
- if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3605
+ if (!state.didFinishInitialScroll) {
3685
3606
  return false;
3686
3607
  }
3687
- return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3608
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
3609
+ return Math.abs(currentOffset - resolveInitialScrollOffset(ctx, target)) > epsilon;
3688
3610
  }
3689
- function handleInitialScrollLayoutReady(ctx) {
3611
+ function getObservedBootstrapInitialScrollOffset(state) {
3612
+ var _a3, _b, _c, _d;
3613
+ const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3614
+ return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3615
+ }
3616
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3690
3617
  var _a3;
3691
- if (!ctx.state.initialScroll) {
3618
+ const state = ctx.state;
3619
+ const initialScroll = state.initialScroll;
3620
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3692
3621
  return;
3693
3622
  }
3694
- const runScroll = () => advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3695
- runScroll();
3696
- if (((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset") {
3697
- requestAnimationFrame(runScroll);
3623
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3624
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3625
+ }
3626
+ function schedulePreservedEndAnchorCorrection(ctx) {
3627
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3628
+ return false;
3698
3629
  }
3699
- checkFinishedScroll(ctx, { onlyIfAligned: true });
3630
+ const correction = {};
3631
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3632
+ return true;
3700
3633
  }
3701
- function initializeInitialScrollOnMount(ctx, options) {
3634
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3635
+ const state = ctx.state;
3636
+ state.preservedEndAnchorCorrection = correction;
3637
+ requestAnimationFrame(() => {
3638
+ var _a3;
3639
+ const activeCorrection = state.preservedEndAnchorCorrection;
3640
+ if (activeCorrection !== correction) {
3641
+ return;
3642
+ }
3643
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3644
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3645
+ state.preservedEndAnchorCorrection = void 0;
3646
+ return;
3647
+ }
3648
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3649
+ if (hasObservedNativeScrollAfterRequest) {
3650
+ activeCorrection.lastRequestTime = Date.now();
3651
+ requestAdjust(ctx, offsetDiff);
3652
+ }
3653
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3654
+ });
3655
+ }
3656
+ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3702
3657
  var _a3, _b;
3703
- const {
3704
- alwaysDispatchInitialScroll,
3705
- dataLength,
3706
- hasFooterComponent,
3707
- initialContentOffset,
3708
- initialScrollAtEnd,
3709
- useBootstrapInitialScroll
3710
- } = options;
3711
3658
  const state = ctx.state;
3712
3659
  const initialScroll = state.initialScroll;
3713
- const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
3714
- const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
3715
- if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
3716
- setInitialScrollTarget(state, {
3717
- ...initialScroll,
3718
- contentOffset: resolvedInitialContentOffset,
3719
- preserveForFooterLayout
3720
- });
3721
- }
3722
- if (useBootstrapInitialScroll && initialScroll && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
3723
- startBootstrapInitialScrollOnMount(ctx, {
3724
- initialScrollAtEnd,
3725
- target: state.initialScroll
3726
- });
3660
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3727
3661
  return;
3728
3662
  }
3729
- const hasPendingDataDependentInitialScroll = !!initialScroll && dataLength === 0 && !(resolvedInitialContentOffset === 0 && !initialScrollAtEnd);
3730
- if (!alwaysDispatchInitialScroll && !resolvedInitialContentOffset && !hasPendingDataDependentInitialScroll) {
3731
- if (initialScroll && !initialScrollAtEnd) {
3732
- finishInitialScroll(ctx, {
3733
- resolvedOffset: resolvedInitialContentOffset
3734
- });
3735
- } else {
3736
- setInitialRenderState(ctx, { didInitialScroll: true });
3663
+ if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3664
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3665
+ if (!shouldKeepEndTargetAlive) {
3666
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3667
+ clearPendingInitialScrollFooterLayout(ctx, {
3668
+ dataLength: state.props.data.length,
3669
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3670
+ target: initialScroll
3671
+ });
3672
+ } else {
3673
+ clearFinishedViewportRetargetableInitialScroll(state);
3674
+ }
3737
3675
  }
3738
3676
  }
3739
3677
  }
3740
- function handleInitialScrollDataChange(ctx, options) {
3678
+ function startBootstrapInitialScrollOnMount(ctx, options) {
3741
3679
  var _a3, _b, _c;
3742
- const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
3680
+ const { initialScrollAtEnd, target } = options;
3743
3681
  const state = ctx.state;
3744
- const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3745
- if (state.initialScrollSession) {
3746
- state.initialScrollSession.previousDataLength = dataLength;
3747
- }
3748
- setInitialScrollSession(state);
3749
- if (useBootstrapInitialScroll) {
3750
- handleBootstrapInitialScrollDataChange(ctx, {
3751
- dataLength,
3752
- didDataChange,
3753
- initialScrollAtEnd,
3754
- previousDataLength,
3755
- stylePaddingBottom
3682
+ const offset = resolveInitialScrollOffset(ctx, target);
3683
+ const shouldFinishAtOrigin = offset === 0 && !initialScrollAtEnd && (isOffsetInitialScrollSession(state) ? Math.abs((_a3 = target.contentOffset) != null ? _a3 : 0) <= 1 : target.index === 0 && ((_b = target.viewPosition) != null ? _b : 0) === 0 && Math.abs((_c = target.viewOffset) != null ? _c : 0) <= 1);
3684
+ const shouldFinishWithPreservedTarget = state.props.data.length === 0 && target.index !== void 0;
3685
+ if (shouldFinishAtOrigin) {
3686
+ clearBootstrapInitialScrollSession(state);
3687
+ finishInitialScroll(ctx, {
3688
+ resolvedOffset: offset
3756
3689
  });
3757
- return;
3758
- }
3759
- const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3760
- if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3761
- return;
3762
- }
3763
- if (shouldReplayFinishedOffsetInitialScroll) {
3764
- state.didFinishInitialScroll = false;
3690
+ } else if (shouldFinishWithPreservedTarget) {
3691
+ clearBootstrapInitialScrollSession(state);
3692
+ finishInitialScroll(ctx, {
3693
+ preserveTarget: true,
3694
+ resolvedOffset: offset
3695
+ });
3696
+ } else {
3697
+ startBootstrapInitialScrollSession(state, {
3698
+ scroll: offset,
3699
+ seedContentOffset: 0 ,
3700
+ targetIndexSeed: target.index
3701
+ });
3702
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3765
3703
  }
3766
- advanceCurrentInitialScrollSession(ctx);
3767
3704
  }
3768
-
3769
- // src/core/mvcp.ts
3770
- var MVCP_POSITION_EPSILON = 0.1;
3771
- var MVCP_ANCHOR_LOCK_TTL_MS = 300;
3772
- var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
3773
- var NATIVE_END_CLAMP_EPSILON = 1;
3774
- function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
3775
- if (!enableMVCPAnchorLock) {
3776
- state.mvcpAnchorLock = void 0;
3777
- return void 0;
3705
+ function handleBootstrapInitialScrollDataChange(ctx, options) {
3706
+ const { dataLength, didDataChange, initialScrollAtEnd, previousDataLength, stylePaddingBottom } = options;
3707
+ const state = ctx.state;
3708
+ const initialScroll = state.initialScroll;
3709
+ if (isOffsetInitialScrollSession(state) || !initialScroll) {
3710
+ return;
3778
3711
  }
3779
- const lock = state.mvcpAnchorLock;
3780
- if (!lock) {
3781
- return void 0;
3712
+ const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
3713
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3714
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3715
+ if (shouldClearFinishedResizePreservation) {
3716
+ clearPreservedInitialScrollTarget(state);
3717
+ return;
3782
3718
  }
3783
- const isExpired = now > lock.expiresAt;
3784
- const isMissing = state.indexByKey.get(lock.id) === void 0;
3785
- if (isExpired || isMissing || !mvcpData) {
3786
- state.mvcpAnchorLock = void 0;
3787
- return void 0;
3719
+ const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
3720
+ if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
3721
+ return;
3788
3722
  }
3789
- return lock;
3790
- }
3791
- function updateAnchorLock(state, params) {
3792
- {
3793
- const { anchorId, anchorPosition, dataChanged, now, positionDiff } = params;
3794
- const enableMVCPAnchorLock = !!dataChanged || !!state.mvcpAnchorLock;
3795
- const mvcpData = state.props.maintainVisibleContentPosition.data;
3796
- if (!enableMVCPAnchorLock || !mvcpData || state.scrollingTo || !anchorId || anchorPosition === void 0) {
3723
+ if (shouldRetargetBottomAligned) {
3724
+ const updatedInitialScroll = initialScrollAtEnd ? createInitialScrollAtEndTarget({
3725
+ dataLength,
3726
+ footerSize: peek$(ctx, "footerSize") || 0,
3727
+ preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3728
+ stylePaddingBottom
3729
+ }) : createRetargetedBottomAlignedInitialScroll({
3730
+ dataLength,
3731
+ footerSize: peek$(ctx, "footerSize") || 0,
3732
+ initialScrollAtEnd,
3733
+ stylePaddingBottom,
3734
+ target: initialScroll
3735
+ });
3736
+ if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3737
+ clearPendingInitialScrollFooterLayout(ctx, {
3738
+ dataLength,
3739
+ stylePaddingBottom,
3740
+ target: initialScroll
3741
+ });
3797
3742
  return;
3798
3743
  }
3799
- const existingLock = state.mvcpAnchorLock;
3800
- const quietPasses = !dataChanged && Math.abs(positionDiff) <= MVCP_POSITION_EPSILON && (existingLock == null ? void 0 : existingLock.id) === anchorId ? existingLock.quietPasses + 1 : 0;
3801
- if (!dataChanged && quietPasses >= MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE) {
3802
- state.mvcpAnchorLock = void 0;
3744
+ if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
3745
+ setInitialScrollTarget(state, updatedInitialScroll, {
3746
+ resetDidFinish: shouldResetDidFinish
3747
+ });
3748
+ rearmBootstrapInitialScroll(ctx, {
3749
+ scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3750
+ seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3751
+ targetIndexSeed: updatedInitialScroll.index
3752
+ });
3803
3753
  return;
3804
3754
  }
3805
- state.mvcpAnchorLock = {
3806
- expiresAt: now + MVCP_ANCHOR_LOCK_TTL_MS,
3807
- id: anchorId,
3808
- position: anchorPosition,
3809
- quietPasses
3810
- };
3811
3755
  }
3812
- }
3813
- function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
3814
- {
3815
- return false;
3756
+ if (!didDataChange) {
3757
+ return;
3758
+ }
3759
+ if (bootstrapInitialScroll || shouldResetDidFinish) {
3760
+ setInitialScrollTarget(state, initialScroll, {
3761
+ resetDidFinish: shouldResetDidFinish
3762
+ });
3763
+ rearmBootstrapInitialScroll(ctx, {
3764
+ scroll: resolveInitialScrollOffset(ctx, initialScroll),
3765
+ seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3766
+ targetIndexSeed: initialScroll.index
3767
+ });
3816
3768
  }
3817
3769
  }
3818
- function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
3819
- if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
3820
- return 0;
3770
+ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3771
+ const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom } = options;
3772
+ const state = ctx.state;
3773
+ if (!initialScrollAtEnd) {
3774
+ return;
3821
3775
  }
3822
- const maxScroll = Math.max(0, totalSize - state.scrollLength);
3823
- const clampDelta = maxScroll - state.scroll;
3824
- if (unresolvedAmount < 0) {
3825
- return Math.max(unresolvedAmount, Math.min(0, clampDelta));
3776
+ const initialScroll = state.initialScroll;
3777
+ if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
3778
+ return;
3826
3779
  }
3827
- if (unresolvedAmount > 0) {
3828
- return Math.min(unresolvedAmount, Math.max(0, clampDelta));
3780
+ const shouldProcessFooterLayout = !!getBootstrapInitialScrollSession(state) || shouldPreserveInitialScrollForFooterLayout(initialScroll);
3781
+ if (!shouldProcessFooterLayout) {
3782
+ return;
3783
+ }
3784
+ if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3785
+ clearPendingInitialScrollFooterLayout(ctx, {
3786
+ dataLength,
3787
+ stylePaddingBottom,
3788
+ target: initialScroll
3789
+ });
3790
+ } else {
3791
+ const updatedInitialScroll = createInitialScrollAtEndTarget({
3792
+ dataLength,
3793
+ footerSize,
3794
+ preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3795
+ stylePaddingBottom
3796
+ });
3797
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3798
+ if (!didTargetChange) {
3799
+ clearPendingInitialScrollFooterLayout(ctx, {
3800
+ dataLength,
3801
+ stylePaddingBottom,
3802
+ target: initialScroll
3803
+ });
3804
+ } else {
3805
+ const didFinishInitialScroll = !!state.didFinishInitialScroll;
3806
+ setInitialScrollTarget(state, updatedInitialScroll, {
3807
+ resetDidFinish: didFinishInitialScroll
3808
+ });
3809
+ rearmBootstrapInitialScroll(ctx, {
3810
+ scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3811
+ targetIndexSeed: updatedInitialScroll.index
3812
+ });
3813
+ }
3829
3814
  }
3830
- return 0;
3831
- }
3832
- function getProgressTowardAmount(targetDelta, nativeDelta) {
3833
- return targetDelta < 0 ? -nativeDelta : nativeDelta;
3834
3815
  }
3835
- function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
3816
+ function handleBootstrapInitialScrollLayoutChange(ctx) {
3817
+ var _a3, _b, _c;
3836
3818
  const state = ctx.state;
3837
- state.pendingNativeMVCPAdjust = void 0;
3838
- const remaining = remainingAfterManual - nativeDelta;
3839
- if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
3840
- requestAdjust(ctx, remaining);
3819
+ const initialScroll = state.initialScroll;
3820
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3821
+ if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3822
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3823
+ const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
3824
+ if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
3825
+ const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3826
+ const offsetDiff = resolvedOffset - currentOffset;
3827
+ if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3828
+ if (state.didFinishInitialScroll) {
3829
+ schedulePreservedEndAnchorCorrection(ctx);
3830
+ } else if (scrollingTo) {
3831
+ const existingWatchdog = initialScrollWatchdog.get(state);
3832
+ scrollingTo.offset = resolvedOffset;
3833
+ scrollingTo.targetOffset = resolvedOffset;
3834
+ state.initialScroll = {
3835
+ ...initialScroll,
3836
+ contentOffset: resolvedOffset
3837
+ };
3838
+ state.hasScrolled = false;
3839
+ initialScrollWatchdog.set(state, {
3840
+ startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3841
+ targetOffset: resolvedOffset
3842
+ });
3843
+ requestAdjust(ctx, offsetDiff);
3844
+ }
3845
+ }
3846
+ } else {
3847
+ rearmBootstrapInitialScroll(ctx, {
3848
+ scroll: resolvedOffset,
3849
+ targetIndexSeed: initialScroll.index
3850
+ });
3851
+ }
3841
3852
  }
3842
3853
  }
3843
- function maybeApplyPredictedNativeMVCPAdjust(ctx) {
3854
+ function evaluateBootstrapInitialScroll(ctx) {
3855
+ var _a3, _b;
3844
3856
  const state = ctx.state;
3845
- const pending = state.pendingNativeMVCPAdjust;
3846
- if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
3857
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3858
+ const initialScroll = state.initialScroll;
3859
+ if (!bootstrapInitialScroll || !initialScroll || isOffsetInitialScrollSession(state) || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll)) {
3847
3860
  return;
3848
3861
  }
3849
- const totalSize = getContentSize(ctx);
3850
- const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
3851
- if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
3862
+ bootstrapInitialScroll.passCount += 1;
3863
+ if (abortBootstrapRevealIfNeeded(ctx, {
3864
+ mountFrameCount: bootstrapInitialScroll.mountFrameCount,
3865
+ passCount: bootstrapInitialScroll.passCount
3866
+ })) {
3852
3867
  return;
3853
3868
  }
3854
- const manualDesired = pending.amount - predictedNativeClamp;
3855
- if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
3856
- return;
3869
+ if (initialScroll.index !== void 0 && state.startBuffered >= 0 && state.endBuffered >= 0 && initialScroll.index >= state.startBuffered && initialScroll.index <= state.endBuffered) {
3870
+ bootstrapInitialScroll.targetIndexSeed = void 0;
3857
3871
  }
3858
- pending.manualApplied = manualDesired;
3859
- requestAdjust(ctx, manualDesired);
3860
- pending.furthestProgressTowardAmount = 0;
3861
- }
3862
- function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
3863
- const state = ctx.state;
3864
- const pending = state.pendingNativeMVCPAdjust;
3865
- if (!pending) {
3866
- return false;
3872
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3873
+ const mountedBufferedIndices = getMountedBufferedIndices(state);
3874
+ const areMountedBufferedIndicesMeasured = checkAllSizesKnown(state, mountedBufferedIndices);
3875
+ const didResolvedOffsetChange = Math.abs(bootstrapInitialScroll.scroll - resolvedOffset) > 1;
3876
+ const { data } = state.props;
3877
+ const visibleIndices = getBootstrapRevealVisibleIndices({
3878
+ dataLength: data.length,
3879
+ getSize: (index) => {
3880
+ var _a4, _b2;
3881
+ const id = (_a4 = state.idCache[index]) != null ? _a4 : getId(state, index);
3882
+ return (_b2 = state.sizes.get(id)) != null ? _b2 : getItemSize(ctx, id, index, data[index]);
3883
+ },
3884
+ offset: resolvedOffset,
3885
+ positions: state.positions,
3886
+ scrollLength: state.scrollLength,
3887
+ startIndex: (_b = bootstrapInitialScroll.targetIndexSeed) != null ? _b : state.startBuffered >= 0 ? state.startBuffered : void 0
3888
+ });
3889
+ const areVisibleIndicesMeasured = visibleIndices.length > 0 && visibleIndices.every((index) => {
3890
+ var _a4;
3891
+ const id = (_a4 = state.idCache[index]) != null ? _a4 : getId(state, index);
3892
+ return state.sizesKnown.has(id);
3893
+ });
3894
+ const previousResolvedOffset = bootstrapInitialScroll.previousResolvedOffset;
3895
+ const previousVisibleIndices = bootstrapInitialScroll.visibleIndices;
3896
+ bootstrapInitialScroll.previousResolvedOffset = resolvedOffset;
3897
+ bootstrapInitialScroll.visibleIndices = visibleIndices;
3898
+ if (didResolvedOffsetChange) {
3899
+ bootstrapInitialScroll.scroll = resolvedOffset;
3900
+ queueBootstrapInitialScrollReevaluation(state);
3901
+ return;
3867
3902
  }
3868
- const remainingAfterManual = pending.amount - pending.manualApplied;
3869
- const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
3870
- const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
3871
- const progressTowardAmount = getProgressTowardAmount(remainingAfterManual, nativeDelta);
3872
- if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
3873
- state.pendingNativeMVCPAdjust = void 0;
3874
- return true;
3903
+ if (!areMountedBufferedIndicesMeasured || !areVisibleIndicesMeasured) {
3904
+ return;
3875
3905
  }
3876
- if (isWrongDirection) {
3877
- state.pendingNativeMVCPAdjust = void 0;
3878
- return false;
3906
+ const didRevealSettle = previousResolvedOffset !== void 0 && Math.abs(previousResolvedOffset - resolvedOffset) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON && doVisibleIndicesMatch(previousVisibleIndices, visibleIndices);
3907
+ if (!didRevealSettle) {
3908
+ queueBootstrapInitialScrollReevaluation(state);
3909
+ return;
3879
3910
  }
3880
- if (progressTowardAmount + MVCP_POSITION_EPSILON >= Math.abs(remainingAfterManual)) {
3881
- settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
3882
- return true;
3911
+ {
3912
+ clearBootstrapInitialScrollSession(state);
3913
+ dispatchInitialScroll(ctx, {
3914
+ forceScroll: true,
3915
+ resolvedOffset,
3916
+ target: initialScroll,
3917
+ waitForCompletionFrame: Platform.OS === "web"
3918
+ });
3883
3919
  }
3884
- const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
3885
- const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
3886
- const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
3887
- if (isAtExpectedNativeClamp) {
3888
- settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
3889
- return true;
3920
+ }
3921
+ function finishBootstrapInitialScrollWithoutScroll(ctx, resolvedOffset) {
3922
+ var _a3;
3923
+ const state = ctx.state;
3924
+ clearBootstrapInitialScrollSession(state);
3925
+ const shouldPreserveResizeTarget = !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_a3 = state.initialScroll) == null ? void 0 : _a3.viewPosition) === 1;
3926
+ finishInitialScroll(ctx, {
3927
+ preserveTarget: shouldPreserveResizeTarget,
3928
+ recalculateItems: true,
3929
+ resolvedOffset,
3930
+ schedulePreservedTargetClear: shouldPreserveResizeTarget
3931
+ });
3932
+ }
3933
+ function abortBootstrapInitialScroll(ctx) {
3934
+ var _a3, _b, _c, _d;
3935
+ const state = ctx.state;
3936
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3937
+ const initialScroll = state.initialScroll;
3938
+ if (bootstrapInitialScroll && initialScroll && !isOffsetInitialScrollSession(state) && state.refScroller.current) {
3939
+ clearBootstrapInitialScrollSession(state);
3940
+ dispatchInitialScroll(ctx, {
3941
+ forceScroll: true,
3942
+ resolvedOffset: bootstrapInitialScroll.scroll,
3943
+ target: initialScroll,
3944
+ waitForCompletionFrame: Platform.OS === "web"
3945
+ });
3946
+ } else {
3947
+ finishBootstrapInitialScrollWithoutScroll(
3948
+ ctx,
3949
+ (_d = (_c = (_b = (_a3 = getBootstrapInitialScrollSession(state)) == null ? void 0 : _a3.scroll) != null ? _b : state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0
3950
+ );
3890
3951
  }
3891
- if (state.pendingMaintainScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold") && progressTowardAmount > MVCP_POSITION_EPSILON) {
3892
- settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
3893
- return true;
3952
+ }
3953
+
3954
+ // src/core/checkFinishedScroll.ts
3955
+ var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
3956
+ var INITIAL_SCROLL_COMPLETION_TARGET_EPSILON = 1;
3957
+ var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
3958
+ var SILENT_INITIAL_SCROLL_RETRY_DELAY_MS = 16;
3959
+ function checkFinishedScroll(ctx, options) {
3960
+ const scrollingTo = ctx.state.scrollingTo;
3961
+ if (options == null ? void 0 : options.onlyIfAligned) {
3962
+ if (!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || scrollingTo.animated) {
3963
+ return;
3964
+ }
3965
+ if (!getResolvedScrollCompletionState(ctx, scrollingTo).isAtResolvedTarget) {
3966
+ return;
3967
+ }
3894
3968
  }
3895
- if (progressTowardAmount > pending.furthestProgressTowardAmount + MVCP_POSITION_EPSILON) {
3896
- pending.furthestProgressTowardAmount = progressTowardAmount;
3969
+ ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3970
+ }
3971
+ function hasScrollCompletionOwnership(state, options) {
3972
+ const { clampedTargetOffset, scrollingTo } = options;
3973
+ return !scrollingTo.isInitialScroll || state.hasScrolled || clampedTargetOffset <= INITIAL_SCROLL_COMPLETION_TARGET_EPSILON;
3974
+ }
3975
+ function isSilentInitialDispatch(state, scrollingTo) {
3976
+ return !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled;
3977
+ }
3978
+ function getInitialScrollWatchdogTargetOffset(state) {
3979
+ var _a3;
3980
+ return (_a3 = initialScrollWatchdog.get(state)) == null ? void 0 : _a3.targetOffset;
3981
+ }
3982
+ function isNativeInitialNonZeroTarget(state) {
3983
+ const targetOffset = getInitialScrollWatchdogTargetOffset(state);
3984
+ return !state.didFinishInitialScroll && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
3985
+ }
3986
+ function shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) {
3987
+ var _a3, _b;
3988
+ if (!scrollingTo.isInitialScroll || scrollingTo.animated || !state.didContainersLayout) {
3897
3989
  return false;
3898
3990
  }
3899
- if (pending.furthestProgressTowardAmount > MVCP_POSITION_EPSILON && progressTowardAmount < pending.furthestProgressTowardAmount - MVCP_POSITION_EPSILON) {
3900
- state.pendingNativeMVCPAdjust = void 0;
3991
+ if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap") {
3901
3992
  return false;
3902
3993
  }
3903
- return false;
3994
+ const targetOffset = (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset;
3995
+ if (initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset) && initialScrollCompletion.didDispatchNativeScroll(state) && !state.hasScrolled) {
3996
+ return false;
3997
+ }
3998
+ if (initialScrollWatchdog.isAtZeroTargetOffset(targetOffset) || Math.abs(state.scroll - targetOffset) > 1 || Math.abs(state.scrollPending - targetOffset) > 1) {
3999
+ return false;
4000
+ }
4001
+ return !!scrollingTo.waitForInitialScrollCompletionFrame || isNativeInitialNonZeroTarget(state);
3904
4002
  }
3905
- function prepareMVCP(ctx, dataChanged) {
4003
+ function shouldFinishInitialZeroTargetScroll(ctx) {
4004
+ var _a3;
4005
+ const { state } = ctx;
4006
+ 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;
4007
+ }
4008
+ function isEndAlignedLastItemTarget(ctx, scrollingTo) {
4009
+ return scrollingTo.index === ctx.state.props.data.length - 1 && scrollingTo.viewPosition === 1;
4010
+ }
4011
+ function getCurrentTargetOffset(ctx, scrollingTo) {
4012
+ var _a3;
4013
+ const index = scrollingTo.index;
4014
+ const shouldRecomputeEndTarget = isEndAlignedLastItemTarget(ctx, scrollingTo);
4015
+ const requestedTargetOffset = shouldRecomputeEndTarget && index !== void 0 ? calculateOffsetWithOffsetPosition(ctx, calculateOffsetForIndex(ctx, index), scrollingTo) : (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
4016
+ return clampScrollOffset(ctx, requestedTargetOffset, scrollingTo);
4017
+ }
4018
+ function getResolvedScrollCompletionState(ctx, scrollingTo) {
4019
+ const { state } = ctx;
4020
+ const scroll = state.scrollPending;
4021
+ const adjust = state.scrollAdjustHandler.getAdjust();
4022
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
4023
+ const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
4024
+ const diff1 = Math.abs(scroll - clampedTargetOffset);
4025
+ const adjustedTargetOffset = clampedTargetOffset + adjust;
4026
+ const diff2 = Math.abs(scroll - adjustedTargetOffset);
4027
+ const canUseAdjustedCompletion = !scrollingTo.animated || Platform.OS === "ios";
4028
+ return {
4029
+ clampedTargetOffset,
4030
+ isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || canUseAdjustedCompletion && diff2 < 1)
4031
+ };
4032
+ }
4033
+ function checkFinishedScrollFrame(ctx) {
4034
+ const scrollingTo = ctx.state.scrollingTo;
4035
+ if (!scrollingTo) {
4036
+ return;
4037
+ }
4038
+ const { state } = ctx;
4039
+ state.animFrameCheckFinishedScroll = void 0;
4040
+ const completionState = getResolvedScrollCompletionState(ctx, scrollingTo);
4041
+ if (completionState.isAtResolvedTarget && hasScrollCompletionOwnership(state, {
4042
+ clampedTargetOffset: completionState.clampedTargetOffset,
4043
+ scrollingTo
4044
+ })) {
4045
+ finishScrollTo(ctx);
4046
+ }
4047
+ }
4048
+ function scrollToFallbackOffset(ctx, offset) {
4049
+ var _a3;
4050
+ (_a3 = ctx.state.refScroller.current) == null ? void 0 : _a3.scrollTo({
4051
+ animated: false,
4052
+ x: ctx.state.props.horizontal ? offset : 0,
4053
+ y: ctx.state.props.horizontal ? 0 : offset
4054
+ });
4055
+ }
4056
+ function checkFinishedScrollFallback(ctx) {
3906
4057
  const state = ctx.state;
3907
- const { idsInView, positions, props } = state;
3908
- const {
3909
- maintainVisibleContentPosition: { data: mvcpData, size: mvcpScroll, shouldRestorePosition }
3910
- } = props;
3911
- const now = Date.now();
3912
- const enableMVCPAnchorLock = (!!dataChanged || !!state.mvcpAnchorLock);
3913
4058
  const scrollingTo = state.scrollingTo;
3914
- const anchorLock = resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) ;
3915
- let prevPosition;
3916
- let targetId;
3917
- const idsInViewWithPositions = [];
3918
- const scrollTarget = scrollingTo == null ? void 0 : scrollingTo.index;
3919
- const scrollingToViewPosition = scrollingTo == null ? void 0 : scrollingTo.viewPosition;
3920
- const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
3921
- const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
3922
- const indexByKey = state.indexByKey;
3923
- const prevScroll = state.scroll;
3924
- getContentSize(ctx);
3925
- if (shouldMVCP) {
3926
- if (anchorLock && scrollTarget === void 0) {
3927
- targetId = anchorLock.id;
3928
- prevPosition = anchorLock.position;
3929
- } else if (scrollTarget !== void 0) {
3930
- targetId = getId(state, scrollTarget);
3931
- } else if (idsInView.length > 0 && state.didContainersLayout && !dataChanged) {
3932
- targetId = idsInView.find((id) => indexByKey.get(id) !== void 0);
3933
- }
3934
- if (dataChanged && idsInView.length > 0 && state.didContainersLayout) {
3935
- for (let i = 0; i < idsInView.length; i++) {
3936
- const id = idsInView[i];
3937
- const index = indexByKey.get(id);
3938
- if (index !== void 0) {
3939
- const position = positions[index];
3940
- if (position !== void 0) {
3941
- idsInViewWithPositions.push({ id, position });
3942
- }
3943
- }
3944
- }
3945
- }
3946
- if (targetId !== void 0 && prevPosition === void 0) {
3947
- const targetIndex = indexByKey.get(targetId);
3948
- if (targetIndex !== void 0) {
3949
- prevPosition = positions[targetIndex];
3950
- }
3951
- }
3952
- return () => {
3953
- let positionDiff = 0;
3954
- let anchorIdForLock = anchorLock == null ? void 0 : anchorLock.id;
3955
- let anchorPositionForLock;
3956
- let skipTargetAnchor = false;
3957
- const data = state.props.data;
3958
- const shouldValidateLockedAnchor = dataChanged && mvcpData && scrollTarget === void 0 && targetId !== void 0 && (anchorLock == null ? void 0 : anchorLock.id) === targetId && shouldRestorePosition !== void 0;
3959
- if (shouldValidateLockedAnchor && targetId !== void 0) {
3960
- const index = indexByKey.get(targetId);
3961
- if (index !== void 0) {
3962
- const item = data[index];
3963
- skipTargetAnchor = item === void 0 || !shouldRestorePosition(item, index, data);
3964
- if (skipTargetAnchor && (anchorLock == null ? void 0 : anchorLock.id) === targetId) {
3965
- state.mvcpAnchorLock = void 0;
3966
- }
3967
- }
3968
- }
3969
- const shouldUseFallbackVisibleAnchor = dataChanged && mvcpData && scrollTarget === void 0 && (() => {
3970
- if (targetId === void 0 || skipTargetAnchor) {
3971
- return true;
3972
- }
3973
- const targetIndex = indexByKey.get(targetId);
3974
- return targetIndex === void 0 || positions[targetIndex] === void 0;
3975
- })();
3976
- if (shouldUseFallbackVisibleAnchor) {
3977
- for (let i = 0; i < idsInViewWithPositions.length; i++) {
3978
- const { id, position } = idsInViewWithPositions[i];
3979
- const index = indexByKey.get(id);
3980
- if (index !== void 0 && shouldRestorePosition) {
3981
- const item = data[index];
3982
- if (item === void 0 || !shouldRestorePosition(item, index, data)) {
3983
- continue;
3984
- }
3985
- }
3986
- const newPosition = index !== void 0 ? positions[index] : void 0;
3987
- if (newPosition !== void 0) {
3988
- positionDiff = newPosition - position;
3989
- anchorIdForLock = id;
3990
- anchorPositionForLock = newPosition;
3991
- break;
3992
- }
3993
- }
3994
- }
3995
- if (!skipTargetAnchor && targetId !== void 0 && prevPosition !== void 0) {
3996
- const targetIndex = indexByKey.get(targetId);
3997
- const newPosition = targetIndex !== void 0 ? positions[targetIndex] : void 0;
3998
- if (newPosition !== void 0) {
3999
- const totalSize = getContentSize(ctx);
4000
- let diff = newPosition - prevPosition;
4001
- if (diff !== 0 && isEndAnchoredScrollTarget && state.scroll + state.scrollLength > totalSize) {
4002
- if (diff > 0) {
4003
- diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
4004
- } else {
4005
- diff = 0;
4006
- }
4007
- }
4008
- positionDiff = diff;
4009
- anchorIdForLock = targetId;
4010
- anchorPositionForLock = newPosition;
4011
- }
4012
- }
4013
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
4014
- const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
4015
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
4016
- if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
4017
- const diff = newSize - prevSize;
4018
- if (diff !== 0) {
4019
- positionDiff += diff * scrollingToViewPosition;
4020
- scrollingTo.itemSize = newSize;
4021
- }
4022
- }
4023
- }
4024
- updateAnchorLock(state, {
4025
- anchorId: anchorIdForLock,
4026
- anchorPosition: anchorPositionForLock,
4027
- dataChanged,
4028
- now,
4029
- positionDiff
4030
- });
4031
- if (shouldQueueNativeMVCPAdjust()) {
4032
- state.pendingNativeMVCPAdjust = {
4033
- amount: positionDiff,
4034
- furthestProgressTowardAmount: 0,
4035
- manualApplied: 0,
4036
- startScroll: prevScroll
4037
- };
4038
- maybeApplyPredictedNativeMVCPAdjust(ctx);
4039
- return;
4040
- }
4041
- if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
4042
- const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
4043
- if (!shouldSkipAdjustForMaintainedEnd) {
4044
- requestAdjust(ctx, positionDiff);
4059
+ const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
4060
+ const silentInitialDispatch = isSilentInitialDispatch(state, scrollingTo);
4061
+ const canFinishInitialWithoutNativeProgress = scrollingTo !== void 0 ? shouldFinishInitialScrollWithoutNativeProgress(state, scrollingTo) : false;
4062
+ const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget && !canFinishInitialWithoutNativeProgress || !state.didContainersLayout;
4063
+ const initialDelay = shouldFinishInitialZeroTarget || canFinishInitialWithoutNativeProgress ? 0 : silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : slowTimeout ? 500 : 100;
4064
+ state.timeoutCheckFinishedScrollFallback = setTimeout(() => {
4065
+ let numChecks = 0;
4066
+ const scheduleFallbackCheck = (delay) => {
4067
+ state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, delay);
4068
+ };
4069
+ const checkHasScrolled = () => {
4070
+ var _c, _d;
4071
+ state.timeoutCheckFinishedScrollFallback = void 0;
4072
+ const isStillScrollingTo = state.scrollingTo;
4073
+ if (isStillScrollingTo) {
4074
+ numChecks++;
4075
+ const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
4076
+ const maxChecks = silentInitialDispatch ? 5 : isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
4077
+ const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
4078
+ const canFinishInitialScrollWithoutNativeProgress = shouldFinishInitialScrollWithoutNativeProgress(
4079
+ state,
4080
+ isStillScrollingTo
4081
+ );
4082
+ const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
4083
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android";
4084
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
4085
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
4086
+ if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
4087
+ finishScrollTo(ctx);
4088
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
4089
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
4090
+ scrollToFallbackOffset(ctx, targetOffset);
4091
+ scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
4092
+ } else {
4093
+ scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
4045
4094
  }
4046
4095
  }
4047
4096
  };
4097
+ checkHasScrolled();
4098
+ }, initialDelay);
4099
+ }
4100
+
4101
+ // src/core/initialScrollLifecycle.ts
4102
+ function retargetActiveInitialScrollAtEnd(ctx) {
4103
+ var _a3;
4104
+ const state = ctx.state;
4105
+ const initialScroll = state.initialScroll;
4106
+ if (state.didFinishInitialScroll) {
4107
+ return schedulePreservedEndAnchorCorrection(ctx);
4108
+ }
4109
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
4110
+ return false;
4111
+ }
4112
+ return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
4113
+ }
4114
+ function handleInitialScrollLayoutReady(ctx) {
4115
+ var _a3;
4116
+ if (!ctx.state.initialScroll) {
4117
+ return;
4118
+ }
4119
+ const runScroll = () => advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
4120
+ runScroll();
4121
+ if (((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset") {
4122
+ requestAnimationFrame(runScroll);
4123
+ }
4124
+ checkFinishedScroll(ctx, { onlyIfAligned: true });
4125
+ }
4126
+ function initializeInitialScrollOnMount(ctx, options) {
4127
+ var _a3, _b;
4128
+ const {
4129
+ alwaysDispatchInitialScroll,
4130
+ dataLength,
4131
+ hasFooterComponent,
4132
+ initialContentOffset,
4133
+ initialScrollAtEnd,
4134
+ useBootstrapInitialScroll
4135
+ } = options;
4136
+ const state = ctx.state;
4137
+ const initialScroll = state.initialScroll;
4138
+ const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
4139
+ const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
4140
+ if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
4141
+ setInitialScrollTarget(state, {
4142
+ ...initialScroll,
4143
+ contentOffset: resolvedInitialContentOffset,
4144
+ preserveForFooterLayout
4145
+ });
4146
+ }
4147
+ if (useBootstrapInitialScroll && initialScroll && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
4148
+ startBootstrapInitialScrollOnMount(ctx, {
4149
+ initialScrollAtEnd,
4150
+ target: state.initialScroll
4151
+ });
4152
+ return;
4153
+ }
4154
+ const hasPendingDataDependentInitialScroll = !!initialScroll && dataLength === 0 && !(resolvedInitialContentOffset === 0 && !initialScrollAtEnd);
4155
+ if (!alwaysDispatchInitialScroll && !resolvedInitialContentOffset && !hasPendingDataDependentInitialScroll) {
4156
+ if (initialScroll && !initialScrollAtEnd) {
4157
+ finishInitialScroll(ctx, {
4158
+ resolvedOffset: resolvedInitialContentOffset
4159
+ });
4160
+ } else {
4161
+ setInitialRenderState(ctx, { didInitialScroll: true });
4162
+ }
4163
+ }
4164
+ }
4165
+ function handleInitialScrollDataChange(ctx, options) {
4166
+ var _a3, _b, _c;
4167
+ const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
4168
+ const state = ctx.state;
4169
+ const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
4170
+ if (state.initialScrollSession) {
4171
+ state.initialScrollSession.previousDataLength = dataLength;
4172
+ }
4173
+ setInitialScrollSession(state);
4174
+ if (useBootstrapInitialScroll) {
4175
+ handleBootstrapInitialScrollDataChange(ctx, {
4176
+ dataLength,
4177
+ didDataChange,
4178
+ initialScrollAtEnd,
4179
+ previousDataLength,
4180
+ stylePaddingBottom
4181
+ });
4182
+ return;
4183
+ }
4184
+ const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
4185
+ if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
4186
+ return;
4187
+ }
4188
+ if (shouldReplayFinishedOffsetInitialScroll) {
4189
+ state.didFinishInitialScroll = false;
4048
4190
  }
4191
+ advanceCurrentInitialScrollSession(ctx);
4049
4192
  }
4050
4193
 
4051
4194
  // src/core/resetLayoutCachesForDataChange.ts
@@ -5000,7 +5143,7 @@ function calculateItemsInView(ctx, params = {}) {
5000
5143
  scrollBottomBuffered = scrollBottom + scrollBufferBottom;
5001
5144
  };
5002
5145
  updateScrollRange();
5003
- if (!suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
5146
+ if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
5004
5147
  const { top, bottom } = scrollForNextCalculateItemsInView;
5005
5148
  if (top === null && bottom === null) {
5006
5149
  state.scrollForNextCalculateItemsInView = void 0;
@@ -5331,62 +5474,6 @@ function calculateItemsInView(ctx, params = {}) {
5331
5474
  });
5332
5475
  }
5333
5476
 
5334
- // src/core/doMaintainScrollAtEnd.ts
5335
- function doMaintainScrollAtEnd(ctx) {
5336
- const state = ctx.state;
5337
- const {
5338
- didContainersLayout,
5339
- pendingNativeMVCPAdjust,
5340
- refScroller,
5341
- props: { maintainScrollAtEnd }
5342
- } = state;
5343
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
5344
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
5345
- if (pendingNativeMVCPAdjust) {
5346
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
5347
- return false;
5348
- }
5349
- state.pendingMaintainScrollAtEnd = false;
5350
- if (shouldMaintainScrollAtEnd) {
5351
- const contentSize = getContentSize(ctx);
5352
- if (contentSize < state.scrollLength) {
5353
- state.scroll = 0;
5354
- }
5355
- if (!state.maintainingScrollAtEnd) {
5356
- state.maintainingScrollAtEnd = true;
5357
- requestAnimationFrame(() => {
5358
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
5359
- const scroller = refScroller.current;
5360
- if (state.props.horizontal && isHorizontalRTL(state)) {
5361
- const currentContentSize = getContentSize(ctx);
5362
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
5363
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
5364
- scroller == null ? void 0 : scroller.scrollTo({
5365
- animated: maintainScrollAtEnd.animated,
5366
- x: nativeOffset,
5367
- y: 0
5368
- });
5369
- } else {
5370
- scroller == null ? void 0 : scroller.scrollToEnd({
5371
- animated: maintainScrollAtEnd.animated
5372
- });
5373
- }
5374
- setTimeout(
5375
- () => {
5376
- state.maintainingScrollAtEnd = false;
5377
- },
5378
- maintainScrollAtEnd.animated ? 500 : 0
5379
- );
5380
- } else {
5381
- state.maintainingScrollAtEnd = false;
5382
- }
5383
- });
5384
- }
5385
- return true;
5386
- }
5387
- return false;
5388
- }
5389
-
5390
5477
  // src/core/checkResetContainers.ts
5391
5478
  function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {}) {
5392
5479
  const state = ctx.state;
@@ -5571,79 +5658,6 @@ function handleLayout(ctx, layoutParam, setCanRender) {
5571
5658
  setCanRender(true);
5572
5659
  }
5573
5660
 
5574
- // src/core/updateScroll.ts
5575
- function updateScroll(ctx, newScroll, forceUpdate, options) {
5576
- var _a3;
5577
- const state = ctx.state;
5578
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
5579
- const prevScroll = state.scroll;
5580
- if ((options == null ? void 0 : options.markHasScrolled) !== false) {
5581
- state.hasScrolled = true;
5582
- }
5583
- state.lastBatchingAction = Date.now();
5584
- const currentTime = Date.now();
5585
- const adjust = scrollAdjustHandler.getAdjust();
5586
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
5587
- if (adjustChanged) {
5588
- scrollHistory.length = 0;
5589
- }
5590
- state.lastScrollAdjustForHistory = adjust;
5591
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
5592
- if (!adjustChanged) {
5593
- scrollHistory.push({ scroll: newScroll, time: currentTime });
5594
- }
5595
- }
5596
- if (scrollHistory.length > 5) {
5597
- scrollHistory.shift();
5598
- }
5599
- if (ignoreScrollFromMVCP && !scrollingTo) {
5600
- const { lt, gt } = ignoreScrollFromMVCP;
5601
- if (lt && newScroll < lt || gt && newScroll > gt) {
5602
- state.ignoreScrollFromMVCPIgnored = true;
5603
- return;
5604
- }
5605
- }
5606
- state.scrollPrev = prevScroll;
5607
- state.scrollPrevTime = state.scrollTime;
5608
- state.scroll = newScroll;
5609
- state.scrollTime = currentTime;
5610
- const scrollDelta = Math.abs(newScroll - prevScroll);
5611
- const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
5612
- const scrollLength = state.scrollLength;
5613
- const lastCalculated = state.scrollLastCalculate;
5614
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
5615
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
5616
- if (shouldUpdate) {
5617
- state.scrollLastCalculate = state.scroll;
5618
- state.ignoreScrollFromMVCPIgnored = false;
5619
- state.lastScrollDelta = scrollDelta;
5620
- const runCalculateItems = () => {
5621
- var _a4;
5622
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
5623
- checkThresholds(ctx);
5624
- };
5625
- if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
5626
- state.mvcpAnchorLock = void 0;
5627
- state.pendingNativeMVCPAdjust = void 0;
5628
- state.userScrollAnchorResetKeys = /* @__PURE__ */ new Set();
5629
- if (state.queuedMVCPRecalculate !== void 0) {
5630
- cancelAnimationFrame(state.queuedMVCPRecalculate);
5631
- state.queuedMVCPRecalculate = void 0;
5632
- }
5633
- flushSync(runCalculateItems);
5634
- } else {
5635
- runCalculateItems();
5636
- }
5637
- const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
5638
- if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
5639
- state.pendingMaintainScrollAtEnd = false;
5640
- doMaintainScrollAtEnd(ctx);
5641
- }
5642
- state.dataChangeNeedsScrollUpdate = false;
5643
- state.lastScrollDelta = 0;
5644
- }
5645
- }
5646
-
5647
5661
  // src/core/onScroll.ts
5648
5662
  function trackInitialScrollNativeProgress(state, newScroll) {
5649
5663
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);
@@ -6965,6 +6979,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6965
6979
  });
6966
6980
  state.viewabilityConfigCallbackPairs = viewability;
6967
6981
  state.enableScrollForNextCalculateItemsInView = !viewability;
6982
+ if (viewability) {
6983
+ state.scrollForNextCalculateItemsInView = void 0;
6984
+ }
6968
6985
  }, [viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged]);
6969
6986
  useInit(() => {
6970
6987
  });