@legendapp/list 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/react-native.mjs CHANGED
@@ -1981,18 +1981,29 @@ function shouldFinishInitialZeroTargetScroll(ctx) {
1981
1981
  const { state } = ctx;
1982
1982
  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;
1983
1983
  }
1984
- function getResolvedScrollCompletionState(ctx, scrollingTo) {
1984
+ function isEndAlignedLastItemTarget(ctx, scrollingTo) {
1985
+ return scrollingTo.index === ctx.state.props.data.length - 1 && scrollingTo.viewPosition === 1;
1986
+ }
1987
+ function getCurrentTargetOffset(ctx, scrollingTo) {
1985
1988
  var _a3;
1989
+ const index = scrollingTo.index;
1990
+ const shouldRecomputeEndTarget = isEndAlignedLastItemTarget(ctx, scrollingTo);
1991
+ 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);
1992
+ return clampScrollOffset(ctx, requestedTargetOffset, scrollingTo);
1993
+ }
1994
+ function getResolvedScrollCompletionState(ctx, scrollingTo) {
1986
1995
  const { state } = ctx;
1987
1996
  const scroll = state.scrollPending;
1988
1997
  const adjust = state.scrollAdjustHandler.getAdjust();
1989
- const clampedTargetOffset = (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
1998
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
1990
1999
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
1991
2000
  const diff1 = Math.abs(scroll - clampedTargetOffset);
1992
- const diff2 = Math.abs(diff1 - adjust);
2001
+ const adjustedTargetOffset = clampedTargetOffset + adjust;
2002
+ const diff2 = Math.abs(scroll - adjustedTargetOffset);
2003
+ const canUseAdjustedCompletion = !scrollingTo.animated || Platform.OS === "ios";
1993
2004
  return {
1994
2005
  clampedTargetOffset,
1995
- isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || !scrollingTo.animated && diff2 < 1)
2006
+ isAtResolvedTarget: Math.abs(scroll - maxOffset) < 1 && (diff1 < 1 || canUseAdjustedCompletion && diff2 < 1)
1996
2007
  };
1997
2008
  }
1998
2009
  function checkFinishedScrollFrame(ctx) {
@@ -2049,6 +2060,7 @@ function checkFinishedScrollFallback(ctx) {
2049
2060
  const shouldRetrySilentInitialNativeScroll = Platform.OS === "android" && canFinishAfterSilentNativeDispatch && !initialScrollCompletion.didRetrySilentInitialScroll(state);
2050
2061
  const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
2051
2062
  const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
2063
+ const shouldRetryUnalignedEndScroll = Platform.OS === "ios" && !isStillScrollingTo.isInitialScroll && isEndAlignedLastItemTarget(ctx, isStillScrollingTo) && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
2052
2064
  if (shouldRetrySilentInitialNativeScroll) {
2053
2065
  const targetOffset = (_b = (_a3 = getInitialScrollWatchdogTargetOffset(state)) != null ? _a3 : isStillScrollingTo.targetOffset) != null ? _b : 0;
2054
2066
  const jiggleOffset = targetOffset >= SILENT_INITIAL_SCROLL_TARGET_EPSILON ? targetOffset - SILENT_INITIAL_SCROLL_TARGET_EPSILON : targetOffset + SILENT_INITIAL_SCROLL_TARGET_EPSILON;
@@ -2058,6 +2070,9 @@ function checkFinishedScrollFallback(ctx) {
2058
2070
  scrollToFallbackOffset(ctx, targetOffset);
2059
2071
  });
2060
2072
  scheduleFallbackCheck(SILENT_INITIAL_SCROLL_RETRY_DELAY_MS);
2073
+ } else if (shouldRetryUnalignedEndScroll) {
2074
+ scrollToFallbackOffset(ctx, completionState.clampedTargetOffset);
2075
+ scheduleFallbackCheck(100);
2061
2076
  } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
2062
2077
  finishScrollTo(ctx);
2063
2078
  } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
@@ -2100,773 +2115,1199 @@ function doScrollTo(ctx, params) {
2100
2115
  }
2101
2116
  }
2102
2117
 
