@legendapp/list 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,733 +2115,1162 @@ 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
+ scrollTo(ctx, {
2182
+ noScrollingTo: true,
2183
+ offset: state.scroll
2184
+ });
2185
+ } else {
2186
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2187
+ if (state.adjustingFromInitialMount) {
2188
+ state.adjustingFromInitialMount--;
2189
+ }
2190
+ }
2191
+ };
2192
+ state.scroll += positionDiff;
2193
+ state.scrollForNextCalculateItemsInView = void 0;
2194
+ const readyToRender = peek$(ctx, "readyToRender");
2195
+ if (readyToRender) {
2196
+ doit();
2197
+ if (Platform.OS !== "web") {
2198
+ const threshold = state.scroll - positionDiff / 2;
2199
+ if (!state.ignoreScrollFromMVCP) {
2200
+ state.ignoreScrollFromMVCP = {};
2201
+ }
2202
+ if (positionDiff > 0) {
2203
+ state.ignoreScrollFromMVCP.lt = threshold;
2204
+ } else {
2205
+ state.ignoreScrollFromMVCP.gt = threshold;
2206
+ }
2207
+ if (state.ignoreScrollFromMVCPTimeout) {
2208
+ clearTimeout(state.ignoreScrollFromMVCPTimeout);
2209
+ }
2210
+ const delay = needsScrollWorkaround ? 250 : 100;
2211
+ state.ignoreScrollFromMVCPTimeout = setTimeout(() => {
2212
+ var _a3;
2213
+ state.ignoreScrollFromMVCP = void 0;
2214
+ const shouldForceUpdate = state.ignoreScrollFromMVCPIgnored && state.scrollProcessingEnabled !== false;
2215
+ if (shouldForceUpdate) {
2216
+ state.ignoreScrollFromMVCPIgnored = false;
2217
+ state.scrollPending = state.scroll;
2218
+ (_a3 = state.reprocessCurrentScroll) == null ? void 0 : _a3.call(state);
2219
+ }
2220
+ }, delay);
2221
+ }
2222
+ } else {
2223
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2224
+ requestAnimationFrame(doit);
2225
+ }
2131
2226
  }
2132
2227
  }
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);
2228
+
2229
+ // src/core/mvcp.ts
2230
+ var MVCP_POSITION_EPSILON = 0.1;
2231
+ var MVCP_ANCHOR_LOCK_TTL_MS = 300;
2232
+ var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
2233
+ var NATIVE_END_CLAMP_EPSILON = 1;
2234
+ function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
2235
+ if (!enableMVCPAnchorLock) {
2236
+ state.mvcpAnchorLock = void 0;
2237
+ return void 0;
2149
2238
  }
2150
- if (state.timeoutCheckFinishedScrollFallback) {
2151
- clearTimeout(ctx.state.timeoutCheckFinishedScrollFallback);
2239
+ const lock = state.mvcpAnchorLock;
2240
+ if (!lock) {
2241
+ return void 0;
2152
2242
  }
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);
2243
+ const isExpired = now > lock.expiresAt;
2244
+ const isMissing = state.indexByKey.get(lock.id) === void 0;
2245
+ if (isExpired || isMissing || !mvcpData) {
2246
+ state.mvcpAnchorLock = void 0;
2247
+ return void 0;
2248
+ }
2249
+ return lock;
2250
+ }
2251
+ function updateAnchorLock(state, params) {
2252
+ if (Platform.OS === "web") {
2253
+ const { anchorId, anchorPosition, dataChanged, now, positionDiff } = params;
2254
+ const enableMVCPAnchorLock = !!dataChanged || !!state.mvcpAnchorLock;
2255
+ const mvcpData = state.props.maintainVisibleContentPosition.data;
2256
+ if (!enableMVCPAnchorLock || !mvcpData || state.scrollingTo || !anchorId || anchorPosition === void 0) {
2257
+ return;
2161
2258
  }
2162
- const averageSizeSnapshot = getAverageSizeSnapshot(state);
2163
- state.scrollingTo = {
2164
- ...scrollTarget,
2165
- ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
2166
- targetOffset,
2167
- waitForInitialScrollCompletionFrame
2259
+ const existingLock = state.mvcpAnchorLock;
2260
+ const quietPasses = !dataChanged && Math.abs(positionDiff) <= MVCP_POSITION_EPSILON && (existingLock == null ? void 0 : existingLock.id) === anchorId ? existingLock.quietPasses + 1 : 0;
2261
+ if (!dataChanged && quietPasses >= MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE) {
2262
+ state.mvcpAnchorLock = void 0;
2263
+ return;
2264
+ }
2265
+ state.mvcpAnchorLock = {
2266
+ expiresAt: now + MVCP_ANCHOR_LOCK_TTL_MS,
2267
+ id: anchorId,
2268
+ position: anchorPosition,
2269
+ quietPasses
2168
2270
  };
2169
2271
  }
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;
2272
+ }
2273
+ function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
2274
+ if (!dataChanged || Platform.OS === "web" || !state.props.maintainVisibleContentPosition.data || scrollTarget !== void 0 || positionDiff >= -MVCP_POSITION_EPSILON) {
2275
+ return false;
2176
2276
  }
2277
+ const distanceFromEnd = prevTotalSize - prevScroll - state.scrollLength;
2278
+ return distanceFromEnd < Math.abs(positionDiff) - MVCP_POSITION_EPSILON;
2177
2279
  }
2178
-
2179
- // src/core/scrollToIndex.ts
2180
- function clampScrollIndex(index, dataLength) {
2181
- if (dataLength <= 0) {
2182
- return -1;
2280
+ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
2281
+ if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
2282
+ return 0;
2183
2283
  }
2184
- if (index >= dataLength) {
2185
- return dataLength - 1;
2284
+ const maxScroll = Math.max(0, totalSize - state.scrollLength);
2285
+ const clampDelta = maxScroll - state.scroll;
2286
+ if (unresolvedAmount < 0) {
2287
+ return Math.max(unresolvedAmount, Math.min(0, clampDelta));
2186
2288
  }
2187
- if (index < 0) {
2188
- return 0;
2289
+ if (unresolvedAmount > 0) {
2290
+ return Math.min(unresolvedAmount, Math.max(0, clampDelta));
2189
2291
  }
2190
- return index;
2292
+ return 0;
2191
2293
  }
2192
- function scrollToIndex(ctx, {
2193
- index,
2194
- viewOffset = 0,
2195
- animated = true,
2196
- forceScroll,
2197
- isInitialScroll,
2198
- viewPosition
2199
- }) {
2294
+ function getProgressTowardAmount(targetDelta, nativeDelta) {
2295
+ return targetDelta < 0 ? -nativeDelta : nativeDelta;
2296
+ }
2297
+ function settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta) {
2200
2298
  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;
2208
- }
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;
2299
+ state.pendingNativeMVCPAdjust = void 0;
2300
+ const remaining = remainingAfterManual - nativeDelta;
2301
+ if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
2302
+ requestAdjust(ctx, remaining, true);
2251
2303
  }
2252
- setInitialScrollSession(state, {
2253
- kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
2254
- });
2255
2304
  }
2256
- function resolveInitialScrollOffset(ctx, initialScroll) {
2257
- var _a3, _b;
2305
+ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
2258
2306
  const state = ctx.state;
2259
- if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
2260
- return (_b = initialScroll.contentOffset) != null ? _b : 0;
2307
+ const pending = state.pendingNativeMVCPAdjust;
2308
+ if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
2309
+ return;
2261
2310
  }
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;
2311
+ const totalSize = getContentSize(ctx);
2312
+ const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
2313
+ if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
2314
+ return;
2273
2315
  }
