@legendapp/list 3.0.0-beta.40 → 3.0.0-beta.41

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/index.js CHANGED
@@ -1004,6 +1004,47 @@ var StyleSheet = {
1004
1004
  create: (styles) => styles,
1005
1005
  flatten: (style) => flattenStyles(style)
1006
1006
  };
1007
+ function useRafCoalescer(callback) {
1008
+ const callbackRef = React3.useRef(callback);
1009
+ const rafIdRef = React3.useRef(void 0);
1010
+ callbackRef.current = callback;
1011
+ const coalescer = React3.useMemo(
1012
+ () => ({
1013
+ cancel() {
1014
+ if (rafIdRef.current !== void 0) {
1015
+ cancelAnimationFrame(rafIdRef.current);
1016
+ rafIdRef.current = void 0;
1017
+ }
1018
+ },
1019
+ flush() {
1020
+ coalescer.cancel();
1021
+ callbackRef.current();
1022
+ },
1023
+ schedule() {
1024
+ if (rafIdRef.current !== void 0) {
1025
+ return false;
1026
+ }
1027
+ const rafId = requestAnimationFrame(() => {
1028
+ if (rafIdRef.current !== rafId) {
1029
+ return;
1030
+ }
1031
+ rafIdRef.current = void 0;
1032
+ callbackRef.current();
1033
+ });
1034
+ rafIdRef.current = rafId;
1035
+ return true;
1036
+ }
1037
+ }),
1038
+ []
1039
+ );
1040
+ React3.useEffect(
1041
+ () => () => {
1042
+ coalescer.cancel();
1043
+ },
1044
+ [coalescer]
1045
+ );
1046
+ return coalescer;
1047
+ }
1007
1048
 
1008
1049
  // src/components/webScrollUtils.ts
1009
1050
  function getDocumentScrollerNode() {
@@ -1105,6 +1146,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1105
1146
  onLayout,
1106
1147
  ...props
1107
1148
  }, ref) {
1149
+ const ctx = useStateContext();
1108
1150
  const scrollRef = React3.useRef(null);
1109
1151
  const contentRef = React3.useRef(null);
1110
1152
  const isWindowScroll = useWindowScroll;
@@ -1198,37 +1240,46 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1198
1240
  };
1199
1241
  return api;
1200
1242
  }, [getCurrentScrollOffset, getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll, scrollToLocalOffset]);