2103
- // src/core/scrollTo.ts
2104
- function getAverageSizeSnapshot(state) {
2105
- if (Object.keys(state.averageSizes).length === 0) {
2106
- return void 0;
2118
+ // src/core/doMaintainScrollAtEnd.ts
2119
+ function doMaintainScrollAtEnd(ctx) {
2120
+ const state = ctx.state;
2121
+ const {
2122
+ didContainersLayout,
2123
+ pendingNativeMVCPAdjust,
2124
+ refScroller,
2125
+ props: { maintainScrollAtEnd }
2126
+ } = state;
2127
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2128
+ const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
2129
+ if (pendingNativeMVCPAdjust) {
2130
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
2131
+ return false;
2107
2132
  }
2108
- const snapshot = {};
2109
- for (const itemType in state.averageSizes) {
2110
- const averages = state.averageSizes[itemType];
2111
- snapshot[itemType] = averages.avg;
2133
+ state.pendingMaintainScrollAtEnd = false;
2134
+ if (shouldMaintainScrollAtEnd) {
2135
+ const contentSize = getContentSize(ctx);
2136
+ if (contentSize < state.scrollLength) {
2137
+ state.scroll = 0;
2138
+ }
2139
+ if (!state.maintainingScrollAtEnd) {
2140
+ state.maintainingScrollAtEnd = true;
2141
+ requestAnimationFrame(() => {
2142
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
2143
+ const scroller = refScroller.current;
2144
+ if (state.props.horizontal && isHorizontalRTL(state)) {
2145
+ const currentContentSize = getContentSize(ctx);
2146
+ const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
2147
+ const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
2148
+ scroller == null ? void 0 : scroller.scrollTo({
2149
+ animated: maintainScrollAtEnd.animated,
2150
+ x: nativeOffset,
2151
+ y: 0
2152
+ });
2153
+ } else {
2154
+ scroller == null ? void 0 : scroller.scrollToEnd({
2155
+ animated: maintainScrollAtEnd.animated
2156
+ });
2157
+ }
2158
+ setTimeout(
2159
+ () => {
2160
+ state.maintainingScrollAtEnd = false;
2161
+ },
2162
+ maintainScrollAtEnd.animated ? 500 : 0
2163
+ );
2164
+ } else {
2165
+ state.maintainingScrollAtEnd = false;
2166
+ }
2167
+ });
2168
+ }
2169
+ return true;
2112
2170
  }
2113
- return snapshot;
2171
+ return false;
2114
2172
  }
2115
- function syncInitialScrollNativeWatchdog(state, options) {
2116
- var _a3;
2117
- const { isInitialScroll, requestedOffset, targetOffset } = options;
2118
- const existingWatchdog = initialScrollWatchdog.get(state);
2119
- const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!existingWatchdog) && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
2120
- const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!existingWatchdog && initialScrollWatchdog.isAtZeroTargetOffset(requestedOffset);
2121
- if (shouldWatchInitialNativeScroll) {
2122
- state.hasScrolled = false;
2123
- initialScrollWatchdog.set(state, {
2124
- startScroll: (_a3 = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _a3 : state.scroll,
2125
- targetOffset
2126
- });
2127
- return;
2128
- }
2129
- if (shouldClearInitialNativeScrollWatchdog) {
2130
- initialScrollWatchdog.clear(state);
2173
+
2174
+ // src/utils/requestAdjust.ts
2175
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2176
+ const state = ctx.state;
2177
+ if (Math.abs(positionDiff) > 0.1) {
2178
+ const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
2179
+ const doit = () => {
2180
+ if (needsScrollWorkaround) {
2181
+ doScrollTo(ctx, { horizontal: state.props.horizontal, offset: state.scroll });
2182
+ } else {
2183
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2184
+ if (state.adjustingFromInitialMount) {
2185
+ state.adjustingFromInitialMount--;
2186
+ }
2187
+ }
2188
+ };
2189
+ state.scroll += positionDiff;
2190
+ state.scrollForNextCalculateItemsInView = void 0;
2191
+ const readyToRender = peek$(ctx, "readyToRender");
2192
+ if (readyToRender) {
2193
+ doit();
2194
+ if (Platform.OS !== "web") {
2195
+ const threshold = state.scroll - positionDiff / 2;
2196
+ if (!state.ignoreScrollFromMVCP) {
2197
+ state.ignoreScrollFromMVCP = {};
2198
+ }
2199
+ if (positionDiff > 0) {
2200
+ state.ignoreScrollFromMVCP.lt = threshold;
2201
+ } else {
2202
+ state.ignoreScrollFromMVCP.gt = threshold;
2203
+ }
2204
+ if (state.ignoreScrollFromMVCPTimeout) {
2205
+ clearTimeout(state.ignoreScrollFromMVCPTimeout);
2206
+ }
2207
+ const delay = needsScrollWorkaround ? 250 : 100;
2208
+ state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
2209
+ var _a3;
2210
+ state.ignoreScrollFromMVCP = void 0;
2211
+ const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
2212
+ if (shouldForceUpdate) {
2213
+ state.ignoreScrollFromMVCPIgnored = false;
2214
+ state.scrollPending = state.scroll;
2215
+ (_a3 = state.reprocessCurrentScroll) == null ? void 0 : _a3.call(state);
2216
+ }
2217
+ }, delay);
2218
+ }
2219
+ } else {
2220
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2221
+ requestAnimationFrame(doit);
2222
+ }
2131
2223
  }
2132
2224
  }
2133
- function scrollTo(ctx, params) {
2134
- var _a3;
2135
- const state = ctx.state;
2136
- const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2137
- const {
2138
- animated,
2139
- isInitialScroll,
2140
- offset: scrollTargetOffset,
2141
- precomputedWithViewOffset,
2142
- waitForInitialScrollCompletionFrame
2143
- } = scrollTarget;
2144
- const {
2145
- props: { horizontal }
2146
- } = state;
2147
- if (state.animFrameCheckFinishedScroll) {
2148
- cancelAnimationFrame(ctx.state.animFrameCheckFinishedScroll);
2225
+
2226
+ // src/core/mvcp.ts
2227
+ var MVCP_POSITION_EPSILON = 0.1;
2228
+ var MVCP_ANCHOR_LOCK_TTL_MS = 300;
2229
+ var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
2230
+ var NATIVE_END_CLAMP_EPSILON = 1;
2231
+ function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
2232
+ if (!enableMVCPAnchorLock) {
2233
+ state.mvcpAnchorLock = void 0;
2234
+ return void 0;
2149
2235
  }
2150
- if (state.timeoutCheckFinishedScrollFallback) {
2151
- clearTimeout(ctx.state.timeoutCheckFinishedScrollFallback);
2236
+ const lock = state.mvcpAnchorLock;
2237
+ if (!lock) {
2238
+ return void 0;
2152
2239
  }
2153
- const requestedOffset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
2154
- const shouldPreserveRawInitialOffsetRequest = !!isInitialScroll && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2155
- const targetOffset = clampScrollOffset(ctx, requestedOffset, scrollTarget);
2156
- const offset = shouldPreserveRawInitialOffsetRequest ? requestedOffset : targetOffset;
2157
- state.scrollHistory.length = 0;
2158
- if (!noScrollingTo) {
2159
- if (isInitialScroll) {
2160
- initialScrollCompletion.resetFlags(state);
2240
+ const isExpired = now > lock.expiresAt;
2241
+ const isMissing = state.indexByKey.get(lock.id) === void 0;
2242
+ if (isExpired || isMissing || !mvcpData) {
2243
+ state.mvcpAnchorLock = void 0;
2244
+ return void 0;
2245
+ }
2246
+ return lock;
2247
+ }
2248
+ function updateAnchorLock(state, params) {
2249
+ if (Platform.OS === "web") {
2250
+ const { anchorId, anchorPosition, dataChanged, now, positionDiff } = params;
2251
+ const enableMVCPAnchorLock = !!dataChanged || !!state.mvcpAnchorLock;
2252
+ const mvcpData = state.props.maintainVisibleContentPosition.data;
2253
+ if (!enableMVCPAnchorLock || !mvcpData || state.scrollingTo || !anchorId || anchorPosition === void 0) {
2254
+ return;
2161
2255
  }
2162
- const averageSizeSnapshot = getAverageSizeSnapshot(state);
2163
- state.scrollingTo = {
2164
- ...scrollTarget,
2165
- ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
2166
- targetOffset,
2167
- waitForInitialScrollCompletionFrame
2256
+ const existingLock = state.mvcpAnchorLock;
2257
+ const quietPasses = !dataChanged && Math.abs(positionDiff) <= MVCP_POSITION_EPSILON && (existingLock == null ? void 0 : existingLock.id) === anchorId ? existingLock.quietPasses + 1 : 0;
2258
+ if (!dataChanged && quietPasses >= MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE) {
2259
+ state.mvcpAnchorLock = void 0;
2260
+ return;
2261
+ }
2262
+ state.mvcpAnchorLock = {
2263
+ expiresAt: now + MVCP_ANCHOR_LOCK_TTL_MS,
2264
+ id: anchorId,
2265
+ position: anchorPosition,
2266
+ quietPasses
2168
2267
  };
2169
2268
  }
2170
- state.scrollPending = targetOffset;
2171
- syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2172
- if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2173
- doScrollTo(ctx, { animated, horizontal, isInitialScroll, offset });
2174
- } else {
2175
- state.scroll = offset;
2269
+ }
2270
+ function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
2271
+ if (!dataChanged || Platform.OS === "web" || !state.props.maintainVisibleContentPosition.data || scrollTarget !== void 0 || positionDiff >= -MVCP_POSITION_EPSILON) {
2272
+ return false;
2176
2273
  }
2274
+ const distanceFromEnd = prevTotalSize - prevScroll - state.scrollLength;
2275
+ return distanceFromEnd < Math.abs(positionDiff) - MVCP_POSITION_EPSILON;
2177
2276
  }
2178
-
2179
- // src/core/scrollToIndex.ts
2180
- function clampScrollIndex(index, dataLength) {
2181
- if (dataLength <= 0) {
2182
- return -1;
2277
+ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
2278
+ if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
2279
+ return 0;
2183
2280
  }
2184
- if (index >= dataLength) {
2185
- return dataLength - 1;
2281
+ const maxScroll = Math.max(0, totalSize - state.scrollLength);
2282
+ const clampDelta = maxScroll - state.scroll;
2283
+ if (unresolvedAmount < 0) {
2284
+ return Math.max(unresolvedAmount, Math.min(0, clampDelta));
2186
2285
  }
2187
- if (index < 0) {
2188
- return 0;
2286
+ if (unresolvedAmount > 0) {
2287
+ return Math.min(unresolvedAmount, Math.max(0, clampDelta));
2189
2288
  }
2190
- return index;
2289
+ return 0;
2191
2290
  }
2192
- function scrollToIndex(ctx, {
2193
- index,
2194
- viewOffset = 0,
2195
- animated = true,
2196
- forceScroll,
2197
- isInitialScroll,
2198
- viewPosition
2199
- }) {
2291
+ function getProgressTowardAmount(targetDelta, nativeDelta) {
2292
+ return targetDelta < 0 ? -nativeDelta : nativeDelta;
2293
+ }
2294
+ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
2200
2295
  const state = ctx.state;
2201
- const { data } = state.props;
2202
- index = clampScrollIndex(index, data.length);
2203
- const itemSize = getItemSizeAtIndex(ctx, index);
2204
- const firstIndexOffset = calculateOffsetForIndex(ctx, index);
2205
- const isLast = index === data.length - 1;
2206
- if (isLast && viewPosition === void 0) {
2207
- viewPosition = 1;
2296
+ state.pendingNativeMVCPAdjust = void 0;
2297
+ const remaining = remainingAfterManual - nativeDelta;
2298
+ if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
2299
+ requestAdjust(ctx, remaining, true);
2208
2300
  }
2209
- state.scrollForNextCalculateItemsInView = void 0;
2210
- scrollTo(ctx, {
2211
- animated,
2212
- forceScroll,
2213
- index,
2214
- isInitialScroll,
2215
- itemSize,
2216
- offset: firstIndexOffset,
2217
- viewOffset,
2218
- viewPosition: viewPosition != null ? viewPosition : 0
2219
- });
2220
- }
2221
-
2222
- // src/core/initialScroll.ts
2223
- function dispatchInitialScroll(ctx, params) {
2224
- const { forceScroll, resolvedOffset, target, waitForCompletionFrame } = params;
2225
- const requestedIndex = target.index;
2226
- const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0;
2227
- const itemSize = getItemSizeAtIndex(ctx, index);
2228
- scrollTo(ctx, {
2229
- animated: false,
2230
- forceScroll,
2231
- index: index !== void 0 && index >= 0 ? index : void 0,
2232
- isInitialScroll: true,
2233
- itemSize,
2234
- offset: resolvedOffset,
2235
- precomputedWithViewOffset: true,
2236
- viewOffset: target.viewOffset,
2237
- viewPosition: target.viewPosition,
2238
- waitForInitialScrollCompletionFrame: waitForCompletionFrame
2239
- });
2240
- }
2241
- function setInitialScrollTarget(state, target, options) {
2242
- var _a3;
2243
- state.clearPreservedInitialScrollOnNextFinish = void 0;
2244
- if (state.timeoutPreservedInitialScrollClear !== void 0) {
2245
- clearTimeout(state.timeoutPreservedInitialScrollClear);
2246
- state.timeoutPreservedInitialScrollClear = void 0;
2247
- }
2248
- state.initialScroll = target;
2249
- if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
2250
- state.didFinishInitialScroll = false;
2251
- }
2252
- setInitialScrollSession(state, {
2253
- kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
2254
- });
2255
2301
  }
2256
- function resolveInitialScrollOffset(ctx, initialScroll) {
2257
- var _a3, _b;
2302
+ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
2258
2303
  const state = ctx.state;
2259
- if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
2260
- return (_b = initialScroll.contentOffset) != null ? _b : 0;
2304
+ const pending = state.pendingNativeMVCPAdjust;
2305
+ if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
2306
+ return;
2261
2307
  }
2262
- const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
2263
- const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
2264
- return clampScrollOffset(ctx, resolvedOffset, initialScroll);
2265
- }
2266
- function getAdvanceableInitialScrollState(state, options) {
2267
- const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
2268
- const initialScroll = state.initialScroll;
2269
- const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
2270
- const shouldWaitForInitialLayout = !!(options == null ? void 0 : options.requiresMeasuredLayout) && !queuedInitialLayout && !isInitialScrollInProgress;
2271
- if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll || scrollingTo && !isInitialScrollInProgress) {
2272
- return void 0;
2308
+ const totalSize = getContentSize(ctx);
2309
+ const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
2310
+ if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
2311
+ return;
2273
2312
  }
2274
- return {
2275
- initialScroll,
2276
- isInitialScrollInProgress,
2277
- queuedInitialLayout,
2278
- scrollingTo
2279
- };
2313
+ const manualDesired = pending.amount - predictedNativeClamp;
2314
+ if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
2315
+ return;
2316
+ }
2317
+ pending.manualApplied = manualDesired;
2318
+ requestAdjust(ctx, manualDesired, true);
2319
+ pending.furthestProgressTowardAmount = 0;
2280
2320
  }
2281
- function advanceMeasuredInitialScroll(ctx, options) {
2282
- var _a3, _b, _c;
2321
+ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
2283
2322
  const state = ctx.state;
2284
- const advanceableState = getAdvanceableInitialScrollState(state, {
2285
- requiresMeasuredLayout: true
2286
- });
2287
- if (!advanceableState) {
2323
+ const pending = state.pendingNativeMVCPAdjust;
2324
+ if (!pending) {
2288
2325
  return false;
2289
2326
  }
2290
- const { initialScroll, isInitialScrollInProgress, queuedInitialLayout } = advanceableState;
2291
- const scrollingTo = isInitialScrollInProgress ? advanceableState.scrollingTo : void 0;
2292
- const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
2293
- const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2294
- const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2295
- const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2296
- const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2297
- if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2298
- return false;
2327
+ const remainingAfterManual = pending.amount - pending.manualApplied;
2328
+ const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
2329
+ const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
2330
+ const progressTowardAmount = getProgressTowardAmount(remainingAfterManual, nativeDelta);
2331
+ if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
2332
+ state.pendingNativeMVCPAdjust = void 0;
2333
+ return true;
2299
2334
  }
2300
- if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2335
+ if (isWrongDirection) {
2336
+ state.pendingNativeMVCPAdjust = void 0;
2301
2337
  return false;
2302
2338
  }
2303
- if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2304
- setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2339
+ if (progressTowardAmount + MVCP_POSITION_EPSILON >= Math.abs(remainingAfterManual)) {
2340
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2341
+ return true;
2305
2342
  }
2306
- const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
2307
- dispatchInitialScroll(ctx, {
2308
- forceScroll,
2309
- resolvedOffset,
2310
- target: initialScroll
2311
- });
2312
- return true;
2313
- }
2314
- function advanceOffsetInitialScroll(ctx, options) {
2315
- var _a3, _b;
2316
- const state = ctx.state;
2317
- const advanceableState = getAdvanceableInitialScrollState(state);
2318
- if (!advanceableState) {
2319
- return false;
2343
+ const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
2344
+ const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
2345
+ const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
2346
+ if (isAtExpectedNativeClamp) {
2347
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2348
+ return true;
2320
2349
  }
2321
- const { initialScroll, queuedInitialLayout } = advanceableState;
2322
- const resolvedOffset = (_a3 = initialScroll.contentOffset) != null ? _a3 : 0;
2323
- const isAlreadyAtDesiredInitialTarget = Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2324
- if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2350
+ if (state.pendingMaintainScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold") && progressTowardAmount > MVCP_POSITION_EPSILON) {
2351
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2352
+ return true;
2353
+ }
2354
+ if (progressTowardAmount > pending.furthestProgressTowardAmount + MVCP_POSITION_EPSILON) {
2355
+ pending.furthestProgressTowardAmount = progressTowardAmount;
2325
2356
  return false;
2326
2357
  }
2327
- const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
2328
- const forceScroll = (_b = options == null ? void 0 : options.forceScroll) != null ? _b : hasMeasuredScrollLayout || !!queuedInitialLayout;
2329
- dispatchInitialScroll(ctx, {
2330
- forceScroll,
2331
- resolvedOffset,
2332
- target: initialScroll
2333
- });
2334
- return true;
2335
- }
2336
- function advanceCurrentInitialScrollSession(ctx, options) {
2337
- var _a3;
2338
- return ((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? advanceOffsetInitialScroll(ctx, {
2339
- forceScroll: options == null ? void 0 : options.forceScroll
2340
- }) : advanceMeasuredInitialScroll(ctx, {
2341
- forceScroll: options == null ? void 0 : options.forceScroll
2342
- });
2343
- }
2344
-
2345
- // src/utils/checkAllSizesKnown.ts
2346
- function isNullOrUndefined2(value) {
2347
- return value === null || value === void 0;
2348
- }
2349
- function getMountedIndicesInRange(state, start, end) {
2350
- if (!isNullOrUndefined2(end) && !isNullOrUndefined2(start) && start >= 0 && end >= 0) {
2351
- 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);
2358
+ if (pending.furthestProgressTowardAmount > MVCP_POSITION_EPSILON && progressTowardAmount < pending.furthestProgressTowardAmount - MVCP_POSITION_EPSILON) {
2359
+ state.pendingNativeMVCPAdjust = void 0;
2360
+ return false;
2352
2361
  }
2353
- return [];
2354
- }
2355
- function getMountedBufferedIndices(state) {
2356
- return getMountedIndicesInRange(state, state.startBuffered, state.endBuffered);
2357
- }
2358
- function getMountedNoBufferIndices(state) {
2359
- return getMountedIndicesInRange(state, state.startNoBuffer, state.endNoBuffer);
2360
- }
2361
- function checkAllSizesKnown(state, indices) {
2362
- return indices.length > 0 && indices.every((index) => {
2363
- const key = getId(state, index);
2364
- return key !== void 0 && state.sizesKnown.has(key);
2365
- });
2362
+ return false;
2366
2363
  }
2367
-
2368
- // src/utils/requestAdjust.ts
2369
- function requestAdjust(ctx, positionDiff, dataChanged) {
2364
+ function prepareMVCP(ctx, dataChanged) {
2370
2365
  const state = ctx.state;
2371
- if (Math.abs(positionDiff) > 0.1) {
2372
- const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff;
2373
- const doit = () => {
2374
- if (needsScrollWorkaround) {
2375
- scrollTo(ctx, {
2376
- noScrollingTo: true,
2377
- offset: state.scroll
2378
- });
2379
- } else {
2380
- state.scrollAdjustHandler.requestAdjust(positionDiff);
2381
- if (state.adjustingFromInitialMount) {
2382
- state.adjustingFromInitialMount--;
2383
- }
2366
+ const { idsInView, positions, props } = state;
2367
+ const {
2368
+ maintainVisibleContentPosition: { data: mvcpData, size: mvcpScroll, shouldRestorePosition }
2369
+ } = props;
2370
+ const isWeb = Platform.OS === "web";
2371
+ const now = Date.now();
2372
+ const enableMVCPAnchorLock = isWeb && (!!dataChanged || !!state.mvcpAnchorLock);
2373
+ const scrollingTo = state.scrollingTo;
2374
+ const anchorLock = isWeb ? resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) : void 0;
2375
+ let prevPosition;
2376
+ let targetId;
2377
+ const idsInViewWithPositions = [];
2378
+ const scrollTarget = scrollingTo == null ? void 0 : scrollingTo.index;
2379
+ const scrollingToViewPosition = scrollingTo == null ? void 0 : scrollingTo.viewPosition;
2380
+ const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
2381
+ const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
2382
+ const indexByKey = state.indexByKey;
2383
+ const prevScroll = state.scroll;
2384
+ const prevTotalSize = getContentSize(ctx);
2385
+ if (shouldMVCP) {
2386
+ if (!isWeb && state.pendingNativeMVCPAdjust && scrollTarget === void 0) {
2387
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
2388
+ return void 0;
2389
+ }
2390
+ if (anchorLock && scrollTarget === void 0) {
2391
+ targetId = anchorLock.id;
2392
+ prevPosition = anchorLock.position;
2393
+ } else if (scrollTarget !== void 0) {
2394
+ if (!IsNewArchitecture && (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll)) {
2395
+ return void 0;
2384
2396
  }
2385
- };
2386
- state.scroll += positionDiff;
2387
- state.scrollForNextCalculateItemsInView = void 0;
2388
- const readyToRender = peek$(ctx, "readyToRender");
2389
- if (readyToRender) {
2390
- doit();
2391
- if (Platform.OS !== "web") {
2392
- const threshold = state.scroll - positionDiff / 2;
2393
- if (!state.ignoreScrollFromMVCP) {
2394
- state.ignoreScrollFromMVCP = {};
2397
+ targetId = getId(state, scrollTarget);
2398
+ } else if (idsInView.length > 0 && state.didContainersLayout && !dataChanged) {
2399
+ targetId = idsInView.find((id) => indexByKey.get(id) !== void 0);
2400
+ }
2401
+ if (dataChanged && idsInView.length > 0 && state.didContainersLayout) {
2402
+ for (let i = 0; i < idsInView.length; i++) {
2403
+ const id = idsInView[i];
2404
+ const index = indexByKey.get(id);
2405
+ if (index !== void 0) {
2406
+ const position = positions[index];
2407
+ if (position !== void 0) {
2408
+ idsInViewWithPositions.push({ id, position });
2409
+ }
2395
2410
  }
2396
- if (positionDiff > 0) {
2397
- state.ignoreScrollFromMVCP.lt = threshold;
2398
- } else {
2399
- state.ignoreScrollFromMVCP.gt = threshold;
2411
+ }
2412
+ }
2413
+ if (targetId !== void 0 && prevPosition === void 0) {
2414
+ const targetIndex = indexByKey.get(targetId);
2415
+ if (targetIndex !== void 0) {
2416
+ prevPosition = positions[targetIndex];
2417
+ }
2418
+ }
2419
+ return () => {
2420
+ let positionDiff = 0;
2421
+ let anchorIdForLock = anchorLock == null ? void 0 : anchorLock.id;
2422
+ let anchorPositionForLock;
2423
+ let skipTargetAnchor = false;
2424
+ const data = state.props.data;
2425
+ const shouldValidateLockedAnchor = isWeb && dataChanged && mvcpData && scrollTarget === void 0 && targetId !== void 0 && (anchorLock == null ? void 0 : anchorLock.id) === targetId && shouldRestorePosition !== void 0;
2426
+ if (shouldValidateLockedAnchor && targetId !== void 0) {
2427
+ const index = indexByKey.get(targetId);
2428
+ if (index !== void 0) {
2429
+ const item = data[index];
2430
+ skipTargetAnchor = item === void 0 || !shouldRestorePosition(item, index, data);
2431
+ if (skipTargetAnchor && (anchorLock == null ? void 0 : anchorLock.id) === targetId) {
2432
+ state.mvcpAnchorLock = void 0;
2433
+ }
2400
2434
  }
2401
- if (state.ignoreScrollFromMVCPTimeout) {
2402
- clearTimeout(state.ignoreScrollFromMVCPTimeout);
2435
+ }
2436
+ const shouldUseFallbackVisibleAnchor = dataChanged && mvcpData && scrollTarget === void 0 && (() => {
2437
+ if (targetId === void 0 || skipTargetAnchor) {
2438
+ return true;
2403
2439
  }
2404
- const delay = needsScrollWorkaround ? 250 : 100;
2405
- state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
2406
- var _a3;
2407
- state.ignoreScrollFromMVCP = void 0;
2408
- const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
2409
- if (shouldForceUpdate) {
2410
- state.ignoreScrollFromMVCPIgnored = false;
2411
- state.scrollPending = state.scroll;
2412
- (_a3 = state.reprocessCurrentScroll) == null ? void 0 : _a3.call(state);
2440
+ const targetIndex = indexByKey.get(targetId);
2441
+ return targetIndex === void 0 || positions[targetIndex] === void 0;
2442
+ })();
2443
+ if (shouldUseFallbackVisibleAnchor) {
2444
+ for (let i = 0; i < idsInViewWithPositions.length; i++) {
2445
+ const { id, position } = idsInViewWithPositions[i];
2446
+ const index = indexByKey.get(id);
2447
+ if (index !== void 0 && shouldRestorePosition) {
2448
+ const item = data[index];
2449
+ if (item === void 0 || !shouldRestorePosition(item, index, data)) {
2450
+ continue;
2451
+ }
2413
2452
  }
2414
- }, delay);
2453
+ const newPosition = index !== void 0 ? positions[index] : void 0;
2454
+ if (newPosition !== void 0) {
2455
+ positionDiff = newPosition - position;
2456
+ anchorIdForLock = id;
2457
+ anchorPositionForLock = newPosition;
2458
+ break;
2459
+ }
2460
+ }
2415
2461
  }
2416
- } else {
2417
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2418
- requestAnimationFrame(doit);
2419
- }
2462
+ if (!skipTargetAnchor && targetId !== void 0 && prevPosition !== void 0) {
2463
+ const targetIndex = indexByKey.get(targetId);
2464
+ const newPosition = targetIndex !== void 0 ? positions[targetIndex] : void 0;
2465
+ if (newPosition !== void 0) {
2466
+ const totalSize = getContentSize(ctx);
2467
+ let diff = newPosition - prevPosition;
2468
+ if (diff !== 0 && isEndAnchoredScrollTarget && state.scroll + state.scrollLength > totalSize) {
2469
+ if (diff > 0) {
2470
+ diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
2471
+ } else {
2472
+ diff = 0;
2473
+ }
2474
+ }
2475
+ positionDiff = diff;
2476
+ anchorIdForLock = targetId;
2477
+ anchorPositionForLock = newPosition;
2478
+ }
2479
+ }
2480
+ if (scrollingToViewPosition && scrollingToViewPosition > 0) {
2481
+ const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
2482
+ const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
2483
+ if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
2484
+ const diff = newSize - prevSize;
2485
+ if (diff !== 0) {
2486
+ positionDiff += diff * scrollingToViewPosition;
2487
+ scrollingTo.itemSize = newSize;
2488
+ }
2489
+ }
2490
+ }
2491
+ updateAnchorLock(state, {
2492
+ anchorId: anchorIdForLock,
2493
+ anchorPosition: anchorPositionForLock,
2494
+ dataChanged,
2495
+ now,
2496
+ positionDiff
2497
+ });
2498
+ if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
2499
+ state.pendingNativeMVCPAdjust = {
2500
+ amount: positionDiff,
2501
+ furthestProgressTowardAmount: 0,
2502
+ manualApplied: 0,
2503
+ startScroll: prevScroll
2504
+ };
2505
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
2506
+ return;
2507
+ }
2508
+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
2509
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2510
+ if (!shouldSkipAdjustForMaintainedEnd) {
2511
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
2512
+ }
2513
+ }
2514
+ };
2420
2515
  }
2421
2516
  }
2422
2517
 
2423
- // src/core/bootstrapInitialScroll.ts
2424
- var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2425
- var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
2426
- var DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES = 24;
2427
- var BOOTSTRAP_REVEAL_ABORT_WARNING = "LegendList bootstrap initial scroll aborted after exceeding convergence bounds.";
2428
- function getBootstrapInitialScrollSession(state) {
2429
- var _a3;
2430
- return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
2431
- }
2432
- function isOffsetInitialScrollSession(state) {
2518
+ // src/platform/flushSync.native.ts
2519
+ var flushSync = (fn) => {
2520
+ fn();
2521
+ };
2522
+
2523
+ // src/core/updateScroll.ts
2524
+ function updateScroll(ctx, newScroll, forceUpdate, options) {
2433
2525
  var _a3;
2434
- return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2435
- }
2436
- function doVisibleIndicesMatch(previous, next) {
2437
- if (!previous || previous.length !== next.length) {
2438
- return false;
2526
+ const state = ctx.state;
2527
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
2528
+ const prevScroll = state.scroll;
2529
+ if ((options == null ? void 0 : options.markHasScrolled) !== false) {
2530
+ state.hasScrolled = true;
2439
2531
  }
2440
- for (let i = 0; i < previous.length; i++) {
2441
- if (previous[i] !== next[i]) {
2442
- return false;
2443
- }
2532
+ state.lastBatchingAction = Date.now();
2533
+ const currentTime = Date.now();
2534
+ const adjust = scrollAdjustHandler.getAdjust();
2535
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
2536
+ if (adjustChanged) {
2537
+ scrollHistory.length = 0;
2444
2538
  }
2445
- return true;
2446
- }
2447
- function getBootstrapRevealVisibleIndices(options) {
2448
- const { dataLength, getSize, offset, positions, scrollLength, startIndex: requestedStartIndex } = options;
2449
- const endOffset = offset + scrollLength;
2450
- const visibleIndices = [];
2451
- let index = requestedStartIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, requestedStartIndex)) : 0;
2452
- while (index > 0) {
2453
- const previousIndex = index - 1;
2454
- const previousPosition = positions[previousIndex];
2455
- if (previousPosition === void 0) {
2456
- index = previousIndex;
2457
- continue;
2458
- }
2459
- const previousSize = getSize(previousIndex);
2460
- if (previousSize === void 0) {
2461
- index = previousIndex;
2462
- continue;
2463
- }
2464
- if (previousPosition + previousSize <= offset) {
2465
- break;
2539
+ state.lastScrollAdjustForHistory = adjust;
2540
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
2541
+ if (!adjustChanged) {
2542
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
2466
2543
  }
2467
- index = previousIndex;
2468
2544
  }
2469
- for (; index < dataLength; index++) {
2470
- const position = positions[index];
2471
- if (position === void 0) {
2472
- continue;
2545
+ if (scrollHistory.length > 5) {
2546
+ scrollHistory.shift();
2547
+ }
2548
+ if (ignoreScrollFromMVCP && !scrollingTo) {
2549
+ const { lt, gt } = ignoreScrollFromMVCP;
2550
+ if (lt && newScroll < lt || gt && newScroll > gt) {
2551
+ state.ignoreScrollFromMVCPIgnored = true;
2552
+ return;
2473
2553
  }
2474
- const size = getSize(index);
2475
- if (size === void 0) {
2476
- continue;
2554
+ }
2555
+ state.scrollPrev = prevScroll;
2556
+ state.scrollPrevTime = state.scrollTime;
2557
+ state.scroll = newScroll;
2558
+ state.scrollTime = currentTime;
2559
+ const scrollDelta = Math.abs(newScroll - prevScroll);
2560
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2561
+ const scrollLength = state.scrollLength;
2562
+ const lastCalculated = state.scrollLastCalculate;
2563
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2564
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2565
+ if (shouldUpdate) {
2566
+ state.scrollLastCalculate = state.scroll;
2567
+ state.ignoreScrollFromMVCPIgnored = false;
2568
+ state.lastScrollDelta = scrollDelta;
2569
+ const runCalculateItems = () => {
2570
+ var _a4;
2571
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
2572
+ checkThresholds(ctx);
2573
+ };
2574
+ if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
2575
+ state.mvcpAnchorLock = void 0;
2576
+ state.pendingNativeMVCPAdjust = void 0;
2577
+ state.userScrollAnchorResetKeys = /* @__PURE__ */ new Set();
2578
+ if (state.queuedMVCPRecalculate !== void 0) {
2579
+ cancelAnimationFrame(state.queuedMVCPRecalculate);
2580
+ state.queuedMVCPRecalculate = void 0;
2581
+ }
2582
+ flushSync(runCalculateItems);
2583
+ } else {
2584
+ runCalculateItems();
2477
2585
  }
2478
- if (position < endOffset && position + size > offset) {
2479
- visibleIndices.push(index);
2480
- } else if (visibleIndices.length > 0 && position >= endOffset) {
2481
- break;
2586
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
2587
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
2588
+ state.pendingMaintainScrollAtEnd = false;
2589
+ doMaintainScrollAtEnd(ctx);
2482
2590
  }
2591
+ state.dataChangeNeedsScrollUpdate = false;
2592
+ state.lastScrollDelta = 0;
2483
2593
  }
2484
- return visibleIndices;
2485
- }
2486
- function shouldAbortBootstrapReveal(options) {
2487
- const {
2488
- mountFrameCount,
2489
- maxFrames = DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES,
2490
- maxPasses = DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES,
2491
- passCount
2492
- } = options;
2493
- return mountFrameCount >= maxFrames || passCount >= maxPasses;
2494
2594
  }
2495
- function abortBootstrapRevealIfNeeded(ctx, options) {
2496
- if (!shouldAbortBootstrapReveal(options)) {
2497
- return false;
2595
+
2596
+ // src/core/scrollTo.ts
2597
+ function getAverageSizeSnapshot(state) {
2598
+ if (Object.keys(state.averageSizes).length === 0) {
2599
+ return void 0;
2498
2600
  }
2499
- if (IS_DEV) {
2500
- console.warn(BOOTSTRAP_REVEAL_ABORT_WARNING);
2601
+ const snapshot = {};
2602
+ for (const itemType in state.averageSizes) {
2603
+ const averages = state.averageSizes[itemType];
2604
+ snapshot[itemType] = averages.avg;
2501
2605
  }
2502
- abortBootstrapInitialScroll(ctx);
2503
- return true;
2606
+ return snapshot;
2504
2607
  }
2505
- function clearBootstrapInitialScrollSession(state) {
2608
+ function syncInitialScrollNativeWatchdog(state, options) {
2506
2609
  var _a3;
2507
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2508
- const frameHandle = bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle;
2509
- if (frameHandle !== void 0 && typeof cancelAnimationFrame === "function") {
2510
- cancelAnimationFrame(frameHandle);
2610
+ const { isInitialScroll, requestedOffset, targetOffset } = options;
2611
+ const existingWatchdog = initialScrollWatchdog.get(state);
2612
+ const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!existingWatchdog) && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
2613
+ const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!existingWatchdog && initialScrollWatchdog.isAtZeroTargetOffset(requestedOffset);
2614
+ if (shouldWatchInitialNativeScroll) {
2615
+ state.hasScrolled = false;
2616
+ initialScrollWatchdog.set(state, {
2617
+ startScroll: (_a3 = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _a3 : state.scroll,
2618
+ targetOffset
2619
+ });
2620
+ return;
2511
2621
  }
2512
- if (bootstrapInitialScroll) {
2513
- bootstrapInitialScroll.frameHandle = void 0;
2622
+ if (shouldClearInitialNativeScrollWatchdog) {
2623
+ initialScrollWatchdog.clear(state);
2514
2624
  }
2515
- setInitialScrollSession(state, {
2516
- bootstrap: null,
2517
- kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2518
- });
2519
- }
2520
- function startBootstrapInitialScrollSession(state, options) {
2521
- var _a3, _b, _c;
2522
- const previousBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2523
- setInitialScrollSession(state, {
2524
- bootstrap: {
2525
- frameHandle: previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.frameHandle,
2526
- // Re-arming during the initial mount should spend from the same watchdog budget.
2527
- mountFrameCount: (_a3 = previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.mountFrameCount) != null ? _a3 : 0,
2528
- passCount: 0,
2529
- previousResolvedOffset: void 0,
2530
- scroll: options.scroll,
2531
- seedContentOffset: (_c = (_b = options.seedContentOffset) != null ? _b : previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.seedContentOffset) != null ? _c : options.scroll,
2532
- targetIndexSeed: options.targetIndexSeed,
2533
- visibleIndices: void 0
2534
- },
2535
- kind: "bootstrap"
2536
- });
2537
2625
  }
2538
- function resetBootstrapInitialScrollSession(state, options) {
2539
- var _a3, _b, _c;
2540
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2541
- if (!bootstrapInitialScroll) {
2542
- if ((options == null ? void 0 : options.scroll) !== void 0) {
2543
- startBootstrapInitialScrollSession(state, {
2544
- scroll: options.scroll,
2545
- seedContentOffset: options.seedContentOffset,
2546
- targetIndexSeed: options.targetIndexSeed
2547
- });
2626
+ function scrollTo(ctx, params) {
2627
+ var _a3;
2628
+ const state = ctx.state;
2629
+ const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2630
+ const {
2631
+ animated,
2632
+ isInitialScroll,
2633
+ offset: scrollTargetOffset,
2634
+ precomputedWithViewOffset,
2635
+ waitForInitialScrollCompletionFrame
2636
+ } = scrollTarget;
2637
+ const {
2638
+ props: { horizontal }
2639
+ } = state;
2640
+ if (state.animFrameCheckFinishedScroll) {
2641
+ cancelAnimationFrame(ctx.state.animFrameCheckFinishedScroll);
2642
+ }
2643
+ if (state.timeoutCheckFinishedScrollFallback) {
2644
+ clearTimeout(ctx.state.timeoutCheckFinishedScrollFallback);
2645
+ }
2646
+ const requestedOffset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
2647
+ const shouldPreserveRawInitialOffsetRequest = !!isInitialScroll && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2648
+ const targetOffset = clampScrollOffset(ctx, requestedOffset, scrollTarget);
2649
+ const offset = shouldPreserveRawInitialOffsetRequest ? requestedOffset : targetOffset;
2650
+ state.scrollHistory.length = 0;
2651
+ if (!noScrollingTo) {
2652
+ if (isInitialScroll) {
2653
+ initialScrollCompletion.resetFlags(state);
2548
2654
  }
2655
+ const averageSizeSnapshot = getAverageSizeSnapshot(state);
2656
+ state.scrollingTo = {
2657
+ ...scrollTarget,
2658
+ ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
2659
+ targetOffset,
2660
+ waitForInitialScrollCompletionFrame
2661
+ };
2662
+ }
2663
+ state.scrollPending = targetOffset;
2664
+ syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2665
+ if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2666
+ updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2667
+ }
2668
+ if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2669
+ doScrollTo(ctx, { animated, horizontal, isInitialScroll, offset });
2549
2670
  } else {
2550
- bootstrapInitialScroll.passCount = 0;
2551
- bootstrapInitialScroll.previousResolvedOffset = void 0;
2552
- bootstrapInitialScroll.scroll = (_a3 = options == null ? void 0 : options.scroll) != null ? _a3 : bootstrapInitialScroll.scroll;
2553
- bootstrapInitialScroll.seedContentOffset = (_b = options == null ? void 0 : options.seedContentOffset) != null ? _b : bootstrapInitialScroll.seedContentOffset;
2554
- bootstrapInitialScroll.targetIndexSeed = (_c = options == null ? void 0 : options.targetIndexSeed) != null ? _c : bootstrapInitialScroll.targetIndexSeed;
2555
- bootstrapInitialScroll.visibleIndices = void 0;
2556
- setInitialScrollSession(state, {
2557
- bootstrap: bootstrapInitialScroll,
2558
- kind: "bootstrap"
2559
- });
2671
+ state.scroll = offset;
2560
2672
  }
2561
2673
  }
2562
- function queueBootstrapInitialScrollReevaluation(state) {
2563
- requestAnimationFrame(() => {
2564
- var _a3;
2565
- if (getBootstrapInitialScrollSession(state)) {
2566
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
2567
- }
2568
- });
2674
+
2675
+ // src/core/scrollToIndex.ts
2676
+ function clampScrollIndex(index, dataLength) {
2677
+ if (dataLength <= 0) {
2678
+ return -1;
2679
+ }
2680
+ if (index >= dataLength) {
2681
+ return dataLength - 1;
2682
+ }
2683
+ if (index < 0) {
2684
+ return 0;
2685
+ }
2686
+ return index;
2569
2687
  }
2570
- function ensureBootstrapInitialScrollFrameTicker(ctx) {
2688
+ function scrollToIndex(ctx, {
2689
+ index,
2690
+ viewOffset = 0,
2691
+ animated = true,
2692
+ forceScroll,
2693
+ isInitialScroll,
2694
+ viewPosition
2695
+ }) {
2571
2696
  const state = ctx.state;
2572
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2573
- if (!bootstrapInitialScroll || bootstrapInitialScroll.frameHandle !== void 0) {
2574
- return;
2697
+ const { data } = state.props;
2698
+ index = clampScrollIndex(index, data.length);
2699
+ const itemSize = getItemSizeAtIndex(ctx, index);
2700
+ const firstIndexOffset = calculateOffsetForIndex(ctx, index);
2701
+ const isLast = index === data.length - 1;
2702
+ if (isLast && viewPosition === void 0) {
2703
+ viewPosition = 1;
2575
2704
  }
2576
- const tick = () => {
2577
- const activeBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2578
- if (!activeBootstrapInitialScroll) {
2579
- return;
2580
- }
2581
- activeBootstrapInitialScroll.frameHandle = void 0;
2582
- activeBootstrapInitialScroll.mountFrameCount += 1;
2583
- if (abortBootstrapRevealIfNeeded(ctx, {
2584
- mountFrameCount: activeBootstrapInitialScroll.mountFrameCount,
2585
- passCount: activeBootstrapInitialScroll.passCount
2586
- })) {
2587
- return;
2588
- }
2589
- ensureBootstrapInitialScrollFrameTicker(ctx);
2590
- };
2591
- bootstrapInitialScroll.frameHandle = requestAnimationFrame(tick);
2592
- }
2593
- function rearmBootstrapInitialScroll(ctx, options) {
2594
- resetBootstrapInitialScrollSession(ctx.state, options);
2595
- ensureBootstrapInitialScrollFrameTicker(ctx);
2596
- queueBootstrapInitialScrollReevaluation(ctx.state);
2597
- }
2598
- function createInitialScrollAtEndTarget(options) {
2599
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingBottom } = options;
2600
- return {
2601
- contentOffset: void 0,
2602
- index: Math.max(0, dataLength - 1),
2603
- preserveForBottomPadding: true,
2604
- preserveForFooterLayout,
2605
- viewOffset: -stylePaddingBottom - footerSize,
2606
- viewPosition: 1
2607
- };
2705
+ state.scrollForNextCalculateItemsInView = void 0;
2706
+ scrollTo(ctx, {
2707
+ animated,
2708
+ forceScroll,
2709
+ index,
2710
+ isInitialScroll,
2711
+ itemSize,
2712
+ offset: firstIndexOffset,
2713
+ viewOffset,
2714
+ viewPosition: viewPosition != null ? viewPosition : 0
2715
+ });
2608
2716
  }
2609
- function shouldPreserveInitialScrollForBottomPadding(target) {
2610
- return !!(target == null ? void 0 : target.preserveForBottomPadding);
2717
+
2718
+ // src/core/initialScroll.ts
2719
+ function dispatchInitialScroll(ctx, params) {
2720
+ const { forceScroll, resolvedOffset, target, waitForCompletionFrame } = params;
2721
+ const requestedIndex = target.index;
2722
+ const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0;
2723
+ const itemSize = getItemSizeAtIndex(ctx, index);
2724
+ scrollTo(ctx, {
2725
+ animated: false,
2726
+ forceScroll,
2727
+ index: index !== void 0 && index >= 0 ? index : void 0,
2728
+ isInitialScroll: true,
2729
+ itemSize,
2730
+ offset: resolvedOffset,
2731
+ precomputedWithViewOffset: true,
2732
+ viewOffset: target.viewOffset,
2733
+ viewPosition: target.viewPosition,
2734
+ waitForInitialScrollCompletionFrame: waitForCompletionFrame
2735
+ });
2611
2736
  }
2612
- function shouldPreserveInitialScrollForFooterLayout(target) {
2613
- return !!(target == null ? void 0 : target.preserveForFooterLayout);
2737
+ function setInitialScrollTarget(state, target, options) {
2738
+ var _a3;
2739
+ state.clearPreservedInitialScrollOnNextFinish = void 0;
2740
+ if (state.timeoutPreservedInitialScrollClear !== void 0) {
2741
+ clearTimeout(state.timeoutPreservedInitialScrollClear);
2742
+ state.timeoutPreservedInitialScrollClear = void 0;
2743
+ }
2744
+ state.initialScroll = target;
2745
+ if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
2746
+ state.didFinishInitialScroll = false;
2747
+ }
2748
+ setInitialScrollSession(state, {
2749
+ kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
2750
+ });
2614
2751
  }
2615
- function isRetargetableBottomAlignedInitialScrollTarget(target) {
2616
- return !!(target && target.viewPosition === 1 && (shouldPreserveInitialScrollForBottomPadding(target) || shouldPreserveInitialScrollForFooterLayout(target)));
2752
+ function resolveInitialScrollOffset(ctx, initialScroll) {
2753
+ var _a3, _b;
2754
+ const state = ctx.state;
2755
+ if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
2756
+ return (_b = initialScroll.contentOffset) != null ? _b : 0;
2757
+ }
2758
+ const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
2759
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
2760
+ return clampScrollOffset(ctx, resolvedOffset, initialScroll);
2617
2761
  }
2618
- function createRetargetedBottomAlignedInitialScroll(options) {
2619
- const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom, target } = options;
2620
- const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2762
+ function getAdvanceableInitialScrollState(state, options) {
2763
+ const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
2764
+ const initialScroll = state.initialScroll;
2765
+ const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
2766
+ const shouldWaitForInitialLayout = !!(options == null ? void 0 : options.requiresMeasuredLayout) && !queuedInitialLayout && !isInitialScrollInProgress;
2767
+ if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll || scrollingTo && !isInitialScrollInProgress) {
2768
+ return void 0;
2769
+ }
2621
2770
  return {
2622
- ...target,
2623
- contentOffset: void 0,
2624
- index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2625
- preserveForBottomPadding: true,
2626
- preserveForFooterLayout,
2627
- viewOffset: -stylePaddingBottom - (preserveForFooterLayout ? footerSize : 0),
2628
- viewPosition: 1
2771
+ initialScroll,
2772
+ isInitialScrollInProgress,
2773
+ queuedInitialLayout,
2774
+ scrollingTo
2629
2775
  };
2630
2776
  }
2631
- function areEquivalentBootstrapInitialScrollTargets(current, next) {
2632
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2633
- }
2634
- function clearPendingInitialScrollFooterLayout(ctx, options) {
2635
- const { dataLength, stylePaddingBottom, target } = options;
2777
+ function advanceMeasuredInitialScroll(ctx, options) {
2778
+ var _a3, _b, _c;
2636
2779
  const state = ctx.state;
2637
- if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2638
- return;
2780
+ const advanceableState = getAdvanceableInitialScrollState(state, {
2781
+ requiresMeasuredLayout: true
2782
+ });
2783
+ if (!advanceableState) {
2784
+ return false;
2639
2785
  }
2640
- const clearedFooterTarget = createInitialScrollAtEndTarget({
2641
- dataLength,
2642
- footerSize: 0,
2643
- preserveForFooterLayout: void 0,
2644
- stylePaddingBottom
2786
+ const { initialScroll, isInitialScrollInProgress, queuedInitialLayout } = advanceableState;
2787
+ const scrollingTo = isInitialScrollInProgress ? advanceableState.scrollingTo : void 0;
2788
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
2789
+ const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2790
+ const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2791
+ const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2792
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2793
+ if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2794
+ return false;
2795
+ }
2796
+ if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2797
+ return false;
2798
+ }
2799
+ if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2800
+ setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2801
+ }
2802
+ const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
2803
+ dispatchInitialScroll(ctx, {
2804
+ forceScroll,
2805
+ resolvedOffset,
2806
+ target: initialScroll
2645
2807
  });
2646
- setInitialScrollTarget(state, clearedFooterTarget);
2647
- }
2648
- function clearFinishedViewportRetargetableInitialScroll(state) {
2649
- clearPreservedInitialScrollTarget(state);
2808
+ return true;
2650
2809
  }
2651
- function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2810
+ function advanceOffsetInitialScroll(ctx, options) {
2811
+ var _a3, _b;
2652
2812
  const state = ctx.state;
2653
- if (!state.didFinishInitialScroll) {
2813
+ const advanceableState = getAdvanceableInitialScrollState(state);
2814
+ if (!advanceableState) {
2654
2815
  return false;
2655
2816
  }
2656
- const currentOffset = getObservedBootstrapInitialScrollOffset(state);
2657
- return Math.abs(currentOffset - resolveInitialScrollOffset(ctx, target)) > epsilon;
2658
- }
2659
- function getObservedBootstrapInitialScrollOffset(state) {
2660
- var _a3, _b, _c, _d;
2661
- const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
2662
- return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2817
+ const { initialScroll, queuedInitialLayout } = advanceableState;
2818
+ const resolvedOffset = (_a3 = initialScroll.contentOffset) != null ? _a3 : 0;
2819
+ const isAlreadyAtDesiredInitialTarget = Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2820
+ if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2821
+ return false;
2822
+ }
2823
+ const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
2824
+ const forceScroll = (_b = options == null ? void 0 : options.forceScroll) != null ? _b : hasMeasuredScrollLayout || !!queuedInitialLayout;
2825
+ dispatchInitialScroll(ctx, {
2826
+ forceScroll,
2827
+ resolvedOffset,
2828
+ target: initialScroll
2829
+ });
2830
+ return true;
2663
2831
  }
2664
- function getPreservedEndAnchorOffsetDiff(ctx) {
2832
+ function advanceCurrentInitialScrollSession(ctx, options) {
2665
2833
  var _a3;
2666
- const state = ctx.state;
2667
- const initialScroll = state.initialScroll;
2668
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2669
- return;
2834
+ return ((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? advanceOffsetInitialScroll(ctx, {
2835
+ forceScroll: options == null ? void 0 : options.forceScroll
2836
+ }) : advanceMeasuredInitialScroll(ctx, {
2837
+ forceScroll: options == null ? void 0 : options.forceScroll
2838
+ });
2839
+ }
2840
+
2841
+ // src/utils/checkAllSizesKnown.ts
2842
+ function isNullOrUndefined2(value) {
2843
+ return value === null || value === void 0;
2844
+ }
2845
+ function getMountedIndicesInRange(state, start, end) {
2846
+ if (!isNullOrUndefined2(end) && !isNullOrUndefined2(start) && start >= 0 && end >= 0) {
2847
+ 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);
2670
2848
  }
2671
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2672
- return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2849
+ return [];
2673
2850
  }
2674
- function schedulePreservedEndAnchorCorrection(ctx) {
2675
- if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
2851
+ function getMountedBufferedIndices(state) {
2852
+ return getMountedIndicesInRange(state, state.startBuffered, state.endBuffered);
2853
+ }
2854
+ function getMountedNoBufferIndices(state) {
2855
+ return getMountedIndicesInRange(state, state.startNoBuffer, state.endNoBuffer);
2856
+ }
2857
+ function checkAllSizesKnown(state, indices) {
2858
+ return indices.length > 0 && indices.every((index) => {
2859
+ const key = getId(state, index);
2860
+ return key !== void 0 && state.sizesKnown.has(key);
2861
+ });
2862
+ }
2863
+
2864
+ // src/core/bootstrapInitialScroll.ts
2865
+ var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2866
+ var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
2867
+ var DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES = 24;
2868
+ var BOOTSTRAP_REVEAL_ABORT_WARNING = "LegendList bootstrap initial scroll aborted after exceeding convergence bounds.";
2869
+ function getBootstrapInitialScrollSession(state) {
2870
+ var _a3;
2871
+ return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
2872
+ }
2873
+ function isOffsetInitialScrollSession(state) {
2874
+ var _a3;
2875
+ return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2876
+ }
2877
+ function doVisibleIndicesMatch(previous, next) {
2878
+ if (!previous || previous.length !== next.length) {
2676
2879
  return false;
2677
2880
  }
2678
- const correction = {};
2679
- schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
2881
+ for (let i = 0; i < previous.length; i++) {
2882
+ if (previous[i] !== next[i]) {
2883
+ return false;
2884
+ }
2885
+ }
2680
2886
  return true;
2681
2887
  }
2682
- function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2683
- const state = ctx.state;
2684
- state.preservedEndAnchorCorrection = correction;
2685
- requestAnimationFrame(() => {
2686
- var _a3;
2687
- const activeCorrection = state.preservedEndAnchorCorrection;
2688
- if (activeCorrection !== correction) {
2689
- return;
2888
+ function getBootstrapRevealVisibleIndices(options) {
2889
+ const { dataLength, getSize, offset, positions, scrollLength, startIndex: requestedStartIndex } = options;
2890
+ const endOffset = offset + scrollLength;
2891
+ const visibleIndices = [];
2892
+ let index = requestedStartIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, requestedStartIndex)) : 0;
2893
+ while (index > 0) {
2894
+ const previousIndex = index - 1;
2895
+ const previousPosition = positions[previousIndex];
2896
+ if (previousPosition === void 0) {
2897
+ index = previousIndex;
2898
+ continue;
2690
2899
  }
2691
- const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
2692
- if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2693
- state.preservedEndAnchorCorrection = void 0;
2694
- return;
2900
+ const previousSize = getSize(previousIndex);
2901
+ if (previousSize === void 0) {
2902
+ index = previousIndex;
2903
+ continue;
2695
2904
  }
2696
- const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2697
- if (hasObservedNativeScrollAfterRequest) {
2698
- activeCorrection.lastRequestTime = Date.now();
2699
- requestAdjust(ctx, offsetDiff);
2905
+ if (previousPosition + previousSize <= offset) {
2906
+ break;
2700
2907
  }
2701
- schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
2702
- });
2703
- }
2704
- function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2705
- var _a3, _b;
2706
- const state = ctx.state;
2707
- const initialScroll = state.initialScroll;
2708
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
2709
- return;
2908
+ index = previousIndex;
2710
2909
  }
2711
- if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2712
- const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
2713
- if (!shouldKeepEndTargetAlive) {
2714
- if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
2715
- clearPendingInitialScrollFooterLayout(ctx, {
2716
- dataLength: state.props.data.length,
2717
- stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
2718
- target: initialScroll
2719
- });
2720
- } else {
2721
- clearFinishedViewportRetargetableInitialScroll(state);
2722
- }
2910
+ for (; index < dataLength; index++) {
2911
+ const position = positions[index];
2912
+ if (position === void 0) {
2913
+ continue;
2914
+ }
2915
+ const size = getSize(index);
2916
+ if (size === void 0) {
2917
+ continue;
2918
+ }
2919
+ if (position < endOffset && position + size > offset) {
2920
+ visibleIndices.push(index);
2921
+ } else if (visibleIndices.length > 0 && position >= endOffset) {
2922
+ break;
2723
2923
  }
2724
2924
  }
2925
+ return visibleIndices;
2725
2926
  }
2726
- function startBootstrapInitialScrollOnMount(ctx, options) {
2727
- var _a3, _b, _c;
2728
- const { initialScrollAtEnd, target } = options;
2729
- const state = ctx.state;
2730
- const offset = resolveInitialScrollOffset(ctx, target);
2731
- 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);
2732
- const shouldFinishWithPreservedTarget = state.props.data.length === 0 && target.index !== void 0;
2733
- if (shouldFinishAtOrigin) {
2734
- clearBootstrapInitialScrollSession(state);
2735
- finishInitialScroll(ctx, {
2736
- resolvedOffset: offset
2737
- });
2738
- } else if (shouldFinishWithPreservedTarget) {
2739
- clearBootstrapInitialScrollSession(state);
2740
- finishInitialScroll(ctx, {
2741
- preserveTarget: true,
2742
- resolvedOffset: offset
2743
- });
2744
- } else {
2745
- startBootstrapInitialScrollSession(state, {
2746
- scroll: offset,
2747
- seedContentOffset: Platform.OS === "web" ? 0 : offset,
2748
- targetIndexSeed: target.index
2749
- });
2750
- ensureBootstrapInitialScrollFrameTicker(ctx);
2751
- }
2927
+ function shouldAbortBootstrapReveal(options) {
2928
+ const {
2929
+ mountFrameCount,
2930
+ maxFrames = DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES,
2931
+ maxPasses = DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES,
2932
+ passCount
2933
+ } = options;
2934
+ return mountFrameCount >= maxFrames || passCount >= maxPasses;
2752
2935
  }
2753
- function handleBootstrapInitialScrollDataChange(ctx, options) {
2754
- const { dataLength, didDataChange, initialScrollAtEnd, previousDataLength, stylePaddingBottom } = options;
2755
- const state = ctx.state;
2756
- const initialScroll = state.initialScroll;
2757
- if (isOffsetInitialScrollSession(state) || !initialScroll) {
2758
- return;
2936
+ function abortBootstrapRevealIfNeeded(ctx, options) {
2937
+ if (!shouldAbortBootstrapReveal(options)) {
2938
+ return false;
2759
2939
  }
2760
- const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2761
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2762
- const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2763
- if (shouldClearFinishedResizePreservation) {
2764
- clearPreservedInitialScrollTarget(state);
2765
- return;
2940
+ if (IS_DEV) {
2941
+ console.warn(BOOTSTRAP_REVEAL_ABORT_WARNING);
2766
2942
  }
2767
- const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
2768
- if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
2769
- return;
2943
+ abortBootstrapInitialScroll(ctx);
2944
+ return true;
2945
+ }
2946
+ function clearBootstrapInitialScrollSession(state) {
2947
+ var _a3;
2948
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2949
+ const frameHandle = bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle;
2950
+ if (frameHandle !== void 0 && typeof cancelAnimationFrame === "function") {
2951
+ cancelAnimationFrame(frameHandle);
2770
2952
  }
2771
- if (shouldRetargetBottomAligned) {
2772
- const updatedInitialScroll = initialScrollAtEnd ? createInitialScrollAtEndTarget({
2773
- dataLength,
2774
- footerSize: peek$(ctx, "footerSize") || 0,
2775
- preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2776
- stylePaddingBottom
2777
- }) : createRetargetedBottomAlignedInitialScroll({
2778
- dataLength,
2779
- footerSize: peek$(ctx, "footerSize") || 0,
2780
- initialScrollAtEnd,
2781
- stylePaddingBottom,
2782
- target: initialScroll
2783
- });
2784
- if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2785
- clearPendingInitialScrollFooterLayout(ctx, {
2786
- dataLength,
2787
- stylePaddingBottom,
2788
- target: initialScroll
2953
+ if (bootstrapInitialScroll) {
2954
+ bootstrapInitialScroll.frameHandle = void 0;
2955
+ }
2956
+ setInitialScrollSession(state, {
2957
+ bootstrap: null,
2958
+ kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2959
+ });
2960
+ }
2961
+ function startBootstrapInitialScrollSession(state, options) {
2962
+ var _a3, _b, _c;
2963
+ const previousBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2964
+ setInitialScrollSession(state, {
2965
+ bootstrap: {
2966
+ frameHandle: previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.frameHandle,
2967
+ // Re-arming during the initial mount should spend from the same watchdog budget.
2968
+ mountFrameCount: (_a3 = previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.mountFrameCount) != null ? _a3 : 0,
2969
+ passCount: 0,
2970
+ previousResolvedOffset: void 0,
2971
+ scroll: options.scroll,
2972
+ seedContentOffset: (_c = (_b = options.seedContentOffset) != null ? _b : previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.seedContentOffset) != null ? _c : options.scroll,
2973
+ targetIndexSeed: options.targetIndexSeed,
2974
+ visibleIndices: void 0
2975
+ },
2976
+ kind: "bootstrap"
2977
+ });
2978
+ }
2979
+ function resetBootstrapInitialScrollSession(state, options) {
2980
+ var _a3, _b, _c;
2981
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2982
+ if (!bootstrapInitialScroll) {
2983
+ if ((options == null ? void 0 : options.scroll) !== void 0) {
2984
+ startBootstrapInitialScrollSession(state, {
2985
+ scroll: options.scroll,
2986
+ seedContentOffset: options.seedContentOffset,
2987
+ targetIndexSeed: options.targetIndexSeed
2789
2988
  });
2989
+ }
2990
+ } else {
2991
+ bootstrapInitialScroll.passCount = 0;
2992
+ bootstrapInitialScroll.previousResolvedOffset = void 0;
2993
+ bootstrapInitialScroll.scroll = (_a3 = options == null ? void 0 : options.scroll) != null ? _a3 : bootstrapInitialScroll.scroll;
2994
+ bootstrapInitialScroll.seedContentOffset = (_b = options == null ? void 0 : options.seedContentOffset) != null ? _b : bootstrapInitialScroll.seedContentOffset;
2995
+ bootstrapInitialScroll.targetIndexSeed = (_c = options == null ? void 0 : options.targetIndexSeed) != null ? _c : bootstrapInitialScroll.targetIndexSeed;
2996
+ bootstrapInitialScroll.visibleIndices = void 0;
2997
+ setInitialScrollSession(state, {
2998
+ bootstrap: bootstrapInitialScroll,
2999
+ kind: "bootstrap"
3000
+ });
3001
+ }
3002
+ }
3003
+ function queueBootstrapInitialScrollReevaluation(state) {
3004
+ requestAnimationFrame(() => {
3005
+ var _a3;
3006
+ if (getBootstrapInitialScrollSession(state)) {
3007
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
3008
+ }
3009
+ });
3010
+ }
3011
+ function ensureBootstrapInitialScrollFrameTicker(ctx) {
3012
+ const state = ctx.state;
3013
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3014
+ if (!bootstrapInitialScroll || bootstrapInitialScroll.frameHandle !== void 0) {
3015
+ return;
3016
+ }
3017
+ const tick = () => {
3018
+ const activeBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3019
+ if (!activeBootstrapInitialScroll) {
2790
3020
  return;
2791
3021
  }
2792
- if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
2793
- setInitialScrollTarget(state, updatedInitialScroll, {
2794
- resetDidFinish: shouldResetDidFinish
2795
- });
2796
- rearmBootstrapInitialScroll(ctx, {
2797
- scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
2798
- seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
2799
- targetIndexSeed: updatedInitialScroll.index
2800
- });
3022
+ activeBootstrapInitialScroll.frameHandle = void 0;
3023
+ activeBootstrapInitialScroll.mountFrameCount += 1;
3024
+ if (abortBootstrapRevealIfNeeded(ctx, {
3025
+ mountFrameCount: activeBootstrapInitialScroll.mountFrameCount,
3026
+ passCount: activeBootstrapInitialScroll.passCount
3027
+ })) {
2801
3028
  return;
2802
3029
  }
2803
- }
2804
- if (!didDataChange) {
3030
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3031
+ };
3032
+ bootstrapInitialScroll.frameHandle = requestAnimationFrame(tick);
3033
+ }
3034
+ function rearmBootstrapInitialScroll(ctx, options) {
3035
+ resetBootstrapInitialScrollSession(ctx.state, options);
3036
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3037
+ queueBootstrapInitialScrollReevaluation(ctx.state);
3038
+ }
3039
+ function createInitialScrollAtEndTarget(options) {
3040
+ const { dataLength, footerSize, preserveForFooterLayout, stylePaddingBottom } = options;
3041
+ return {
3042
+ contentOffset: void 0,
3043
+ index: Math.max(0, dataLength - 1),
3044
+ preserveForBottomPadding: true,
3045
+ preserveForFooterLayout,
3046
+ viewOffset: -stylePaddingBottom - footerSize,
3047
+ viewPosition: 1
3048
+ };
3049
+ }
3050
+ function shouldPreserveInitialScrollForBottomPadding(target) {
3051
+ return !!(target == null ? void 0 : target.preserveForBottomPadding);
3052
+ }
3053
+ function shouldPreserveInitialScrollForFooterLayout(target) {
3054
+ return !!(target == null ? void 0 : target.preserveForFooterLayout);
3055
+ }
3056
+ function isRetargetableBottomAlignedInitialScrollTarget(target) {
3057
+ return !!(target && target.viewPosition === 1 && (shouldPreserveInitialScrollForBottomPadding(target) || shouldPreserveInitialScrollForFooterLayout(target)));
3058
+ }
3059
+ function createRetargetedBottomAlignedInitialScroll(options) {
3060
+ const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom, target } = options;
3061
+ const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
3062
+ return {
3063
+ ...target,
3064
+ contentOffset: void 0,
3065
+ index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
3066
+ preserveForBottomPadding: true,
3067
+ preserveForFooterLayout,
3068
+ viewOffset: -stylePaddingBottom - (preserveForFooterLayout ? footerSize : 0),
3069
+ viewPosition: 1
3070
+ };
3071
+ }
3072
+ function areEquivalentBootstrapInitialScrollTargets(current, next) {
3073
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
3074
+ }
3075
+ function clearPendingInitialScrollFooterLayout(ctx, options) {
3076
+ const { dataLength, stylePaddingBottom, target } = options;
3077
+ const state = ctx.state;
3078
+ if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2805
3079
  return;
2806
3080
  }
2807
- if (bootstrapInitialScroll || shouldResetDidFinish) {
2808
- setInitialScrollTarget(state, initialScroll, {
2809
- resetDidFinish: shouldResetDidFinish
2810
- });
2811
- rearmBootstrapInitialScroll(ctx, {
2812
- scroll: resolveInitialScrollOffset(ctx, initialScroll),
2813
- seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
2814
- targetIndexSeed: initialScroll.index
2815
- });
2816
- }
3081
+ const clearedFooterTarget = createInitialScrollAtEndTarget({
3082
+ dataLength,
3083
+ footerSize: 0,
3084
+ preserveForFooterLayout: void 0,
3085
+ stylePaddingBottom
3086
+ });
3087
+ setInitialScrollTarget(state, clearedFooterTarget);
2817
3088
  }
2818
- function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2819
- const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom } = options;
3089
+ function clearFinishedViewportRetargetableInitialScroll(state) {
3090
+ clearPreservedInitialScrollTarget(state);
3091
+ }
3092
+ function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2820
3093
  const state = ctx.state;
2821
- if (!initialScrollAtEnd) {
2822
- return;
3094
+ if (!state.didFinishInitialScroll) {
3095
+ return false;
2823
3096
  }
3097
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
3098
+ return Math.abs(currentOffset - resolveInitialScrollOffset(ctx, target)) > epsilon;
3099
+ }
3100
+ function getObservedBootstrapInitialScrollOffset(state) {
3101
+ var _a3, _b, _c, _d;
3102
+ const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3103
+ return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3104
+ }
3105
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3106
+ var _a3;
3107
+ const state = ctx.state;
2824
3108
  const initialScroll = state.initialScroll;
2825
- if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2826
- return;
2827
- }
2828
- const shouldProcessFooterLayout = !!getBootstrapInitialScrollSession(state) || shouldPreserveInitialScrollForFooterLayout(initialScroll);
2829
- if (!shouldProcessFooterLayout) {
3109
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2830
3110
  return;
2831
3111
  }
2832
- if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2833
- clearPendingInitialScrollFooterLayout(ctx, {
2834
- dataLength,
2835
- stylePaddingBottom,
2836
- target: initialScroll
2837
- });
2838
- } else {
2839
- const updatedInitialScroll = createInitialScrollAtEndTarget({
2840
- dataLength,
2841
- footerSize,
2842
- preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2843
- stylePaddingBottom
2844
- });
2845
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2846
- if (!didTargetChange) {
2847
- clearPendingInitialScrollFooterLayout(ctx, {
2848
- dataLength,
2849
- stylePaddingBottom,
2850
- target: initialScroll
2851
- });
2852
- } else {
2853
- const didFinishInitialScroll = !!state.didFinishInitialScroll;
2854
- setInitialScrollTarget(state, updatedInitialScroll, {
2855
- resetDidFinish: didFinishInitialScroll
2856
- });
2857
- rearmBootstrapInitialScroll(ctx, {
2858
- scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
2859
- targetIndexSeed: updatedInitialScroll.index
2860
- });
2861
- }
3112
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3113
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3114
+ }
3115
+ function schedulePreservedEndAnchorCorrection(ctx) {
3116
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3117
+ return false;
2862
3118
  }
3119
+ const correction = {};
3120
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3121
+ return true;
2863
3122
  }
2864
- function handleBootstrapInitialScrollLayoutChange(ctx) {
2865
- var _a3, _b, _c;
3123
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2866
3124
  const state = ctx.state;
2867
- const initialScroll = state.initialScroll;
2868
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2869
- if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3125
+ state.preservedEndAnchorCorrection = correction;
3126
+ requestAnimationFrame(() => {
3127
+ var _a3;
3128
+ const activeCorrection = state.preservedEndAnchorCorrection;
3129
+ if (activeCorrection !== correction) {
3130
+ return;
3131
+ }
3132
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3133
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3134
+ state.preservedEndAnchorCorrection = void 0;
3135
+ return;
3136
+ }
3137
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3138
+ if (hasObservedNativeScrollAfterRequest) {
3139
+ activeCorrection.lastRequestTime = Date.now();
3140
+ requestAdjust(ctx, offsetDiff);
3141
+ }
3142
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3143
+ });
3144
+ }
3145
+ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3146
+ var _a3, _b;
3147
+ const state = ctx.state;
3148
+ const initialScroll = state.initialScroll;
3149
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3150
+ return;
3151
+ }
3152
+ if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3153
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3154
+ if (!shouldKeepEndTargetAlive) {
3155
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3156
+ clearPendingInitialScrollFooterLayout(ctx, {
3157
+ dataLength: state.props.data.length,
3158
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3159
+ target: initialScroll
3160
+ });
3161
+ } else {
3162
+ clearFinishedViewportRetargetableInitialScroll(state);
3163
+ }
3164
+ }
3165
+ }
3166
+ }
3167
+ function startBootstrapInitialScrollOnMount(ctx, options) {
3168
+ var _a3, _b, _c;
3169
+ const { initialScrollAtEnd, target } = options;
3170
+ const state = ctx.state;
3171
+ const offset = resolveInitialScrollOffset(ctx, target);
3172
+ 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);
3173
+ const shouldFinishWithPreservedTarget = state.props.data.length === 0 && target.index !== void 0;
3174
+ if (shouldFinishAtOrigin) {
3175
+ clearBootstrapInitialScrollSession(state);
3176
+ finishInitialScroll(ctx, {
3177
+ resolvedOffset: offset
3178
+ });
3179
+ } else if (shouldFinishWithPreservedTarget) {
3180
+ clearBootstrapInitialScrollSession(state);
3181
+ finishInitialScroll(ctx, {
3182
+ preserveTarget: true,
3183
+ resolvedOffset: offset
3184
+ });
3185
+ } else {
3186
+ startBootstrapInitialScrollSession(state, {
3187
+ scroll: offset,
3188
+ seedContentOffset: Platform.OS === "web" ? 0 : offset,
3189
+ targetIndexSeed: target.index
3190
+ });
3191
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3192
+ }
3193
+ }
3194
+ function handleBootstrapInitialScrollDataChange(ctx, options) {
3195
+ const { dataLength, didDataChange, initialScrollAtEnd, previousDataLength, stylePaddingBottom } = options;
3196
+ const state = ctx.state;
3197
+ const initialScroll = state.initialScroll;
3198
+ if (isOffsetInitialScrollSession(state) || !initialScroll) {
3199
+ return;
3200
+ }
3201
+ const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
3202
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3203
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3204
+ if (shouldClearFinishedResizePreservation) {
3205
+ clearPreservedInitialScrollTarget(state);
3206
+ return;
3207
+ }
3208
+ const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
3209
+ if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
3210
+ return;
3211
+ }
3212
+ if (shouldRetargetBottomAligned) {
3213
+ const updatedInitialScroll = initialScrollAtEnd ? createInitialScrollAtEndTarget({
3214
+ dataLength,
3215
+ footerSize: peek$(ctx, "footerSize") || 0,
3216
+ preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3217
+ stylePaddingBottom
3218
+ }) : createRetargetedBottomAlignedInitialScroll({
3219
+ dataLength,
3220
+ footerSize: peek$(ctx, "footerSize") || 0,
3221
+ initialScrollAtEnd,
3222
+ stylePaddingBottom,
3223
+ target: initialScroll
3224
+ });
3225
+ if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3226
+ clearPendingInitialScrollFooterLayout(ctx, {
3227
+ dataLength,
3228
+ stylePaddingBottom,
3229
+ target: initialScroll
3230
+ });
3231
+ return;
3232
+ }
3233
+ if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
3234
+ setInitialScrollTarget(state, updatedInitialScroll, {
3235
+ resetDidFinish: shouldResetDidFinish
3236
+ });
3237
+ rearmBootstrapInitialScroll(ctx, {
3238
+ scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3239
+ seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3240
+ targetIndexSeed: updatedInitialScroll.index
3241
+ });
3242
+ return;
3243
+ }
3244
+ }
3245
+ if (!didDataChange) {
3246
+ return;
3247
+ }
3248
+ if (bootstrapInitialScroll || shouldResetDidFinish) {
3249
+ setInitialScrollTarget(state, initialScroll, {
3250
+ resetDidFinish: shouldResetDidFinish
3251
+ });
3252
+ rearmBootstrapInitialScroll(ctx, {
3253
+ scroll: resolveInitialScrollOffset(ctx, initialScroll),
3254
+ seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3255
+ targetIndexSeed: initialScroll.index
3256
+ });
3257
+ }
3258
+ }
3259
+ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3260
+ const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom } = options;
3261
+ const state = ctx.state;
3262
+ if (!initialScrollAtEnd) {
3263
+ return;
3264
+ }
3265
+ const initialScroll = state.initialScroll;
3266
+ if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
3267
+ return;
3268
+ }
3269
+ const shouldProcessFooterLayout = !!getBootstrapInitialScrollSession(state) || shouldPreserveInitialScrollForFooterLayout(initialScroll);
3270
+ if (!shouldProcessFooterLayout) {
3271
+ return;
3272
+ }
3273
+ if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3274
+ clearPendingInitialScrollFooterLayout(ctx, {
3275
+ dataLength,
3276
+ stylePaddingBottom,
3277
+ target: initialScroll
3278
+ });
3279
+ } else {
3280
+ const updatedInitialScroll = createInitialScrollAtEndTarget({
3281
+ dataLength,
3282
+ footerSize,
3283
+ preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3284
+ stylePaddingBottom
3285
+ });
3286
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3287
+ if (!didTargetChange) {
3288
+ clearPendingInitialScrollFooterLayout(ctx, {
3289
+ dataLength,
3290
+ stylePaddingBottom,
3291
+ target: initialScroll
3292
+ });
3293
+ } else {
3294
+ const didFinishInitialScroll = !!state.didFinishInitialScroll;
3295
+ setInitialScrollTarget(state, updatedInitialScroll, {
3296
+ resetDidFinish: didFinishInitialScroll
3297
+ });
3298
+ rearmBootstrapInitialScroll(ctx, {
3299
+ scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3300
+ targetIndexSeed: updatedInitialScroll.index
3301
+ });
3302
+ }
3303
+ }
3304
+ }
3305
+ function handleBootstrapInitialScrollLayoutChange(ctx) {
3306
+ var _a3, _b, _c;
3307
+ const state = ctx.state;
3308
+ const initialScroll = state.initialScroll;
3309
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3310
+ if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
2870
3311
  const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
2871
3312
  const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
2872
3313
  if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
@@ -2987,403 +3428,111 @@ function abortBootstrapInitialScroll(ctx) {
2987
3428
  const initialScroll = state.initialScroll;
2988
3429
  if (bootstrapInitialScroll && initialScroll && !isOffsetInitialScrollSession(state) && state.refScroller.current) {
2989
3430
  clearBootstrapInitialScrollSession(state);
2990
- dispatchInitialScroll(ctx, {
2991
- forceScroll: true,
2992
- resolvedOffset: bootstrapInitialScroll.scroll,
2993
- target: initialScroll,
2994
- waitForCompletionFrame: Platform.OS === "web"
2995
- });
2996
- } else {
2997
- finishBootstrapInitialScrollWithoutScroll(
2998
- ctx,
2999
- (_d = (_c = (_b = (_a3 = getBootstrapInitialScrollSession(state)) == null ? void 0 : _a3.scroll) != null ? _b : state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0
3000
- );
3001
- }
3002
- }
3003
-
3004
- // src/core/initialScrollLifecycle.ts
3005
- function retargetActiveInitialScrollAtEnd(ctx) {
3006
- var _a3;
3007
- const state = ctx.state;
3008
- const initialScroll = state.initialScroll;
3009
- if (state.didFinishInitialScroll) {
3010
- return schedulePreservedEndAnchorCorrection(ctx);
3011
- }
3012
- if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3013
- return false;
3014
- }
3015
- return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3016
- }
3017
- function handleInitialScrollLayoutReady(ctx) {
3018
- var _a3;
3019
- if (!ctx.state.initialScroll) {
3020
- return;
3021
- }
3022
- const runScroll = () => advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3023
- runScroll();
3024
- if (((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset") {
3025
- requestAnimationFrame(runScroll);
3026
- }
3027
- checkFinishedScroll(ctx, { onlyIfAligned: true });
3028
- }
3029
- function initializeInitialScrollOnMount(ctx, options) {
3030
- var _a3, _b;
3031
- const {
3032
- alwaysDispatchInitialScroll,
3033
- dataLength,
3034
- hasFooterComponent,
3035
- initialContentOffset,
3036
- initialScrollAtEnd,
3037
- useBootstrapInitialScroll
3038
- } = options;
3039
- const state = ctx.state;
3040
- const initialScroll = state.initialScroll;
3041
- const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
3042
- const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
3043
- if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
3044
- setInitialScrollTarget(state, {
3045
- ...initialScroll,
3046
- contentOffset: resolvedInitialContentOffset,
3047
- preserveForFooterLayout
3048
- });
3049
- }
3050
- if (useBootstrapInitialScroll && initialScroll && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
3051
- startBootstrapInitialScrollOnMount(ctx, {
3052
- initialScrollAtEnd,
3053
- target: state.initialScroll
3054
- });
3055
- return;
3056
- }
3057
- const hasPendingDataDependentInitialScroll = !!initialScroll && dataLength === 0 && !(resolvedInitialContentOffset === 0 && !initialScrollAtEnd);
3058
- if (!alwaysDispatchInitialScroll && !resolvedInitialContentOffset && !hasPendingDataDependentInitialScroll) {
3059
- if (initialScroll && !initialScrollAtEnd) {
3060
- finishInitialScroll(ctx, {
3061
- resolvedOffset: resolvedInitialContentOffset
3062
- });
3063
- } else {
3064
- setInitialRenderState(ctx, { didInitialScroll: true });
3065
- }
3066
- }
3067
- }
3068
- function handleInitialScrollDataChange(ctx, options) {
3069
- var _a3, _b, _c;
3070
- const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
3071
- const state = ctx.state;
3072
- const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3073
- if (state.initialScrollSession) {
3074
- state.initialScrollSession.previousDataLength = dataLength;
3075
- }
3076
- setInitialScrollSession(state);
3077
- if (useBootstrapInitialScroll) {
3078
- handleBootstrapInitialScrollDataChange(ctx, {
3079
- dataLength,
3080
- didDataChange,
3081
- initialScrollAtEnd,
3082
- previousDataLength,
3083
- stylePaddingBottom
3084
- });
3085
- return;
3086
- }
3087
- const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3088
- if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3089
- return;
3090
- }
3091
- if (shouldReplayFinishedOffsetInitialScroll) {
3092
- state.didFinishInitialScroll = false;
3093
- }
3094
- advanceCurrentInitialScrollSession(ctx);
3095
- }
3096
-
3097
- // src/core/mvcp.ts
3098
- var MVCP_POSITION_EPSILON = 0.1;
3099
- var MVCP_ANCHOR_LOCK_TTL_MS = 300;
3100
- var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
3101
- var NATIVE_END_CLAMP_EPSILON = 1;
3102
- function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
3103
- if (!enableMVCPAnchorLock) {
3104
- state.mvcpAnchorLock = void 0;
3105
- return void 0;
3106
- }
3107
- const lock = state.mvcpAnchorLock;
3108
- if (!lock) {
3109
- return void 0;
3110
- }
3111
- const isExpired = now > lock.expiresAt;
3112
- const isMissing = state.indexByKey.get(lock.id) === void 0;
3113
- if (isExpired || isMissing || !mvcpData) {
3114
- state.mvcpAnchorLock = void 0;
3115
- return void 0;
3116
- }
3117
- return lock;
3118
- }
3119
- function updateAnchorLock(state, params) {
3120
- if (Platform.OS === "web") {
3121
- const { anchorId, anchorPosition, dataChanged, now, positionDiff } = params;
3122
- const enableMVCPAnchorLock = !!dataChanged || !!state.mvcpAnchorLock;
3123
- const mvcpData = state.props.maintainVisibleContentPosition.data;
3124
- if (!enableMVCPAnchorLock || !mvcpData || state.scrollingTo || !anchorId || anchorPosition === void 0) {
3125
- return;
3126
- }
3127
- const existingLock = state.mvcpAnchorLock;
3128
- const quietPasses = !dataChanged && Math.abs(positionDiff) <= MVCP_POSITION_EPSILON && (existingLock == null ? void 0 : existingLock.id) === anchorId ? existingLock.quietPasses + 1 : 0;
3129
- if (!dataChanged && quietPasses >= MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE) {
3130
- state.mvcpAnchorLock = void 0;
3131
- return;
3132
- }
3133
- state.mvcpAnchorLock = {
3134
- expiresAt: now + MVCP_ANCHOR_LOCK_TTL_MS,
3135
- id: anchorId,
3136
- position: anchorPosition,
3137
- quietPasses
3138
- };
3139
- }
3140
- }
3141
- function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
3142
- if (!dataChanged || Platform.OS === "web" || !state.props.maintainVisibleContentPosition.data || scrollTarget !== void 0 || positionDiff >= -MVCP_POSITION_EPSILON) {
3143
- return false;
3144
- }
3145
- const distanceFromEnd = prevTotalSize - prevScroll - state.scrollLength;
3146
- return distanceFromEnd < Math.abs(positionDiff) - MVCP_POSITION_EPSILON;
3147
- }
3148
- function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
3149
- if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
3150
- return 0;
3151
- }
3152
- const maxScroll = Math.max(0, totalSize - state.scrollLength);
3153
- const clampDelta = maxScroll - state.scroll;
3154
- if (unresolvedAmount < 0) {
3155
- return Math.max(unresolvedAmount, Math.min(0, clampDelta));
3156
- }
3157
- if (unresolvedAmount > 0) {
3158
- return Math.min(unresolvedAmount, Math.max(0, clampDelta));
3159
- }
3160
- return 0;
3161
- }
3162
- function getProgressTowardAmount(targetDelta, nativeDelta) {
3163
- return targetDelta < 0 ? -nativeDelta : nativeDelta;
3164
- }
3165
- function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
3166
- const state = ctx.state;
3167
- state.pendingNativeMVCPAdjust = void 0;
3168
- const remaining = remainingAfterManual - nativeDelta;
3169
- if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
3170
- requestAdjust(ctx, remaining, true);
3171
- }
3172
- }
3173
- function maybeApplyPredictedNativeMVCPAdjust(ctx) {
3174
- const state = ctx.state;
3175
- const pending = state.pendingNativeMVCPAdjust;
3176
- if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
3177
- return;
3178
- }
3179
- const totalSize = getContentSize(ctx);
3180
- const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
3181
- if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
3182
- return;
3183
- }
3184
- const manualDesired = pending.amount - predictedNativeClamp;
3185
- if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
3186
- return;
3187
- }
3188
- pending.manualApplied = manualDesired;
3189
- requestAdjust(ctx, manualDesired, true);
3190
- pending.furthestProgressTowardAmount = 0;
3191
- }
3192
- function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
3193
- const state = ctx.state;
3194
- const pending = state.pendingNativeMVCPAdjust;
3195
- if (!pending) {
3196
- return false;
3197
- }
3198
- const remainingAfterManual = pending.amount - pending.manualApplied;
3199
- const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
3200
- const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
3201
- const progressTowardAmount = getProgressTowardAmount(remainingAfterManual, nativeDelta);
3202
- if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
3203
- state.pendingNativeMVCPAdjust = void 0;
3204
- return true;
3205
- }
3206
- if (isWrongDirection) {
3207
- state.pendingNativeMVCPAdjust = void 0;
3208
- return false;
3209
- }
3210
- if (progressTowardAmount + MVCP_POSITION_EPSILON >= Math.abs(remainingAfterManual)) {
3211
- settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
3212
- return true;
3213
- }
3214
- const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
3215
- const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
3216
- const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
3217
- if (isAtExpectedNativeClamp) {
3218
- settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
3219
- return true;
3220
- }
3221
- if (state.pendingMaintainScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold") && progressTowardAmount > MVCP_POSITION_EPSILON) {
3222
- settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
3223
- return true;
3224
- }
3225
- if (progressTowardAmount > pending.furthestProgressTowardAmount + MVCP_POSITION_EPSILON) {
3226
- pending.furthestProgressTowardAmount = progressTowardAmount;
3227
- return false;
3228
- }
3229
- if (pending.furthestProgressTowardAmount > MVCP_POSITION_EPSILON && progressTowardAmount < pending.furthestProgressTowardAmount - MVCP_POSITION_EPSILON) {
3230
- state.pendingNativeMVCPAdjust = void 0;
3231
- return false;
3232
- }
3233
- return false;
3234
- }
3235
- function prepareMVCP(ctx, dataChanged) {
3236
- const state = ctx.state;
3237
- const { idsInView, positions, props } = state;
3238
- const {
3239
- maintainVisibleContentPosition: { data: mvcpData, size: mvcpScroll, shouldRestorePosition }
3240
- } = props;
3241
- const isWeb = Platform.OS === "web";
3242
- const now = Date.now();
3243
- const enableMVCPAnchorLock = isWeb && (!!dataChanged || !!state.mvcpAnchorLock);
3244
- const scrollingTo = state.scrollingTo;
3245
- const anchorLock = isWeb ? resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) : void 0;
3246
- let prevPosition;
3247
- let targetId;
3248
- const idsInViewWithPositions = [];
3249
- const scrollTarget = scrollingTo == null ? void 0 : scrollingTo.index;
3250
- const scrollingToViewPosition = scrollingTo == null ? void 0 : scrollingTo.viewPosition;
3251
- const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
3252
- const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
3253
- const indexByKey = state.indexByKey;
3254
- const prevScroll = state.scroll;
3255
- const prevTotalSize = getContentSize(ctx);
3256
- if (shouldMVCP) {
3257
- if (!isWeb && state.pendingNativeMVCPAdjust && scrollTarget === void 0) {
3258
- maybeApplyPredictedNativeMVCPAdjust(ctx);
3259
- return void 0;
3260
- }
3261
- if (anchorLock && scrollTarget === void 0) {
3262
- targetId = anchorLock.id;
3263
- prevPosition = anchorLock.position;
3264
- } else if (scrollTarget !== void 0) {
3265
- if (!IsNewArchitecture && (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll)) {
3266
- return void 0;
3267
- }
3268
- targetId = getId(state, scrollTarget);
3269
- } else if (idsInView.length > 0 && state.didContainersLayout && !dataChanged) {
3270
- targetId = idsInView.find((id) => indexByKey.get(id) !== void 0);
3271
- }
3272
- if (dataChanged && idsInView.length > 0 && state.didContainersLayout) {
3273
- for (let i = 0; i < idsInView.length; i++) {
3274
- const id = idsInView[i];
3275
- const index = indexByKey.get(id);
3276
- if (index !== void 0) {
3277
- const position = positions[index];
3278
- if (position !== void 0) {
3279
- idsInViewWithPositions.push({ id, position });
3280
- }
3281
- }
3282
- }
3283
- }
3284
- if (targetId !== void 0 && prevPosition === void 0) {
3285
- const targetIndex = indexByKey.get(targetId);
3286
- if (targetIndex !== void 0) {
3287
- prevPosition = positions[targetIndex];
3288
- }
3289
- }
3290
- return () => {
3291
- let positionDiff = 0;
3292
- let anchorIdForLock = anchorLock == null ? void 0 : anchorLock.id;
3293
- let anchorPositionForLock;
3294
- let skipTargetAnchor = false;
3295
- const data = state.props.data;
3296
- const shouldValidateLockedAnchor = isWeb && dataChanged && mvcpData && scrollTarget === void 0 && targetId !== void 0 && (anchorLock == null ? void 0 : anchorLock.id) === targetId && shouldRestorePosition !== void 0;
3297
- if (shouldValidateLockedAnchor && targetId !== void 0) {
3298
- const index = indexByKey.get(targetId);
3299
- if (index !== void 0) {
3300
- const item = data[index];
3301
- skipTargetAnchor = item === void 0 || !shouldRestorePosition(item, index, data);
3302
- if (skipTargetAnchor && (anchorLock == null ? void 0 : anchorLock.id) === targetId) {
3303
- state.mvcpAnchorLock = void 0;
3304
- }
3305
- }
3306
- }
3307
- const shouldUseFallbackVisibleAnchor = dataChanged && mvcpData && scrollTarget === void 0 && (() => {
3308
- if (targetId === void 0 || skipTargetAnchor) {
3309
- return true;
3310
- }
3311
- const targetIndex = indexByKey.get(targetId);
3312
- return targetIndex === void 0 || positions[targetIndex] === void 0;
3313
- })();
3314
- if (shouldUseFallbackVisibleAnchor) {
3315
- for (let i = 0; i < idsInViewWithPositions.length; i++) {
3316
- const { id, position } = idsInViewWithPositions[i];
3317
- const index = indexByKey.get(id);
3318
- if (index !== void 0 && shouldRestorePosition) {
3319
- const item = data[index];
3320
- if (item === void 0 || !shouldRestorePosition(item, index, data)) {
3321
- continue;
3322
- }
3323
- }
3324
- const newPosition = index !== void 0 ? positions[index] : void 0;
3325
- if (newPosition !== void 0) {
3326
- positionDiff = newPosition - position;
3327
- anchorIdForLock = id;
3328
- anchorPositionForLock = newPosition;
3329
- break;
3330
- }
3331
- }
3332
- }
3333
- if (!skipTargetAnchor && targetId !== void 0 && prevPosition !== void 0) {
3334
- const targetIndex = indexByKey.get(targetId);
3335
- const newPosition = targetIndex !== void 0 ? positions[targetIndex] : void 0;
3336
- if (newPosition !== void 0) {
3337
- const totalSize = getContentSize(ctx);
3338
- let diff = newPosition - prevPosition;
3339
- if (diff !== 0 && isEndAnchoredScrollTarget && state.scroll + state.scrollLength > totalSize) {
3340
- if (diff > 0) {
3341
- diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
3342
- } else {
3343
- diff = 0;
3344
- }
3345
- }
3346
- positionDiff = diff;
3347
- anchorIdForLock = targetId;
3348
- anchorPositionForLock = newPosition;
3349
- }
3350
- }
3351
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
3352
- const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
3353
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
3354
- if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
3355
- const diff = newSize - prevSize;
3356
- if (diff !== 0) {
3357
- positionDiff += diff * scrollingToViewPosition;
3358
- scrollingTo.itemSize = newSize;
3359
- }
3360
- }
3361
- }
3362
- updateAnchorLock(state, {
3363
- anchorId: anchorIdForLock,
3364
- anchorPosition: anchorPositionForLock,
3365
- dataChanged,
3366
- now,
3367
- positionDiff
3431
+ dispatchInitialScroll(ctx, {
3432
+ forceScroll: true,
3433
+ resolvedOffset: bootstrapInitialScroll.scroll,
3434
+ target: initialScroll,
3435
+ waitForCompletionFrame: Platform.OS === "web"
3436
+ });
3437
+ } else {
3438
+ finishBootstrapInitialScrollWithoutScroll(
3439
+ ctx,
3440
+ (_d = (_c = (_b = (_a3 = getBootstrapInitialScrollSession(state)) == null ? void 0 : _a3.scroll) != null ? _b : state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0
3441
+ );
3442
+ }
3443
+ }
3444
+
3445
+ // src/core/initialScrollLifecycle.ts
3446
+ function retargetActiveInitialScrollAtEnd(ctx) {
3447
+ var _a3;
3448
+ const state = ctx.state;
3449
+ const initialScroll = state.initialScroll;
3450
+ if (state.didFinishInitialScroll) {
3451
+ return schedulePreservedEndAnchorCorrection(ctx);
3452
+ }
3453
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3454
+ return false;
3455
+ }
3456
+ return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3457
+ }
3458
+ function handleInitialScrollLayoutReady(ctx) {
3459
+ var _a3;
3460
+ if (!ctx.state.initialScroll) {
3461
+ return;
3462
+ }
3463
+ const runScroll = () => advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3464
+ runScroll();
3465
+ if (((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset") {
3466
+ requestAnimationFrame(runScroll);
3467
+ }
3468
+ checkFinishedScroll(ctx, { onlyIfAligned: true });
3469
+ }
3470
+ function initializeInitialScrollOnMount(ctx, options) {
3471
+ var _a3, _b;
3472
+ const {
3473
+ alwaysDispatchInitialScroll,
3474
+ dataLength,
3475
+ hasFooterComponent,
3476
+ initialContentOffset,
3477
+ initialScrollAtEnd,
3478
+ useBootstrapInitialScroll
3479
+ } = options;
3480
+ const state = ctx.state;
3481
+ const initialScroll = state.initialScroll;
3482
+ const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
3483
+ const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
3484
+ if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
3485
+ setInitialScrollTarget(state, {
3486
+ ...initialScroll,
3487
+ contentOffset: resolvedInitialContentOffset,
3488
+ preserveForFooterLayout
3489
+ });
3490
+ }
3491
+ if (useBootstrapInitialScroll && initialScroll && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
3492
+ startBootstrapInitialScrollOnMount(ctx, {
3493
+ initialScrollAtEnd,
3494
+ target: state.initialScroll
3495
+ });
3496
+ return;
3497
+ }
3498
+ const hasPendingDataDependentInitialScroll = !!initialScroll && dataLength === 0 && !(resolvedInitialContentOffset === 0 && !initialScrollAtEnd);
3499
+ if (!alwaysDispatchInitialScroll && !resolvedInitialContentOffset && !hasPendingDataDependentInitialScroll) {
3500
+ if (initialScroll && !initialScrollAtEnd) {
3501
+ finishInitialScroll(ctx, {
3502
+ resolvedOffset: resolvedInitialContentOffset
3368
3503
  });
3369
- if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
3370
- state.pendingNativeMVCPAdjust = {
3371
- amount: positionDiff,
3372
- furthestProgressTowardAmount: 0,
3373
- manualApplied: 0,
3374
- startScroll: prevScroll
3375
- };
3376
- maybeApplyPredictedNativeMVCPAdjust(ctx);
3377
- return;
3378
- }
3379
- if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3380
- const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3381
- if (!shouldSkipAdjustForMaintainedEnd) {
3382
- requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
3383
- }
3384
- }
3385
- };
3504
+ } else {
3505
+ setInitialRenderState(ctx, { didInitialScroll: true });
3506
+ }
3507
+ }
3508
+ }
3509
+ function handleInitialScrollDataChange(ctx, options) {
3510
+ var _a3, _b, _c;
3511
+ const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
3512
+ const state = ctx.state;
3513
+ const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3514
+ if (state.initialScrollSession) {
3515
+ state.initialScrollSession.previousDataLength = dataLength;
3516
+ }
3517
+ setInitialScrollSession(state);
3518
+ if (useBootstrapInitialScroll) {
3519
+ handleBootstrapInitialScrollDataChange(ctx, {
3520
+ dataLength,
3521
+ didDataChange,
3522
+ initialScrollAtEnd,
3523
+ previousDataLength,
3524
+ stylePaddingBottom
3525
+ });
3526
+ return;
3386
3527
  }
3528
+ const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3529
+ if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3530
+ return;
3531
+ }
3532
+ if (shouldReplayFinishedOffsetInitialScroll) {
3533
+ state.didFinishInitialScroll = false;
3534
+ }
3535
+ advanceCurrentInitialScrollSession(ctx);
3387
3536
  }
