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