@legendapp/list 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/react.js CHANGED
@@ -1069,32 +1069,34 @@ function scheduleAdaptiveRenderExit(ctx, exitDelay) {
1069
1069
  const state = ctx.state;
1070
1070
  clearAdaptiveRenderExitTimeout(ctx);
1071
1071
  if (exitDelay <= 0) {
1072
- setAdaptiveRender(ctx, "normal");
1072
+ setAdaptiveRender(ctx, "normal", "scroll");
1073
1073
  } else {
1074
1074
  const timeout = setTimeout(() => {
1075
1075
  state.timeouts.delete(timeout);
1076
1076
  state.timeoutAdaptiveRender = void 0;
1077
- setAdaptiveRender(ctx, "normal");
1077
+ setAdaptiveRender(ctx, "normal", "scroll");
1078
1078
  }, exitDelay);
1079
1079
  state.timeoutAdaptiveRender = timeout;
1080
1080
  state.timeouts.add(timeout);
1081
1081
  }
1082
1082
  }
1083
- function setAdaptiveRender(ctx, mode) {
1083
+ function setAdaptiveRender(ctx, mode, reason) {
1084
1084
  var _a3, _b;
1085
1085
  const previousMode = peek$(ctx, "adaptiveRender");
1086
1086
  if (previousMode !== mode) {
1087
1087
  set$(ctx, "adaptiveRender", mode);
1088
- (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.onChange) == null ? void 0 : _b.call(_a3, mode);
1088
+ (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.onChange) == null ? void 0 : _b.call(_a3, mode, reason);
1089
1089
  }
1090
1090
  }
1091
1091
  function resetAdaptiveRender(ctx) {
1092
+ var _a3, _b;
1092
1093
  clearAdaptiveRenderExitTimeout(ctx);
1093
- if (peek$(ctx, "adaptiveRender") !== "normal") {
1094
- setAdaptiveRender(ctx, "normal");
1094
+ const mode = (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.initialMode) != null ? _b : "normal";
1095
+ if (peek$(ctx, "adaptiveRender") !== mode) {
1096
+ setAdaptiveRender(ctx, mode, "initial");
1095
1097
  }
1096
1098
  }
1097
- function updateAdaptiveRender(ctx, scrollVelocity) {
1099
+ function updateAdaptiveRender(ctx, scrollVelocity, options) {
1098
1100
  var _a3, _b, _c;
1099
1101
  const state = ctx.state;
1100
1102
  const adaptiveRender = state.props.adaptiveRender;
@@ -1105,11 +1107,11 @@ function updateAdaptiveRender(ctx, scrollVelocity) {
1105
1107
  const exitVelocity = (_b = adaptiveRender.exitVelocity) != null ? _b : DEFAULT_WEB_ADAPTIVE_RENDER_EXIT_VELOCITY ;
1106
1108
  const exitDelay = (_c = adaptiveRender.exitDelay) != null ? _c : DEFAULT_WEB_ADAPTIVE_RENDER_EXIT_DELAY ;
1107
1109
  const threshold = currentMode === "light" ? exitVelocity : enterVelocity;
1108
- const nextMode = Math.abs(scrollVelocity) > threshold ? "light" : "normal";
1110
+ const nextMode = (options == null ? void 0 : options.forceLight) || Math.abs(scrollVelocity) > threshold ? "light" : "normal";
1109
1111
  const previousMode = state.timeoutAdaptiveRender !== void 0 ? "normal" : currentMode;
1110
1112
  if (nextMode !== previousMode) {
1111
1113
  if (nextMode === "light") {
1112
- setAdaptiveRender(ctx, "light");
1114
+ setAdaptiveRender(ctx, "light", "scroll");
1113
1115
  scheduleAdaptiveRenderExit(ctx, exitDelay);
1114
1116
  } else if (currentMode === "light") {
1115
1117
  scheduleAdaptiveRenderExit(ctx, exitDelay);
@@ -1122,16 +1124,43 @@ function updateAdaptiveRender(ctx, scrollVelocity) {
1122
1124
  }
1123
1125
 
1124
1126
  // src/utils/getEffectiveDrawDistance.ts
1125
- var INITIAL_DRAW_DISTANCE = 100;
1126
- function getEffectiveDrawDistance(ctx) {
1127
+ var INITIAL_DRAW_DISTANCE = 50;
1128
+ function getEffectiveDrawDistance(ctx, mode) {
1127
1129
  var _a3;
1128
1130
  const drawDistance = ctx.state.props.drawDistance;
1129
1131
  const initialScroll = ctx.state.initialScroll;
1130
1132
  const needsFullInitialDrawDistance = initialScroll !== void 0 && ((_a3 = initialScroll.viewPosition) != null ? _a3 : 0) > 0;
1131
- return peek$(ctx, "readyToRender") || needsFullInitialDrawDistance ? drawDistance : Math.min(drawDistance, INITIAL_DRAW_DISTANCE);
1133
+ const shouldCapDrawDistance = mode === "visible-first" || mode !== "full" && !peek$(ctx, "readyToRender") && !needsFullInitialDrawDistance;
1134
+ return shouldCapDrawDistance ? Math.min(drawDistance, INITIAL_DRAW_DISTANCE) : drawDistance;
1135
+ }
1136
+ function scheduleFullDrawDistancePrewarm(ctx) {
1137
+ const { state } = ctx;
1138
+ if (state.props.drawDistance <= INITIAL_DRAW_DISTANCE || state.queuedFullDrawDistancePrewarm !== void 0) {
1139
+ return;
1140
+ }
1141
+ state.queuedFullDrawDistancePrewarm = requestAnimationFrame(() => {
1142
+ var _a3;
1143
+ state.queuedFullDrawDistancePrewarm = void 0;
1144
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
1145
+ });
1132
1146
  }
1133
1147
 
1134
1148
  // src/utils/setInitialRenderState.ts
1149
+ function resetInitialRenderState(ctx, {
1150
+ resetLayout,
1151
+ resetInitialScroll
1152
+ }) {
1153
+ const { state } = ctx;
1154
+ if (resetLayout) {
1155
+ state.didContainersLayout = false;
1156
+ state.queuedInitialLayout = false;
1157
+ }
1158
+ if (resetInitialScroll) {
1159
+ state.didFinishInitialScroll = false;
1160
+ }
1161
+ set$(ctx, "readyToRender", false);
1162
+ resetAdaptiveRender(ctx);
1163
+ }
1135
1164
  function setInitialRenderState(ctx, {
1136
1165
  didLayout,
1137
1166
  didInitialScroll
@@ -1150,15 +1179,15 @@ function setInitialRenderState(ctx, {
1150
1179
  const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll);
1151
1180
  if (isReadyToRender && !peek$(ctx, "readyToRender")) {
1152
1181
  set$(ctx, "readyToRender", true);
1153
- setAdaptiveRender(ctx, "normal");
1182
+ setAdaptiveRender(ctx, "normal", "ready");
1154
1183
  if (state.props.drawDistance > INITIAL_DRAW_DISTANCE) {
1155
- requestAnimationFrame(() => {
1156
- var _a3;
1157
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
1158
- });
1184
+ scheduleFullDrawDistancePrewarm(ctx);
1159
1185
  }
1160
- if (onLoad) {
1161
- onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1186
+ if (!state.didLoad) {
1187
+ state.didLoad = true;
1188
+ if (onLoad) {
1189
+ onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1190
+ }
1162
1191
  }
1163
1192
  }
1164
1193
  }
@@ -1455,6 +1484,7 @@ function finishScrollTo(ctx) {
1455
1484
  const scrollingTo = state.scrollingTo;
1456
1485
  state.scrollHistory.length = 0;
1457
1486
  state.scrollingTo = void 0;
1487
+ state.scrollTargetPinnedRange = void 0;
1458
1488
  if (state.pendingTotalSize !== void 0) {
1459
1489
  addTotalSize(ctx, null, state.pendingTotalSize);
1460
1490
  }
@@ -1582,8 +1612,8 @@ function doMaintainScrollAtEnd(ctx) {
1582
1612
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1583
1613
  return false;
1584
1614
  }
1585
- state.pendingMaintainScrollAtEnd = false;
1586
1615
  if (shouldMaintainScrollAtEnd) {
1616
+ state.pendingMaintainScrollAtEnd = false;
1587
1617
  const contentSize = getContentSize(ctx);
1588
1618
  if (contentSize < state.scrollLength) {
1589
1619
  state.scroll = 0;
@@ -1614,6 +1644,9 @@ function doMaintainScrollAtEnd(ctx) {
1614
1644
  () => {
1615
1645
  if (state.maintainingScrollAtEnd === activeState) {
1616
1646
  state.maintainingScrollAtEnd = void 0;
1647
+ if (state.pendingMaintainScrollAtEnd) {
1648
+ doMaintainScrollAtEnd(ctx);
1649
+ }
1617
1650
  }
1618
1651
  },
1619
1652
  maintainScrollAtEnd.animated ? 500 : 0
@@ -1622,9 +1655,12 @@ function doMaintainScrollAtEnd(ctx) {
1622
1655
  state.maintainingScrollAtEnd = void 0;
1623
1656
  }
1624
1657
  });
1658
+ } else {
1659
+ state.pendingMaintainScrollAtEnd = true;
1625
1660
  }
1626
1661
  return true;
1627
1662
  }
1663
+ state.pendingMaintainScrollAtEnd = false;
1628
1664
  return false;
1629
1665
  }
1630
1666
 
@@ -1942,6 +1978,8 @@ function prepareMVCP(ctx, dataChanged) {
1942
1978
  }
1943
1979
 
1944
1980
  // src/utils/getScrollVelocity.ts
1981
+ var MAX_SCROLL_VELOCITY_WINDOW_MS = 1e3;
1982
+ var SCROLL_VELOCITY_HALF_LIFE_MS = 200;
1945
1983
  var getScrollVelocity = (state) => {
1946
1984
  const { scrollHistory } = state;
1947
1985
  const newestIndex = scrollHistory.length - 1;
@@ -1949,35 +1987,37 @@ var getScrollVelocity = (state) => {
1949
1987
  return 0;
1950
1988
  }
1951
1989
  const newest = scrollHistory[newestIndex];
1952
- const now = Date.now();
1953
- let direction = 0;
1954
- for (let i = newestIndex; i > 0; i--) {
1955
- const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
1956
- if (delta !== 0) {
1957
- direction = Math.sign(delta);
1958
- break;
1959
- }
1960
- }
1961
- if (direction === 0) {
1990
+ if (Date.now() - newest.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1962
1991
  return 0;
1963
1992
  }
1964
- let oldest = newest;
1965
- for (let i = newestIndex - 1; i >= 0; i--) {
1993
+ let direction = 0;
1994
+ let weightedVelocity = 0;
1995
+ let totalWeight = 0;
1996
+ for (let i = newestIndex; i > 0; i--) {
1966
1997
  const current = scrollHistory[i];
1967
- const next = scrollHistory[i + 1];
1968
- const delta = next.scroll - current.scroll;
1969
- const deltaSign = Math.sign(delta);
1970
- if (deltaSign !== 0 && deltaSign !== direction) {
1971
- break;
1998
+ const previous = scrollHistory[i - 1];
1999
+ const scrollDiff = current.scroll - previous.scroll;
2000
+ const timeDiff = current.time - previous.time;
2001
+ const deltaSign = Math.sign(scrollDiff);
2002
+ if (deltaSign !== 0) {
2003
+ if (direction === 0) {
2004
+ direction = deltaSign;
2005
+ } else if (deltaSign !== direction) {
2006
+ break;
2007
+ }
1972
2008
  }
1973
- if (now - current.time > 1e3) {
2009
+ if (newest.time - previous.time > MAX_SCROLL_VELOCITY_WINDOW_MS) {
1974
2010
  break;
1975
2011
  }
1976
- oldest = current;
2012
+ if (scrollDiff === 0 || timeDiff <= 0) {
2013
+ continue;
2014
+ }
2015
+ const age = newest.time - current.time;
2016
+ const weight = Math.exp(-age / SCROLL_VELOCITY_HALF_LIFE_MS);
2017
+ weightedVelocity += scrollDiff / timeDiff * weight;
2018
+ totalWeight += weight;
1977
2019
  }
1978
- const scrollDiff = newest.scroll - oldest.scroll;
1979
- const timeDiff = newest.time - oldest.time;
1980
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
2020
+ return totalWeight > 0 ? weightedVelocity / totalWeight : 0;
1981
2021
  };
1982
2022
 
1983
2023
  // src/core/updateScroll.ts
@@ -2005,8 +2045,6 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2005
2045
  if (scrollHistory.length > 5) {
2006
2046
  scrollHistory.shift();
2007
2047
  }
2008
- const scrollVelocity = getScrollVelocity(state);
2009
- updateAdaptiveRender(ctx, scrollVelocity);
2010
2048
  if (ignoreScrollFromMVCP && !scrollingTo) {
2011
2049
  const { lt, gt } = ignoreScrollFromMVCP;
2012
2050
  if (lt && newScroll < lt || gt && newScroll > gt) {
@@ -2021,6 +2059,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2021
2059
  const scrollDelta = Math.abs(newScroll - prevScroll);
2022
2060
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2023
2061
  const scrollLength = state.scrollLength;
2062
+ const isLargeUserScrollJump = scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust;
2063
+ const scrollVelocity = getScrollVelocity(state);
2064
+ updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2024
2065
  const lastCalculated = state.scrollLastCalculate;
2025
2066
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2026
2067
  const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
@@ -2030,10 +2071,17 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2030
2071
  state.lastScrollDelta = scrollDelta;
2031
2072
  const runCalculateItems = () => {
2032
2073
  var _a4;
2033
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0, scrollVelocity });
2074
+ const calculateItemsParams = {
2075
+ doMVCP: scrollingTo !== void 0,
2076
+ scrollVelocity
2077
+ };
2078
+ if (isLargeUserScrollJump) {
2079
+ calculateItemsParams.drawDistanceMode = "visible-first";
2080
+ }
2081
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, calculateItemsParams);
2034
2082
  checkThresholds(ctx);
2035
2083
  };
2036
- if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
2084
+ if (isLargeUserScrollJump) {
2037
2085
  state.mvcpAnchorLock = void 0;
2038
2086
  state.pendingNativeMVCPAdjust = void 0;
2039
2087
  state.userScrollAnchorReset = { keys: /* @__PURE__ */ new Set() };
@@ -2042,6 +2090,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2042
2090
  state.queuedMVCPRecalculate = void 0;
2043
2091
  }
2044
2092
  ReactDOM.flushSync(runCalculateItems);
2093
+ scheduleFullDrawDistancePrewarm(ctx);
2045
2094
  } else {
2046
2095
  runCalculateItems();
2047
2096
  }
@@ -2085,8 +2134,93 @@ function syncInitialScrollNativeWatchdog(state, options) {
2085
2134
  initialScrollWatchdog.clear(state);
2086
2135
  }
2087
2136
  }
2088
- function scrollTo(ctx, params) {
2137
+ function findPositionIndexAtOrBeforeOffset(ctx, offset) {
2138
+ const state = ctx.state;
2139
+ const dataLength = state.props.data.length;
2140
+ let low = 0;
2141
+ let high = dataLength - 1;
2142
+ let match;
2143
+ while (low <= high) {
2144
+ const mid = Math.floor((low + high) / 2);
2145
+ const top = state.positions[mid];
2146
+ if (top === void 0) {
2147
+ high = mid - 1;
2148
+ } else {
2149
+ if (top <= offset) {
2150
+ match = mid;
2151
+ low = mid + 1;
2152
+ } else {
2153
+ high = mid - 1;
2154
+ }
2155
+ }
2156
+ }
2157
+ return match;
2158
+ }
2159
+ function getItemBottom(ctx, index) {
2089
2160
  var _a3;
2161
+ const top = ctx.state.positions[index];
2162
+ if (top === void 0) {
2163
+ return void 0;
2164
+ }
2165
+ const itemSize = (_a3 = getItemSizeAtIndex(ctx, index)) != null ? _a3 : 0;
2166
+ return top + (Number.isFinite(itemSize) ? itemSize : 0);
2167
+ }
2168
+ function getTargetViewportRenderRange(ctx, targetOffset, targetIndex) {
2169
+ const state = ctx.state;
2170
+ const dataLength = state.props.data.length;
2171
+ if (dataLength === 0) {
2172
+ return void 0;
2173
+ }
2174
+ const viewportStart = Math.max(0, targetOffset);
2175
+ const viewportEnd = Math.max(viewportStart, targetOffset + state.scrollLength);
2176
+ let start = targetIndex !== void 0 ? Math.max(0, Math.min(dataLength - 1, targetIndex)) : findPositionIndexAtOrBeforeOffset(ctx, viewportStart);
2177
+ if (start === void 0) {
2178
+ return void 0;
2179
+ }
2180
+ if (targetIndex !== void 0 && state.positions[start] === void 0) {
2181
+ return { end: start, start };
2182
+ }
2183
+ if (targetIndex === void 0) {
2184
+ const startBottom = getItemBottom(ctx, start);
2185
+ if (startBottom === void 0 || startBottom <= viewportStart) {
2186
+ return void 0;
2187
+ }
2188
+ }
2189
+ while (start > 0) {
2190
+ const top = state.positions[start];
2191
+ if (top === void 0 || top <= viewportStart || state.positions[start - 1] === void 0) {
2192
+ break;
2193
+ }
2194
+ start--;
2195
+ }
2196
+ while (start > 0) {
2197
+ const previousBottom = getItemBottom(ctx, start - 1);
2198
+ if (previousBottom === void 0 || previousBottom <= viewportStart) {
2199
+ break;
2200
+ }
2201
+ start--;
2202
+ }
2203
+ let end = start;
2204
+ while (end + 1 < dataLength) {
2205
+ const nextTop = state.positions[end + 1];
2206
+ if (nextTop === void 0 || nextTop > viewportEnd) {
2207
+ break;
2208
+ }
2209
+ end++;
2210
+ }
2211
+ return { end, start };
2212
+ }
2213
+ function pinScrollTargetRenderRange(ctx, targetOffset, targetIndex) {
2214
+ const range = getTargetViewportRenderRange(ctx, targetOffset, targetIndex);
2215
+ if (range) {
2216
+ ctx.state.scrollTargetPinnedRange = range;
2217
+ ctx.state.scrollForNextCalculateItemsInView = void 0;
2218
+ } else {
2219
+ ctx.state.scrollTargetPinnedRange = void 0;
2220
+ }
2221
+ }
2222
+ function scrollTo(ctx, params) {
2223
+ var _a3, _b;
2090
2224
  const state = ctx.state;
2091
2225
  const { noScrollingTo, forceScroll, ...scrollTarget } = params;
2092
2226
  const {
@@ -2121,11 +2255,20 @@ function scrollTo(ctx, params) {
2121
2255
  targetOffset,
2122
2256
  waitForInitialScrollCompletionFrame
2123
2257
  };
2258
+ if (!isInitialScroll) {
2259
+ pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2260
+ }
2124
2261
  }
2125
2262
  state.scrollPending = targetOffset;
2126
2263
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2127
- if (!animated && !isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2128
- updateScroll(ctx, targetOffset, false, { markHasScrolled: false });
2264
+ if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2265
+ if (animated) {
2266
+ if (state.scrollTargetPinnedRange) {
2267
+ (_b = state.triggerCalculateItemsInView) == null ? void 0 : _b.call(state);
2268
+ }
2269
+ } else {
2270
+ updateScroll(ctx, targetOffset, true, { markHasScrolled: false });
2271
+ }
2129
2272
  }
2130
2273
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
2131
2274
  doScrollTo(ctx, { animated, horizontal, offset });
@@ -2196,16 +2339,17 @@ function dispatchInitialScroll(ctx, params) {
2196
2339
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2197
2340
  });
2198
2341
  }
2199
- function setInitialScrollTarget(state, target, options) {
2342
+ function setInitialScrollTarget(ctx, target, options) {
2200
2343
  var _a3;
2344
+ const { state } = ctx;
2201
2345
  state.clearPreservedInitialScrollOnNextFinish = void 0;
2202
2346
  if (state.timeoutPreservedInitialScrollClear !== void 0) {
2203
2347
  clearTimeout(state.timeoutPreservedInitialScrollClear);
2204
2348
  state.timeoutPreservedInitialScrollClear = void 0;
2205
2349
  }
2206
2350
  state.initialScroll = target;
2207
- if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
2208
- state.didFinishInitialScroll = false;
2351
+ if (options == null ? void 0 : options.resetDidFinish) {
2352
+ resetInitialRenderState(ctx, { resetInitialScroll: true });
2209
2353
  }
2210
2354
  setInitialScrollSession(state, {
2211
2355
  kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
@@ -2259,7 +2403,7 @@ function advanceMeasuredInitialScroll(ctx, options) {
2259
2403
  return false;
2260
2404
  }
2261
2405
  if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2262
- setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2406
+ setInitialScrollTarget(ctx, { ...initialScroll, contentOffset: resolvedOffset });
2263
2407
  }
2264
2408
  const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
2265
2409
  dispatchInitialScroll(ctx, {
@@ -2532,7 +2676,6 @@ function areEquivalentBootstrapInitialScrollTargets(current, next) {
2532
2676
  }
2533
2677
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2534
2678
  const { dataLength, stylePaddingBottom, target } = options;
2535
- const state = ctx.state;
2536
2679
  if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2537
2680
  return;
2538
2681
  }
@@ -2542,7 +2685,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2542
2685
  preserveForFooterLayout: void 0,
2543
2686
  stylePaddingBottom
2544
2687
  });
2545
- setInitialScrollTarget(state, clearedFooterTarget);
2688
+ setInitialScrollTarget(ctx, clearedFooterTarget);
2546
2689
  }
2547
2690
  function clearFinishedViewportRetargetableInitialScroll(state) {
2548
2691
  clearPreservedInitialScrollTarget(state);
@@ -2689,7 +2832,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2689
2832
  return;
2690
2833
  }
2691
2834
  if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
2692
- setInitialScrollTarget(state, updatedInitialScroll, {
2835
+ setInitialScrollTarget(ctx, updatedInitialScroll, {
2693
2836
  resetDidFinish: shouldResetDidFinish
2694
2837
  });
2695
2838
  rearmBootstrapInitialScroll(ctx, {
@@ -2704,7 +2847,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2704
2847
  return;
2705
2848
  }
2706
2849
  if (bootstrapInitialScroll || shouldResetDidFinish) {
2707
- setInitialScrollTarget(state, initialScroll, {
2850
+ setInitialScrollTarget(ctx, initialScroll, {
2708
2851
  resetDidFinish: shouldResetDidFinish
2709
2852
  });
2710
2853
  rearmBootstrapInitialScroll(ctx, {
@@ -2750,7 +2893,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2750
2893
  });
2751
2894
  } else {
2752
2895
  const didFinishInitialScroll = !!state.didFinishInitialScroll;
2753
- setInitialScrollTarget(state, updatedInitialScroll, {
2896
+ setInitialScrollTarget(ctx, updatedInitialScroll, {
2754
2897
  resetDidFinish: didFinishInitialScroll
2755
2898
  });
2756
2899
  rearmBootstrapInitialScroll(ctx, {
@@ -3084,7 +3227,7 @@ function initializeInitialScrollOnMount(ctx, options) {
3084
3227
  const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
3085
3228
  const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
3086
3229
  if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
3087
- setInitialScrollTarget(state, {
3230
+ setInitialScrollTarget(ctx, {
3088
3231
  ...initialScroll,
3089
3232
  contentOffset: resolvedInitialContentOffset,
3090
3233
  preserveForFooterLayout
@@ -3113,6 +3256,7 @@ function handleInitialScrollDataChange(ctx, options) {
3113
3256
  const {
3114
3257
  dataLength,
3115
3258
  didDataChange,
3259
+ didStartFreshData,
3116
3260
  initialScrollAtEnd,
3117
3261
  latestInitialScroll,
3118
3262
  latestInitialScrollSessionKind,
@@ -3120,17 +3264,17 @@ function handleInitialScrollDataChange(ctx, options) {
3120
3264
  useBootstrapInitialScroll
3121
3265
  } = options;
3122
3266
  const state = ctx.state;
3123
- const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3124
- const isFirstNonEmptyData = !state.hasHadNonEmptyData && dataLength > 0;
3267
+ const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3268
+ const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3125
3269
  if (dataLength > 0) {
3126
3270
  state.hasHadNonEmptyData = true;
3127
3271
  }
3128
- if (isFirstNonEmptyData) {
3272
+ if (shouldUseLatestInitialScroll) {
3129
3273
  if (latestInitialScroll) {
3130
- setInitialScrollTarget(state, latestInitialScroll);
3274
+ setInitialScrollTarget(ctx, latestInitialScroll);
3131
3275
  setInitialScrollSession(state, {
3132
3276
  kind: latestInitialScrollSessionKind,
3133
- previousDataLength
3277
+ previousDataLength: previousInitialScrollDataLength
3134
3278
  });
3135
3279
  } else {
3136
3280
  clearPreservedInitialScrollTarget(state);
@@ -3145,17 +3289,17 @@ function handleInitialScrollDataChange(ctx, options) {
3145
3289
  dataLength,
3146
3290
  didDataChange,
3147
3291
  initialScrollAtEnd,
3148
- previousDataLength,
3292
+ previousDataLength: previousInitialScrollDataLength,
3149
3293
  stylePaddingBottom
3150
3294
  });
3151
3295
  return;
3152
3296
  }
3153
- const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3154
- if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3297
+ const shouldReplayFinishedOffsetInitialScroll = previousInitialScrollDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3298
+ if (previousInitialScrollDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3155
3299
  return;
3156
3300
  }
3157
3301
  if (shouldReplayFinishedOffsetInitialScroll) {
3158
- state.didFinishInitialScroll = false;
3302
+ resetInitialRenderState(ctx, { resetInitialScroll: true });
3159
3303
  }
3160
3304
  advanceCurrentInitialScrollSession(ctx);
3161
3305
  }
@@ -3941,6 +4085,32 @@ function setDidLayout(ctx) {
3941
4085
  }
3942
4086
 
3943
4087
  // src/core/calculateItemsInView.ts
4088
+ var RENDER_RANGE_PROJECTION_FULL_VELOCITY = 4;
4089
+ var RENDER_RANGE_PROJECTION_SETTLE_DELAY = 100;
4090
+ function getProjectedBufferAdjustment(scrollVelocity, trailingBuffer) {
4091
+ if (trailingBuffer <= 0) {
4092
+ return 0;
4093
+ }
4094
+ const velocityProgress = Math.min(1, Math.abs(scrollVelocity) / RENDER_RANGE_PROJECTION_FULL_VELOCITY);
4095
+ return Math.sign(scrollVelocity) * trailingBuffer * velocityProgress;
4096
+ }
4097
+ function scheduleRenderRangeProjectionSettle(ctx) {
4098
+ const state = ctx.state;
4099
+ const previousTimeout = state.timeoutRenderRangeProjectionSettle;
4100
+ if (previousTimeout !== void 0) {
4101
+ clearTimeout(previousTimeout);
4102
+ state.timeouts.delete(previousTimeout);
4103
+ }
4104
+ const timeout = setTimeout(() => {
4105
+ var _a3;
4106
+ state.timeoutRenderRangeProjectionSettle = void 0;
4107
+ state.timeouts.delete(timeout);
4108
+ state.scrollHistory.length = 0;
4109
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
4110
+ }, RENDER_RANGE_PROJECTION_SETTLE_DELAY);
4111
+ state.timeoutRenderRangeProjectionSettle = timeout;
4112
+ state.timeouts.add(timeout);
4113
+ }
3944
4114
  function findCurrentStickyIndex(stickyArray, scroll, state) {
3945
4115
  const positions = state.positions;
3946
4116
  for (let i = stickyArray.length - 1; i >= 0; i--) {
@@ -3981,14 +4151,14 @@ function handleStickyActivation(ctx, stickyArray, currentStickyIdx, needNewConta
3981
4151
  }
3982
4152
  }
3983
4153
  }
3984
- function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, alwaysRenderIndicesSet) {
4154
+ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentStickyIdx, pendingRemoval, isPinnedRenderIndex) {
3985
4155
  var _a3, _b;
3986
4156
  const state = ctx.state;
3987
4157
  for (const containerIndex of state.stickyContainerPool) {
3988
4158
  const itemKey = peek$(ctx, `containerItemKey${containerIndex}`);
3989
4159
  const itemIndex = itemKey ? state.indexByKey.get(itemKey) : void 0;
3990
4160
  if (itemIndex === void 0) continue;
3991
- if (alwaysRenderIndicesSet.has(itemIndex)) continue;
4161
+ if (isPinnedRenderIndex(itemIndex)) continue;
3992
4162
  const arrayIdx = stickyArray.indexOf(itemIndex);
3993
4163
  if (arrayIdx === -1) {
3994
4164
  state.stickyContainerPool.delete(containerIndex);
@@ -4152,9 +4322,7 @@ function calculateItemsInView(ctx, params = {}) {
4152
4322
  const { data } = state.props;
4153
4323
  const stickyHeaderIndicesArr = state.props.stickyHeaderIndicesArr || [];
4154
4324
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4155
- const alwaysRenderArr = alwaysRenderIndicesArr || [];
4156
- const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4157
- const drawDistance = getEffectiveDrawDistance(ctx);
4325
+ const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4158
4326
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4159
4327
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4160
4328
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
@@ -4220,14 +4388,19 @@ function calculateItemsInView(ctx, params = {}) {
4220
4388
  scrollBufferTop = drawDistance * 1.5;
4221
4389
  scrollBufferBottom = drawDistance * 0.5;
4222
4390
  }
4391
+ const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4392
+ const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4223
4393
  const updateScrollRange = () => {
4224
4394
  const scrollStart = Math.max(0, scroll);
4225
4395
  const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4226
- scrollTopBuffered = scrollStart - scrollBufferTop;
4227
4396
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4228
- scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4397
+ scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4398
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
4229
4399
  };
4230
4400
  updateScrollRange();
4401
+ if (projectedBufferAdjustment !== 0) {
4402
+ scheduleRenderRangeProjectionSettle(ctx);
4403
+ }
4231
4404
  if (enableScrollForNextCalculateItemsInView && !suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4232
4405
  const { top, bottom } = scrollForNextCalculateItemsInView;
4233
4406
  if (top === null && bottom === null) {
@@ -4385,9 +4558,34 @@ function calculateItemsInView(ctx, params = {}) {
4385
4558
  }
4386
4559
  }
4387
4560
  }
4561
+ const scrollTargetPinnedRange = state.scrollTargetPinnedRange;
4562
+ let scrollTargetPinnedStart = 0;
4563
+ let scrollTargetPinnedEnd = -1;
4564
+ if (scrollTargetPinnedRange) {
4565
+ scrollTargetPinnedStart = Math.max(0, Math.min(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end));
4566
+ scrollTargetPinnedEnd = Math.min(
4567
+ dataLength - 1,
4568
+ Math.max(scrollTargetPinnedRange.start, scrollTargetPinnedRange.end)
4569
+ );
4570
+ }
4571
+ const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4572
+ const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4388
4573
  if (startBuffered !== null && endBuffered !== null) {
4389
4574
  const needNewContainers = [];
4390
4575
  const needNewContainersSet = /* @__PURE__ */ new Set();
4576
+ const addPinnedIndex = (index) => {
4577
+ var _a4;
4578
+ if (index >= 0 && index < dataLength) {
4579
+ const id = (_a4 = idCache[index]) != null ? _a4 : getId(state, index);
4580
+ const containerIndex = containerItemKeys.get(id);
4581
+ if (containerIndex !== void 0) {
4582
+ state.stickyContainerPool.add(containerIndex);
4583
+ } else if (!isNullOrUndefined(id) && !needNewContainersSet.has(index)) {
4584
+ needNewContainersSet.add(index);
4585
+ needNewContainers.push(index);
4586
+ }
4587
+ }
4588
+ };
4391
4589
  for (let i = startBuffered; i <= endBuffered; i++) {
4392
4590
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4393
4591
  if (!containerItemKeys.has(id)) {
@@ -4395,21 +4593,19 @@ function calculateItemsInView(ctx, params = {}) {
4395
4593
  needNewContainers.push(i);
4396
4594
  }
4397
4595
  }
4398
- if (alwaysRenderArr.length > 0) {
4399
- for (const index of alwaysRenderArr) {
4400
- if (index < 0 || index >= dataLength) continue;
4401
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4402
- if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4403
- needNewContainersSet.add(index);
4404
- needNewContainers.push(index);
4405
- }
4596
+ for (const index of alwaysRenderIndicesArr) {
4597
+ addPinnedIndex(index);
4598
+ }
4599
+ if (hasScrollTargetPinnedRange) {
4600
+ for (let index = scrollTargetPinnedStart; index <= scrollTargetPinnedEnd; index++) {
4601
+ addPinnedIndex(index);
4406
4602
  }
4407
4603
  }
4408
4604
  if (stickyHeaderIndicesArr.length > 0) {
4409
4605
  handleStickyActivation(
4410
4606
  ctx,
4411
4607
  stickyHeaderIndicesArr,
4412
- (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
4608
+ (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4413
4609
  needNewContainers,
4414
4610
  needNewContainersSet,
4415
4611
  startBuffered,
@@ -4435,7 +4631,7 @@ function calculateItemsInView(ctx, params = {}) {
4435
4631
  for (const allocation of availableContainerAllocations) {
4436
4632
  const i = allocation.itemIndex;
4437
4633
  const containerIndex = allocation.containerIndex;
4438
- const id = (_p = idCache[i]) != null ? _p : getId(state, i);
4634
+ const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4439
4635
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4440
4636
  if (oldKey && oldKey !== id) {
4441
4637
  containerItemKeys.delete(oldKey);
@@ -4446,10 +4642,14 @@ function calculateItemsInView(ctx, params = {}) {
4446
4642
  state.containerItemTypes.set(containerIndex, allocation.itemType);
4447
4643
  }
4448
4644
  containerItemKeys.set(id, containerIndex);
4449
- (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
4645
+ (_p = state.userScrollAnchorReset) == null ? void 0 : _p.keys.add(id);
4646
+ {
4647
+ (_q = state.pendingLayoutEffectMeasurements) != null ? _q : state.pendingLayoutEffectMeasurements = /* @__PURE__ */ new Set();
4648
+ state.pendingLayoutEffectMeasurements.add(id);
4649
+ }
4450
4650
  const containerSticky = `containerSticky${containerIndex}`;
4451
4651
  const isSticky = stickyHeaderIndicesSet.has(i);
4452
- const isAlwaysRender = alwaysRenderSet.has(i);
4652
+ const isPinnedRender = isPinnedRenderIndex(i);
4453
4653
  if (isSticky) {
4454
4654
  set$(ctx, containerSticky, true);
4455
4655
  state.stickyContainerPool.add(containerIndex);
@@ -4457,9 +4657,9 @@ function calculateItemsInView(ctx, params = {}) {
4457
4657
  if (peek$(ctx, containerSticky)) {
4458
4658
  set$(ctx, containerSticky, false);
4459
4659
  }
4460
- if (isAlwaysRender) {
4660
+ if (isPinnedRender) {
4461
4661
  state.stickyContainerPool.add(containerIndex);
4462
- } else if (state.stickyContainerPool.has(containerIndex)) {
4662
+ } else {
4463
4663
  state.stickyContainerPool.delete(containerIndex);
4464
4664
  }
4465
4665
  }
@@ -4474,20 +4674,8 @@ function calculateItemsInView(ctx, params = {}) {
4474
4674
  }
4475
4675
  }
4476
4676
  }
4477
- if (state.userScrollAnchorReset) {
4478
- if (state.userScrollAnchorReset.keys.size === 0) {
4479
- state.userScrollAnchorReset = void 0;
4480
- }
4481
- }
4482
- if (alwaysRenderArr.length > 0) {
4483
- for (const index of alwaysRenderArr) {
4484
- if (index < 0 || index >= dataLength) continue;
4485
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
4486
- const containerIndex = containerItemKeys.get(id);
4487
- if (containerIndex !== void 0) {
4488
- state.stickyContainerPool.add(containerIndex);
4489
- }
4490
- }
4677
+ if (((_r = state.userScrollAnchorReset) == null ? void 0 : _r.keys.size) === 0) {
4678
+ state.userScrollAnchorReset = void 0;
4491
4679
  }
4492
4680
  }
4493
4681
  if (state.stickyContainerPool.size > 0) {
@@ -4498,7 +4686,7 @@ function calculateItemsInView(ctx, params = {}) {
4498
4686
  drawDistance,
4499
4687
  (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
4500
4688
  pendingRemoval,
4501
- alwaysRenderSet
4689
+ isPinnedRenderIndex
4502
4690
  );
4503
4691
  }
4504
4692
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
@@ -4657,6 +4845,36 @@ function mergeItemSizeUpdateResult(result, next) {
4657
4845
  result.needsRecalculate || (result.needsRecalculate = next.needsRecalculate);
4658
4846
  result.shouldMaintainScrollAtEnd || (result.shouldMaintainScrollAtEnd = next.shouldMaintainScrollAtEnd);
4659
4847
  }
4848
+ var batchedItemSizeRecalculates = /* @__PURE__ */ new WeakMap();
4849
+ function flushBatchedItemSizeRecalculate(ctx, didFallback = false, expectedResult) {
4850
+ const result = batchedItemSizeRecalculates.get(ctx);
4851
+ if (!result || expectedResult && result !== expectedResult) {
4852
+ return;
4853
+ }
4854
+ batchedItemSizeRecalculates.delete(ctx);
4855
+ if (didFallback) {
4856
+ ctx.state.pendingLayoutEffectMeasurements = void 0;
4857
+ }
4858
+ flushItemSizeUpdates(ctx, result);
4859
+ }
4860
+ function queueItemSizeRecalculate(ctx, result) {
4861
+ var _a3, _b;
4862
+ const batch = batchedItemSizeRecalculates.get(ctx);
4863
+ if (batch) {
4864
+ mergeItemSizeUpdateResult(batch, result);
4865
+ } else {
4866
+ const nextBatch = { ...result };
4867
+ batchedItemSizeRecalculates.set(ctx, nextBatch);
4868
+ if ((_a3 = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _a3.size) {
4869
+ requestAnimationFrame(() => {
4870
+ flushBatchedItemSizeRecalculate(ctx, true, nextBatch);
4871
+ });
4872
+ }
4873
+ }
4874
+ if (!((_b = ctx.state.pendingLayoutEffectMeasurements) == null ? void 0 : _b.size)) {
4875
+ flushBatchedItemSizeRecalculate(ctx);
4876
+ }
4877
+ }
4660
4878
  function flushItemSizeUpdates(ctx, result) {
4661
4879
  var _a3;
4662
4880
  const state = ctx.state;
@@ -4673,12 +4891,22 @@ function flushItemSizeUpdates(ctx, result) {
4673
4891
  function updateItemSizes(ctx, measurement) {
4674
4892
  var _a3, _b, _c, _d;
4675
4893
  const state = ctx.state;
4894
+ let didDrainLayoutEffectMeasurements = false;
4895
+ if (measurement.fromLayoutEffect) {
4896
+ const pendingLayoutEffectMeasurements = state.pendingLayoutEffectMeasurements;
4897
+ if ((pendingLayoutEffectMeasurements == null ? void 0 : pendingLayoutEffectMeasurements.delete(measurement.itemKey)) && pendingLayoutEffectMeasurements.size === 0) {
4898
+ state.pendingLayoutEffectMeasurements = void 0;
4899
+ didDrainLayoutEffectMeasurements = true;
4900
+ }
4901
+ }
4676
4902
  const pendingKeys = (_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys;
4677
4903
  const shouldBatchPendingMeasurements = measurement.fromLayoutEffect && !!(pendingKeys == null ? void 0 : pendingKeys.size);
4904
+ const shouldQueueRecalculate = !!measurement.fromLayoutEffect;
4905
+ let result;
4678
4906
  if (!shouldBatchPendingMeasurements) {
4679
- flushItemSizeUpdates(ctx, applyItemSize(ctx, measurement.itemKey, measurement.size));
4907
+ result = applyItemSize(ctx, measurement.itemKey, measurement.size);
4680
4908
  } else {
4681
- const result = {};
4909
+ result = {};
4682
4910
  const updateContainerItemSize = (itemKey, containerId, size) => {
4683
4911
  if (containerId !== void 0 && peek$(ctx, `containerItemKey${containerId}`) === itemKey) {
4684
4912
  mergeItemSizeUpdateResult(result, applyItemSize(ctx, itemKey, size));
@@ -4696,8 +4924,15 @@ function updateItemSizes(ctx, measurement) {
4696
4924
  });
4697
4925
  }
4698
4926
  }
4927
+ }
4928
+ if (shouldQueueRecalculate && result.needsRecalculate) {
4929
+ queueItemSizeRecalculate(ctx, result);
4930
+ } else {
4699
4931
  flushItemSizeUpdates(ctx, result);
4700
4932
  }
4933
+ if (didDrainLayoutEffectMeasurements) {
4934
+ flushBatchedItemSizeRecalculate(ctx);
4935
+ }
4701
4936
  }
4702
4937
  function applyItemSize(ctx, itemKey, sizeObj) {
4703
4938
  var _a3;
@@ -7067,7 +7302,7 @@ var LegendList = typedMemo(
7067
7302
  })
7068
7303
  );
7069
7304
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
7070
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
7305
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
7071
7306
  const noopOnScroll = React3.useCallback((_event) => {
7072
7307
  }, []);
7073
7308
  if (props.recycleItems === void 0) {
@@ -7084,6 +7319,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7084
7319
  contentContainerStyle: contentContainerStyleProp,
7085
7320
  contentInset,
7086
7321
  data: dataProp = [],
7322
+ dataKey,
7087
7323
  dataVersion,
7088
7324
  drawDistance = 250,
7089
7325
  contentInsetEndAdjustment,
@@ -7214,6 +7450,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7214
7450
  (_j = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _j.join(","),
7215
7451
  (_k = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _k.join(","),
7216
7452
  dataProp,
7453
+ dataKey,
7217
7454
  dataVersion,
7218
7455
  keyExtractor
7219
7456
  ]);
@@ -7236,6 +7473,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7236
7473
  dataChangeNeedsScrollUpdate: false,
7237
7474
  didColumnsChange: false,
7238
7475
  didDataChange: false,
7476
+ didLoad: false,
7239
7477
  enableScrollForNextCalculateItemsInView: true,
7240
7478
  endBuffered: -1,
7241
7479
  endNoBuffer: -1,
@@ -7265,6 +7503,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7265
7503
  positions: [],
7266
7504
  props: {},
7267
7505
  queuedCalculateItemsInView: 0,
7506
+ queuedFullDrawDistancePrewarm: void 0,
7268
7507
  refScroller: { current: null },
7269
7508
  scroll: 0,
7270
7509
  scrollAdjustHandler: new ScrollAdjustHandler(ctx),
@@ -7307,10 +7546,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7307
7546
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7308
7547
  ctx.scrollAxisGap = nextScrollAxisGap;
7309
7548
  state.didColumnsChange = numColumnsProp !== previousNumColumnsProp || didScrollAxisGapChange;
7549
+ const previousDataLength = (_n = (_m = state.props.data) == null ? void 0 : _m.length) != null ? _n : 0;
7310
7550
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
7551
+ const didDataKeyChangeLocal = state.props.dataKey !== dataKey;
7311
7552
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
7312
- const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
7313
- if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_m = state.initialScroll) == null ? void 0 : _m.viewPosition) === 1 && state.props.data.length > 0) {
7553
+ const didDataChangeLocal = didDataKeyChangeLocal || didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
7554
+ const shouldResetFreshDataLayout = !isFirstLocal && didDataChangeLocal && state.hasHadNonEmptyData && (didDataKeyChangeLocal || previousDataLength === 0) && dataProp.length > 0;
7555
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_o = state.initialScroll) == null ? void 0 : _o.viewPosition) === 1 && state.props.data.length > 0) {
7314
7556
  clearPreservedInitialScrollTarget(state);
7315
7557
  }
7316
7558
  if (didDataChangeLocal) {
@@ -7322,7 +7564,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7322
7564
  const throttledOnScroll = useThrottledOnScroll(onScrollProp != null ? onScrollProp : noopOnScroll, scrollEventThrottle != null ? scrollEventThrottle : 0);
7323
7565
  const throttleScrollFn = scrollEventThrottle && onScrollProp ? throttledOnScroll : onScrollProp;
7324
7566
  const anchoredEndSpaceResolved = anchoredEndSpace ? { ...anchoredEndSpace, includeInEndInset: true } : anchoredEndSpace;
7325
- const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_n = state.props.anchoredEndSpace) == null ? void 0 : _n.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
7567
+ const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_p = state.props.anchoredEndSpace) == null ? void 0 : _p.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
7326
7568
  state.props = {
7327
7569
  adaptiveRender: experimental_adaptiveRender,
7328
7570
  alignItemsAtEnd,
@@ -7336,6 +7578,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7336
7578
  contentInset,
7337
7579
  contentInsetEndAdjustment: contentInsetEndAdjustmentResolved,
7338
7580
  data: dataProp,
7581
+ dataKey,
7339
7582
  dataVersion,
7340
7583
  drawDistance,
7341
7584
  estimatedItemSize,
@@ -7376,13 +7619,19 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7376
7619
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
7377
7620
  resetAdaptiveRender(ctx);
7378
7621
  }
7622
+ if (shouldResetFreshDataLayout) {
7623
+ resetInitialRenderState(ctx, {
7624
+ resetInitialScroll: !!initialScrollProp,
7625
+ resetLayout: true
7626
+ });
7627
+ }
7379
7628
  const memoizedLastItemKeys = React3.useMemo(() => {
7380
7629
  if (!dataProp.length) return [];
7381
7630
  return Array.from(
7382
7631
  { length: Math.min(numColumnsProp, dataProp.length) },
7383
7632
  (_, i) => getId(state, dataProp.length - 1 - i)
7384
7633
  );
7385
- }, [dataProp, dataVersion, numColumnsProp]);
7634
+ }, [dataProp, dataKey, dataVersion, numColumnsProp]);
7386
7635
  const initializeStateVars = (shouldAdjustPadding) => {
7387
7636
  set$(ctx, "lastItemKeys", memoizedLastItemKeys);
7388
7637
  set$(ctx, "numColumns", numColumnsProp);
@@ -7437,13 +7686,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7437
7686
  handleInitialScrollDataChange(ctx, {
7438
7687
  dataLength: dataProp.length,
7439
7688
  didDataChange: didDataChangeLocal,
7689
+ didStartFreshData: shouldResetFreshDataLayout,
7440
7690
  initialScrollAtEnd,
7441
7691
  latestInitialScroll: initialScrollProp,
7442
7692
  latestInitialScrollSessionKind: initialScrollUsesOffsetOnly ? "offset" : "bootstrap",
7443
7693
  stylePaddingBottom: stylePaddingBottomState,
7444
7694
  useBootstrapInitialScroll: usesBootstrapInitialScroll
7445
7695
  });
7446
- }, [dataProp.length, didDataChangeLocal, initialScrollAtEnd, stylePaddingBottomState, usesBootstrapInitialScroll]);
7696
+ }, [
7697
+ dataProp.length,
7698
+ dataKey,
7699
+ didDataChangeLocal,
7700
+ shouldResetFreshDataLayout,
7701
+ initialScrollAtEnd,
7702
+ stylePaddingBottomState,
7703
+ usesBootstrapInitialScroll
7704
+ ]);
7447
7705
  React3.useLayoutEffect(() => {
7448
7706
  var _a4;
7449
7707
  if (didAnchoredEndSpaceAnchorIndexChange) {
@@ -7511,6 +7769,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7511
7769
  React3.useLayoutEffect(
7512
7770
  () => initializeStateVars(true),
7513
7771
  [
7772
+ dataKey,
7514
7773
  dataVersion,
7515
7774
  memoizedLastItemKeys.join(","),
7516
7775
  numColumnsProp,
@@ -7537,7 +7796,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7537
7796
  state.didColumnsChange = false;
7538
7797
  state.didDataChange = false;
7539
7798
  state.isFirst = false;
7540
- }, [dataProp, dataVersion, numColumnsProp, nextScrollAxisGap]);
7799
+ }, [dataProp, dataKey, dataVersion, numColumnsProp, nextScrollAxisGap]);
7541
7800
  React3.useLayoutEffect(() => {
7542
7801
  var _a4;
7543
7802
  set$(ctx, "extraData", extraData);
@@ -7587,6 +7846,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7587
7846
  React3.useImperativeHandle(forwardedRef, () => createImperativeHandle(ctx, scheduleImperativeScrollCommit), []);
7588
7847
  React3.useEffect(() => {
7589
7848
  return () => {
7849
+ if (state.queuedFullDrawDistancePrewarm !== void 0) {
7850
+ cancelAnimationFrame(state.queuedFullDrawDistancePrewarm);
7851
+ state.queuedFullDrawDistancePrewarm = void 0;
7852
+ }
7590
7853
  for (const timeout of state.timeouts) {
7591
7854
  clearTimeout(timeout);
7592
7855
  }
@@ -7639,7 +7902,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7639
7902
  onScroll: onScrollHandler,
7640
7903
  recycleItems,
7641
7904
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React3__namespace.cloneElement(refreshControlElement, {
7642
- progressViewOffset: ((_o = refreshControlElement.props.progressViewOffset) != null ? _o : 0) + stylePaddingTopState
7905
+ progressViewOffset: ((_q = refreshControlElement.props.progressViewOffset) != null ? _q : 0) + stylePaddingTopState
7643
7906
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React3__namespace.createElement(
7644
7907
  RefreshControl,
7645
7908
  {
@@ -7650,7 +7913,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7650
7913
  ),
7651
7914
  refScrollView: combinedRef,
7652
7915
  renderScrollComponent,
7653
- scrollAdjustHandler: (_p = refState.current) == null ? void 0 : _p.scrollAdjustHandler,
7916
+ scrollAdjustHandler: (_r = refState.current) == null ? void 0 : _r.scrollAdjustHandler,
7654
7917
  scrollEventThrottle: 0,
7655
7918
  snapToIndices,
7656
7919
  stickyHeaderIndices,