3388
3537
 
3389
3538
  // src/core/resetLayoutCachesForDataChange.ts
@@ -4338,7 +4487,7 @@ function calculateItemsInView(ctx, params = {}) {
4338
4487
  scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4339
4488
  };
4340
4489
  updateScrollRange();
4341
- if (!suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4490
+ if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4342
4491
  const { top, bottom } = scrollForNextCalculateItemsInView;
4343
4492
  if (top === null && bottom === null) {
4344
4493
  state.scrollForNextCalculateItemsInView = void 0;
@@ -4669,62 +4818,6 @@ function calculateItemsInView(ctx, params = {}) {
4669
4818
  });
4670
4819
  }
4671
4820
 
4672
- // src/core/doMaintainScrollAtEnd.ts
4673
- function doMaintainScrollAtEnd(ctx) {
4674
- const state = ctx.state;
4675
- const {
4676
- didContainersLayout,
4677
- pendingNativeMVCPAdjust,
4678
- refScroller,
4679
- props: { maintainScrollAtEnd }
4680
- } = state;
4681
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
4682
- const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout);
4683
- if (pendingNativeMVCPAdjust) {
4684
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
4685
- return false;
4686
- }
4687
- state.pendingMaintainScrollAtEnd = false;
4688
- if (shouldMaintainScrollAtEnd) {
4689
- const contentSize = getContentSize(ctx);
4690
- if (contentSize < state.scrollLength) {
4691
- state.scroll = 0;
4692
- }
4693
- if (!state.maintainingScrollAtEnd) {
4694
- state.maintainingScrollAtEnd = true;
4695
- requestAnimationFrame(() => {
4696
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
4697
- const scroller = refScroller.current;
4698
- if (state.props.horizontal && isHorizontalRTL(state)) {
4699
- const currentContentSize = getContentSize(ctx);
4700
- const logicalEndOffset = getLogicalHorizontalMaxOffset(state, currentContentSize);
4701
- const nativeOffset = toNativeHorizontalOffset(state, logicalEndOffset, currentContentSize);
4702
- scroller == null ? void 0 : scroller.scrollTo({
4703
- animated: maintainScrollAtEnd.animated,
4704
- x: nativeOffset,
4705
- y: 0
4706
- });
4707
- } else {
4708
- scroller == null ? void 0 : scroller.scrollToEnd({
4709
- animated: maintainScrollAtEnd.animated
4710
- });
4711
- }
4712
- setTimeout(
4713
- () => {
4714
- state.maintainingScrollAtEnd = false;
4715
- },
4716
- maintainScrollAtEnd.animated ? 500 : 0
4717
- );
4718
- } else {
4719
- state.maintainingScrollAtEnd = false;
4720
- }
4721
- });
4722
- }
4723
- return true;
4724
- }
4725
- return false;
4726
- }
4727
-
4728
4821
  // src/core/checkResetContainers.ts