1243
+ const emitScroll = React3.useCallback(() => {
1244
+ if (!onScroll2 || !scrollRef.current) {
1245
+ return;
1246
+ }
1247
+ const contentSize = getContentSize2(contentRef.current);
1248
+ const layoutMeasurement = getLayoutMeasurement(scrollRef.current, isWindowScroll, horizontal);
1249
+ const offset = getCurrentScrollOffset();
1250
+ const scrollEvent = {
1251
+ nativeEvent: {
1252
+ contentOffset: {
1253
+ x: horizontal ? offset : 0,
1254
+ y: horizontal ? 0 : offset
1255
+ },
1256
+ contentSize: {
1257
+ height: contentSize.height,
1258
+ width: contentSize.width
1259
+ },
1260
+ layoutMeasurement: {
1261
+ height: layoutMeasurement.height,
1262
+ width: layoutMeasurement.width
1263
+ }
1264
+ }
1265
+ };
1266
+ onScroll2(scrollEvent);
1267
+ }, [getCurrentScrollOffset, horizontal, isWindowScroll, onScroll2]);
1268
+ const scrollEventCoalescer = useRafCoalescer(emitScroll);
1201
1269
  const handleScroll = React3.useCallback(
1202
1270
  (_event) => {
1271
+ var _a3;
1203
1272
  if (!onScroll2) {
1204
1273
  return;
1205
1274
  }
1206
- const target = scrollRef.current;
1207
- if (!target) {
1208
- return;
1275
+ const scrollingTo = (_a3 = ctx.state) == null ? void 0 : _a3.scrollingTo;
1276
+ if (scrollingTo && !scrollingTo.animated) {
1277
+ scrollEventCoalescer.flush();
1278
+ } else {
1279
+ scrollEventCoalescer.schedule();
1209
1280
  }
1210
- const contentSize = getContentSize2(contentRef.current);
1211
- const layoutMeasurement = getLayoutMeasurement(scrollRef.current, isWindowScroll, horizontal);
1212
- const offset = getCurrentScrollOffset();
1213
- const scrollEvent = {
1214
- nativeEvent: {
1215
- contentOffset: {
1216
- x: horizontal ? offset : 0,
1217
- y: horizontal ? 0 : offset
1218
- },
1219
- contentSize: {
1220
- height: contentSize.height,
1221
- width: contentSize.width
1222
- },
1223
- layoutMeasurement: {
1224
- height: layoutMeasurement.height,
1225
- width: layoutMeasurement.width
1226
- }
1227
- }
1228
- };
1229
- onScroll2(scrollEvent);
1230
1281
  },
1231
- [getCurrentScrollOffset, horizontal, isWindowScroll, onScroll2]
1282
+ [onScroll2, scrollEventCoalescer]
1232
1283
  );
1233
1284
  React3.useLayoutEffect(() => {
1234
1285
  const target = getScrollTarget();
@@ -1236,8 +1287,9 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1236
1287
  target.addEventListener("scroll", handleScroll, { passive: true });
1237
1288
  return () => {
1238
1289
  target.removeEventListener("scroll", handleScroll);
1290
+ scrollEventCoalescer.cancel();
1239
1291
  };
1240
- }, [getScrollTarget, handleScroll]);
1292
+ }, [getScrollTarget, handleScroll, scrollEventCoalescer]);
1241
1293
  React3.useEffect(() => {
1242
1294
  const doScroll = () => {
1243
1295
  if (contentOffset) {
@@ -1589,6 +1641,7 @@ function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize) {
1589
1641
 
1590
1642
  // src/core/calculateOffsetWithOffsetPosition.ts
1591
1643
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1644
+ var _a3;
1592
1645
  const state = ctx.state;
1593
1646
  const { index, viewOffset, viewPosition } = params;
1594
1647
  let offset = offsetParam;
@@ -1602,10 +1655,16 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1602
1655
  }
1603
1656
  }
1604
1657
  if (viewPosition !== void 0 && index !== void 0) {
1605
- const itemSize = getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1658
+ const dataLength = state.props.data.length;
1659
+ if (dataLength === 0) {
1660
+ return offset;
1661
+ }
1662
+ const isOutOfBounds = index < 0 || index >= dataLength;
1663
+ const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
1664
+ const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1606
1665
  const trailingInset = getContentInsetEnd(state);
1607
1666
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1608
- if (index === state.props.data.length - 1) {
1667
+ if (!isOutOfBounds && index === state.props.data.length - 1) {
1609
1668
  const footerSize = peek$(ctx, "footerSize") || 0;
1610
1669
  offset += footerSize;
1611
1670
  }
@@ -1807,7 +1866,9 @@ function finishScrollTo(ctx) {
1807
1866
  const scrollingTo = state.scrollingTo;
1808
1867
  state.scrollHistory.length = 0;
1809
1868
  state.initialScroll = void 0;
1869
+ state.initialScrollUsesOffset = false;
1810
1870
  state.initialAnchor = void 0;
1871
+ state.initialNativeScrollWatchdog = void 0;
1811
1872
  state.scrollingTo = void 0;
1812
1873
  if (state.pendingTotalSize !== void 0) {
1813
1874
  addTotalSize(ctx, null, state.pendingTotalSize);
@@ -1909,7 +1970,9 @@ function listenForScrollEnd(ctx, params) {
1909
1970
  }
1910
1971
 
1911
1972
  // src/core/scrollTo.ts
1973
+ var WATCHDOG_OFFSET_EPSILON = 1;
1912
1974
  function scrollTo(ctx, params) {
1975
+ var _a3, _b;
1913
1976
  const state = ctx.state;
1914
1977
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
1915
1978
  const { animated, isInitialScroll, offset: scrollTargetOffset, precomputedWithViewOffset } = scrollTarget;
@@ -1926,9 +1989,23 @@ function scrollTo(ctx, params) {
1926
1989
  offset = clampScrollOffset(ctx, offset, scrollTarget);
1927
1990
  state.scrollHistory.length = 0;
1928
1991
  if (!noScrollingTo) {
1929
- state.scrollingTo = scrollTarget;
1992
+ state.scrollingTo = {
1993
+ ...scrollTarget,
1994
+ targetOffset: offset
1995
+ };
1930
1996
  }
1931
1997
  state.scrollPending = offset;
1998
+ const shouldWatchInitialNativeScroll = !state.didFinishInitialScroll && (isInitialScroll || !!state.initialNativeScrollWatchdog) && offset > WATCHDOG_OFFSET_EPSILON;
1999
+ const shouldClearInitialNativeScrollWatchdog = !state.didFinishInitialScroll && !!state.initialNativeScrollWatchdog && offset <= WATCHDOG_OFFSET_EPSILON;
2000
+ if (shouldWatchInitialNativeScroll) {
2001
+ state.hasScrolled = false;
2002
+ state.initialNativeScrollWatchdog = {
2003
+ startScroll: (_b = (_a3 = state.initialNativeScrollWatchdog) == null ? void 0 : _a3.startScroll) != null ? _b : state.scroll,
2004
+ targetOffset: offset
2005
+ };
2006
+ } else if (shouldClearInitialNativeScrollWatchdog) {
2007
+ state.initialNativeScrollWatchdog = void 0;
2008
+ }
1932
2009
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
1933
2010
  doScrollTo(ctx, { animated, horizontal, offset });
1934
2011
  } else {
@@ -1936,91 +2013,52 @@ function scrollTo(ctx, params) {
1936
2013
  }
1937
2014
  }
1938
2015
 
1939
- // src/core/updateScroll.ts
1940
- function updateScroll(ctx, newScroll, forceUpdate) {
2016
+ // src/core/doMaintainScrollAtEnd.ts
2017
+ function doMaintainScrollAtEnd(ctx) {
1941
2018
  const state = ctx.state;
1942
- const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
1943
- const prevScroll = state.scroll;
1944
- state.hasScrolled = true;
1945
- state.lastBatchingAction = Date.now();
1946
- const currentTime = Date.now();
1947
- const adjust = scrollAdjustHandler.getAdjust();
1948
- const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
1949
- if (adjustChanged) {
1950
- scrollHistory.length = 0;
1951
- }
1952
- state.lastScrollAdjustForHistory = adjust;
1953
- if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
1954
- if (!adjustChanged) {
1955
- scrollHistory.push({ scroll: newScroll, time: currentTime });
1956
- }
1957
- }
1958
- if (scrollHistory.length > 5) {
1959
- scrollHistory.shift();
2019
+ const {
2020
+ didContainersLayout,
2021
+ isAtEnd,
2022
+ pendingNativeMVCPAdjust,
2023
+ refScroller,
2024
+ props: { maintainScrollAtEnd }
2025
+ } = state;
2026
+ const shouldMaintainScrollAtEnd = !!(isAtEnd && maintainScrollAtEnd && didContainersLayout);
2027
+ if (pendingNativeMVCPAdjust) {
2028
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
2029
+ return false;
1960
2030
  }
1961
- if (ignoreScrollFromMVCP && !scrollingTo) {
1962
- const { lt, gt } = ignoreScrollFromMVCP;
1963
- if (lt && newScroll < lt || gt && newScroll > gt) {
1964
- state.ignoreScrollFromMVCPIgnored = true;
1965
- return;
2031
+ state.pendingMaintainScrollAtEnd = false;
2032
+ if (shouldMaintainScrollAtEnd) {
2033
+ const contentSize = getContentSize(ctx);
2034
+ if (contentSize < state.scrollLength) {
2035
+ state.scroll = 0;
1966
2036
  }
1967
- }
1968
- state.scrollPrev = prevScroll;
1969
- state.scrollPrevTime = state.scrollTime;
1970
- state.scroll = newScroll;
1971
- state.scrollTime = currentTime;
1972
- const scrollDelta = Math.abs(newScroll - prevScroll);
1973
- const scrollLength = state.scrollLength;
1974
- const lastCalculated = state.scrollLastCalculate;
1975
- const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
1976
- const shouldUpdate = useAggressiveItemRecalculation || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
1977
- if (shouldUpdate) {
1978
- state.scrollLastCalculate = state.scroll;
1979
- state.ignoreScrollFromMVCPIgnored = false;
1980
- state.lastScrollDelta = scrollDelta;
1981
- const runCalculateItems = () => {
2037
+ requestAnimationFrame(() => {
1982
2038
  var _a3;
1983
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { doMVCP: scrollingTo !== void 0 });
1984
- checkThresholds(ctx);
1985
- };
1986
- if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
1987
- ReactDOM.flushSync(runCalculateItems);
1988
- } else {
1989
- runCalculateItems();
1990
- }
1991
- state.dataChangeNeedsScrollUpdate = false;
1992
- state.lastScrollDelta = 0;
1993
- }
1994
- }
1995
-
1996
- // src/utils/requestAdjust.ts
1997
- function requestAdjust(ctx, positionDiff, dataChanged) {
1998
- const state = ctx.state;
1999
- if (Math.abs(positionDiff) > 0.1) {
2000
- const doit = () => {
2001
- {
2002
- state.scrollAdjustHandler.requestAdjust(positionDiff);
2003
- if (state.adjustingFromInitialMount) {
2004
- state.adjustingFromInitialMount--;
2005
- }
2039
+ if (state.isAtEnd) {
2040
+ state.maintainingScrollAtEnd = true;
2041
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
2042
+ animated: maintainScrollAtEnd.animated
2043
+ });
2044
+ setTimeout(
2045
+ () => {
2046
+ state.maintainingScrollAtEnd = false;
2047
+ },
2048
+ maintainScrollAtEnd.animated ? 500 : 0
2049
+ );
2006
2050
  }
2007
- };
2008
- state.scroll += positionDiff;
2009
- state.scrollForNextCalculateItemsInView = void 0;
2010
- const readyToRender = peek$(ctx, "readyToRender");
2011
- if (readyToRender) {
2012
- doit();
2013
- } else {
2014
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2015
- requestAnimationFrame(doit);
2016
- }
2051
+ });
2052
+ return true;
2017
2053
  }
2054
+ return false;
2018
2055
  }
2019
2056
 
2020
2057
  // src/core/mvcp.ts
2021
2058
  var MVCP_POSITION_EPSILON = 0.1;
2022
2059
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
2023
2060
  var MVCP_ANCHOR_LOCK_QUIET_PASSES_TO_RELEASE = 2;
2061
+ var NATIVE_END_CLAMP_EPSILON = 1;
2024
2062
  function resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) {
2025
2063
  if (!enableMVCPAnchorLock) {
2026
2064
  state.mvcpAnchorLock = void 0;
@@ -2060,6 +2098,82 @@ function updateAnchorLock(state, params) {
2060
2098
  };
2061
2099
  }
2062
2100
  }
2101
+ function shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget) {
2102
+ {
2103
+ return false;
2104
+ }
2105
+ }
2106
+ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
2107
+ if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) {
2108
+ return 0;
2109
+ }
2110
+ const maxScroll = Math.max(0, totalSize - state.scrollLength);
2111
+ const clampDelta = maxScroll - state.scroll;
2112
+ if (unresolvedAmount < 0) {
2113
+ return Math.max(unresolvedAmount, Math.min(0, clampDelta));
2114
+ }
2115
+ if (unresolvedAmount > 0) {
2116
+ return Math.min(unresolvedAmount, Math.max(0, clampDelta));
2117
+ }
2118
+ return 0;
2119
+ }
2120
+ function maybeApplyPredictedNativeMVCPAdjust(ctx) {
2121
+ const state = ctx.state;
2122
+ const pending = state.pendingNativeMVCPAdjust;
2123
+ if (!pending || Math.abs(pending.manualApplied) > MVCP_POSITION_EPSILON) {
2124
+ return;
2125
+ }
2126
+ const totalSize = getContentSize(ctx);
2127
+ const predictedNativeClamp = getPredictedNativeClamp(state, pending.amount, totalSize);
2128
+ if (Math.abs(predictedNativeClamp) <= MVCP_POSITION_EPSILON) {
2129
+ return;
2130
+ }
2131
+ const manualDesired = pending.amount - predictedNativeClamp;
2132
+ if (Math.abs(manualDesired) <= MVCP_POSITION_EPSILON) {
2133
+ return;
2134
+ }
2135
+ pending.manualApplied = manualDesired;
2136
+ requestAdjust(ctx, manualDesired);
2137
+ }
2138
+ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
2139
+ const state = ctx.state;
2140
+ const pending = state.pendingNativeMVCPAdjust;
2141
+ if (!pending) {
2142
+ return false;
2143
+ }
2144
+ const remainingAfterManual = pending.amount - pending.manualApplied;
2145
+ const nativeDelta = newScroll - (pending.startScroll + pending.manualApplied);
2146
+ const isWrongDirection = remainingAfterManual < 0 && nativeDelta > MVCP_POSITION_EPSILON || remainingAfterManual > 0 && nativeDelta < -MVCP_POSITION_EPSILON;
2147
+ if (Math.abs(remainingAfterManual) <= MVCP_POSITION_EPSILON) {
2148
+ state.pendingNativeMVCPAdjust = void 0;
2149
+ return true;
2150
+ }
2151
+ if (isWrongDirection) {
2152
+ state.pendingNativeMVCPAdjust = void 0;
2153
+ return false;
2154
+ }
2155
+ const expectedNativeClampScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
2156
+ const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll);
2157
+ const didApproachClamp = distanceToClamp < pending.closestDistanceToClamp - MVCP_POSITION_EPSILON;
2158
+ const didMoveAwayAfterApproach = pending.hasApproachedClamp && distanceToClamp > pending.closestDistanceToClamp + MVCP_POSITION_EPSILON;
2159
+ if (didApproachClamp) {
2160
+ pending.closestDistanceToClamp = distanceToClamp;
2161
+ pending.hasApproachedClamp = true;
2162
+ } else if (didMoveAwayAfterApproach) {
2163
+ state.pendingNativeMVCPAdjust = void 0;
2164
+ return false;
2165
+ }
2166
+ const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON;
2167
+ if (!isAtExpectedNativeClamp) {
2168
+ return false;
2169
+ }
2170
+ state.pendingNativeMVCPAdjust = void 0;
2171
+ const remaining = remainingAfterManual - nativeDelta;
2172
+ if (Math.abs(remaining) > MVCP_POSITION_EPSILON) {
2173
+ requestAdjust(ctx, remaining);
2174
+ }
2175
+ return true;
2176
+ }
2063
2177
  function prepareMVCP(ctx, dataChanged) {
2064
2178
  const state = ctx.state;
2065
2179
  const { idsInView, positions, props } = state;
@@ -2078,6 +2192,8 @@ function prepareMVCP(ctx, dataChanged) {
2078
2192
  const isEndAnchoredScrollTarget = scrollTarget !== void 0 && state.props.data.length > 0 && scrollTarget >= state.props.data.length - 1 && (scrollingToViewPosition != null ? scrollingToViewPosition : 0) > 0;
2079
2193
  const shouldMVCP = dataChanged ? mvcpData : mvcpScroll;
2080
2194
  const indexByKey = state.indexByKey;
2195
+ const prevScroll = state.scroll;
2196
+ getContentSize(ctx);
2081
2197
  if (shouldMVCP) {
2082
2198
  if (anchorLock && scrollTarget === void 0) {
2083
2199
  targetId = anchorLock.id;
@@ -2184,6 +2300,19 @@ function prepareMVCP(ctx, dataChanged) {
2184
2300
  now,
2185
2301
  positionDiff
2186
2302
  });
2303
+ if (shouldQueueNativeMVCPAdjust()) {
2304
+ state.pendingNativeMVCPAdjust = {
2305
+ amount: positionDiff,
2306
+ closestDistanceToClamp: Math.abs(
2307
+ prevScroll - Math.max(0, getContentSize(ctx) - state.scrollLength)
2308
+ ),
2309
+ hasApproachedClamp: false,
2310
+ manualApplied: 0,
2311
+ startScroll: prevScroll
2312
+ };
2313
+ maybeApplyPredictedNativeMVCPAdjust(ctx);
2314
+ return;
2315
+ }
2187
2316
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
2188
2317
  requestAdjust(ctx, positionDiff);
2189
2318
  }
@@ -2191,6 +2320,94 @@ function prepareMVCP(ctx, dataChanged) {
2191
2320
  }
2192
2321
  }
2193
2322
 
2323
+ // src/core/updateScroll.ts
2324
+ function updateScroll(ctx, newScroll, forceUpdate) {
2325
+ var _a3;
2326
+ const state = ctx.state;
2327
+ const { ignoreScrollFromMVCP, lastScrollAdjustForHistory, scrollAdjustHandler, scrollHistory, scrollingTo } = state;
2328
+ const prevScroll = state.scroll;
2329
+ state.hasScrolled = true;
2330
+ state.lastBatchingAction = Date.now();
2331
+ const currentTime = Date.now();
2332
+ const adjust = scrollAdjustHandler.getAdjust();
2333
+ const adjustChanged = lastScrollAdjustForHistory !== void 0 && Math.abs(adjust - lastScrollAdjustForHistory) > 0.1;
2334
+ if (adjustChanged) {
2335
+ scrollHistory.length = 0;
2336
+ }
2337
+ state.lastScrollAdjustForHistory = adjust;
2338
+ if (scrollingTo === void 0 && !(scrollHistory.length === 0 && newScroll === state.scroll)) {
2339
+ if (!adjustChanged) {
2340
+ scrollHistory.push({ scroll: newScroll, time: currentTime });
2341
+ }
2342
+ }
2343
+ if (scrollHistory.length > 5) {
2344
+ scrollHistory.shift();
2345
+ }
2346
+ if (ignoreScrollFromMVCP && !scrollingTo) {
2347
+ const { lt, gt } = ignoreScrollFromMVCP;
2348
+ if (lt && newScroll < lt || gt && newScroll > gt) {
2349
+ state.ignoreScrollFromMVCPIgnored = true;
2350
+ return;
2351
+ }
2352
+ }
2353
+ state.scrollPrev = prevScroll;
2354
+ state.scrollPrevTime = state.scrollTime;
2355
+ state.scroll = newScroll;
2356
+ state.scrollTime = currentTime;
2357
+ const scrollDelta = Math.abs(newScroll - prevScroll);
2358
+ const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2359
+ const scrollLength = state.scrollLength;
2360
+ const lastCalculated = state.scrollLastCalculate;
2361
+ const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2362
+ const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
2363
+ if (shouldUpdate) {
2364
+ state.scrollLastCalculate = state.scroll;
2365
+ state.ignoreScrollFromMVCPIgnored = false;
2366
+ state.lastScrollDelta = scrollDelta;
2367
+ const runCalculateItems = () => {
2368
+ var _a4;
2369
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
2370
+ checkThresholds(ctx);
2371
+ };
2372
+ if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
2373
+ ReactDOM.flushSync(runCalculateItems);
2374
+ } else {
2375
+ runCalculateItems();
2376
+ }
2377
+ const shouldMaintainScrollAtEndAfterPendingSettle = !!state.pendingMaintainScrollAtEnd || !!((_a3 = state.props.maintainScrollAtEnd) == null ? void 0 : _a3.onDataChange);
2378
+ if (didResolvePendingNativeMVCPAdjust && shouldMaintainScrollAtEndAfterPendingSettle) {
2379
+ state.pendingMaintainScrollAtEnd = false;
2380
+ doMaintainScrollAtEnd(ctx);
2381
+ }
2382
+ state.dataChangeNeedsScrollUpdate = false;
2383
+ state.lastScrollDelta = 0;
2384
+ }
2385
+ }
2386
+
2387
+ // src/utils/requestAdjust.ts
2388
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2389
+ const state = ctx.state;
2390
+ if (Math.abs(positionDiff) > 0.1) {
2391
+ const doit = () => {
2392
+ {
2393
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2394
+ if (state.adjustingFromInitialMount) {
2395
+ state.adjustingFromInitialMount--;
2396
+ }
2397
+ }
2398
+ };
2399
+ state.scroll += positionDiff;
2400
+ state.scrollForNextCalculateItemsInView = void 0;
2401
+ const readyToRender = peek$(ctx, "readyToRender");
2402
+ if (readyToRender) {
2403
+ doit();
2404
+ } else {
2405
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2406
+ requestAnimationFrame(doit);
2407
+ }
2408
+ }
2409
+ }
2410
+
2194
2411
  // src/core/prepareColumnStartState.ts