2274
- return {
2275
- initialScroll,
2276
- isInitialScrollInProgress,
2277
- queuedInitialLayout,
2278
- scrollingTo
2279
- };
2316
+ const manualDesired = pending.amount - predictedNativeClamp;
2317
+ if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
2318
+ return;
2319
+ }
2320
+ pending.manualApplied = manualDesired;
2321
+ requestAdjust(ctx, manualDesired, true);
2322
+ pending.furthestProgressTowardAmount = 0;
2280
2323
  }
2281
- function advanceMeasuredInitialScroll(ctx, options) {
2282
- var _a3, _b, _c;
2324
+ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
2283
2325
  const state = ctx.state;
2284
- const advanceableState = getAdvanceableInitialScrollState(state, {
2285
- requiresMeasuredLayout: true
2286
- });
2287
- if (!advanceableState) {
2326
+ const pending = state.pendingNativeMVCPAdjust;
2327
+ if (!pending) {
2288
2328
  return false;
2289
2329
  }
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;
2330
+ const remainingAfterManual = pending.amount - pending.manualApplied;
2331
+ const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
2332
+ const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
2333
+ const progressTowardAmount = getProgressTowardAmount(remainingAfterManual, nativeDelta);
2334
+ if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
2335
+ state.pendingNativeMVCPAdjust = void 0;
2336
+ return true;
2299
2337
  }
2300
- if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2338
+ if (isWrongDirection) {
2339
+ state.pendingNativeMVCPAdjust = void 0;
2301
2340
  return false;
2302
2341
  }
2303
- if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2304
- setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2342
+ if (progressTowardAmount + MVCP_POSITION_EPSILON >= Math.abs(remainingAfterManual)) {
2343
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2344
+ return true;
2305
2345
  }
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;
2346
+ const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
2347
+ const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
2348
+ const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
2349
+ if (isAtExpectedNativeClamp) {
2350
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2351
+ return true;
2320
2352
  }
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) {
2353
+ if (state.pendingMaintainScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold") && progressTowardAmount > MVCP_POSITION_EPSILON) {
2354
+ settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta);
2355
+ return true;
2356
+ }
2357
+ if (progressTowardAmount > pending.furthestProgressTowardAmount + MVCP_POSITION_EPSILON) {
2358
+ pending.furthestProgressTowardAmount = progressTowardAmount;
2325
2359
  return false;
2326
2360
  }
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);
2361
+ if (pending.furthestProgressTowardAmount > MVCP_POSITION_EPSILON && progressTowardAmount < pending.furthestProgressTowardAmount - MVCP_POSITION_EPSILON) {
2362
+ state.pendingNativeMVCPAdjust = void 0;
2363
+ return false;
2352
2364
  }
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
- });
2365
+ return false;
2366
2366
  }
2367
-
2368
- // src/utils/requestAdjust.ts
2369
- function requestAdjust(ctx, positionDiff, dataChanged) {
2367
+ function prepareMVCP(ctx, dataChanged) {
2370
2368
  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--;
2369
+ const { idsInView, positions, props } = state;
2370
+ const {
2371
+ maintainVisibleContentPosition: { data: mvcpData, size: mvcpScroll, shouldRestorePosition }
2372
+ } = props;
2373
+ const isWeb = Platform.OS === "web";
2374
+ const now = Date.now();
2375
+ const enableMVCPAnchorLock = isWeb && (!!dataChanged || !!state.mvcpAnchorLock);
2376
+ const scrollingTo = state.scrollingTo;
2377
+ const anchorLock = isWeb ? resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) : void 0;
2378
+ let prevPosition;
2379
+ let targetId;
2380
+ const idsInViewWithPositions = [];
2381
+ const scrollTarget = scrollingTo == null ? void 0 : scrollingTo.index;
2382
+ const scrollingToViewPosition = scrollingTo == null ? void 0 : scrollingTo.viewPosition;
2383
+ const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
2384
+ const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
2385
+ const indexByKey = state.indexByKey;
2386
+ const prevScroll = state.scroll;
2387
+ const prevTotalSize = getContentSize(ctx);
2388
+ if (shouldMVCP) {
2389
+ if (!isWeb && state.pendingNativeMVCPAdjust && scrollTarget === void 0) {
2390
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
2391
+ return void 0;
2392
+ }
2393
+ if (anchorLock && scrollTarget === void 0) {
2394
+ targetId = anchorLock.id;
2395
+ prevPosition = anchorLock.position;
2396
+ } else if (scrollTarget !== void 0) {
2397
+ if (!IsNewArchitecture && (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll)) {
2398
+ return void 0;
2399
+ }
2400
+ targetId = getId(state, scrollTarget);
2401
+ } else if (idsInView.length > 0 && state.didContainersLayout && !dataChanged) {
2402
+ targetId = idsInView.find((id) => indexByKey.get(id) !== void 0);
2403
+ }
2404
+ if (dataChanged && idsInView.length > 0 && state.didContainersLayout) {
2405
+ for (let i = 0; i < idsInView.length; i++) {
2406
+ const id = idsInView[i];
2407
+ const index = indexByKey.get(id);
2408
+ if (index !== void 0) {
2409
+ const position = positions[index];
2410
+ if (position !== void 0) {
2411
+ idsInViewWithPositions.push({ id, position });
2412
+ }
2383
2413
  }
2384
2414
  }
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 = {};
2415
+ }
2416
+ if (targetId !== void 0 && prevPosition === void 0) {
2417
+ const targetIndex = indexByKey.get(targetId);
2418
+ if (targetIndex !== void 0) {
2419
+ prevPosition = positions[targetIndex];
2420
+ }
2421
+ }
2422
+ return () => {
2423
+ let positionDiff = 0;
2424
+ let anchorIdForLock = anchorLock == null ? void 0 : anchorLock.id;
2425
+ let anchorPositionForLock;
2426
+ let skipTargetAnchor = false;
2427
+ const data = state.props.data;
2428
+ const shouldValidateLockedAnchor = isWeb && dataChanged && mvcpData && scrollTarget === void 0 && targetId !== void 0 && (anchorLock == null ? void 0 : anchorLock.id) === targetId && shouldRestorePosition !== void 0;
2429
+ if (shouldValidateLockedAnchor && targetId !== void 0) {
2430
+ const index = indexByKey.get(targetId);
2431
+ if (index !== void 0) {
2432
+ const item = data[index];
2433
+ skipTargetAnchor = item === void 0 || !shouldRestorePosition(item, index, data);
2434
+ if (skipTargetAnchor && (anchorLock == null ? void 0 : anchorLock.id) === targetId) {
2435
+ state.mvcpAnchorLock = void 0;
2436
+ }
2395
2437
  }
2396
- if (positionDiff > 0) {
2397
- state.ignoreScrollFromMVCP.lt = threshold;
2398
- } else {
2399
- state.ignoreScrollFromMVCP.gt = threshold;
2438
+ }
2439
+ const shouldUseFallbackVisibleAnchor = dataChanged && mvcpData && scrollTarget === void 0 && (() => {
2440
+ if (targetId === void 0 || skipTargetAnchor) {
2441
+ return true;
2400
2442
  }
2401
- if (state.ignoreScrollFromMVCPTimeout) {
2402
- clearTimeout(state.ignoreScrollFromMVCPTimeout);
2443
+ const targetIndex = indexByKey.get(targetId);
2444
+ return targetIndex === void 0 || positions[targetIndex] === void 0;
2445
+ })();
2446
+ if (shouldUseFallbackVisibleAnchor) {
2447
+ for (let i = 0; i < idsInViewWithPositions.length; i++) {
2448
+ const { id, position } = idsInViewWithPositions[i];
2449
+ const index = indexByKey.get(id);
2450
+ if (index !== void 0 && shouldRestorePosition) {
2451
+ const item = data[index];
2452
+ if (item === void 0 || !shouldRestorePosition(item, index, data)) {
2453
+ continue;
2454
+ }
2455
+ }
2456
+ const newPosition = index !== void 0 ? positions[index] : void 0;
2457
+ if (newPosition !== void 0) {
2458
+ positionDiff = newPosition - position;
2459
+ anchorIdForLock = id;
2460
+ anchorPositionForLock = newPosition;
2461
+ break;
2462
+ }
2403
2463
  }
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);
2464
+ }
2465
+ if (!skipTargetAnchor && targetId !== void 0 && prevPosition !== void 0) {
2466
+ const targetIndex = indexByKey.get(targetId);
2467
+ const newPosition = targetIndex !== void 0 ? positions[targetIndex] : void 0;
2468
+ if (newPosition !== void 0) {
2469
+ const totalSize = getContentSize(ctx);
2470
+ let diff = newPosition - prevPosition;
2471
+ if (diff !== 0 && isEndAnchoredScrollTarget && state.scroll + state.scrollLength > totalSize) {
2472
+ if (diff > 0) {
2473
+ diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
2474
+ } else {
2475
+ diff = 0;
2476
+ }
2413
2477
  }
2414
- }, delay);
2478
+ positionDiff = diff;
2479
+ anchorIdForLock = targetId;
2480
+ anchorPositionForLock = newPosition;
2481
+ }
2415
2482
  }
2416
- } else {
2417
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2418
- requestAnimationFrame(doit);
2419
- }
2483
+ if (scrollingToViewPosition && scrollingToViewPosition > 0) {
2484
+ const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
2485
+ const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
2486
+ if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
2487
+ const diff = newSize - prevSize;
2488
+ if (diff !== 0) {
2489
+ positionDiff += diff * scrollingToViewPosition;
2490
+ scrollingTo.itemSize = newSize;
2491
+ }
2492
+ }
2493
+ }
2494
+ updateAnchorLock(state, {
2495
+ anchorId: anchorIdForLock,
2496
+ anchorPosition: anchorPositionForLock,
2497
+ dataChanged,
2498
+ now,
2499
+ positionDiff
2500
+ });
2501
+ if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
2502
+ state.pendingNativeMVCPAdjust = {
2503
+ amount: positionDiff,
2504
+ furthestProgressTowardAmount: 0,
2505
+ manualApplied: 0,
2506
+ startScroll: prevScroll
2507
+ };
2508
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
2509
+ return;
2510
+ }
2511
+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
2512
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2513
+ if (!shouldSkipAdjustForMaintainedEnd) {
2514
+ requestAdjust(ctx, positionDiff, dataChanged && mvcpData);
2515
+ }
2516
+ }
2517
+ };
2420
2518
  }
