@legendapp/list 3.2.0 → 3.3.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 3.3.0
2
+
3
+ - Feat: Add `dataKey` so apps can tell the list when the current array represents a different dataset, such as switching conversations or feeds without remounting `LegendList`.
4
+ - Feat: `experimental_adaptiveRender.onChange` now receives a reason (`initial`, `ready`, or `scroll`) so item components can tell why the render mode changed.
5
+ - Fix: Large user scroll jumps render the visible rows first and fill the normal draw distance on the next frame, making long jumps less likely to land on a blank range.
6
+ - Fix: `maintainScrollAtEnd` reruns a pending keep-at-end request after the current one settles, so a footer resize and a new message arriving together keep chat-style lists pinned to the end.
7
+ - Fix: Replacing data after clearing the list, or changing `dataKey` for a non-empty replacement, resets layout readiness and uses the latest initial scroll target for the new dataset.
8
+
1
9
  ## 3.2.0
2
10
 
3
11
  - Feat: Add `onFirstVisibleItemChanged` so apps can track the item at the top of the viewport without setting up full viewability callbacks.
package/animated.d.ts CHANGED
@@ -92,6 +92,7 @@ interface NativeSyntheticEvent<T> {
92
92
  type ViewStyle = Record<string, unknown>;
93
93
  type StyleProp<T> = T | T[] | null | undefined | false;
94
94
  type AdaptiveRender = "normal" | "light";
95
+ type AdaptiveRenderChangeReason = "initial" | "ready" | "scroll";
95
96
  interface AdaptiveRenderConfig {
96
97
  /**
97
98
  * Mode to use before the list is ready to render.
@@ -116,7 +117,7 @@ interface AdaptiveRenderConfig {
116
117
  /**
117
118
  * Called when the list-level adaptive render changes.
118
119
  */
119
- onChange?: (mode: AdaptiveRender) => void;
120
+ onChange?: (mode: AdaptiveRender, reason: AdaptiveRenderChangeReason) => void;
120
121
  }
121
122
  type BaseScrollViewProps<TScrollView> = Omit<TScrollView, "contentOffset" | "maintainVisibleContentPosition" | "stickyHeaderIndices" | "removeClippedSubviews" | "children" | "onScroll">;
122
123
  interface DataModeProps<ItemT, TItemType extends string | undefined> {
@@ -158,6 +159,11 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
158
159
  * Style applied to each column's wrapper view.
159
160
  */
160
161
  columnWrapperStyle?: ColumnWrapperStyle;
162
+ /**
163
+ * Identity token for the dataset represented by `data`.
164
+ * Change this when replacing the current dataset with a different logical dataset.
165
+ */
166
+ dataKey?: Key;
161
167
  /**
162
168
  * Version token that forces the list to treat data as updated even when the array reference is stable.
163
169
  * Increment or change this when mutating the data array in place.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
5
  "sideEffects": false,
6
6
  "private": false,
package/react-native.d.ts CHANGED
@@ -92,6 +92,7 @@ interface NativeSyntheticEvent<T> {
92
92
  type ViewStyle = Record<string, unknown>;
93
93
  type StyleProp<T> = T | T[] | null | undefined | false;
94
94
  type AdaptiveRender = "normal" | "light";
95
+ type AdaptiveRenderChangeReason = "initial" | "ready" | "scroll";
95
96
  interface AdaptiveRenderConfig {
96
97
  /**
97
98
  * Mode to use before the list is ready to render.
@@ -116,7 +117,7 @@ interface AdaptiveRenderConfig {
116
117
  /**
117
118
  * Called when the list-level adaptive render changes.
118
119
  */
119
- onChange?: (mode: AdaptiveRender) => void;
120
+ onChange?: (mode: AdaptiveRender, reason: AdaptiveRenderChangeReason) => void;
120
121
  }
121
122
  type BaseScrollViewProps<TScrollView> = Omit<TScrollView, "contentOffset" | "maintainVisibleContentPosition" | "stickyHeaderIndices" | "removeClippedSubviews" | "children" | "onScroll">;
122
123
  interface DataModeProps<ItemT, TItemType extends string | undefined> {
@@ -158,6 +159,11 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
158
159
  * Style applied to each column's wrapper view.
159
160
  */
160
161
  columnWrapperStyle?: ColumnWrapperStyle;
162
+ /**
163
+ * Identity token for the dataset represented by `data`.
164
+ * Change this when replacing the current dataset with a different logical dataset.
165
+ */
166
+ dataKey?: Key;
161
167
  /**
162
168
  * Version token that forces the list to treat data as updated even when the array reference is stable.
163
169
  * Increment or change this when mutating the data array in place.
@@ -764,4 +770,4 @@ declare function useSyncLayout(): () => void;
764
770
 
765
771
  declare const LegendList: LegendListComponent;
766
772
 
767
- export { type AdaptiveRender, type AdaptiveRenderConfig, type AlwaysRenderConfig, type ColumnWrapperStyle, type Insets, type LayoutRectangle, LegendList, type LegendListAverageItemSize, type LegendListComponent, type LegendListMetrics, type LegendListProps, type LegendListRecyclingState, type LegendListRef, type LegendListRenderItemProps, type LegendListState, type MaintainScrollAtEndOnOptions, type MaintainScrollAtEndOptions, type MaintainVisibleContentPositionConfig, type NativeScrollEvent, type NativeSyntheticEvent, type OnViewableItemsChanged, type OnViewableItemsChangedInfo, type ScrollIndexWithOffset, type ScrollIndexWithOffsetAndContentOffset, type ScrollIndexWithOffsetPosition, type StickyHeaderConfig, type StyleProp, type ViewAmountToken, type ViewStyle, type ViewToken, type ViewabilityAmountCallback, type ViewabilityCallback, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, useAdaptiveRender, useAdaptiveRenderChange, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };
773
+ export { type AdaptiveRender, type AdaptiveRenderChangeReason, type AdaptiveRenderConfig, type AlwaysRenderConfig, type ColumnWrapperStyle, type Insets, type LayoutRectangle, LegendList, type LegendListAverageItemSize, type LegendListComponent, type LegendListMetrics, type LegendListProps, type LegendListRecyclingState, type LegendListRef, type LegendListRenderItemProps, type LegendListState, type MaintainScrollAtEndOnOptions, type MaintainScrollAtEndOptions, type MaintainVisibleContentPositionConfig, type NativeScrollEvent, type NativeSyntheticEvent, type OnViewableItemsChanged, type OnViewableItemsChangedInfo, type ScrollIndexWithOffset, type ScrollIndexWithOffsetAndContentOffset, type ScrollIndexWithOffsetPosition, type StickyHeaderConfig, type StyleProp, type ViewAmountToken, type ViewStyle, type ViewToken, type ViewabilityAmountCallback, type ViewabilityCallback, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, useAdaptiveRender, useAdaptiveRenderChange, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };
package/react-native.js CHANGED
@@ -1116,32 +1116,34 @@ function scheduleAdaptiveRenderExit(ctx, exitDelay) {
1116
1116
  const state = ctx.state;
1117
1117
  clearAdaptiveRenderExitTimeout(ctx);
1118
1118
  if (exitDelay <= 0) {
1119
- setAdaptiveRender(ctx, "normal");
1119
+ setAdaptiveRender(ctx, "normal", "scroll");
1120
1120
  } else {
1121
1121
  const timeout = setTimeout(() => {
1122
1122
  state.timeouts.delete(timeout);
1123
1123
  state.timeoutAdaptiveRender = void 0;
1124
- setAdaptiveRender(ctx, "normal");
1124
+ setAdaptiveRender(ctx, "normal", "scroll");
1125
1125
  }, exitDelay);
1126
1126
  state.timeoutAdaptiveRender = timeout;
1127
1127
  state.timeouts.add(timeout);
1128
1128
  }
1129
1129
  }
1130
- function setAdaptiveRender(ctx, mode) {
1130
+ function setAdaptiveRender(ctx, mode, reason) {
1131
1131
  var _a3, _b;
1132
1132
  const previousMode = peek$(ctx, "adaptiveRender");
1133
1133
  if (previousMode !== mode) {
1134
1134
  set$(ctx, "adaptiveRender", mode);
1135
- (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.onChange) == null ? void 0 : _b.call(_a3, mode);
1135
+ (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.onChange) == null ? void 0 : _b.call(_a3, mode, reason);
1136
1136
  }
1137
1137
  }
1138
1138
  function resetAdaptiveRender(ctx) {
1139
+ var _a3, _b;
1139
1140
  clearAdaptiveRenderExitTimeout(ctx);
1140
- if (peek$(ctx, "adaptiveRender") !== "normal") {
1141
- setAdaptiveRender(ctx, "normal");
1141
+ const mode = (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.initialMode) != null ? _b : "normal";
1142
+ if (peek$(ctx, "adaptiveRender") !== mode) {
1143
+ setAdaptiveRender(ctx, mode, "initial");
1142
1144
  }
1143
1145
  }
1144
- function updateAdaptiveRender(ctx, scrollVelocity) {
1146
+ function updateAdaptiveRender(ctx, scrollVelocity, options) {
1145
1147
  var _a3, _b, _c;
1146
1148
  const state = ctx.state;
1147
1149
  const adaptiveRender = state.props.adaptiveRender;
@@ -1153,11 +1155,11 @@ function updateAdaptiveRender(ctx, scrollVelocity) {
1153
1155
  const exitVelocity = (_b = adaptiveRender.exitVelocity) != null ? _b : isWeb ? DEFAULT_WEB_ADAPTIVE_RENDER_EXIT_VELOCITY : DEFAULT_ADAPTIVE_RENDER_EXIT_VELOCITY;
1154
1156
  const exitDelay = (_c = adaptiveRender.exitDelay) != null ? _c : isWeb ? DEFAULT_WEB_ADAPTIVE_RENDER_EXIT_DELAY : DEFAULT_ADAPTIVE_RENDER_EXIT_DELAY;
1155
1157
  const threshold = currentMode === "light" ? exitVelocity : enterVelocity;
1156
- const nextMode = Math.abs(scrollVelocity) > threshold ? "light" : "normal";
1158
+ const nextMode = (options == null ? void 0 : options.forceLight) || Math.abs(scrollVelocity) > threshold ? "light" : "normal";
1157
1159
  const previousMode = state.timeoutAdaptiveRender !== void 0 ? "normal" : currentMode;
1158
1160
  if (nextMode !== previousMode) {
1159
1161
  if (nextMode === "light") {
1160
- setAdaptiveRender(ctx, "light");
1162
+ setAdaptiveRender(ctx, "light", "scroll");
1161
1163
  scheduleAdaptiveRenderExit(ctx, exitDelay);
1162
1164
  } else if (currentMode === "light") {
1163
1165
  scheduleAdaptiveRenderExit(ctx, exitDelay);
@@ -1170,16 +1172,43 @@ function updateAdaptiveRender(ctx, scrollVelocity) {
1170
1172
  }
1171
1173
 
1172
1174
  // src/utils/getEffectiveDrawDistance.ts
1173
- var INITIAL_DRAW_DISTANCE = 100;
1174
- function getEffectiveDrawDistance(ctx) {
1175
+ var INITIAL_DRAW_DISTANCE = 50;
1176
+ function getEffectiveDrawDistance(ctx, mode) {
1175
1177
  var _a3;
1176
1178
  const drawDistance = ctx.state.props.drawDistance;
1177
1179
  const initialScroll = ctx.state.initialScroll;
1178
1180
  const needsFullInitialDrawDistance = initialScroll !== void 0 && ((_a3 = initialScroll.viewPosition) != null ? _a3 : 0) > 0;
1179
- return peek$(ctx, "readyToRender") || needsFullInitialDrawDistance ? drawDistance : Math.min(drawDistance, INITIAL_DRAW_DISTANCE);
1181
+ const shouldCapDrawDistance = mode === "visible-first" || mode !== "full" && !peek$(ctx, "readyToRender") && !needsFullInitialDrawDistance;
1182
+ return shouldCapDrawDistance ? Math.min(drawDistance, INITIAL_DRAW_DISTANCE) : drawDistance;
1183
+ }
1184
+ function scheduleFullDrawDistancePrewarm(ctx) {
1185
+ const { state } = ctx;
1186
+ if (state.props.drawDistance <= INITIAL_DRAW_DISTANCE || state.queuedFullDrawDistancePrewarm !== void 0) {
1187
+ return;
1188
+ }
1189
+ state.queuedFullDrawDistancePrewarm = requestAnimationFrame(() => {
1190
+ var _a3;
1191
+ state.queuedFullDrawDistancePrewarm = void 0;
1192
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
1193
+ });
1180
1194
  }
1181
1195
 
1182
1196
  // src/utils/setInitialRenderState.ts
1197
+ function resetInitialRenderState(ctx, {
1198
+ resetLayout,
1199
+ resetInitialScroll
1200
+ }) {
1201
+ const { state } = ctx;
1202
+ if (resetLayout) {
1203
+ state.didContainersLayout = false;
1204
+ state.queuedInitialLayout = false;
1205
+ }
1206
+ if (resetInitialScroll) {
1207
+ state.didFinishInitialScroll = false;
1208
+ }
1209
+ set$(ctx, "readyToRender", false);
1210
+ resetAdaptiveRender(ctx);
1211
+ }
1183
1212
  function setInitialRenderState(ctx, {
1184
1213
  didLayout,
1185
1214
  didInitialScroll
@@ -1198,15 +1227,15 @@ function setInitialRenderState(ctx, {
1198
1227
  const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll);
1199
1228
  if (isReadyToRender && !peek$(ctx, "readyToRender")) {
1200
1229
  set$(ctx, "readyToRender", true);
1201
- setAdaptiveRender(ctx, "normal");
1230
+ setAdaptiveRender(ctx, "normal", "ready");
1202
1231
  if (state.props.drawDistance > INITIAL_DRAW_DISTANCE) {
1203
- requestAnimationFrame(() => {
1204
- var _a3;
1205
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
1206
- });
1232
+ scheduleFullDrawDistancePrewarm(ctx);
1207
1233
  }
1208
- if (onLoad) {
1209
- onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1234
+ if (!state.didLoad) {
1235
+ state.didLoad = true;
1236
+ if (onLoad) {
1237
+ onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1238
+ }
1210
1239
  }
1211
1240
  }
1212
1241
  }
@@ -1735,8 +1764,8 @@ function doMaintainScrollAtEnd(ctx) {
1735
1764
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1736
1765
  return false;
1737
1766
  }
1738
- state.pendingMaintainScrollAtEnd = false;
1739
1767
  if (shouldMaintainScrollAtEnd) {
1768
+ state.pendingMaintainScrollAtEnd = false;
1740
1769
  const contentSize = getContentSize(ctx);
1741
1770
  if (contentSize < state.scrollLength) {
1742
1771
  state.scroll = 0;
@@ -1767,6 +1796,9 @@ function doMaintainScrollAtEnd(ctx) {
1767
1796
  () => {
1768
1797
  if (state.maintainingScrollAtEnd === activeState) {
1769
1798
  state.maintainingScrollAtEnd = void 0;
1799
+ if (state.pendingMaintainScrollAtEnd) {
1800
+ doMaintainScrollAtEnd(ctx);
1801
+ }
1770
1802
  }
1771
1803
  },
1772
1804
  maintainScrollAtEnd.animated ? 500 : 0
@@ -1775,9 +1807,12 @@ function doMaintainScrollAtEnd(ctx) {
1775
1807
  state.maintainingScrollAtEnd = void 0;
1776
1808
  }
1777
1809
  });
1810
+ } else {
1811
+ state.pendingMaintainScrollAtEnd = true;
1778
1812
  }
1779
1813
  return true;
1780
1814
  }
1815
+ state.pendingMaintainScrollAtEnd = false;
1781
1816
  return false;
1782
1817
  }
1783
1818
 
@@ -2201,8 +2236,6 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2201
2236
  if (scrollHistory.length > 5) {
2202
2237
  scrollHistory.shift();
2203
2238
  }
2204
- const scrollVelocity = getScrollVelocity(state);
2205
- updateAdaptiveRender(ctx, scrollVelocity);
2206
2239
  if (ignoreScrollFromMVCP && !scrollingTo) {
2207
2240
  const { lt, gt } = ignoreScrollFromMVCP;
2208
2241
  if (lt && newScroll < lt || gt && newScroll > gt) {
@@ -2217,6 +2250,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2217
2250
  const scrollDelta = Math.abs(newScroll - prevScroll);
2218
2251
  const didResolvePendingNativeMVCPAdjust = resolvePendingNativeMVCPAdjust(ctx, newScroll);
2219
2252
  const scrollLength = state.scrollLength;
2253
+ const isLargeUserScrollJump = scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust;
2254
+ const scrollVelocity = getScrollVelocity(state);
2255
+ updateAdaptiveRender(ctx, scrollVelocity, { forceLight: isLargeUserScrollJump });
2220
2256
  const lastCalculated = state.scrollLastCalculate;
2221
2257
  const useAggressiveItemRecalculation = isInMVCPActiveMode(state);
2222
2258
  const shouldUpdate = useAggressiveItemRecalculation || didResolvePendingNativeMVCPAdjust || forceUpdate || lastCalculated === void 0 || Math.abs(state.scroll - lastCalculated) > 2;
@@ -2226,10 +2262,17 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2226
2262
  state.lastScrollDelta = scrollDelta;
2227
2263
  const runCalculateItems = () => {
2228
2264
  var _a4;
2229
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0, scrollVelocity });
2265
+ const calculateItemsParams = {
2266
+ doMVCP: scrollingTo !== void 0,
2267
+ scrollVelocity
2268
+ };
2269
+ if (isLargeUserScrollJump) {
2270
+ calculateItemsParams.drawDistanceMode = "visible-first";
2271
+ }
2272
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, calculateItemsParams);
2230
2273
  checkThresholds(ctx);
2231
2274
  };
2232
- if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
2275
+ if (isLargeUserScrollJump) {
2233
2276
  state.mvcpAnchorLock = void 0;
2234
2277
  state.pendingNativeMVCPAdjust = void 0;
2235
2278
  state.userScrollAnchorReset = { keys: /* @__PURE__ */ new Set() };
@@ -2238,6 +2281,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2238
2281
  state.queuedMVCPRecalculate = void 0;
2239
2282
  }