2195
2412
  function prepareColumnStartState(ctx, startIndex, useAverageSize) {
2196
2413
  var _a3;
@@ -2844,7 +3061,14 @@ function comparatorByDistance(a, b) {
2844
3061
  }
2845
3062
 
2846
3063
  // src/core/scrollToIndex.ts
2847
- function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPosition }) {
3064
+ function scrollToIndex(ctx, {
3065
+ index,
3066
+ viewOffset = 0,
3067
+ animated = true,
3068
+ forceScroll,
3069
+ isInitialScroll,
3070
+ viewPosition
3071
+ }) {
2848
3072
  const state = ctx.state;
2849
3073
  const { data } = state.props;
2850
3074
  if (index >= data.length) {
@@ -2862,7 +3086,9 @@ function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPositi
2862
3086
  const itemSize = getItemSize(ctx, targetId, index, state.props.data[index]);
2863
3087
  scrollTo(ctx, {
2864
3088
  animated,
3089
+ forceScroll,
2865
3090
  index,
3091
+ isInitialScroll,
2866
3092
  itemSize,
2867
3093
  offset: firstIndexOffset,
2868
3094
  viewOffset,
@@ -2870,15 +3096,58 @@ function scrollToIndex(ctx, { index, viewOffset = 0, animated = true, viewPositi
2870
3096
  });
2871
3097
  }
2872
3098
 
3099
+ // src/utils/performInitialScroll.ts
3100
+ function performInitialScroll(ctx, params) {
3101
+ var _a3;
3102
+ const { forceScroll, initialScrollUsesOffset, resolvedOffset, target } = params;
3103
+ if (initialScrollUsesOffset || resolvedOffset !== void 0) {
3104
+ scrollTo(ctx, {
3105
+ animated: false,
3106
+ forceScroll,
3107
+ index: initialScrollUsesOffset ? void 0 : target.index,
3108
+ isInitialScroll: true,
3109
+ offset: (_a3 = resolvedOffset != null ? resolvedOffset : target.contentOffset) != null ? _a3 : 0,
3110
+ precomputedWithViewOffset: resolvedOffset !== void 0
3111
+ });
3112
+ return;
3113
+ }
3114
+ if (target.index === void 0) {
3115
+ return;
3116
+ }
3117
+ scrollToIndex(ctx, {
3118
+ ...target,
3119
+ animated: false,
3120
+ forceScroll,
3121
+ isInitialScroll: true
3122
+ });
3123
+ }
3124
+
2873
3125
  // src/utils/setDidLayout.ts
2874
3126
  function setDidLayout(ctx) {
2875
3127
  const state = ctx.state;
2876
3128
  const { initialScroll } = state;
2877
3129
  state.queuedInitialLayout = true;
2878
3130
  checkAtBottom(ctx);
2879
- if ((initialScroll == null ? void 0 : initialScroll.index) !== void 0) {
2880
- const target = initialScroll;
2881
- const runScroll = () => scrollToIndex(ctx, { ...target, animated: false });
3131
+ if (initialScroll) {
3132
+ const runScroll = () => {
3133
+ var _a3, _b;
3134
+ const target = state.initialScroll;
3135
+ if (!target) {
3136
+ return;
3137
+ }
3138
+ const activeInitialTargetOffset = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? (_b = state.scrollingTo.targetOffset) != null ? _b : state.scrollingTo.offset : void 0;
3139
+ const desiredInitialTargetOffset = state.initialScrollUsesOffset ? target.contentOffset : activeInitialTargetOffset;
3140
+ const isAlreadyAtDesiredInitialTarget = desiredInitialTargetOffset !== void 0 && Math.abs(state.scroll - desiredInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - desiredInitialTargetOffset) <= 1;
3141
+ if (!isAlreadyAtDesiredInitialTarget) {
3142
+ performInitialScroll(ctx, {
3143
+ forceScroll: true,
3144
+ initialScrollUsesOffset: state.initialScrollUsesOffset,
3145
+ // Offset-based initial scrolls do not need item lookup, so they can run even before data exists.
3146
+ // Re-run on the next frame to pick up measured viewport size without waiting for index resolution.
3147
+ target
3148
+ });
3149
+ }
3150
+ };
2882
3151
  runScroll();
2883
3152
  requestAnimationFrame(runScroll);
2884
3153
  }
@@ -2956,7 +3225,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
2956
3225
  function calculateItemsInView(ctx, params = {}) {
2957
3226
  const state = ctx.state;
2958
3227
  batchedUpdates(() => {
2959
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
3228
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
2960
3229
  const {
2961
3230
  columns,
2962
3231
  columnSpans,
@@ -2992,7 +3261,7 @@ function calculateItemsInView(ctx, params = {}) {
2992
3261
  if (!data || scrollLength === 0 || !prevNumContainers) {
2993
3262
  return;
2994
3263
  }
2995
- const totalSize = getContentSize(ctx);
3264
+ let totalSize = getContentSize(ctx);
2996
3265
  const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
2997
3266
  const numColumns = peek$(ctx, "numColumns");
2998
3267
  const speed = getScrollVelocity(state);
@@ -3000,14 +3269,14 @@ function calculateItemsInView(ctx, params = {}) {
3000
3269
  const { queuedInitialLayout } = state;
3001
3270
  let { scroll: scrollState } = state;
3002
3271
  if (!queuedInitialLayout && initialScroll) {
3003
- const updatedOffset = calculateOffsetWithOffsetPosition(
3272
+ const updatedOffset = state.initialScrollUsesOffset ? (_a3 = initialScroll.contentOffset) != null ? _a3 : 0 : calculateOffsetWithOffsetPosition(
3004
3273
  ctx,
3005
3274
  calculateOffsetForIndex(ctx, initialScroll.index),
3006
3275
  initialScroll
3007
3276
  );
3008
3277
  scrollState = updatedOffset;
3009
3278
  }
3010
- const scrollAdjustPending = (_a3 = peek$(ctx, "scrollAdjustPending")) != null ? _a3 : 0;
3279
+ const scrollAdjustPending = (_b = peek$(ctx, "scrollAdjustPending")) != null ? _b : 0;
3011
3280
  const scrollAdjustPad = scrollAdjustPending - topPad;
3012
3281
  let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
3013
3282
  if (scroll + scrollLength > totalSize) {
@@ -3049,13 +3318,14 @@ function calculateItemsInView(ctx, params = {}) {
3049
3318
  columns.length = 0;
3050
3319
  columnSpans.length = 0;
3051
3320
  }
3052
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_b = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _b : 0;
3321
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
3053
3322
  updateItemPositions(ctx, dataChanged, {
3054
3323
  doMVCP,
3055
3324
  forceFullUpdate: !!forceFullItemPositions,
3056
3325
  scrollBottomBuffered,
3057
3326
  startIndex
3058
3327
  });
3328
+ totalSize = getContentSize(ctx);
3059
3329
  if (minIndexSizeChanged !== void 0) {
3060
3330
  state.minIndexSizeChanged = void 0;
3061
3331
  }
@@ -3067,9 +3337,9 @@ function calculateItemsInView(ctx, params = {}) {
3067
3337
  let endBuffered = null;
3068
3338
  let loopStart = !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
3069
3339
  for (let i = loopStart; i >= 0; i--) {
3070
- const id = (_c = idCache[i]) != null ? _c : getId(state, i);
3340
+ const id = (_d = idCache[i]) != null ? _d : getId(state, i);
3071
3341
  const top = positions[i];
3072
- const size = (_d = sizes.get(id)) != null ? _d : getItemSize(ctx, id, i, data[i]);
3342
+ const size = (_e = sizes.get(id)) != null ? _e : getItemSize(ctx, id, i, data[i]);
3073
3343
  const bottom = top + size;
3074
3344
  if (bottom > scroll - scrollBufferTop) {
3075
3345
  loopStart = i;
@@ -3100,14 +3370,14 @@ function calculateItemsInView(ctx, params = {}) {
3100
3370
  let firstFullyOnScreenIndex;
3101
3371
  const dataLength = data.length;
3102
3372
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
3103
- const id = (_e = idCache[i]) != null ? _e : getId(state, i);
3104
- const size = (_f = sizes.get(id)) != null ? _f : getItemSize(ctx, id, i, data[i]);
3373
+ const id = (_f = idCache[i]) != null ? _f : getId(state, i);
3374
+ const size = (_g = sizes.get(id)) != null ? _g : getItemSize(ctx, id, i, data[i]);
3105
3375
  const top = positions[i];
3106
3376
  if (!foundEnd) {
3107
3377
  if (startNoBuffer === null && top + size > scroll) {
3108
3378
  startNoBuffer = i;
3109
3379
  }
3110
- if (firstFullyOnScreenIndex === void 0 && top >= scroll - 10) {
3380
+ if (firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
3111
3381
  firstFullyOnScreenIndex = i;
3112
3382
  }
3113
3383
  if (startBuffered === null && top + size > scrollTopBuffered) {
@@ -3137,9 +3407,12 @@ function calculateItemsInView(ctx, params = {}) {
3137
3407
  }
3138
3408
  }
3139
3409
  const idsInView = [];
3140
- for (let i = firstFullyOnScreenIndex; i <= endNoBuffer; i++) {
3141
- const id = (_g = idCache[i]) != null ? _g : getId(state, i);
3142
- idsInView.push(id);
3410
+ const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
3411
+ if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
3412
+ for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
3413
+ const id = (_h = idCache[i]) != null ? _h : getId(state, i);
3414
+ idsInView.push(id);
3415
+ }
3143
3416
  }
3144
3417
  Object.assign(state, {
3145
3418
  endBuffered,
@@ -3170,7 +3443,7 @@ function calculateItemsInView(ctx, params = {}) {
3170
3443
  const needNewContainers = [];
3171
3444
  const needNewContainersSet = /* @__PURE__ */ new Set();
3172
3445
  for (let i = startBuffered; i <= endBuffered; i++) {
3173
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
3446
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
3174
3447
  if (!containerItemKeys.has(id)) {
3175
3448
  needNewContainersSet.add(i);
3176
3449
  needNewContainers.push(i);
@@ -3179,7 +3452,7 @@ function calculateItemsInView(ctx, params = {}) {
3179
3452
  if (alwaysRenderArr.length > 0) {
3180
3453
  for (const index of alwaysRenderArr) {
3181
3454
  if (index < 0 || index >= dataLength) continue;
3182
- const id = (_i = idCache[index]) != null ? _i : getId(state, index);
3455
+ const id = (_j = idCache[index]) != null ? _j : getId(state, index);
3183
3456
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
3184
3457
  needNewContainersSet.add(index);
3185
3458
  needNewContainers.push(index);
@@ -3217,7 +3490,7 @@ function calculateItemsInView(ctx, params = {}) {
3217
3490
  for (let idx = 0; idx < needNewContainers.length; idx++) {
3218
3491
  const i = needNewContainers[idx];
3219
3492
  const containerIndex = availableContainers[idx];
3220
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
3493
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
3221
3494
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
3222
3495
  if (oldKey && oldKey !== id) {
3223
3496
  containerItemKeys.delete(oldKey);
@@ -3258,7 +3531,7 @@ function calculateItemsInView(ctx, params = {}) {
3258
3531
  if (alwaysRenderArr.length > 0) {
3259
3532
  for (const index of alwaysRenderArr) {
3260
3533
  if (index < 0 || index >= dataLength) continue;
3261
- const id = (_k = idCache[index]) != null ? _k : getId(state, index);
3534
+ const id = (_l = idCache[index]) != null ? _l : getId(state, index);
3262
3535
  const containerIndex = containerItemKeys.get(id);
3263
3536
  if (containerIndex !== void 0) {
3264
3537
  state.stickyContainerPool.add(containerIndex);
@@ -3367,21 +3640,21 @@ function checkActualChange(state, dataProp, previousData) {
3367
3640
  }
3368
3641
 
3369
3642
  // src/core/checkFinishedScroll.ts
3643
+ var INITIAL_SCROLL_MIN_TARGET_OFFSET = 1;
3644
+ var INITIAL_SCROLL_MAX_FALLBACK_CHECKS = 20;
3645
+ var INITIAL_SCROLL_ZERO_TARGET_EPSILON = 1;
3370
3646
  function checkFinishedScroll(ctx) {
3371
3647
  ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3372
3648
  }
3373
3649
  function checkFinishedScrollFrame(ctx) {
3650
+ var _a3;
3374
3651
  const scrollingTo = ctx.state.scrollingTo;
3375
3652
  if (scrollingTo) {
3376
3653
  const { state } = ctx;
3377
3654
  state.animFrameCheckFinishedScroll = void 0;
3378
3655
  const scroll = state.scrollPending;
3379
3656
  const adjust = state.scrollAdjustHandler.getAdjust();
3380
- const clampedTargetOffset = clampScrollOffset(
3381
- ctx,
3382
- scrollingTo.offset - (scrollingTo.viewOffset || 0),
3383
- scrollingTo
3384
- );
3657
+ const clampedTargetOffset = (_a3 = scrollingTo.targetOffset) != null ? _a3 : clampScrollOffset(ctx, scrollingTo.offset - (scrollingTo.viewOffset || 0), scrollingTo);
3385
3658
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
3386
3659
  const diff1 = Math.abs(scroll - clampedTargetOffset);
3387
3660
  const diff2 = Math.abs(diff1 - adjust);
@@ -3395,17 +3668,33 @@ function checkFinishedScrollFrame(ctx) {
3395
3668
  function checkFinishedScrollFallback(ctx) {
3396
3669
  const state = ctx.state;
3397
3670
  const scrollingTo = state.scrollingTo;
3398
- const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) || !state.didContainersLayout;
3671
+ const shouldFinishInitialZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
3672
+ const slowTimeout = (scrollingTo == null ? void 0 : scrollingTo.isInitialScroll) && !shouldFinishInitialZeroTarget || !state.didContainersLayout;
3399
3673
  state.timeoutCheckFinishedScrollFallback = setTimeout(
3400
3674
  () => {
3401
3675
  let numChecks = 0;
3402
3676
  const checkHasScrolled = () => {
3677
+ var _a3, _b;
3403
3678
  state.timeoutCheckFinishedScrollFallback = void 0;
3404
3679
  const isStillScrollingTo = state.scrollingTo;
3405
3680
  if (isStillScrollingTo) {
3406
3681
  numChecks++;
3407
- if (state.hasScrolled || numChecks > 5) {
3682
+ const isNativeInitialPending = isNativeInitialNonZeroTarget(state) && !state.hasScrolled;
3683
+ const maxChecks = isNativeInitialPending ? INITIAL_SCROLL_MAX_FALLBACK_CHECKS : 5;
3684
+ const shouldFinishZeroTarget = shouldFinishInitialZeroTargetScroll(ctx);
3685
+ if (shouldFinishZeroTarget || state.hasScrolled || numChecks > maxChecks) {
3408
3686
  finishScrollTo(ctx);
3687
+ } else if (isNativeInitialPending && numChecks <= maxChecks) {
3688
+ const targetOffset = (_b = (_a3 = state.initialNativeScrollWatchdog) == null ? void 0 : _a3.targetOffset) != null ? _b : state.scrollPending;
3689
+ const scroller = state.refScroller.current;
3690
+ if (scroller) {
3691
+ scroller.scrollTo({
3692
+ animated: false,
3693
+ x: state.props.horizontal ? targetOffset : 0,
3694
+ y: state.props.horizontal ? 0 : targetOffset
3695
+ });
3696
+ }
3697
+ state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, 100);
3409
3698
  } else {
3410
3699
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, 100);
3411
3700
  }
@@ -3416,39 +3705,13 @@ function checkFinishedScrollFallback(ctx) {
3416
3705
  slowTimeout ? 500 : 100
3417
3706
  );
3418
3707
  }
3419
-
3420
- // src/core/doMaintainScrollAtEnd.ts
3421
- function doMaintainScrollAtEnd(ctx, animated) {
3422
- const state = ctx.state;
3423
- const {
3424
- didContainersLayout,
3425
- isAtEnd,
3426
- refScroller,
3427
- props: { maintainScrollAtEnd }
3428
- } = state;
3429
- if (isAtEnd && maintainScrollAtEnd && didContainersLayout) {
3430
- const contentSize = getContentSize(ctx);
3431
- if (contentSize < state.scrollLength) {
3432
- state.scroll = 0;
3433
- }
3434
- requestAnimationFrame(() => {
3435
- var _a3;
3436
- if (state.isAtEnd) {
3437
- state.maintainingScrollAtEnd = true;
3438
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
3439
- animated
3440
- });
3441
- setTimeout(
3442
- () => {
3443
- state.maintainingScrollAtEnd = false;
3444
- },
3445
- 0
3446
- );
3447
- }
3448
- });
3449
- return true;
3450
- }
3451
- return false;
3708
+ function isNativeInitialNonZeroTarget(state) {
3709
+ return !state.didFinishInitialScroll && !!state.initialNativeScrollWatchdog && state.initialNativeScrollWatchdog.targetOffset > INITIAL_SCROLL_MIN_TARGET_OFFSET;
3710
+ }
3711
+ function shouldFinishInitialZeroTargetScroll(ctx) {
3712
+ var _a3;
3713
+ const { state } = ctx;
3714
+ 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;
3452
3715
  }
3453
3716
 
3454
3717
  // src/utils/updateAveragesOnDataChange.ts
@@ -3512,8 +3775,8 @@ function checkResetContainers(ctx, dataProp) {
3512
3775
  }
3513
3776
  const { maintainScrollAtEnd } = state.props;
3514
3777
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
3515
- const shouldMaintainScrollAtEnd = maintainScrollAtEnd === true || maintainScrollAtEnd.onDataChange;
3516
- const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx, false);
3778
+ const shouldMaintainScrollAtEnd = maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onDataChange;
3779
+ const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx);
3517
3780
  if (!didMaintainScrollAtEnd && previousData && dataProp.length > previousData.length) {
3518
3781
  state.isEndReached = false;
3519
3782
  }
@@ -3621,8 +3884,8 @@ function handleLayout(ctx, layoutParam, setCanRender) {
3621
3884
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
3622
3885
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
3623
3886
  }
3624
- if (maintainScrollAtEnd === true || maintainScrollAtEnd.onLayout) {
3625
- doMaintainScrollAtEnd(ctx, false);
3887
+ if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
3888
+ doMaintainScrollAtEnd(ctx);
3626
3889
  }
3627
3890
  checkThresholds(ctx);
3628
3891
  if (state) {
@@ -3639,6 +3902,12 @@ function handleLayout(ctx, layoutParam, setCanRender) {
3639
3902
  }
3640
3903
 
3641
3904
  // src/core/onScroll.ts
3905
+ var INITIAL_SCROLL_PROGRESS_EPSILON = 1;
3906
+ function didObserveInitialScrollProgress(newScroll, watchdog) {
3907
+ const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
3908
+ const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
3909
+ return nextDistance <= INITIAL_SCROLL_PROGRESS_EPSILON || nextDistance + INITIAL_SCROLL_PROGRESS_EPSILON < previousDistance;
3910
+ }
3642
3911
  function onScroll(ctx, event) {
3643
3912
  var _a3, _b, _c, _d;
3644
3913
  const state = ctx.state;
@@ -3676,7 +3945,16 @@ function onScroll(ctx, event) {
3676
3945
  }
3677
3946
  }
3678
3947
  state.scrollPending = newScroll;
3948
+ const initialNativeScrollWatchdog = state.initialNativeScrollWatchdog;
3949
+ const didInitialScrollProgress = !!initialNativeScrollWatchdog && didObserveInitialScrollProgress(newScroll, initialNativeScrollWatchdog);
3950
+ if (didInitialScrollProgress) {
3951
+ state.initialNativeScrollWatchdog = void 0;
3952
+ }
3679
3953
  updateScroll(ctx, newScroll, insetChanged);
3954
+ if (initialNativeScrollWatchdog && !didInitialScrollProgress) {
3955
+ state.hasScrolled = false;
3956
+ state.initialNativeScrollWatchdog = initialNativeScrollWatchdog;
3957
+ }
3680
3958
  if (state.scrollingTo) {
3681
3959
  checkFinishedScroll(ctx);
3682
3960
  }
@@ -3841,8 +4119,8 @@ function updateItemSize(ctx, itemKey, sizeObj) {
3841
4119
  runOrScheduleMVCPRecalculate(ctx);
3842
4120
  }
3843
4121
  if (shouldMaintainScrollAtEnd) {
3844
- if (maintainScrollAtEnd === true || maintainScrollAtEnd.onItemLayout) {
3845
- doMaintainScrollAtEnd(ctx, false);
4122
+ if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onItemLayout) {
4123
+ doMaintainScrollAtEnd(ctx);
3846
4124
  }
3847
4125
  }
3848
4126
  }
@@ -4192,6 +4470,34 @@ function getRenderedItem(ctx, key) {
4192
4470
  return { index, item: data[index], renderedItem };
4193
4471
  }
4194
4472
 
4473
+ // src/utils/normalizeMaintainScrollAtEnd.ts
4474
+ function normalizeMaintainScrollAtEndOn(on, hasExplicitOn) {
4475
+ var _a3, _b, _c;
4476
+ return {
4477
+ animated: false,
4478
+ onDataChange: hasExplicitOn ? (_a3 = on == null ? void 0 : on.dataChange) != null ? _a3 : false : true,
4479
+ onItemLayout: hasExplicitOn ? (_b = on == null ? void 0 : on.itemLayout) != null ? _b : false : true,
4480
+ onLayout: hasExplicitOn ? (_c = on == null ? void 0 : on.layout) != null ? _c : false : true
4481
+ };
4482
+ }
4483
+ function normalizeMaintainScrollAtEnd(value) {
4484
+ var _a3;
4485
+ if (!value) {
4486
+ return void 0;
4487
+ }
4488
+ if (value === true) {
4489
+ return {
4490
+ ...normalizeMaintainScrollAtEndOn(void 0, false),
4491
+ animated: false
4492
+ };
4493
+ }
4494
+ const normalizedTriggers = normalizeMaintainScrollAtEndOn(value.on, "on" in value);
4495
+ return {
4496
+ ...normalizedTriggers,
4497
+ animated: (_a3 = value.animated) != null ? _a3 : false
4498
+ };
4499
+ }
4500
+
4195
4501
  // src/utils/normalizeMaintainVisibleContentPosition.ts
4196
4502
  function normalizeMaintainVisibleContentPosition(value) {
4197
4503
  var _a3, _b;
@@ -4293,7 +4599,7 @@ var LegendList = typedMemo(
4293
4599
  })
4294
4600
  );
4295
4601
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
4296
- var _a3, _b, _c, _d, _e;
4602
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
4297
4603
  const {
4298
4604
  alignItemsAtEnd = false,
4299
4605
  alwaysRender,
@@ -4379,16 +4685,24 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4379
4685
  const style = { ...StyleSheet.flatten(styleProp) };
4380
4686
  const stylePaddingTopState = extractPadding(style, contentContainerStyle, "Top");
4381
4687
  const stylePaddingBottomState = extractPadding(style, contentContainerStyle, "Bottom");
4688
+ const maintainScrollAtEndConfig = normalizeMaintainScrollAtEnd(maintainScrollAtEnd);
4382
4689
  const maintainVisibleContentPositionConfig = normalizeMaintainVisibleContentPosition(
4383
4690
  maintainVisibleContentPositionProp
4384
4691
  );
4385
- const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? {
4386
- index: initialScrollIndexProp.index || 0,
4387
- viewOffset: initialScrollIndexProp.viewOffset || (initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0),
4388
- viewPosition: initialScrollIndexProp.viewPosition || 0
4692
+ const hasInitialScrollIndex = initialScrollIndexProp !== void 0 && initialScrollIndexProp !== null;
4693
+ const hasInitialScrollOffset = initialScrollOffsetProp !== void 0 && initialScrollOffsetProp !== null;
4694
+ const initialScrollUsesOffsetOnly = !initialScrollAtEnd && !hasInitialScrollIndex && hasInitialScrollOffset;
4695
+ const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
4696
+ index: (_a3 = initialScrollIndexProp.index) != null ? _a3 : 0,
4697
+ viewOffset: (_b = initialScrollIndexProp.viewOffset) != null ? _b : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0,
4698
+ viewPosition: (_c = initialScrollIndexProp.viewPosition) != null ? _c : 0
4389
4699
  } : {
4390
- index: initialScrollIndexProp || 0,
4391
- viewOffset: initialScrollOffsetProp || 0
4700
+ index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
4701
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0
4702
+ } : initialScrollUsesOffsetOnly ? {
4703
+ contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
4704
+ index: 0,
4705
+ viewOffset: 0
4392
4706
  } : void 0;
4393
4707
  const [canRender, setCanRender] = React3__namespace.useState(false);
4394
4708
  const ctx = useStateContext();
@@ -4403,8 +4717,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4403
4717
  }, [
4404
4718
  alwaysRender == null ? void 0 : alwaysRender.top,
4405
4719
  alwaysRender == null ? void 0 : alwaysRender.bottom,
4406
- (_a3 = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _a3.join(","),
4407
- (_b = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _b.join(","),
4720
+ (_d = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _d.join(","),
4721
+ (_e = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _e.join(","),
4408
4722
  dataProp,
4409
4723
  dataVersion,
4410
4724
  keyExtractor
@@ -4448,14 +4762,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4448
4762
  idCache: [],
4449
4763
  idsInView: [],
4450
4764
  indexByKey: /* @__PURE__ */ new Map(),
4451
- initialAnchor: (initialScrollProp == null ? void 0 : initialScrollProp.index) !== void 0 && (initialScrollProp == null ? void 0 : initialScrollProp.viewPosition) !== void 0 ? {
4765
+ initialAnchor: !initialScrollUsesOffsetOnly && (initialScrollProp == null ? void 0 : initialScrollProp.index) !== void 0 && (initialScrollProp == null ? void 0 : initialScrollProp.viewPosition) !== void 0 ? {
4452
4766
  attempts: 0,
4453
4767
  index: initialScrollProp.index,
4454
4768
  settledTicks: 0,
4455
- viewOffset: (_c = initialScrollProp.viewOffset) != null ? _c : 0,
4769
+ viewOffset: (_f = initialScrollProp.viewOffset) != null ? _f : 0,
4456
4770
  viewPosition: initialScrollProp.viewPosition
4457
4771
  } : void 0,
4772
+ initialNativeScrollWatchdog: void 0,
4458
4773
  initialScroll: initialScrollProp,
4774
+ initialScrollLastDidFinish: false,
4775
+ initialScrollLastTarget: initialScrollProp,
4776
+ initialScrollLastTargetUsesOffset: initialScrollUsesOffsetOnly,
4777
+ initialScrollPreviousDataLength: dataProp.length,
4778
+ initialScrollRetryLastLength: void 0,
4779
+ initialScrollRetryWindowUntil: 0,
4780
+ initialScrollUsesOffset: initialScrollUsesOffsetOnly,
4459
4781
  isAtEnd: false,
4460
4782
  isAtStart: false,
4461
4783
  isEndReached: null,
@@ -4468,6 +4790,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4468
4790
  minIndexSizeChanged: 0,
4469
4791
  nativeContentInset: void 0,
4470
4792
  nativeMarginTop: 0,
4793
+ pendingNativeMVCPAdjust: void 0,
4471
4794
  positions: [],
4472
4795
  props: {},
4473
4796
  queuedCalculateItemsInView: 0,
@@ -4505,7 +4828,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4505
4828
  const state = refState.current;
4506
4829
  const isFirstLocal = state.isFirst;
4507
4830
  state.didColumnsChange = numColumnsProp !== state.props.numColumns;
4508
- const didDataChangeLocal = state.props.dataVersion !== dataVersion || state.props.data !== dataProp && checkActualChange(state, dataProp, state.props.data);
4831
+ const didDataReferenceChangeLocal = state.props.data !== dataProp;
4832
+ const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
4833
+ const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkActualChange(state, dataProp, state.props.data);
4509
4834
  if (didDataChangeLocal) {
4510
4835
  state.dataChangeEpoch += 1;
4511
4836
  state.dataChangeNeedsScrollUpdate = true;
@@ -4531,7 +4856,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4531
4856
  initialContainerPoolRatio,
4532
4857
  itemsAreEqual,
4533
4858
  keyExtractor: useWrapIfItem(keyExtractor),
4534
- maintainScrollAtEnd,
4859
+ maintainScrollAtEnd: maintainScrollAtEndConfig,
4535
4860
  maintainScrollAtEndThreshold,
4536
4861
  maintainVisibleContentPosition: maintainVisibleContentPositionConfig,
4537
4862
  numColumns: numColumnsProp,
@@ -4582,29 +4907,93 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4582
4907
  );
4583
4908
  }
4584
4909
  const resolveInitialScrollOffset = React3.useCallback((initialScroll) => {
4910
+ var _a4;
4911
+ if (state.initialScrollUsesOffset) {
4912
+ return clampScrollOffset(ctx, (_a4 = initialScroll.contentOffset) != null ? _a4 : 0);
4913
+ }
4585
4914
  const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4586
4915
  const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4587
4916
  return clampScrollOffset(ctx, resolvedOffset, initialScroll);
4588
4917
  }, []);
4918
+ const finishInitialScrollWithoutScroll = React3.useCallback(() => {
4919
+ refState.current.initialAnchor = void 0;
4920
+ refState.current.initialScroll = void 0;
4921
+ state.initialAnchor = void 0;
4922
+ state.initialScroll = void 0;
4923
+ state.initialScrollUsesOffset = false;
4924
+ state.initialScrollLastTarget = void 0;
4925
+ state.initialScrollLastTargetUsesOffset = false;
4926
+ setInitialRenderState(ctx, { didInitialScroll: true });
4927
+ }, []);
4928
+ const setActiveInitialScrollTarget = React3.useCallback(
4929
+ (target, options) => {
4930
+ const usesOffset = !!(options == null ? void 0 : options.usesOffset);
4931
+ state.initialScrollUsesOffset = usesOffset;
4932
+ state.initialScrollLastTarget = target;
4933
+ state.initialScrollLastTargetUsesOffset = usesOffset;
4934
+ refState.current.initialScroll = target;
4935
+ state.initialScroll = target;
4936
+ if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
4937
+ state.didFinishInitialScroll = false;
4938
+ }
4939
+ if (!(options == null ? void 0 : options.syncAnchor)) {
4940
+ return;
4941
+ }
4942
+ },
4943
+ []
4944
+ );
4945
+ const shouldFinishInitialScrollAtOrigin = React3.useCallback(
4946
+ (initialScroll, offset) => {
4947
+ var _a4, _b2, _c2;
4948
+ if (offset !== 0 || initialScrollAtEnd) {
4949
+ return false;
4950
+ }
4951
+ if (state.initialScrollUsesOffset) {
4952
+ return Math.abs((_a4 = initialScroll.contentOffset) != null ? _a4 : 0) <= 1;
4953
+ }
4954
+ return initialScroll.index === 0 && ((_b2 = initialScroll.viewPosition) != null ? _b2 : 0) === 0 && Math.abs((_c2 = initialScroll.viewOffset) != null ? _c2 : 0) <= 1;
4955
+ },
4956
+ [initialScrollAtEnd]
4957
+ );
4958
+ const shouldFinishEmptyInitialScrollAtEnd = React3.useCallback(
4959
+ (initialScroll, offset) => {
4960
+ return dataProp.length === 0 && initialScrollAtEnd && offset === 0 && initialScroll.viewPosition === 1;
4961
+ },
4962
+ [dataProp.length, initialScrollAtEnd]
4963
+ );
4964
+ const shouldRearmFinishedEmptyInitialScrollAtEnd = React3.useCallback(
4965
+ (initialScroll) => {
4966
+ var _a4;
4967
+ return !!(state.didFinishInitialScroll && dataProp.length > 0 && initialScroll && !state.initialScrollUsesOffset && initialScroll.index === 0 && initialScroll.viewPosition === 1 && ((_a4 = initialScroll.contentOffset) != null ? _a4 : 0) === 0);
4968
+ },
4969
+ [dataProp.length]
4970
+ );
4589
4971
  const initialContentOffset = React3.useMemo(() => {
4590
4972
  let value;
4591
4973
  const { initialScroll, initialAnchor } = refState.current;
4592
4974
  if (initialScroll) {
4975
+ if (!state.initialScrollUsesOffset && false) ;
4593
4976
  if (initialScroll.contentOffset !== void 0) {
4594
4977
  value = initialScroll.contentOffset;
4595
4978
  } else {
4596
4979
  const clampedOffset = resolveInitialScrollOffset(initialScroll);
4597
4980
  const updatedInitialScroll = { ...initialScroll, contentOffset: clampedOffset };
4598
- refState.current.initialScroll = updatedInitialScroll;
4599
- state.initialScroll = updatedInitialScroll;
4981
+ setActiveInitialScrollTarget(updatedInitialScroll, {
4982
+ usesOffset: state.initialScrollUsesOffset
4983
+ });
4600
4984
  value = clampedOffset;
4601
4985
  }
4602
4986
  } else {
4603
4987
  refState.current.initialAnchor = void 0;
4604
4988
  value = 0;
4605
4989
  }
4606
- if (!value) {
4607
- setInitialRenderState(ctx, { didInitialScroll: true });
4990
+ const hasPendingDataDependentInitialScroll = !!initialScroll && dataProp.length === 0 && !shouldFinishInitialScrollAtOrigin(initialScroll, value) && !shouldFinishEmptyInitialScrollAtEnd(initialScroll, value);
4991
+ if (!value && !hasPendingDataDependentInitialScroll) {
4992
+ if (initialScroll && shouldFinishInitialScrollAtOrigin(initialScroll, value)) {
4993
+ finishInitialScrollWithoutScroll();
4994
+ } else {
4995
+ setInitialRenderState(ctx, { didInitialScroll: true });
4996
+ }
4608
4997
  }
4609
4998
  return value;
4610
4999
  }, []);
@@ -4621,24 +5010,131 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4621
5010
  set$(ctx, "totalSize", 0);
4622
5011
  }
4623
5012
  }
4624
- const doInitialScroll = React3.useCallback(() => {
4625
- const { initialScroll, didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4626
- if (initialScroll && !queuedInitialLayout && !didFinishInitialScroll && !scrollingTo) {
4627
- const offset = resolveInitialScrollOffset(initialScroll);
5013
+ const doInitialScroll = React3.useCallback((options) => {
5014
+ var _a4, _b2;
5015
+ const allowPostFinishRetry = !!(options == null ? void 0 : options.allowPostFinishRetry);
5016
+ const { didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
5017
+ const initialScroll = (_a4 = state.initialScroll) != null ? _a4 : allowPostFinishRetry ? state.initialScrollLastTarget : void 0;
5018
+ const isInitialScrollInProgress = !!(scrollingTo == null ? void 0 : scrollingTo.isInitialScroll);
5019
+ const needsContainerLayoutForInitialScroll = !state.initialScrollUsesOffset;
5020
+ const shouldWaitForInitialLayout = waitForInitialLayout && needsContainerLayoutForInitialScroll && !queuedInitialLayout && !allowPostFinishRetry && !isInitialScrollInProgress;
5021
+ if (!initialScroll || shouldWaitForInitialLayout || didFinishInitialScroll && !allowPostFinishRetry || scrollingTo && !isInitialScrollInProgress) {
5022
+ return;
5023
+ }
5024
+ if (allowPostFinishRetry && state.initialScrollLastTargetUsesOffset) {
5025
+ return;
5026
+ }
5027
+ const didMoveAwayFromInitialTarget = allowPostFinishRetry && initialScroll.contentOffset !== void 0 && Math.abs(state.scroll - initialScroll.contentOffset) > 1;
5028
+ if (didMoveAwayFromInitialTarget) {
5029
+ state.initialScrollRetryWindowUntil = 0;
5030
+ return;
5031
+ }
5032
+ const offset = resolveInitialScrollOffset(initialScroll);
5033
+ const activeInitialTargetOffset = isInitialScrollInProgress ? (_b2 = scrollingTo.targetOffset) != null ? _b2 : scrollingTo.offset : void 0;
5034
+ const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - offset) > 1;
5035
+ const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - offset) > 1;
5036
+ if (!didOffsetChange && (allowPostFinishRetry || isInitialScrollInProgress && !didActiveInitialTargetChange)) {
5037
+ return;
5038
+ }
5039
+ if (didOffsetChange) {
4628
5040
  const updatedInitialScroll = { ...initialScroll, contentOffset: offset };
4629
- refState.current.initialScroll = updatedInitialScroll;
4630
- state.initialScroll = updatedInitialScroll;
4631
- scrollTo(ctx, {
4632
- animated: false,
4633
- index: initialScroll.index,
4634
- isInitialScroll: true,
4635
- offset,
4636
- precomputedWithViewOffset: true
4637
- });
5041
+ if (!state.initialScrollUsesOffset) {
5042
+ state.initialScrollLastTarget = updatedInitialScroll;
5043
+ state.initialScrollLastTargetUsesOffset = false;
5044
+ if (state.initialScroll) {
5045
+ refState.current.initialScroll = updatedInitialScroll;
5046
+ state.initialScroll = updatedInitialScroll;
5047
+ }
5048
+ }
4638
5049
  }
5050
+ const hasMeasuredScrollLayout = !!state.lastLayout && state.scrollLength > 0;
5051
+ const shouldForceNativeInitialScroll = state.initialScrollUsesOffset && hasMeasuredScrollLayout || allowPostFinishRetry || !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
5052
+ performInitialScroll(ctx, {
5053
+ forceScroll: shouldForceNativeInitialScroll,
5054
+ initialScrollUsesOffset: state.initialScrollUsesOffset,
5055
+ resolvedOffset: offset,
5056
+ target: initialScroll
5057
+ });
4639
5058
  }, []);
5059
+ React3.useLayoutEffect(() => {
5060
+ var _a4;
5061
+ const previousDataLength = state.initialScrollPreviousDataLength;
5062
+ state.initialScrollPreviousDataLength = dataProp.length;
5063
+ if (previousDataLength !== 0 || dataProp.length === 0 || !state.initialScroll || !state.queuedInitialLayout) {
5064
+ return;
5065
+ }
5066
+ if (initialScrollAtEnd) {
5067
+ const lastIndex = Math.max(0, dataProp.length - 1);
5068
+ const initialScroll = state.initialScroll;
5069
+ const shouldRearm = shouldRearmFinishedEmptyInitialScrollAtEnd(initialScroll);
5070
+ if (state.didFinishInitialScroll && !shouldRearm) {
5071
+ return;
5072
+ }
5073
+ if (initialScroll && !state.initialScrollUsesOffset && initialScroll.index === lastIndex && initialScroll.viewPosition === 1 && !shouldRearm) {
5074
+ return;
5075
+ }
5076
+ const updatedInitialScroll = {
5077
+ contentOffset: void 0,
5078
+ index: lastIndex,
5079
+ viewOffset: (_a4 = initialScroll == null ? void 0 : initialScroll.viewOffset) != null ? _a4 : -stylePaddingBottomState,
5080
+ viewPosition: 1
5081
+ };
5082
+ setActiveInitialScrollTarget(updatedInitialScroll, {
5083
+ resetDidFinish: shouldRearm,
5084
+ syncAnchor: true
5085
+ });
5086
+ doInitialScroll();
5087
+ return;
5088
+ }
5089
+ if (state.didFinishInitialScroll) {
5090
+ return;
5091
+ }
5092
+ doInitialScroll();
5093
+ }, [
5094
+ dataProp.length,
5095
+ doInitialScroll,
5096
+ initialScrollAtEnd,
5097
+ shouldRearmFinishedEmptyInitialScrollAtEnd,
5098
+ stylePaddingBottomState
5099
+ ]);
5100
+ React3.useLayoutEffect(() => {
5101
+ var _a4;
5102
+ if (!initialScrollAtEnd) {
5103
+ return;
5104
+ }
5105
+ const lastIndex = Math.max(0, dataProp.length - 1);
5106
+ const initialScroll = state.initialScroll;
5107
+ const shouldRearm = shouldRearmFinishedEmptyInitialScrollAtEnd(initialScroll);
5108
+ if (state.didFinishInitialScroll && !shouldRearm) {
5109
+ return;
5110
+ }
5111
+ if (shouldRearm) {
5112
+ state.didFinishInitialScroll = false;
5113
+ }
5114
+ if (initialScroll && !state.initialScrollUsesOffset && initialScroll.index === lastIndex && initialScroll.viewPosition === 1 && !shouldRearm) {
5115
+ return;
5116
+ }
5117
+ const updatedInitialScroll = {
5118
+ contentOffset: void 0,
5119
+ index: lastIndex,
5120
+ viewOffset: (_a4 = initialScroll == null ? void 0 : initialScroll.viewOffset) != null ? _a4 : -stylePaddingBottomState,
5121
+ viewPosition: 1
5122
+ };
5123
+ setActiveInitialScrollTarget(updatedInitialScroll, {
5124
+ resetDidFinish: shouldRearm,
5125
+ syncAnchor: true
5126
+ });
5127
+ doInitialScroll();
5128
+ }, [
5129
+ dataProp.length,
5130
+ doInitialScroll,
5131
+ initialScrollAtEnd,
5132
+ shouldRearmFinishedEmptyInitialScrollAtEnd,
5133
+ stylePaddingBottomState
5134
+ ]);
4640
5135
  const onLayoutFooter = React3.useCallback(
4641
5136
  (layout) => {
5137
+ var _a4;
4642
5138
  if (!initialScrollAtEnd) {
4643
5139
  return;
4644
5140
  }
@@ -4653,16 +5149,48 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4653
5149
  const footerSize = layout[horizontal ? "width" : "height"];
4654
5150
  const viewOffset = -stylePaddingBottomState - footerSize;
4655
5151
  if (initialScroll.viewOffset !== viewOffset) {
5152
+ const previousTargetOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(initialScroll);
5153
+ const didMoveAwayFromFinishedInitialTarget = state.didFinishInitialScroll && Math.abs(state.scroll - previousTargetOffset) > 1;
5154
+ if (didMoveAwayFromFinishedInitialTarget) {
5155
+ return;
5156
+ }
4656
5157
  const updatedInitialScroll = { ...initialScroll, viewOffset };
4657
- refState.current.initialScroll = updatedInitialScroll;
4658
- state.initialScroll = updatedInitialScroll;
5158
+ setActiveInitialScrollTarget(updatedInitialScroll, {
5159
+ resetDidFinish: true
5160
+ });
5161
+ doInitialScroll();
4659
5162
  }
4660
5163
  },
4661
- [dataProp.length, horizontal, initialScrollAtEnd, stylePaddingBottomState]
5164
+ [
5165
+ dataProp.length,
5166
+ doInitialScroll,
5167
+ horizontal,
5168
+ initialScrollAtEnd,
5169
+ resolveInitialScrollOffset,
5170
+ stylePaddingBottomState
5171
+ ]
4662
5172
  );
4663
5173
  const onLayoutChange = React3.useCallback((layout) => {
4664
- doInitialScroll();
5174
+ var _a4;
4665
5175
  handleLayout(ctx, layout, setCanRender);
5176
+ const SCROLL_LENGTH_RETRY_WINDOW_MS = 600;
5177
+ const now = Date.now();
5178
+ const didFinishInitialScroll = !!state.didFinishInitialScroll;
5179
+ if (didFinishInitialScroll && !state.initialScrollLastDidFinish) {
5180
+ state.initialScrollRetryWindowUntil = now + SCROLL_LENGTH_RETRY_WINDOW_MS;
5181
+ }
5182
+ state.initialScrollLastDidFinish = didFinishInitialScroll;
5183
+ const previousScrollLength = state.initialScrollRetryLastLength;
5184
+ const currentScrollLength = state.scrollLength;
5185
+ const didScrollLengthChange = previousScrollLength === void 0 || Math.abs(currentScrollLength - previousScrollLength) > 1;
5186
+ if (didScrollLengthChange) {
5187
+ state.initialScrollRetryLastLength = currentScrollLength;
5188
+ }
5189
+ if (didFinishInitialScroll && didScrollLengthChange && now <= state.initialScrollRetryWindowUntil && !state.initialScrollLastTargetUsesOffset && ((_a4 = state.initialScrollLastTarget) == null ? void 0 : _a4.index) !== void 0) {
5190
+ doInitialScroll({ allowPostFinishRetry: true });
5191
+ return;
5192
+ }
5193
+ doInitialScroll();
4666
5194
  }, []);
4667
5195
  const { onLayout } = useOnLayoutSync({
4668
5196
  onLayoutChange,
@@ -4774,7 +5302,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4774
5302
  onScroll: onScrollHandler,
4775
5303
  recycleItems,
4776
5304
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React3__namespace.cloneElement(refreshControlElement, {
4777
- progressViewOffset: ((_d = refreshControlElement.props.progressViewOffset) != null ? _d : 0) + stylePaddingTopState
5305
+ progressViewOffset: ((_g = refreshControlElement.props.progressViewOffset) != null ? _g : 0) + stylePaddingTopState
4778
5306
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React3__namespace.createElement(
4779
5307
  RefreshControl,
4780
5308
  {
@@ -4785,7 +5313,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4785
5313
  ),
4786
5314
  refScrollView: combinedRef,
4787
5315
  renderScrollComponent,
4788
- scrollAdjustHandler: (_e = refState.current) == null ? void 0 : _e.scrollAdjustHandler,
5316
+ scrollAdjustHandler: (_h = refState.current) == null ? void 0 : _h.scrollAdjustHandler,
4789
5317
  scrollEventThrottle: 0,
4790
5318
  snapToIndices,
4791
5319
  stickyHeaderIndices,