2421
2519
  }
2422
2520
 
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) {
2521
+ // src/platform/flushSync.native.ts
2522
+ var flushSync = (fn) => {
2523
+ fn();
2524
+ };
2525
+
2526
+ // src/core/updateScroll.ts
2527
+ function updateScroll(ctx, newScroll, forceUpdate, options) {
2433
2528
  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;
2529
+ const state = ctx.state;
2530
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
2531
+ const prevScroll = state.scroll;
2532
+ if ((options == null ? void 0 : options.markHasScrolled) !== false) {
2533
+ state.hasScrolled = true;
2439
2534
  }
2440
- for (let i = 0; i < previous.length; i++) {
2441
- if (previous[i] !== next[i]) {
2442
- return false;
2443
- }
2535
+ state.lastBatchingAction = Date.now();
2536
+ const currentTime = Date.now();
2537
+ const adjust = scrollAdjustHandler.getAdjust();
2538
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
2539
+ if (adjustChanged) {
2540
+ scrollHistory.length = 0;
2444
2541
  }
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;
2542
+ state.lastScrollAdjustForHistory = adjust;
2543
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
2544
+ if (!adjustChanged) {
2545
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
2466
2546
  }
2467
- index = previousIndex;
2468
2547
  }
2469
- for (; index < dataLength; index++) {
2470
- const position = positions[index];
2471
- if (position === void 0) {
2472
- continue;
2548
+ if (scrollHistory.length > 5) {
2549
+ scrollHistory.shift();
2550
+ }
2551
+ if (ignoreScrollFromMVCP && !scrollingTo) {
2552
+ const { lt, gt } = ignoreScrollFromMVCP;
2553
+ if (lt && newScroll < lt || gt && newScroll > gt) {
2554
+ state.ignoreScrollFromMVCPIgnored = true;
2555
+ return;
2473
2556
  }
2474
- const size = getSize(index);
2475
- if (size === void 0) {
2476
- continue;
2557
+ }
2558
+ state.scrollPrev = prevScroll;
2559
+ state.scrollPrevTime = state.scrollTime;
2560
+ state.scroll = newScroll;
2561
+ state.scrollTime = currentTime;
2562
+ const scrollDelta = Math.abs(newScroll - prevScroll);
2563
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2564
+ const scrollLength = state.scrollLength;
2565
+ const lastCalculated = state.scrollLastCalculate;
2566
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2567
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2568
+ if (shouldUpdate) {
2569
+ state.scrollLastCalculate = state.scroll;
2570
+ state.ignoreScrollFromMVCPIgnored = false;
2571
+ state.lastScrollDelta = scrollDelta;
2572
+ const runCalculateItems = () => {
2573
+ var _a4;
2574
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
2575
+ checkThresholds(ctx);
2576
+ };
2577
+ if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
2578
+ state.mvcpAnchorLock = void 0;
2579
+ state.pendingNativeMVCPAdjust = void 0;
2580
+ state.userScrollAnchorResetKeys = /* @__PURE__ */ new Set();
2581
+ if (state.queuedMVCPRecalculate !== void 0) {
2582
+ cancelAnimationFrame(state.queuedMVCPRecalculate);
2583
+ state.queuedMVCPRecalculate = void 0;
2584
+ }
2585
+ flushSync(runCalculateItems);
2586
+ } else {
2587
+ runCalculateItems();
2477
2588
  }
2478
- if (position < endOffset && position + size > offset) {
2479
- visibleIndices.push(index);
2480
- } else if (visibleIndices.length > 0 && position >= endOffset) {
2481
- break;
2589
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
2590
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
2591
+ state.pendingMaintainScrollAtEnd = false;
2592
+ doMaintainScrollAtEnd(ctx);
2482
2593
  }
2594
+ state.dataChangeNeedsScrollUpdate = false;
2595
+ state.lastScrollDelta = 0;
2483
2596
  }
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
2597
  }
2495
- function abortBootstrapRevealIfNeeded(ctx, options) {
2496
- if (!shouldAbortBootstrapReveal(options)) {
2497
- return false;
2498
- }
2499
- if (IS_DEV) {
2500
- console.warn(BOOTSTRAP_REVEAL_ABORT_WARNING);
2598
+
2599
+ // src/core/scrollTo.ts
2600
+ function getAverageSizeSnapshot(state) {
2601
+ if (Object.keys(state.averageSizes).length === 0) {
2602
+ return void 0;
2501
2603
  }
2502
- abortBootstrapInitialScroll(ctx);
2503
- return true;
2604
+ const snapshot = {};
2605
+ for (const itemType in state.averageSizes) {
2606
+ const averages = state.averageSizes[itemType];
2607
+ snapshot[itemType] = averages.avg;
2608
+ }
2609
+ return snapshot;
2504
2610
  }
2505
- function clearBootstrapInitialScrollSession(state) {
2611
+ function syncInitialScrollNativeWatchdog(state, options) {
2506
2612
  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);
2613
+ const { isInitialScroll, requestedOffset, targetOffset } = options;
2614
+ const existingWatchdog = initialScrollWatchdog.get(state);
2615
+ const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!existingWatchdog) && initialScrollWatchdog.hasNonZeroTargetOffset(targetOffset);
2616
+ const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!existingWatchdog && initialScrollWatchdog.isAtZeroTargetOffset(requestedOffset);
2617
+ if (shouldWatchInitialNativeScroll) {
2618
+ state.hasScrolled = false;
2619
+ initialScrollWatchdog.set(state, {
2620
+ startScroll: (_a3 = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _a3 : state.scroll,
2621
+ targetOffset
2622
+ });
2623
+ return;
2511
2624
  }
2512
- if (bootstrapInitialScroll) {
2513
- bootstrapInitialScroll.frameHandle = void 0;
2625
+ if (shouldClearInitialNativeScrollWatchdog) {
2626
+ initialScrollWatchdog.clear(state);
2514
2627
  }
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
2628
  }
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
- });
2629
+ function scrollTo(ctx, params) {
2630
+ var _a3;
2631
+ const state = ctx.state;
2632
+ const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2633
+ const {
2634
+ animated,
2635
+ isInitialScroll,
2636
+ offset: scrollTargetOffset,
2637
+ precomputedWithViewOffset,
2638
+ waitForInitialScrollCompletionFrame
2639
+ } = scrollTarget;
2640
+ const {
2641
+ props: { horizontal }
2642
+ } = state;
2643
+ if (state.animFrameCheckFinishedScroll) {
2644
+ cancelAnimationFrame(ctx.state.animFrameCheckFinishedScroll);
2645
+ }
2646
+ if (state.timeoutCheckFinishedScrollFallback) {
2647
+ clearTimeout(ctx.state.timeoutCheckFinishedScrollFallback);
2648
+ }
2649
+ const requestedOffset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
2650
+ const shouldPreserveRawInitialOffsetRequest = !!isInitialScroll && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2651
+ const targetOffset = clampScrollOffset(ctx, requestedOffset, scrollTarget);
2652
+ const offset = shouldPreserveRawInitialOffsetRequest ? requestedOffset : targetOffset;
2653
+ state.scrollHistory.length = 0;
2654
+ if (!noScrollingTo) {
2655
+ if (isInitialScroll) {
2656
+ initialScrollCompletion.resetFlags(state);
2548
2657
  }
2658
+ const averageSizeSnapshot = getAverageSizeSnapshot(state);
2659
+ state.scrollingTo = {
2660
+ ...scrollTarget,
2661
+ ...averageSizeSnapshot ? { averageSizeSnapshot } : {},
2662
+ targetOffset,
2663
+ waitForInitialScrollCompletionFrame
2664
+ };
2665
+ }
2666
+ state.scrollPending = targetOffset;
2667
+ syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2668
+ if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2669
+ updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2670
+ }
2671
+ if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2672
+ doScrollTo(ctx, { animated, horizontal, isInitialScroll, offset });
2549
2673
  } 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
- });
2674
+ state.scroll = offset;
2560
2675
  }
