@legendapp/list 3.0.0 → 3.0.2

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