2240
2283
  flushSync(runCalculateItems);
2284
+ scheduleFullDrawDistancePrewarm(ctx);
2241
2285
  } else {
2242
2286
  runCalculateItems();
2243
2287
  }
@@ -2392,16 +2436,17 @@ function dispatchInitialScroll(ctx, params) {
2392
2436
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2393
2437
  });
2394
2438
  }
2395
- function setInitialScrollTarget(state, target, options) {
2439
+ function setInitialScrollTarget(ctx, target, options) {
2396
2440
  var _a3;
2441
+ const { state } = ctx;
2397
2442
  state.clearPreservedInitialScrollOnNextFinish = void 0;
2398
2443
  if (state.timeoutPreservedInitialScrollClear !== void 0) {
2399
2444
  clearTimeout(state.timeoutPreservedInitialScrollClear);
2400
2445
  state.timeoutPreservedInitialScrollClear = void 0;
2401
2446
  }
2402
2447
  state.initialScroll = target;
2403
- if ((options == null ? void 0 : options.resetDidFinish) && state.didFinishInitialScroll) {
2404
- state.didFinishInitialScroll = false;
2448
+ if (options == null ? void 0 : options.resetDidFinish) {
2449
+ resetInitialRenderState(ctx, { resetInitialScroll: true });
2405
2450
  }
2406
2451
  setInitialScrollSession(state, {
2407
2452
  kind: ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" ? "offset" : "bootstrap"
@@ -2455,7 +2500,7 @@ function advanceMeasuredInitialScroll(ctx, options) {
2455
2500
  return false;
2456
2501
  }
2457
2502
  if (didOffsetChange && ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) !== "offset") {
2458
- setInitialScrollTarget(state, { ...initialScroll, contentOffset: resolvedOffset });
2503
+ setInitialScrollTarget(ctx, { ...initialScroll, contentOffset: resolvedOffset });
2459
2504
  }
2460
2505
  const forceScroll = (_c = options == null ? void 0 : options.forceScroll) != null ? _c : !!queuedInitialLayout || isInitialScrollInProgress && didOffsetChange;
2461
2506
  dispatchInitialScroll(ctx, {
@@ -2728,7 +2773,6 @@ function areEquivalentBootstrapInitialScrollTargets(current, next) {
2728
2773
  }
2729
2774
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2730
2775
  const { dataLength, stylePaddingBottom, target } = options;
2731
- const state = ctx.state;
2732
2776
  if (!shouldPreserveInitialScrollForFooterLayout(target)) {
2733
2777
  return;
2734
2778
  }
@@ -2738,7 +2782,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2738
2782
  preserveForFooterLayout: void 0,
2739
2783
  stylePaddingBottom
2740
2784
  });
2741
- setInitialScrollTarget(state, clearedFooterTarget);
2785
+ setInitialScrollTarget(ctx, clearedFooterTarget);
2742
2786
  }
2743
2787
  function clearFinishedViewportRetargetableInitialScroll(state) {
2744
2788
  clearPreservedInitialScrollTarget(state);
@@ -2885,7 +2929,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2885
2929
  return;
2886
2930
  }
2887
2931
  if (!areEquivalentBootstrapInitialScrollTargets(initialScroll, updatedInitialScroll) || !!bootstrapInitialScroll || shouldResetDidFinish || didDataChange) {
2888
- setInitialScrollTarget(state, updatedInitialScroll, {
2932
+ setInitialScrollTarget(ctx, updatedInitialScroll, {
2889
2933
  resetDidFinish: shouldResetDidFinish
2890
2934
  });
2891
2935
  rearmBootstrapInitialScroll(ctx, {
@@ -2900,7 +2944,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2900
2944
  return;
2901
2945
  }
2902
2946
  if (bootstrapInitialScroll || shouldResetDidFinish) {
2903
- setInitialScrollTarget(state, initialScroll, {
2947
+ setInitialScrollTarget(ctx, initialScroll, {
2904
2948
  resetDidFinish: shouldResetDidFinish
2905
2949
  });
2906
2950
  rearmBootstrapInitialScroll(ctx, {
@@ -2946,7 +2990,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2946
2990
  });
2947
2991
  } else {
2948
2992
  const didFinishInitialScroll = !!state.didFinishInitialScroll;
2949
- setInitialScrollTarget(state, updatedInitialScroll, {
2993
+ setInitialScrollTarget(ctx, updatedInitialScroll, {
2950
2994
  resetDidFinish: didFinishInitialScroll
2951
2995
  });
2952
2996
  rearmBootstrapInitialScroll(ctx, {
@@ -3135,7 +3179,7 @@ function initializeInitialScrollOnMount(ctx, options) {
3135
3179
  const resolvedInitialContentOffset = initialContentOffset != null ? initialContentOffset : 0;
3136
3180
  const preserveForFooterLayout = useBootstrapInitialScroll && initialScrollAtEnd && hasFooterComponent;
3137
3181
  if (initialScroll && (initialScroll.contentOffset === void 0 || !!initialScroll.preserveForFooterLayout !== preserveForFooterLayout && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) !== "offset")) {
3138
- setInitialScrollTarget(state, {
3182
+ setInitialScrollTarget(ctx, {
3139
3183
  ...initialScroll,
3140
3184
  contentOffset: resolvedInitialContentOffset,
3141
3185
  preserveForFooterLayout
@@ -3164,6 +3208,7 @@ function handleInitialScrollDataChange(ctx, options) {
3164
3208
  const {
3165
3209
  dataLength,
3166
3210
  didDataChange,
3211
+ didStartFreshData,
3167
3212
  initialScrollAtEnd,
3168
3213
  latestInitialScroll,
3169
3214
  latestInitialScrollSessionKind,
@@ -3171,17 +3216,17 @@ function handleInitialScrollDataChange(ctx, options) {
3171
3216
  useBootstrapInitialScroll
3172
3217
  } = options;
3173
3218
  const state = ctx.state;
3174
- const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3175
- const isFirstNonEmptyData = !state.hasHadNonEmptyData && dataLength > 0;
3219
+ const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3220
+ const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3176
3221
  if (dataLength > 0) {
3177
3222
  state.hasHadNonEmptyData = true;
3178
3223
  }
3179
- if (isFirstNonEmptyData) {
3224
+ if (shouldUseLatestInitialScroll) {
3180
3225
  if (latestInitialScroll) {
3181
- setInitialScrollTarget(state, latestInitialScroll);
3226
+ setInitialScrollTarget(ctx, latestInitialScroll);
3182
3227
  setInitialScrollSession(state, {
3183
3228
  kind: latestInitialScrollSessionKind,
3184
- previousDataLength
3229
+ previousDataLength: previousInitialScrollDataLength
3185
3230
  });
3186
3231
  } else {
3187
3232
  clearPreservedInitialScrollTarget(state);
@@ -3196,17 +3241,17 @@ function handleInitialScrollDataChange(ctx, options) {
3196
3241
  dataLength,
3197
3242
  didDataChange,
3198
3243
  initialScrollAtEnd,
3199
- previousDataLength,
3244
+ previousDataLength: previousInitialScrollDataLength,
3200
3245
  stylePaddingBottom
3201
3246
  });
3202
3247
  return;
3203
3248
  }
3204
- const shouldReplayFinishedOffsetInitialScroll = previousDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3205
- if (previousDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3249
+ const shouldReplayFinishedOffsetInitialScroll = previousInitialScrollDataLength === 0 && dataLength > 0 && !!state.initialScroll && ((_c = ctx.state.initialScrollSession) == null ? void 0 : _c.kind) === "offset" && !!state.didFinishInitialScroll;
3250
+ if (previousInitialScrollDataLength !== 0 || dataLength === 0 || !state.initialScroll || !state.queuedInitialLayout || state.didFinishInitialScroll && !shouldReplayFinishedOffsetInitialScroll) {
3206
3251
  return;
3207
3252
  }
3208
3253
  if (shouldReplayFinishedOffsetInitialScroll) {
3209
- state.didFinishInitialScroll = false;
3254
+ resetInitialRenderState(ctx, { resetInitialScroll: true });
3210
3255
  }
3211
3256
  advanceCurrentInitialScrollSession(ctx);
3212
3257
  }
@@ -4205,7 +4250,7 @@ function calculateItemsInView(ctx, params = {}) {
4205
4250
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
4206
4251
  const alwaysRenderArr = alwaysRenderIndicesArr || [];
4207
4252
  const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
4208
- const drawDistance = getEffectiveDrawDistance(ctx);
4253
+ const drawDistance = getEffectiveDrawDistance(ctx, params.drawDistanceMode);
4209
4254
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
4210
4255
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
4211
4256
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
@@ -6442,7 +6487,7 @@ var LegendList = typedMemo(
6442
6487
  })
6443
6488
  );
6444
6489
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
6445
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
6490
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
6446
6491
  const noopOnScroll = React2.useCallback((_event) => {
6447
6492
  }, []);
6448
6493
  if (props.recycleItems === void 0) {
@@ -6459,6 +6504,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6459
6504
  contentContainerStyle: contentContainerStyleProp,
6460
6505
  contentInset,
6461
6506
  data: dataProp = [],
6507
+ dataKey,
6462
6508
  dataVersion,
6463
6509
  drawDistance = 250,
6464
6510
  contentInsetEndAdjustment,
@@ -6589,6 +6635,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6589
6635
  (_j = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _j.join(","),
6590
6636
  (_k = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _k.join(","),
6591
6637
  dataProp,
6638
+ dataKey,
6592
6639
  dataVersion,
6593
6640
  keyExtractor
6594
6641
  ]);
@@ -6611,6 +6658,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6611
6658
  dataChangeNeedsScrollUpdate: false,
6612
6659
  didColumnsChange: false,
6613
6660
  didDataChange: false,
6661
+ didLoad: false,
6614
6662
  enableScrollForNextCalculateItemsInView: true,
6615
6663
  endBuffered: -1,
6616
6664
  endNoBuffer: -1,
@@ -6640,6 +6688,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6640
6688
  positions: [],
6641
6689
  props: {},
6642
6690
  queuedCalculateItemsInView: 0,
6691
+ queuedFullDrawDistancePrewarm: void 0,
6643
6692
  refScroller: { current: null },
6644
6693
  scroll: 0,
6645
6694
  scrollAdjustHandler: new ScrollAdjustHandler(ctx),
@@ -6682,10 +6731,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6682
6731
  const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
6683
6732
  ctx.scrollAxisGap = nextScrollAxisGap;
6684
6733
  state.didColumnsChange = numColumnsProp !== previousNumColumnsProp || didScrollAxisGapChange;
6734
+ const previousDataLength = (_n = (_m = state.props.data) == null ? void 0 : _m.length) != null ? _n : 0;
6685
6735
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6736
+ const didDataKeyChangeLocal = state.props.dataKey !== dataKey;
6686
6737
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6687
- const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6688
- if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_m = state.initialScroll) == null ? void 0 : _m.viewPosition) === 1 && state.props.data.length > 0) {
6738
+ const didDataChangeLocal = didDataKeyChangeLocal || didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6739
+ const shouldResetFreshDataLayout = !isFirstLocal && didDataChangeLocal && state.hasHadNonEmptyData && (didDataKeyChangeLocal || previousDataLength === 0) && dataProp.length > 0;
6740
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_o = state.initialScroll) == null ? void 0 : _o.viewPosition) === 1 && state.props.data.length > 0) {
6689
6741
  clearPreservedInitialScrollTarget(state);
6690
6742
  }
6691
6743
  if (didDataChangeLocal) {
@@ -6697,7 +6749,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6697
6749
  const throttledOnScroll = useThrottledOnScroll(onScrollProp != null ? onScrollProp : noopOnScroll, scrollEventThrottle != null ? scrollEventThrottle : 0);
6698
6750
  const throttleScrollFn = scrollEventThrottle && onScrollProp ? throttledOnScroll : onScrollProp;
6699
6751
  const anchoredEndSpaceResolved = Platform.OS === "web" && anchoredEndSpace ? { ...anchoredEndSpace, includeInEndInset: true } : anchoredEndSpace;
6700
- const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_n = state.props.anchoredEndSpace) == null ? void 0 : _n.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
6752
+ const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_p = state.props.anchoredEndSpace) == null ? void 0 : _p.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
6701
6753
  state.props = {
6702
6754
  adaptiveRender: experimental_adaptiveRender,
6703
6755
  alignItemsAtEnd,
@@ -6711,6 +6763,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6711
6763
  contentInset,
6712
6764
  contentInsetEndAdjustment: contentInsetEndAdjustmentResolved,
6713
6765
  data: dataProp,
6766
+ dataKey,
6714
6767
  dataVersion,
6715
6768
  drawDistance,
6716
6769
  estimatedItemSize,
@@ -6751,13 +6804,19 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6751
6804
  if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
6752
6805
  resetAdaptiveRender(ctx);
6753
6806
  }
6807
+ if (shouldResetFreshDataLayout) {
6808
+ resetInitialRenderState(ctx, {
6809
+ resetInitialScroll: !!initialScrollProp,
6810
+ resetLayout: true
6811
+ });
6812
+ }
6754
6813
  const memoizedLastItemKeys = React2.useMemo(() => {
6755
6814
  if (!dataProp.length) return [];
6756
6815
  return Array.from(
6757
6816
  { length: Math.min(numColumnsProp, dataProp.length) },
6758
6817
  (_, i) => getId(state, dataProp.length - 1 - i)
6759
6818
  );
6760
- }, [dataProp, dataVersion, numColumnsProp]);
6819
+ }, [dataProp, dataKey, dataVersion, numColumnsProp]);
6761
6820
  const initializeStateVars = (shouldAdjustPadding) => {
6762
6821
  set$(ctx, "lastItemKeys", memoizedLastItemKeys);
6763
6822
  set$(ctx, "numColumns", numColumnsProp);
@@ -6817,13 +6876,22 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6817
6876
  handleInitialScrollDataChange(ctx, {
6818
6877
  dataLength: dataProp.length,
6819
6878
  didDataChange: didDataChangeLocal,
6879
+ didStartFreshData: shouldResetFreshDataLayout,
6820
6880
  initialScrollAtEnd,
6821
6881
  latestInitialScroll: initialScrollProp,
6822
6882
  latestInitialScrollSessionKind: initialScrollUsesOffsetOnly ? "offset" : "bootstrap",
6823
6883
  stylePaddingBottom: stylePaddingBottomState,
6824
6884
  useBootstrapInitialScroll: usesBootstrapInitialScroll
6825
6885
  });
6826
- }, [dataProp.length, didDataChangeLocal, initialScrollAtEnd, stylePaddingBottomState, usesBootstrapInitialScroll]);
6886
+ }, [
6887
+ dataProp.length,
6888
+ dataKey,
6889
+ didDataChangeLocal,
6890
+ shouldResetFreshDataLayout,
6891
+ initialScrollAtEnd,
6892
+ stylePaddingBottomState,
6893
+ usesBootstrapInitialScroll
6894
+ ]);
6827
6895
  React2.useLayoutEffect(() => {
6828
6896
  var _a4;
6829
6897
  if (didAnchoredEndSpaceAnchorIndexChange) {
@@ -6891,6 +6959,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6891
6959
  React2.useLayoutEffect(
6892
6960
  () => initializeStateVars(true),
6893
6961
  [
6962
+ dataKey,
6894
6963
  dataVersion,
6895
6964
  memoizedLastItemKeys.join(","),
6896
6965
  numColumnsProp,
@@ -6917,7 +6986,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6917
6986
  state.didColumnsChange = false;
6918
6987
  state.didDataChange = false;
6919
6988
  state.isFirst = false;
6920
- }, [dataProp, dataVersion, numColumnsProp, nextScrollAxisGap]);
6989
+ }, [dataProp, dataKey, dataVersion, numColumnsProp, nextScrollAxisGap]);
6921
6990
  React2.useLayoutEffect(() => {
6922
6991
  var _a4;
6923
6992
  set$(ctx, "extraData", extraData);
@@ -6970,6 +7039,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6970
7039
  React2.useImperativeHandle(forwardedRef, () => createImperativeHandle(ctx, scheduleImperativeScrollCommit), []);
6971
7040
  React2.useEffect(() => {
6972
7041
  return () => {
7042
+ if (state.queuedFullDrawDistancePrewarm !== void 0) {
7043
+ cancelAnimationFrame(state.queuedFullDrawDistancePrewarm);
7044
+ state.queuedFullDrawDistancePrewarm = void 0;
7045
+ }
6973
7046
  for (const timeout of state.timeouts) {
6974
7047
  clearTimeout(timeout);
6975
7048
  }
@@ -7022,7 +7095,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7022
7095
  onScroll: onScrollHandler,
7023
7096
  recycleItems,
7024
7097
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2__namespace.cloneElement(refreshControlElement, {
7025
- progressViewOffset: ((_o = refreshControlElement.props.progressViewOffset) != null ? _o : 0) + stylePaddingTopState
7098
+ progressViewOffset: ((_q = refreshControlElement.props.progressViewOffset) != null ? _q : 0) + stylePaddingTopState
7026
7099
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React2__namespace.createElement(
7027
7100
  ReactNative.RefreshControl,
7028
7101
  {
@@ -7033,7 +7106,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7033
7106
  ),
7034
7107
  refScrollView: combinedRef,
7035
7108
  renderScrollComponent,
7036
- scrollAdjustHandler: (_p = refState.current) == null ? void 0 : _p.scrollAdjustHandler,
7109
+ scrollAdjustHandler: (_r = refState.current) == null ? void 0 : _r.scrollAdjustHandler,
7037
7110
  scrollEventThrottle: 0,
7038
7111
  snapToIndices,
7039
7112
  stickyHeaderIndices,