2561
2676
  }
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
- });
2677
+
2678
+ // src/core/scrollToIndex.ts
2679
+ function clampScrollIndex(index, dataLength) {
2680
+ if (dataLength <= 0) {
2681
+ return -1;
2682
+ }
2683
+ if (index >= dataLength) {
2684
+ return dataLength - 1;
2685
+ }
2686
+ if (index < 0) {
2687
+ return 0;
2688
+ }
2689
+ return index;
2569
2690
  }
2570
- function ensureBootstrapInitialScrollFrameTicker(ctx) {
2691
+ function scrollToIndex(ctx, {
2692
+ index,
2693
+ viewOffset = 0,
2694
+ animated = true,
2695
+ forceScroll,
2696
+ isInitialScroll,
2697
+ viewPosition
2698
+ }) {
2571
2699
  const state = ctx.state;
2572
- const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2573
- if (!bootstrapInitialScroll || bootstrapInitialScroll.frameHandle !== void 0) {
2574
- return;
2700
+ const { data } = state.props;
2701
+ index = clampScrollIndex(index, data.length);
2702
+ const itemSize = getItemSizeAtIndex(ctx, index);
2703
+ const firstIndexOffset = calculateOffsetForIndex(ctx, index);
2704
+ const isLast = index === data.length - 1;
2705
+ if (isLast && viewPosition === void 0) {
2706
+ viewPosition = 1;
2575
2707
  }
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);
2708
+ state.scrollForNextCalculateItemsInView = void 0;
2709
+ scrollTo(ctx, {
2710
+ animated,
2711
+ forceScroll,
2712
+ index,
2713
+ isInitialScroll,
2714
+ itemSize,
2715
+ offset: firstIndexOffset,
2716
+ viewOffset,
2717
+ viewPosition: viewPosition != null ? viewPosition : 0
2718
+ });
2592
2719
  }
2593
- function rearmBootstrapInitialScroll(ctx, options) {
2594
- resetBootstrapInitialScrollSession(ctx.state, options);
2595
- ensureBootstrapInitialScrollFrameTicker(ctx);
2596
- queueBootstrapInitialScrollReevaluation(ctx.state);
2720
+
2721
+ // src/core/initialScroll.ts
2722
+ function dispatchInitialScroll(ctx, params) {
2723
+ const { forceScroll, resolvedOffset, target, waitForCompletionFrame } = params;
2724
+ const requestedIndex = target.index;
2725
+ const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0;
2726
+ const itemSize = getItemSizeAtIndex(ctx, index);
2727
+ scrollTo(ctx, {
2728
+ animated: false,
2729
+ forceScroll,
2730
+ index: index !== void 0 && index >= 0 ? index : void 0,
2731
+ isInitialScroll: true,
2732
+ itemSize,
2733
+ offset: resolvedOffset,
2734
+ precomputedWithViewOffset: true,
2735
+ viewOffset: target.viewOffset,
2736
+ viewPosition: target.viewPosition,
2737
+ waitForInitialScrollCompletionFrame: waitForCompletionFrame
2738
+ });
2597
2739
  }
2598
- function createInitialScrollAtEndTarget(options) {
2599
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingBottom } = options;
2740
+ function setInitialScrollTarget(state, target, options) {
2741
+ var _a3;
2742
+ state.clearPreservedInitialScrollOnNextFinish = void 0;
2743
+ if (state.timeoutPreservedInitialScrollClear !== void 0) {
2744
+ clearTimeout(state.timeoutPreservedInitialScrollClear);
2745
+ state.timeoutPreservedInitialScrollClear = void 0;
2746
+ }
2747
+ state.initialScroll = target;
2748
+ if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
2749
+ state.didFinishInitialScroll = false;
2750
+ }
2751
+ setInitialScrollSession(state, {
2752
+ kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
2753
+ });
2754
+ }
2755
+ function resolveInitialScrollOffset(ctx, initialScroll) {
2756
+ var _a3, _b;
2757
+ const state = ctx.state;
2758
+ if (((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
2759
+ return (_b = initialScroll.contentOffset) != null ? _b : 0;
2760
+ }
2761
+ const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
2762
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
2763
+ return clampScrollOffset(ctx, resolvedOffset, initialScroll);
2764
+ }
2765
+ function getAdvanceableInitialScrollState(state, options) {
2766
+ const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
2767
+ const initialScroll = state.initialScroll;
2768
+ const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
2769
+ const shouldWaitForInitialLayout = !!(options == null ? void 0 : options.requiresMeasuredLayout) && !queuedInitialLayout && !isInitialScrollInProgress;
2770
+ if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll || scrollingTo && !isInitialScrollInProgress) {
2771
+ return void 0;
2772
+ }
2600
2773
  return {
2601
- contentOffset: void 0,
2602
- index: Math.max(0, dataLength - 1),
2603
- preserveForBottomPadding: true,
2604
- preserveForFooterLayout,
2605
- viewOffset: -stylePaddingBottom - footerSize,
2606
- viewPosition: 1
2774
+ initialScroll,
2775
+ isInitialScrollInProgress,
2776
+ queuedInitialLayout,
2777
+ scrollingTo
2607
2778
  };
2608
2779
  }
2609
- function shouldPreserveInitialScrollForBottomPadding(target) {
2610
- return !!(target == null ? void 0 : target.preserveForBottomPadding);
2611
- }
2612
- function shouldPreserveInitialScrollForFooterLayout(target) {
2613
- return !!(target == null ? void 0 : target.preserveForFooterLayout);
2614
- }
2615
- function isRetargetableBottomAlignedInitialScrollTarget(target) {
2616
- return !!(target && target.viewPosition === 1 && (shouldPreserveInitialScrollForBottomPadding(target) || shouldPreserveInitialScrollForFooterLayout(target)));
2617
- }
2618
- function createRetargetedBottomAlignedInitialScroll(options) {
2619
- const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom, target } = options;
2620
- const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2621
- 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
2629
- };
2630
- }
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;
2780
+ function advanceMeasuredInitialScroll(ctx, options) {
2781
+ var _a3, _b, _c;
2636
2782
  const state = ctx.state;
2637
- if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2638
- return;
2783
+ const advanceableState = getAdvanceableInitialScrollState(state, {
2784
+ requiresMeasuredLayout: true
2785
+ });
2786
+ if (!advanceableState) {
2787
+ return false;
2639
2788
  }
2640
- const clearedFooterTarget = createInitialScrollAtEndTarget({
2641
- dataLength,
2642
- footerSize: 0,
2643
- preserveForFooterLayout: void 0,
2644
- stylePaddingBottom
2789
+ const { initialScroll, isInitialScrollInProgress, queuedInitialLayout } = advanceableState;
2790
+ const scrollingTo = isInitialScrollInProgress ? advanceableState.scrollingTo : void 0;
2791
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
2792
+ const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2793
+ const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2794
+ const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2795
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2796
+ if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2797
+ return false;
2798
+ }
2799
+ if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2800
+ return false;
2801
+ }
2802
+ if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2803
+ setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2804
+ }
2805
+ const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
2806
+ dispatchInitialScroll(ctx, {
2807
+ forceScroll,
2808
+ resolvedOffset,
2809
+ target: initialScroll
2645
2810
  });