4729
4822
  function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {}) {
4730
4823
  const state = ctx.state;
@@ -4905,84 +4998,6 @@ function handleLayout(ctx, layoutParam, setCanRender) {
4905
4998
  setCanRender(true);
4906
4999
  }
4907
5000
 
4908
- // src/platform/flushSync.native.ts
4909
- var flushSync = (fn) => {
4910
- fn();
4911
- };
4912
-
4913
- // src/core/updateScroll.ts
4914
- function updateScroll(ctx, newScroll, forceUpdate, options) {
4915
- var _a3;
4916
- const state = ctx.state;
4917
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
4918
- const prevScroll = state.scroll;
4919
- if ((options == null ? void 0 : options.markHasScrolled) !== false) {
4920
- state.hasScrolled = true;
4921
- }
4922
- state.lastBatchingAction = Date.now();
4923
- const currentTime = Date.now();
4924
- const adjust = scrollAdjustHandler.getAdjust();
4925
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
4926
- if (adjustChanged) {
4927
- scrollHistory.length = 0;
4928
- }
4929
- state.lastScrollAdjustForHistory = adjust;
4930
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
4931
- if (!adjustChanged) {
4932
- scrollHistory.push({ scroll: newScroll, time: currentTime });
4933
- }
4934
- }
4935
- if (scrollHistory.length > 5) {
4936
- scrollHistory.shift();
4937
- }
4938
- if (ignoreScrollFromMVCP && !scrollingTo) {
4939
- const { lt, gt } = ignoreScrollFromMVCP;
4940
- if (lt && newScroll < lt || gt && newScroll > gt) {
4941
- state.ignoreScrollFromMVCPIgnored = true;
4942
- return;
4943
- }
4944
- }
4945
- state.scrollPrev = prevScroll;
4946
- state.scrollPrevTime = state.scrollTime;
4947
- state.scroll = newScroll;
4948
- state.scrollTime = currentTime;
4949
- const scrollDelta = Math.abs(newScroll - prevScroll);
4950
- const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
4951
- const scrollLength = state.scrollLength;
4952
- const lastCalculated = state.scrollLastCalculate;
4953
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
4954
- const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
4955
- if (shouldUpdate) {
4956
- state.scrollLastCalculate = state.scroll;
4957
- state.ignoreScrollFromMVCPIgnored = false;
4958
- state.lastScrollDelta = scrollDelta;
4959
- const runCalculateItems = () => {
4960
- var _a4;
4961
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
4962
- checkThresholds(ctx);
4963
- };
4964
- if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
4965
- state.mvcpAnchorLock = void 0;
4966
- state.pendingNativeMVCPAdjust = void 0;
4967
- state.userScrollAnchorResetKeys = /* @__PURE__ */ new Set();
4968
- if (state.queuedMVCPRecalculate !== void 0) {
4969
- cancelAnimationFrame(state.queuedMVCPRecalculate);
4970
- state.queuedMVCPRecalculate = void 0;
4971
- }
4972
- flushSync(runCalculateItems);
4973
- } else {
4974
- runCalculateItems();
4975
- }
4976
- const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
4977
- if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
4978
- state.pendingMaintainScrollAtEnd = false;
4979
- doMaintainScrollAtEnd(ctx);
4980
- }
4981
- state.dataChangeNeedsScrollUpdate = false;
4982
- state.lastScrollDelta = 0;
4983
- }
4984
- }
4985
-
4986
5001
  // src/core/onScroll.ts
4987
5002
  function trackInitialScrollNativeProgress(state, newScroll) {
4988
5003
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);
@@ -6328,6 +6343,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6328
6343
  });
6329
6344
  state.viewabilityConfigCallbackPairs = viewability;
6330
6345
  state.enableScrollForNextCalculateItemsInView = !viewability;
6346
+ if (viewability) {
6347
+ state.scrollForNextCalculateItemsInView = void 0;
6348
+ }
6331
6349
  }, [viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged]);
6332
6350
  useInit(() => {
6333
6351
  if (!IsNewArchitecture) {