@legendapp/list 3.0.0 → 3.0.1

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