2646
- setInitialScrollTarget(state, clearedFooterTarget);
2647
- }
2648
- function clearFinishedViewportRetargetableInitialScroll(state) {
2649
- clearPreservedInitialScrollTarget(state);
2811
+ return true;
2650
2812
  }
2651
- function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2813
+ function advanceOffsetInitialScroll(ctx, options) {
2814
+ var _a3, _b;
2652
2815
  const state = ctx.state;
2653
- if (!state.didFinishInitialScroll) {
2816
+ const advanceableState = getAdvanceableInitialScrollState(state);
2817
+ if (!advanceableState) {
2654
2818
  return false;
2655
2819
  }
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;
2820
+ const { initialScroll, queuedInitialLayout } = advanceableState;
2821
+ const resolvedOffset = (_a3 = initialScroll.contentOffset) != null ? _a3 : 0;
2822
+ const isAlreadyAtDesiredInitialTarget = Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2823
+ if ((options == null ? void 0 : options.forceScroll) && isAlreadyAtDesiredInitialTarget) {
2824
+ return false;
2825
+ }
2826
+ const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
2827
+ const forceScroll = (_b = options == null ? void 0 : options.forceScroll) != null ? _b : hasMeasuredScrollLayout || !!queuedInitialLayout;
2828
+ dispatchInitialScroll(ctx, {
2829
+ forceScroll,
2830
+ resolvedOffset,
2831
+ target: initialScroll
2832
+ });
2833
+ return true;
2663
2834
  }
2664
- function getPreservedEndAnchorOffsetDiff(ctx) {
2835
+ function advanceCurrentInitialScrollSession(ctx, options) {
2665
2836
  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;
2837
+ return ((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? advanceOffsetInitialScroll(ctx, {
2838
+ forceScroll: options == null ? void 0 : options.forceScroll
2839
+ }) : advanceMeasuredInitialScroll(ctx, {
2840
+ forceScroll: options == null ? void 0 : options.forceScroll
2841
+ });
2842
+ }
2843
+
2844
+ // src/utils/checkAllSizesKnown.ts
2845
+ function isNullOrUndefined2(value) {
2846
+ return value === null || value === void 0;
2847
+ }
2848
+ function getMountedIndicesInRange(state, start, end) {
2849
+ if (!isNullOrUndefined2(end) && !isNullOrUndefined2(start) && start >= 0 && end >= 0) {
2850
+ 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
2851
  }
2671
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2672
- return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2852
+ return [];
2673
2853
  }
2674
- function schedulePreservedEndAnchorCorrection(ctx) {
2675
- if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
2854
+ function getMountedBufferedIndices(state) {
2855
+ return getMountedIndicesInRange(state, state.startBuffered, state.endBuffered);
2856
+ }
2857
+ function getMountedNoBufferIndices(state) {
2858
+ return getMountedIndicesInRange(state, state.startNoBuffer, state.endNoBuffer);
2859
+ }
2860
+ function checkAllSizesKnown(state, indices) {
2861
+ return indices.length > 0 && indices.every((index) => {
2862
+ const key = getId(state, index);
2863
+ return key !== void 0 && state.sizesKnown.has(key);
2864
+ });
2865
+ }
2866
+
2867
+ // src/core/bootstrapInitialScroll.ts
2868
+ var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2869
+ var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
2870
+ var DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES = 24;
2871
+ var BOOTSTRAP_REVEAL_ABORT_WARNING = "LegendList bootstrap initial scroll aborted after exceeding convergence bounds.";
2872
+ function getBootstrapInitialScrollSession(state) {
2873
+ var _a3;
2874
+ return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
2875
+ }
2876
+ function isOffsetInitialScrollSession(state) {
2877
+ var _a3;
2878
+ return ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2879
+ }
2880
+ function doVisibleIndicesMatch(previous, next) {
2881
+ if (!previous || previous.length !== next.length) {
2676
2882
  return false;
2677
2883
  }
2678
- const correction = {};
2679
- schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
2884
+ for (let i = 0; i < previous.length; i++) {
2885
+ if (previous[i] !== next[i]) {
2886
+ return false;
2887
+ }
2888
+ }
2680
2889
  return true;
2681
2890
  }
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;
2891
+ function getBootstrapRevealVisibleIndices(options) {
2892
+ const { dataLength, getSize, offset, positions, scrollLength, startIndex: requestedStartIndex } = options;
2893
+ const endOffset = offset + scrollLength;
2894
+ const visibleIndices = [];
2895
+ let index = requestedStartIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, requestedStartIndex)) : 0;
2896
+ while (index > 0) {
2897
+ const previousIndex = index - 1;
2898
+ const previousPosition = positions[previousIndex];
2899
+ if (previousPosition === void 0) {
2900
+ index = previousIndex;
2901
+ continue;
2690
2902
  }
2691
- const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
2692
- if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2693
- state.preservedEndAnchorCorrection = void 0;
2694
- return;
2903
+ const previousSize = getSize(previousIndex);
2904
+ if (previousSize === void 0) {
2905
+ index = previousIndex;
2906
+ continue;
2695
2907
  }
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);
2908
+ if (previousPosition + previousSize <= offset) {
2909
+ break;
2700
2910
  }
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;
2911
+ index = previousIndex;
2710
2912
  }
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
- }
2913
+ for (; index < dataLength; index++) {
2914
+ const position = positions[index];
2915
+ if (position === void 0) {
2916
+ continue;
2917
+ }
2918
+ const size = getSize(index);
2919
+ if (size === void 0) {
2920
+ continue;
2921
+ }
2922
+ if (position < endOffset && position + size > offset) {
2923
+ visibleIndices.push(index);
2924
+ } else if (visibleIndices.length > 0 && position >= endOffset) {
2925
+ break;
2723
2926
  }
2724
2927
  }
2928
+ return visibleIndices;
2725
2929
  }
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
- }
2930
+ function shouldAbortBootstrapReveal(options) {
2931
+ const {
2932
+ mountFrameCount,
2933
+ maxFrames = DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES,
2934
+ maxPasses = DEFAULT_BOOTSTRAP_REVEAL_MAX_PASSES,
2935
+ passCount
2936
+ } = options;
2937
+ return mountFrameCount >= maxFrames || passCount >= maxPasses;
2752
2938
  }
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;
2939
+ function abortBootstrapRevealIfNeeded(ctx, options) {
2940
+ if (!shouldAbortBootstrapReveal(options)) {
2941
+ return false;
2759
2942
  }
2760
- const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2943
+ if (IS_DEV) {
2944
+ console.warn(BOOTSTRAP_REVEAL_ABORT_WARNING);
2945
+ }
2946
+ abortBootstrapInitialScroll(ctx);
2947
+ return true;
2948
+ }
2949
+ function clearBootstrapInitialScrollSession(state) {
2950
+ var _a3;
2761
2951
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2762
- const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2763
- if (shouldClearFinishedResizePreservation) {
2764
- clearPreservedInitialScrollTarget(state);
2765
- return;
2952
+ const frameHandle = bootstrapInitialScroll == null ? void 0 : bootstrapInitialScroll.frameHandle;
2953
+ if (frameHandle !== void 0 && typeof cancelAnimationFrame === "function") {
2954
+ cancelAnimationFrame(frameHandle);
2766
2955
  }
2767
- const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
2768
- if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
2769
- return;
2956
+ if (bootstrapInitialScroll) {
2957
+ bootstrapInitialScroll.frameHandle = void 0;
2770
2958
  }
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
2959
+ setInitialScrollSession(state, {
2960
+ bootstrap: null,
2961
+ kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2962
+ });
2963
+ }
2964
+ function startBootstrapInitialScrollSession(state, options) {
2965
+ var _a3, _b, _c;
2966
+ const previousBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2967
+ setInitialScrollSession(state, {
2968
+ bootstrap: {
2969
+ frameHandle: previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.frameHandle,
2970
+ // Re-arming during the initial mount should spend from the same watchdog budget.
2971
+ mountFrameCount: (_a3 = previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.mountFrameCount) != null ? _a3 : 0,
2972
+ passCount: 0,
2973
+ previousResolvedOffset: void 0,
2974
+ scroll: options.scroll,
2975
+ seedContentOffset: (_c = (_b = options.seedContentOffset) != null ? _b : previousBootstrapInitialScroll == null ? void 0 : previousBootstrapInitialScroll.seedContentOffset) != null ? _c : options.scroll,
2976
+ targetIndexSeed: options.targetIndexSeed,
2977
+ visibleIndices: void 0
2978
+ },
2979
+ kind: "bootstrap"
2980
+ });
2981
+ }
2982
+ function resetBootstrapInitialScrollSession(state, options) {
2983
+ var _a3, _b, _c;
2984
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2985
+ if (!bootstrapInitialScroll) {
2986
+ if ((options == null ? void 0 : options.scroll) !== void 0) {
2987
+ startBootstrapInitialScrollSession(state, {
2988
+ scroll: options.scroll,
2989
+ seedContentOffset: options.seedContentOffset,
2990
+ targetIndexSeed: options.targetIndexSeed
2789
2991
  });
2992
+ }
2993
+ } else {
2994
+ bootstrapInitialScroll.passCount = 0;
2995
+ bootstrapInitialScroll.previousResolvedOffset = void 0;
2996
+ bootstrapInitialScroll.scroll = (_a3 = options == null ? void 0 : options.scroll) != null ? _a3 : bootstrapInitialScroll.scroll;
2997
+ bootstrapInitialScroll.seedContentOffset = (_b = options == null ? void 0 : options.seedContentOffset) != null ? _b : bootstrapInitialScroll.seedContentOffset;
2998
+ bootstrapInitialScroll.targetIndexSeed = (_c = options == null ? void 0 : options.targetIndexSeed) != null ? _c : bootstrapInitialScroll.targetIndexSeed;
2999
+ bootstrapInitialScroll.visibleIndices = void 0;
3000
+ setInitialScrollSession(state, {
3001
+ bootstrap: bootstrapInitialScroll,
3002
+ kind: "bootstrap"
3003
+ });
3004
+ }
3005
+ }
3006
+ function queueBootstrapInitialScrollReevaluation(state) {
3007
+ requestAnimationFrame(() => {
3008
+ var _a3;
3009
+ if (getBootstrapInitialScrollSession(state)) {
3010
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
3011
+ }
3012
+ });
3013
+ }
3014
+ function ensureBootstrapInitialScrollFrameTicker(ctx) {
3015
+ const state = ctx.state;
3016
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3017
+ if (!bootstrapInitialScroll || bootstrapInitialScroll.frameHandle !== void 0) {
3018
+ return;
3019
+ }
3020
+ const tick = () => {
3021
+ const activeBootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3022
+ if (!activeBootstrapInitialScroll) {
2790
3023
  return;
2791
3024
  }
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
- });
3025
+ activeBootstrapInitialScroll.frameHandle = void 0;
3026
+ activeBootstrapInitialScroll.mountFrameCount += 1;
3027
+ if (abortBootstrapRevealIfNeeded(ctx, {
3028
+ mountFrameCount: activeBootstrapInitialScroll.mountFrameCount,
3029
+ passCount: activeBootstrapInitialScroll.passCount
3030
+ })) {
2801
3031
  return;
2802
3032
  }
2803
- }
2804
- if (!didDataChange) {
3033
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3034
+ };
3035
+ bootstrapInitialScroll.frameHandle = requestAnimationFrame(tick);
3036
+ }
3037
+ function rearmBootstrapInitialScroll(ctx, options) {
3038
+ resetBootstrapInitialScrollSession(ctx.state, options);
3039
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3040
+ queueBootstrapInitialScrollReevaluation(ctx.state);
3041
+ }
3042
+ function createInitialScrollAtEndTarget(options) {
3043
+ const { dataLength, footerSize, preserveForFooterLayout, stylePaddingBottom } = options;
3044
+ return {
3045
+ contentOffset: void 0,
3046
+ index: Math.max(0, dataLength - 1),
3047
+ preserveForBottomPadding: true,
3048
+ preserveForFooterLayout,
3049
+ viewOffset: -stylePaddingBottom - footerSize,
3050
+ viewPosition: 1
3051
+ };
3052
+ }
3053
+ function shouldPreserveInitialScrollForBottomPadding(target) {
3054
+ return !!(target == null ? void 0 : target.preserveForBottomPadding);
3055
+ }
3056
+ function shouldPreserveInitialScrollForFooterLayout(target) {
3057
+ return !!(target == null ? void 0 : target.preserveForFooterLayout);
3058
+ }
3059
+ function isRetargetableBottomAlignedInitialScrollTarget(target) {
3060
+ return !!(target && target.viewPosition === 1 && (shouldPreserveInitialScrollForBottomPadding(target) || shouldPreserveInitialScrollForFooterLayout(target)));
3061
+ }
3062
+ function createRetargetedBottomAlignedInitialScroll(options) {
3063
+ const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom, target } = options;
3064
+ const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
3065
+ return {
3066
+ ...target,
3067
+ contentOffset: void 0,
3068
+ index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
3069
+ preserveForBottomPadding: true,
3070
+ preserveForFooterLayout,
3071
+ viewOffset: -stylePaddingBottom - (preserveForFooterLayout ? footerSize : 0),
3072
+ viewPosition: 1
3073
+ };
3074
+ }
3075
+ function areEquivalentBootstrapInitialScrollTargets(current, next) {
3076
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
3077
+ }
3078
+ function clearPendingInitialScrollFooterLayout(ctx, options) {
3079
+ const { dataLength, stylePaddingBottom, target } = options;
3080
+ const state = ctx.state;
3081
+ if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2805
3082
  return;
2806
3083
  }
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
- }
3084
+ const clearedFooterTarget = createInitialScrollAtEndTarget({
3085
+ dataLength,
3086
+ footerSize: 0,
3087
+ preserveForFooterLayout: void 0,
3088
+ stylePaddingBottom
3089
+ });
3090
+ setInitialScrollTarget(state, clearedFooterTarget);
2817
3091
  }
2818
- function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2819
- const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom } = options;
3092
+ function clearFinishedViewportRetargetableInitialScroll(state) {
3093
+ clearPreservedInitialScrollTarget(state);
3094
+ }
3095
+ function didFinishedInitialScrollMoveAwayFromTarget(ctx, target, epsilon = DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2820
3096
  const state = ctx.state;
2821
- if (!initialScrollAtEnd) {
2822
- return;
3097
+ if (!state.didFinishInitialScroll) {
3098
+ return false;
2823
3099
  }
3100
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
3101
+ return Math.abs(currentOffset - resolveInitialScrollOffset(ctx, target)) > epsilon;
3102
+ }
3103
+ function getObservedBootstrapInitialScrollOffset(state) {
3104
+ var _a3, _b, _c, _d;
3105
+ const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3106
+ return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3107
+ }
3108
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3109
+ var _a3;
3110
+ const state = ctx.state;
2824
3111
  const initialScroll = state.initialScroll;
2825
- if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
3112
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2826
3113
  return;
2827
3114
  }
2828
- const shouldProcessFooterLayout = !!getBootstrapInitialScrollSession(state) || shouldPreserveInitialScrollForFooterLayout(initialScroll);
2829
- if (!shouldProcessFooterLayout) {
3115
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3116
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3117
+ }
3118
+ function schedulePreservedEndAnchorCorrection(ctx) {
3119
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3120
+ return false;
3121
+ }
3122
+ const correction = {};
3123
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3124
+ return true;
3125
+ }
3126
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3127
+ const state = ctx.state;
3128
+ state.preservedEndAnchorCorrection = correction;
3129
+ requestAnimationFrame(() => {
3130
+ var _a3;
3131
+ const activeCorrection = state.preservedEndAnchorCorrection;
3132
+ if (activeCorrection !== correction) {
3133
+ return;
3134
+ }
3135
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3136
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3137
+ state.preservedEndAnchorCorrection = void 0;
3138
+ return;
3139
+ }
3140
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3141
+ if (hasObservedNativeScrollAfterRequest) {
3142
+ activeCorrection.lastRequestTime = Date.now();
3143
+ requestAdjust(ctx, offsetDiff);
3144
+ }
3145
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3146
+ });
3147
+ }
3148
+ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3149
+ var _a3, _b;
3150
+ const state = ctx.state;
3151
+ const initialScroll = state.initialScroll;
3152
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3153
+ return;
3154
+ }
3155
+ if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3156
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3157
+ if (!shouldKeepEndTargetAlive) {
3158
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3159
+ clearPendingInitialScrollFooterLayout(ctx, {
3160
+ dataLength: state.props.data.length,
3161
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3162
+ target: initialScroll
3163
+ });
3164
+ } else {
3165
+ clearFinishedViewportRetargetableInitialScroll(state);
3166
+ }
3167
+ }
3168
+ }
3169
+ }
3170
+ function startBootstrapInitialScrollOnMount(ctx, options) {
3171
+ var _a3, _b, _c;
3172
+ const { initialScrollAtEnd, target } = options;
3173
+ const state = ctx.state;
3174
+ const offset = resolveInitialScrollOffset(ctx, target);
3175
+ 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);
3176
+ const shouldFinishWithPreservedTarget = state.props.data.length === 0 && target.index !== void 0;
3177
+ if (shouldFinishAtOrigin) {
3178
+ clearBootstrapInitialScrollSession(state);
3179
+ finishInitialScroll(ctx, {
3180
+ resolvedOffset: offset
3181
+ });
3182
+ } else if (shouldFinishWithPreservedTarget) {
3183
+ clearBootstrapInitialScrollSession(state);
3184
+ finishInitialScroll(ctx, {
3185
+ preserveTarget: true,
3186
+ resolvedOffset: offset
3187
+ });
3188
+ } else {
3189
+ startBootstrapInitialScrollSession(state, {
3190
+ scroll: offset,
3191
+ seedContentOffset: Platform.OS === "web" ? 0 : offset,
3192
+ targetIndexSeed: target.index
3193
+ });
3194
+ ensureBootstrapInitialScrollFrameTicker(ctx);
3195
+ }
3196
+ }
3197
+ function handleBootstrapInitialScrollDataChange(ctx, options) {
3198
+ const { dataLength, didDataChange, initialScrollAtEnd, previousDataLength, stylePaddingBottom } = options;
3199
+ const state = ctx.state;
3200
+ const initialScroll = state.initialScroll;
3201
+ if (isOffsetInitialScrollSession(state) || !initialScroll) {
3202
+ return;
3203
+ }
3204
+ const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
3205
+ const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3206
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3207
+ if (shouldClearFinishedResizePreservation) {
3208
+ clearPreservedInitialScrollTarget(state);
3209
+ return;
3210
+ }
3211
+ const shouldRetargetBottomAligned = dataLength > 0 && (initialScrollAtEnd || isRetargetableBottomAlignedInitialScrollTarget(initialScroll));
3212
+ if (!didDataChange && !shouldResetDidFinish && !shouldRetargetBottomAligned) {
3213
+ return;
3214
+ }
3215
+ if (shouldRetargetBottomAligned) {
3216
+ const updatedInitialScroll = initialScrollAtEnd ? createInitialScrollAtEndTarget({
3217
+ dataLength,
3218
+ footerSize: peek$(ctx, "footerSize") || 0,
3219
+ preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3220
+ stylePaddingBottom
3221
+ }) : createRetargetedBottomAlignedInitialScroll({
3222
+ dataLength,
3223
+ footerSize: peek$(ctx, "footerSize") || 0,
3224
+ initialScrollAtEnd,
3225
+ stylePaddingBottom,
3226
+ target: initialScroll
3227
+ });
3228
+ if (!shouldResetDidFinish && didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
3229
+ clearPendingInitialScrollFooterLayout(ctx, {
3230
+ dataLength,
3231
+ stylePaddingBottom,
3232
+ target: initialScroll
3233
+ });
3234
+ return;
3235
+ }
3236
+ if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
3237
+ setInitialScrollTarget(state, updatedInitialScroll, {
3238
+ resetDidFinish: shouldResetDidFinish
3239
+ });
3240
+ rearmBootstrapInitialScroll(ctx, {
3241
+ scroll: resolveInitialScrollOffset(ctx, updatedInitialScroll),
3242
+ seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3243
+ targetIndexSeed: updatedInitialScroll.index
3244
+ });
3245
+ return;
3246
+ }
3247
+ }
3248
+ if (!didDataChange) {
3249
+ return;
3250
+ }
3251
+ if (bootstrapInitialScroll || shouldResetDidFinish) {
3252
+ setInitialScrollTarget(state, initialScroll, {
3253
+ resetDidFinish: shouldResetDidFinish
3254
+ });
3255
+ rearmBootstrapInitialScroll(ctx, {
3256
+ scroll: resolveInitialScrollOffset(ctx, initialScroll),
3257
+ seedContentOffset: shouldResetDidFinish && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3258
+ targetIndexSeed: initialScroll.index
3259
+ });
3260
+ }
3261
+ }
3262
+ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3263
+ const { dataLength, footerSize, initialScrollAtEnd, stylePaddingBottom } = options;
3264
+ const state = ctx.state;
3265
+ if (!initialScrollAtEnd) {
3266
+ return;
3267
+ }
3268
+ const initialScroll = state.initialScroll;
3269
+ if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
3270
+ return;
3271
+ }
3272
+ const shouldProcessFooterLayout = !!getBootstrapInitialScrollSession(state) || shouldPreserveInitialScrollForFooterLayout(initialScroll);
3273
+ if (!shouldProcessFooterLayout) {
2830
3274
  return;
2831
3275
  }
2832
3276
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -2985,405 +3429,113 @@ function abortBootstrapInitialScroll(ctx) {
2985
3429
  const state = ctx.state;
2986
3430
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2987
3431
  const initialScroll = state.initialScroll;
2988
- if (bootstrapInitialScroll && initialScroll && !isOffsetInitialScrollSession(state) && state.refScroller.current) {
2989
- 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
3432
+ if (bootstrapInitialScroll && initialScroll && !isOffsetInitialScrollSession(state) && state.refScroller.current) {
3433
+ clearBootstrapInitialScrollSession(state);
3434
+ dispatchInitialScroll(ctx, {
3435
+ forceScroll: true,
3436
+ resolvedOffset: bootstrapInitialScroll.scroll,
3437
+ target: initialScroll,
3438
+ waitForCompletionFrame: Platform.OS === "web"
3439
+ });
3440
+ } else {
3441
+ finishBootstrapInitialScrollWithoutScroll(
3442
+ ctx,
3443
+ (_d = (_c = (_b = (_a3 = getBootstrapInitialScrollSession(state)) == null ? void 0 : _a3.scroll) != null ? _b : state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0
3444
+ );
3445
+ }
3446
+ }
3447
+
3448
+ // src/core/initialScrollLifecycle.ts
3449
+ function retargetActiveInitialScrollAtEnd(ctx) {
3450
+ var _a3;
3451
+ const state = ctx.state;
3452
+ const initialScroll = state.initialScroll;
3453
+ if (state.didFinishInitialScroll) {
3454
+ return schedulePreservedEndAnchorCorrection(ctx);
3455
+ }
3456
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3457
+ return false;
3458
+ }
3459
+ return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3460
+ }
3461
+ function handleInitialScrollLayoutReady(ctx) {
3462
+ var _a3;
3463
+ if (!ctx.state.initialScroll) {
3464
+ return;
3465
+ }
3466
+ const runScroll = () => advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
3467
+ runScroll();
3468
+ if (((_a3 = ctx.state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset") {
3469
+ requestAnimationFrame(runScroll);
3470
+ }
3471
+ checkFinishedScroll(ctx, { onlyIfAligned: true });
3472
+ }
3473
+ function initializeInitialScrollOnMount(ctx, options) {
3474
+ var _a3, _b;
3475
+ const {
3476
+ alwaysDispatchInitialScroll,
3477
+ dataLength,
3478
+ hasFooterComponent,
3479
+ initialContentOffset,
3480
+ initialScrollAtEnd,
3481
+ useBootstrapInitialScroll
3482
+ } = options;
3483
+ const state = ctx.state;
3484
+ const initialScroll = state.initialScroll;
3485
+ const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
3486
+ const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
3487
+ if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
3488
+ setInitialScrollTarget(state, {
3489
+ ...initialScroll,
3490
+ contentOffset: resolvedInitialContentOffset,
3491
+ preserveForFooterLayout
3492
+ });
3493
+ }
3494
+ if (useBootstrapInitialScroll && initialScroll && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
3495
+ startBootstrapInitialScrollOnMount(ctx, {
3496
+ initialScrollAtEnd,
3497
+ target: state.initialScroll
3498
+ });
3499
+ return;
3500
+ }
3501
+ const hasPendingDataDependentInitialScroll = !!initialScroll && dataLength === 0 && !(resolvedInitialContentOffset === 0 && !initialScrollAtEnd);
3502
+ if (!alwaysDispatchInitialScroll && !resolvedInitialContentOffset && !hasPendingDataDependentInitialScroll) {
3503
+ if (initialScroll && !initialScrollAtEnd) {
3504
+ finishInitialScroll(ctx, {
3505
+ resolvedOffset: resolvedInitialContentOffset
3368
3506
  });
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
- };
3507
+ } else {
3508
+ setInitialRenderState(ctx, { didInitialScroll: true });
3509
+ }
3510
+ }
3511
+ }
3512
+ function handleInitialScrollDataChange(ctx, options) {
3513
+ var _a3, _b, _c;
3514
+ const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
3515
+ const state = ctx.state;
3516
+ const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3517
+ if (state.initialScrollSession) {
3518
+ state.initialScrollSession.previousDataLength = dataLength;
3519
+ }
3520
+ setInitialScrollSession(state);
3521
+ if (useBootstrapInitialScroll) {
3522
+ handleBootstrapInitialScrollDataChange(ctx, {
3523
+ dataLength,
3524
+ didDataChange,
3525
+ initialScrollAtEnd,
3526
+ previousDataLength,
3527
+ stylePaddingBottom
3528
+ });
3529
+ return;
3530
+ }
3531
+ const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3532
+ if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3533
+ return;
3534
+ }
3535
+ if (shouldReplayFinishedOffsetInitialScroll) {
3536
+ state.didFinishInitialScroll = false;
3386
3537
  }
3538
+ advanceCurrentInitialScrollSession(ctx);
3387
3539
  }
3388
3540
 
3389
3541
  // src/core/resetLayoutCachesForDataChange.ts
@@ -4669,62 +4821,6 @@ function calculateItemsInView(ctx, params = {}) {
4669
4821
  });
4670
4822
  }
4671
4823
 
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
4824
  // src/core/checkResetContainers.ts
4729
4825
  function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {}) {
4730
4826
  const state = ctx.state;
@@ -4905,84 +5001,6 @@ function handleLayout(ctx, layoutParam, setCanRender) {
4905
5001
  setCanRender(true);
4906
5002
  }
4907
5003
 
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
5004
  // src/core/onScroll.ts
4987
5005
  function trackInitialScrollNativeProgress(state, newScroll) {
4988
5006
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);