@legendapp/list 1.0.0-beta.50 → 1.0.0-beta.52

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Legend List
1
+ # Legend List
2
2
 
3
3
  **Legend List** is a high-performance list component for **React Native**, written purely in Javascript / Typescript (no native dependencies). It aims to be a drop-in replacement for `FlatList` and/or `FlashList` with better performance, especially when handling dynamically sized items.
4
4
 
5
5
  ---
6
6
 
7
- ## ⚠️ Caution: Experimental ⚠️
7
+ ## ⚠️ Caution: Experimental ⚠️
8
8
 
9
9
  This is an early release to test and gather feedback. It's not used in production yet and needs more work to reach parity with FlatList (and FlashList) features.
10
10
 
@@ -13,7 +13,7 @@ This is an early release to test and gather feedback. It's not used in productio
13
13
  ## 🤔 Why Legend List?
14
14
 
15
15
  * **Performance:** Designed from the ground up for speed, aiming to outperform `FlatList` in common scenarios.
16
- * **Dynamic Item Sizes:** Natively supports items with varying heights without performance hits. Just provide an `estimatedItemSize`.
16
+ * **Dynamic Item Sizes:** Natively supports items with varying heights without performance hits.
17
17
  * **Drop-in Potential:** Aims for API compatibility with `FlatList` for easier migration.
18
18
  * **Pure JS/TS:** No native module linking required, ensuring easier integration and compatibility across platforms.
19
19
  * **Lightweight:** Our goal is to keep LegendList as small of a dependency as possible. For more advanced use cases, we plan on supporting optional plugins. This ensures that we keep the package size as small as possible.
@@ -21,7 +21,7 @@ This is an early release to test and gather feedback. It's not used in productio
21
21
  For more information, listen to the podcast we had on [React Native Radio](https://infinite.red/react-native-radio/rnr-325-legend-list-with-jay-meistrich)!
22
22
 
23
23
  ---
24
- ## ✨ Additional Features
24
+ ## ✨ Additional Features
25
25
 
26
26
  Beyond standard `FlatList` capabilities:
27
27
 
@@ -57,7 +57,7 @@ yarn add @legendapp/list
57
57
 
58
58
  ### Example
59
59
  ```ts
60
- import React, { useRef } from "react";
60
+ import React, { useRef } from "react";
61
61
  import { View, Image, Text, StyleSheet } from "react-native";
62
62
  import { LegendList, LegendListRef, LegendListRenderItemProps } from "@legendapp/list";
63
63
  import { userData } from "../userData"; // Assuming userData is defined elsewhere
@@ -87,17 +87,16 @@ const LegendListExample = () => {
87
87
  <LegendList<UserData>
88
88
  // Required Props
89
89
  data={data}
90
- renderItem={renderItem}
91
- estimatedItemSize={70}
90
+ renderItem={renderItem}
92
91
 
93
92
  // Strongly Recommended Prop (Improves performance)
94
- keyExtractor={(item) => item.id}
93
+ keyExtractor={(item) => item.id}
95
94
 
96
95
  // Optional Props
97
- ref={listRef}
98
- recycleItems={true}
99
- maintainScrollAtEnd={false}
100
- maintainScrollAtEndThreshold={1}
96
+ ref={listRef}
97
+ recycleItems={true}
98
+ maintainScrollAtEnd={false}
99
+ maintainScrollAtEndThreshold={1}
101
100
 
102
101
  // See docs for all available props!
103
102
  />
package/animated.d.mts CHANGED
@@ -3,7 +3,7 @@ import * as _legendapp_list from '@legendapp/list';
3
3
  import * as react_native from 'react-native';
4
4
  import { Animated } from 'react-native';
5
5
 
6
- declare const AnimatedLegendList: Animated.AnimatedComponent<(<T>(props: Omit<react_native.ScrollViewProps, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
6
+ declare const AnimatedLegendList: Animated.AnimatedComponent<(<T>(props: Omit<Omit<react_native.ScrollViewProps, "scrollEventThrottle">, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
7
7
  alignItemsAtEnd?: boolean;
8
8
  columnWrapperStyle?: _legendapp_list.ColumnWrapperStyle;
9
9
  data: readonly T[];
@@ -50,6 +50,7 @@ declare const AnimatedLegendList: Animated.AnimatedComponent<(<T>(props: Omit<re
50
50
  refreshing?: boolean;
51
51
  renderItem?: ((props: _legendapp_list.LegendListRenderItemProps<T>) => React$1.ReactNode) | undefined;
52
52
  renderScrollComponent?: (props: react_native.ScrollViewProps) => React.ReactElement<react_native.ScrollViewProps>;
53
+ suggestEstimatedItemSize?: boolean;
53
54
  viewabilityConfig?: _legendapp_list.ViewabilityConfig;
54
55
  viewabilityConfigCallbackPairs?: _legendapp_list.ViewabilityConfigCallbackPairs | undefined;
55
56
  waitForInitialLayout?: boolean;
package/animated.d.ts CHANGED
@@ -3,7 +3,7 @@ import * as _legendapp_list from '@legendapp/list';
3
3
  import * as react_native from 'react-native';
4
4
  import { Animated } from 'react-native';
5
5
 
6
- declare const AnimatedLegendList: Animated.AnimatedComponent<(<T>(props: Omit<react_native.ScrollViewProps, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
6
+ declare const AnimatedLegendList: Animated.AnimatedComponent<(<T>(props: Omit<Omit<react_native.ScrollViewProps, "scrollEventThrottle">, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
7
7
  alignItemsAtEnd?: boolean;
8
8
  columnWrapperStyle?: _legendapp_list.ColumnWrapperStyle;
9
9
  data: readonly T[];
@@ -50,6 +50,7 @@ declare const AnimatedLegendList: Animated.AnimatedComponent<(<T>(props: Omit<re
50
50
  refreshing?: boolean;
51
51
  renderItem?: ((props: _legendapp_list.LegendListRenderItemProps<T>) => React$1.ReactNode) | undefined;
52
52
  renderScrollComponent?: (props: react_native.ScrollViewProps) => React.ReactElement<react_native.ScrollViewProps>;
53
+ suggestEstimatedItemSize?: boolean;
53
54
  viewabilityConfig?: _legendapp_list.ViewabilityConfig;
54
55
  viewabilityConfigCallbackPairs?: _legendapp_list.ViewabilityConfigCallbackPairs | undefined;
55
56
  waitForInitialLayout?: boolean;
package/index.d.mts CHANGED
@@ -41,10 +41,8 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
41
41
  */
42
42
  drawDistance?: number;
43
43
  /**
44
- * Estimated size of each item in pixels; sufficient for most cases. If
45
- * you leave this blank, a warning should appear and you will get
46
- * a suggested size.
47
- * @required
44
+ * Estimated size of each item in pixels, a hint for the first render. After some
45
+ * items are rendered, the average size of rendered items will be used instead.
48
46
  * @default undefined
49
47
  */
50
48
  estimatedItemSize?: number;
@@ -191,6 +189,12 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
191
189
  * @default (props) => <ScrollView {...props} />
192
190
  */
193
191
  renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement<ScrollViewProps>;
192
+ /**
193
+ * This will log a suggested estimatedItemSize.
194
+ * @required
195
+ * @default false
196
+ */
197
+ suggestEstimatedItemSize?: boolean;
194
198
  /**
195
199
  * Configuration for determining item viewability.
196
200
  */
@@ -215,7 +219,7 @@ type AnchoredPosition = {
215
219
  relativeCoordinate: number;
216
220
  top: number;
217
221
  };
218
- type LegendListProps<ItemT> = LegendListPropsBase<ItemT, ComponentProps<typeof ScrollView>>;
222
+ type LegendListProps<ItemT> = LegendListPropsBase<ItemT, Omit<ComponentProps<typeof ScrollView>, "scrollEventThrottle">>;
219
223
  interface InternalState {
220
224
  anchorElement?: {
221
225
  id: string;
@@ -226,7 +230,7 @@ interface InternalState {
226
230
  positions: Map<string, number>;
227
231
  columns: Map<string, number>;
228
232
  sizes: Map<string, number>;
229
- sizesKnown: Map<string, number> | undefined;
233
+ sizesKnown: Map<string, number>;
230
234
  pendingAdjust: number;
231
235
  isStartReached: boolean;
232
236
  isEndReached: boolean;
@@ -246,6 +250,7 @@ interface InternalState {
246
250
  scrollPrevTime: number;
247
251
  scrollVelocity: number;
248
252
  scrollAdjustHandler: ScrollAdjustHandler;
253
+ maintainingScrollAtEnd?: boolean;
249
254
  totalSize: number;
250
255
  totalSizeBelowAnchor: number;
251
256
  timeouts: Set<number>;
@@ -273,6 +278,10 @@ interface InternalState {
273
278
  lastBatchingAction: number;
274
279
  ignoreScrollFromCalcTotal?: boolean;
275
280
  scrollingToOffset?: number | undefined;
281
+ averageSizes: Record<string, {
282
+ num: number;
283
+ avg: number;
284
+ }>;
276
285
  onScroll: ((event: NativeSyntheticEvent<NativeScrollEvent>) => void) | undefined;
277
286
  }
278
287
  interface ViewableRange<T> {
@@ -453,7 +462,7 @@ type TypedMemo = <T extends React.ComponentType<any>>(Component: T, propsAreEqua
453
462
  };
454
463
  declare const typedMemo: TypedMemo;
455
464
 
456
- declare const LegendList: <T>(props: Omit<react_native.ScrollViewProps, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
465
+ declare const LegendList: <T>(props: Omit<Omit<react_native.ScrollViewProps, "scrollEventThrottle">, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
457
466
  alignItemsAtEnd?: boolean;
458
467
  columnWrapperStyle?: ColumnWrapperStyle;
459
468
  data: readonly T[];
@@ -500,6 +509,7 @@ declare const LegendList: <T>(props: Omit<react_native.ScrollViewProps, "content
500
509
  refreshing?: boolean;
501
510
  renderItem?: ((props: LegendListRenderItemProps<T>) => React$1.ReactNode) | undefined;
502
511
  renderScrollComponent?: (props: react_native.ScrollViewProps) => React$1.ReactElement<react_native.ScrollViewProps>;
512
+ suggestEstimatedItemSize?: boolean;
503
513
  viewabilityConfig?: ViewabilityConfig;
504
514
  viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs | undefined;
505
515
  waitForInitialLayout?: boolean;
@@ -508,6 +518,6 @@ declare const LegendList: <T>(props: Omit<react_native.ScrollViewProps, "content
508
518
  declare function useViewability(callback: ViewabilityCallback, configId?: string): void;
509
519
  declare function useViewabilityAmount(callback: ViewabilityAmountCallback): void;
510
520
  declare function useRecyclingEffect(effect: (info: LegendListRecyclingState<unknown>) => void | (() => void)): void;
511
- declare function useRecyclingState<ItemT>(valueOrFun: ((info: LegendListRecyclingState<ItemT>) => ItemT) | ItemT): (ItemT | Dispatch<SetStateAction<ItemT>> | null)[];
521
+ declare function useRecyclingState<ItemT>(valueOrFun: ((info: LegendListRecyclingState<ItemT>) => ItemT) | ItemT): readonly [ItemT | null, Dispatch<SetStateAction<ItemT>>];
512
522
 
513
523
  export { type AnchoredPosition, type ColumnWrapperStyle, type InternalState, LegendList, type LegendListProps, type LegendListPropsBase, type LegendListRecyclingState, type LegendListRef, type LegendListRenderItemProps, type OnViewableItemsChanged, type ScrollState, type TypedForwardRef, type TypedMemo, type ViewAmountToken, type ViewToken, type ViewabilityAmountCallback, type ViewabilityCallback, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, type ViewableRange, typedForwardRef, typedMemo, useRecyclingEffect, useRecyclingState, useViewability, useViewabilityAmount };
package/index.d.ts CHANGED
@@ -41,10 +41,8 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
41
41
  */
42
42
  drawDistance?: number;
43
43
  /**
44
- * Estimated size of each item in pixels; sufficient for most cases. If
45
- * you leave this blank, a warning should appear and you will get
46
- * a suggested size.
47
- * @required
44
+ * Estimated size of each item in pixels, a hint for the first render. After some
45
+ * items are rendered, the average size of rendered items will be used instead.
48
46
  * @default undefined
49
47
  */
50
48
  estimatedItemSize?: number;
@@ -191,6 +189,12 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
191
189
  * @default (props) => <ScrollView {...props} />
192
190
  */
193
191
  renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement<ScrollViewProps>;
192
+ /**
193
+ * This will log a suggested estimatedItemSize.
194
+ * @required
195
+ * @default false
196
+ */
197
+ suggestEstimatedItemSize?: boolean;
194
198
  /**
195
199
  * Configuration for determining item viewability.
196
200
  */
@@ -215,7 +219,7 @@ type AnchoredPosition = {
215
219
  relativeCoordinate: number;
216
220
  top: number;
217
221
  };
218
- type LegendListProps<ItemT> = LegendListPropsBase<ItemT, ComponentProps<typeof ScrollView>>;
222
+ type LegendListProps<ItemT> = LegendListPropsBase<ItemT, Omit<ComponentProps<typeof ScrollView>, "scrollEventThrottle">>;
219
223
  interface InternalState {
220
224
  anchorElement?: {
221
225
  id: string;
@@ -226,7 +230,7 @@ interface InternalState {
226
230
  positions: Map<string, number>;
227
231
  columns: Map<string, number>;
228
232
  sizes: Map<string, number>;
229
- sizesKnown: Map<string, number> | undefined;
233
+ sizesKnown: Map<string, number>;
230
234
  pendingAdjust: number;
231
235
  isStartReached: boolean;
232
236
  isEndReached: boolean;
@@ -246,6 +250,7 @@ interface InternalState {
246
250
  scrollPrevTime: number;
247
251
  scrollVelocity: number;
248
252
  scrollAdjustHandler: ScrollAdjustHandler;
253
+ maintainingScrollAtEnd?: boolean;
249
254
  totalSize: number;
250
255
  totalSizeBelowAnchor: number;
251
256
  timeouts: Set<number>;
@@ -273,6 +278,10 @@ interface InternalState {
273
278
  lastBatchingAction: number;
274
279
  ignoreScrollFromCalcTotal?: boolean;
275
280
  scrollingToOffset?: number | undefined;
281
+ averageSizes: Record<string, {
282
+ num: number;
283
+ avg: number;
284
+ }>;
276
285
  onScroll: ((event: NativeSyntheticEvent<NativeScrollEvent>) => void) | undefined;
277
286
  }
278
287
  interface ViewableRange<T> {
@@ -453,7 +462,7 @@ type TypedMemo = <T extends React.ComponentType<any>>(Component: T, propsAreEqua
453
462
  };
454
463
  declare const typedMemo: TypedMemo;
455
464
 
456
- declare const LegendList: <T>(props: Omit<react_native.ScrollViewProps, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
465
+ declare const LegendList: <T>(props: Omit<Omit<react_native.ScrollViewProps, "scrollEventThrottle">, "contentOffset" | "contentInset" | "maintainVisibleContentPosition" | "stickyHeaderIndices"> & {
457
466
  alignItemsAtEnd?: boolean;
458
467
  columnWrapperStyle?: ColumnWrapperStyle;
459
468
  data: readonly T[];
@@ -500,6 +509,7 @@ declare const LegendList: <T>(props: Omit<react_native.ScrollViewProps, "content
500
509
  refreshing?: boolean;
501
510
  renderItem?: ((props: LegendListRenderItemProps<T>) => React$1.ReactNode) | undefined;
502
511
  renderScrollComponent?: (props: react_native.ScrollViewProps) => React$1.ReactElement<react_native.ScrollViewProps>;
512
+ suggestEstimatedItemSize?: boolean;
503
513
  viewabilityConfig?: ViewabilityConfig;
504
514
  viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs | undefined;
505
515
  waitForInitialLayout?: boolean;
@@ -508,6 +518,6 @@ declare const LegendList: <T>(props: Omit<react_native.ScrollViewProps, "content
508
518
  declare function useViewability(callback: ViewabilityCallback, configId?: string): void;
509
519
  declare function useViewabilityAmount(callback: ViewabilityAmountCallback): void;
510
520
  declare function useRecyclingEffect(effect: (info: LegendListRecyclingState<unknown>) => void | (() => void)): void;
511
- declare function useRecyclingState<ItemT>(valueOrFun: ((info: LegendListRecyclingState<ItemT>) => ItemT) | ItemT): (ItemT | Dispatch<SetStateAction<ItemT>> | null)[];
521
+ declare function useRecyclingState<ItemT>(valueOrFun: ((info: LegendListRecyclingState<ItemT>) => ItemT) | ItemT): readonly [ItemT | null, Dispatch<SetStateAction<ItemT>>];
512
522
 
513
523
  export { type AnchoredPosition, type ColumnWrapperStyle, type InternalState, LegendList, type LegendListProps, type LegendListPropsBase, type LegendListRecyclingState, type LegendListRef, type LegendListRenderItemProps, type OnViewableItemsChanged, type ScrollState, type TypedForwardRef, type TypedMemo, type ViewAmountToken, type ViewToken, type ViewabilityAmountCallback, type ViewabilityCallback, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, type ViewableRange, typedForwardRef, typedMemo, useRecyclingEffect, useRecyclingState, useViewability, useViewabilityAmount };
package/index.js CHANGED
@@ -149,6 +149,16 @@ function useInterval(callback, delay) {
149
149
  function isFunction(obj) {
150
150
  return typeof obj === "function";
151
151
  }
152
+ var warned = /* @__PURE__ */ new Set();
153
+ function warnDevOnce(id, text) {
154
+ if (__DEV__ && !warned.has(id)) {
155
+ warned.add(id);
156
+ console.warn(`[legend-list] ${text}`);
157
+ }
158
+ }
159
+ function roundSize(size) {
160
+ return Math.floor(size * 8) / 8;
161
+ }
152
162
  var symbolFirst = Symbol();
153
163
  function useInit(cb) {
154
164
  const refValue = React6.useRef(symbolFirst);
@@ -327,7 +337,7 @@ var Container = ({
327
337
  const onLayout = (event) => {
328
338
  if (itemKey !== void 0) {
329
339
  const layout = event.nativeEvent.layout;
330
- const size = Math.floor(layout[horizontal ? "width" : "height"] * 8) / 8;
340
+ const size = roundSize(layout[horizontal ? "width" : "height"]);
331
341
  refLastSize.current = size;
332
342
  updateItemSize(itemKey, size);
333
343
  }
@@ -897,9 +907,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
897
907
  renderItem,
898
908
  estimatedItemSize,
899
909
  getEstimatedItemSize,
910
+ suggestEstimatedItemSize,
900
911
  ListEmptyComponent,
901
912
  onItemSizeChanged,
902
- scrollEventThrottle,
903
913
  refScrollView,
904
914
  waitForInitialLayout = true,
905
915
  extraData,
@@ -947,14 +957,31 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
947
957
  const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
948
958
  return `${ret}`;
949
959
  };
950
- const getItemSize = (key, index, data) => {
951
- var _a;
952
- const sizeKnown = refState.current.sizes.get(key);
960
+ const getItemSize = (key, index, data, useAverageSize = false) => {
961
+ const state = refState.current;
962
+ const sizeKnown = state.sizesKnown.get(key);
953
963
  if (sizeKnown !== void 0) {
954
964
  return sizeKnown;
955
965
  }
956
- const size = (_a = getEstimatedItemSize ? getEstimatedItemSize(index, data) : estimatedItemSize) != null ? _a : DEFAULT_ITEM_SIZE;
957
- refState.current.sizes.set(key, size);
966
+ let size;
967
+ if (getEstimatedItemSize) {
968
+ size = getEstimatedItemSize(index, data);
969
+ }
970
+ if (size === void 0 && useAverageSize) {
971
+ const itemType = "";
972
+ const average = state.averageSizes[itemType];
973
+ if (average) {
974
+ size = roundSize(average.avg);
975
+ }
976
+ }
977
+ const sizePrevious = state.sizesKnown.get(key);
978
+ if (sizePrevious !== void 0) {
979
+ return sizePrevious;
980
+ }
981
+ if (size === void 0) {
982
+ size = estimatedItemSize != null ? estimatedItemSize : DEFAULT_ITEM_SIZE;
983
+ }
984
+ state.sizes.set(key, size);
958
985
  return size;
959
986
  };
960
987
  const calculateOffsetForIndex = (index = initialScrollIndex) => {
@@ -1025,6 +1052,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1025
1052
  numPendingInitialLayout: 0,
1026
1053
  queuedCalculateItemsInView: 0,
1027
1054
  lastBatchingAction: Date.now(),
1055
+ averageSizes: {},
1028
1056
  onScroll: onScrollProp
1029
1057
  };
1030
1058
  const dataLength = dataProp.length;
@@ -1040,7 +1068,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1040
1068
  id: getId(0)
1041
1069
  };
1042
1070
  } else {
1043
- console.warn("[legend-list] maintainVisibleContentPosition was not able to find an anchor element");
1071
+ __DEV__ && warnDevOnce(
1072
+ "maintainVisibleContentPosition",
1073
+ "[legend-list] maintainVisibleContentPosition was not able to find an anchor element"
1074
+ );
1044
1075
  }
1045
1076
  }
1046
1077
  set$(ctx, "scrollAdjust", 0);
@@ -1202,14 +1233,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1202
1233
  var _a;
1203
1234
  const state = refState.current;
1204
1235
  const { data, scrollLength, positions, startBuffered, endBuffered } = state;
1205
- if (!data || scrollLength === 0) {
1236
+ const numColumns = peek$(ctx, "numColumns");
1237
+ if (!data || scrollLength === 0 || numColumns > 1) {
1206
1238
  return;
1207
1239
  }
1208
1240
  const numContainers = ctx.values.get("numContainers");
1209
1241
  let numMeasurements = 0;
1210
1242
  for (let i = 0; i < numContainers; i++) {
1211
1243
  const itemKey = peek$(ctx, `containerItemKey${i}`);
1212
- const isSizeKnown = refState.current.sizesKnown.get(itemKey);
1244
+ const isSizeKnown = state.sizesKnown.get(itemKey);
1213
1245
  if (itemKey && !isSizeKnown) {
1214
1246
  const containerRef = ctx.viewRefs.get(i);
1215
1247
  if (containerRef) {
@@ -1334,7 +1366,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1334
1366
  }
1335
1367
  const top2 = newPosition || positions.get(id);
1336
1368
  if (top2 !== void 0) {
1337
- const size = getItemSize(id, i, data[i]);
1369
+ const size = getItemSize(
1370
+ id,
1371
+ i,
1372
+ data[i],
1373
+ /*useAverageSize*/
1374
+ true
1375
+ );
1338
1376
  const bottom = top2 + size;
1339
1377
  if (bottom > scroll - scrollBuffer) {
1340
1378
  loopStart = i;
@@ -1364,7 +1402,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1364
1402
  };
1365
1403
  for (let i = Math.max(0, loopStart); i < data.length; i++) {
1366
1404
  const id = getId(i);
1367
- const size = getItemSize(id, i, data[i]);
1405
+ const size = getItemSize(
1406
+ id,
1407
+ i,
1408
+ data[i],
1409
+ /*useAverageSize*/
1410
+ true
1411
+ );
1368
1412
  maxSizeInRow = Math.max(maxSizeInRow, size);
1369
1413
  if (top === void 0 || id === ((_a = state.anchorElement) == null ? void 0 : _a.id)) {
1370
1414
  top = getInitialTop(i);
@@ -1588,9 +1632,16 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1588
1632
  }
1589
1633
  requestAnimationFrame(() => {
1590
1634
  var _a;
1635
+ state.maintainingScrollAtEnd = true;
1591
1636
  (_a = refScroller.current) == null ? void 0 : _a.scrollToEnd({
1592
1637
  animated
1593
1638
  });
1639
+ setTimeout(
1640
+ () => {
1641
+ state.maintainingScrollAtEnd = false;
1642
+ },
1643
+ 0
1644
+ );
1594
1645
  });
1595
1646
  return true;
1596
1647
  }
@@ -1618,9 +1669,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1618
1669
  if (!refState.current) {
1619
1670
  return;
1620
1671
  }
1621
- const { queuedInitialLayout, scrollLength, scroll } = refState.current;
1672
+ const { queuedInitialLayout, scrollLength, scroll, maintainingScrollAtEnd } = refState.current;
1622
1673
  const contentSize = getContentSize(ctx);
1623
- if (contentSize > 0 && queuedInitialLayout) {
1674
+ if (contentSize > 0 && queuedInitialLayout && !maintainingScrollAtEnd) {
1624
1675
  const distanceFromEnd = contentSize - scroll - scrollLength;
1625
1676
  const distanceFromEndAbs = Math.abs(distanceFromEnd);
1626
1677
  const isContentLess = contentSize < scrollLength;
@@ -1688,13 +1739,16 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1688
1739
  if (!didMaintainScrollAtEnd && dataProp.length > state.data.length) {
1689
1740
  state.isEndReached = false;
1690
1741
  }
1691
- checkAtTop();
1692
- checkAtBottom();
1742
+ if (!didMaintainScrollAtEnd) {
1743
+ checkAtTop();
1744
+ checkAtBottom();
1745
+ }
1693
1746
  }
1694
1747
  }
1695
1748
  };
1696
1749
  const calcTotalSizesAndPositions = ({ forgetPositions = false }) => {
1697
1750
  var _a, _b;
1751
+ const state = refState.current;
1698
1752
  let totalSize = 0;
1699
1753
  let totalSizeBelowIndex = 0;
1700
1754
  const indexByKey = /* @__PURE__ */ new Map();
@@ -1702,7 +1756,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1702
1756
  let column = 1;
1703
1757
  let maxSizeInRow = 0;
1704
1758
  const numColumns = (_a = peek$(ctx, "numColumns")) != null ? _a : numColumnsProp;
1705
- if (!refState.current) {
1759
+ if (!state) {
1706
1760
  return;
1707
1761
  }
1708
1762
  for (let i = 0; i < dataProp.length; i++) {
@@ -1715,36 +1769,36 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1715
1769
  }
1716
1770
  }
1717
1771
  indexByKey.set(key, i);
1718
- if (!forgetPositions && refState.current.positions.get(key) != null && refState.current.indexByKey.get(key) === i) {
1719
- newPositions.set(key, refState.current.positions.get(key));
1772
+ if (!forgetPositions && state.positions.get(key) != null && state.indexByKey.get(key) === i) {
1773
+ newPositions.set(key, state.positions.get(key));
1720
1774
  }
1721
1775
  }
1722
- refState.current.indexByKey = indexByKey;
1723
- refState.current.positions = newPositions;
1776
+ state.indexByKey = indexByKey;
1777
+ state.positions = newPositions;
1724
1778
  if (!forgetPositions && !isFirst) {
1725
1779
  if (maintainVisibleContentPosition) {
1726
- if (refState.current.anchorElement == null || indexByKey.get(refState.current.anchorElement.id) == null) {
1780
+ if (state.anchorElement == null || indexByKey.get(state.anchorElement.id) == null) {
1727
1781
  if (dataProp.length) {
1728
1782
  const newAnchorElement = {
1729
1783
  coordinate: 0,
1730
1784
  id: getId(0)
1731
1785
  };
1732
- refState.current.anchorElement = newAnchorElement;
1733
- (_b = refState.current.belowAnchorElementPositions) == null ? void 0 : _b.clear();
1786
+ state.anchorElement = newAnchorElement;
1787
+ (_b = state.belowAnchorElementPositions) == null ? void 0 : _b.clear();
1734
1788
  scrollTo(0, false);
1735
1789
  setTimeout(() => {
1736
1790
  calculateItemsInView();
1737
1791
  }, 0);
1738
1792
  } else {
1739
- refState.current.startBufferedId = void 0;
1793
+ state.startBufferedId = void 0;
1740
1794
  }
1741
1795
  }
1742
1796
  } else {
1743
- if (refState.current.startBufferedId != null && newPositions.get(refState.current.startBufferedId) == null) {
1797
+ if (state.startBufferedId != null && newPositions.get(state.startBufferedId) == null) {
1744
1798
  if (dataProp.length) {
1745
- refState.current.startBufferedId = getId(0);
1799
+ state.startBufferedId = getId(0);
1746
1800
  } else {
1747
- refState.current.startBufferedId = void 0;
1801
+ state.startBufferedId = void 0;
1748
1802
  }
1749
1803
  scrollTo(0, false);
1750
1804
  setTimeout(() => {
@@ -1771,7 +1825,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1771
1825
  if (maxSizeInRow > 0) {
1772
1826
  totalSize += maxSizeInRow;
1773
1827
  }
1774
- const state = refState.current;
1775
1828
  state.ignoreScrollFromCalcTotal = true;
1776
1829
  requestAnimationFrame(() => {
1777
1830
  state.ignoreScrollFromCalcTotal = false;
@@ -1804,6 +1857,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1804
1857
  if (isFirst || didDataChange || numColumnsProp !== peek$(ctx, "numColumns")) {
1805
1858
  refState.current.lastBatchingAction = Date.now();
1806
1859
  if (!keyExtractorProp && !isFirst && didDataChange) {
1860
+ __DEV__ && warnDevOnce(
1861
+ "keyExtractor",
1862
+ "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
1863
+ );
1807
1864
  refState.current.sizes.clear();
1808
1865
  refState.current.positions.clear();
1809
1866
  }
@@ -1834,24 +1891,28 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1834
1891
  if (index === void 0) {
1835
1892
  return null;
1836
1893
  }
1837
- const useViewability2 = __DEV__ ? (configId, callback) => {
1838
- console.warn(
1839
- `[legend-list] useViewability has been moved from a render prop to a regular import: import { useViewability } from "@legendapp/list";`
1894
+ const useViewability2 = __DEV__ ? () => {
1895
+ warnDevOnce(
1896
+ "useViewability",
1897
+ `useViewability has been moved from a render prop to a regular import: import { useViewability } from "@legendapp/list";`
1840
1898
  );
1841
1899
  } : void 0;
1842
- const useViewabilityAmount2 = __DEV__ ? (callback) => {
1843
- console.warn(
1844
- `[legend-list] useViewabilityAmount has been moved from a render prop to a regular import: import { useViewabilityAmount } from "@legendapp/list";`
1900
+ const useViewabilityAmount2 = __DEV__ ? () => {
1901
+ warnDevOnce(
1902
+ "useViewabilityAmount",
1903
+ `useViewabilityAmount has been moved from a render prop to a regular import: import { useViewabilityAmount } from "@legendapp/list";`
1845
1904
  );
1846
1905
  } : void 0;
1847
- const useRecyclingEffect2 = __DEV__ ? (effect) => {
1848
- console.warn(
1849
- `[legend-list] useRecyclingEffect has been moved from a render prop to a regular import: import { useRecyclingEffect } from "@legendapp/list";`
1906
+ const useRecyclingEffect2 = __DEV__ ? () => {
1907
+ warnDevOnce(
1908
+ "useRecyclingEffect",
1909
+ `useRecyclingEffect has been moved from a render prop to a regular import: import { useRecyclingEffect } from "@legendapp/list";`
1850
1910
  );
1851
1911
  } : void 0;
1852
- const useRecyclingState2 = __DEV__ ? (valueOrFun) => {
1853
- console.warn(
1854
- `[legend-list] useRecyclingState has been moved from a render prop to a regular import: import { useRecyclingState } from "@legendapp/list";`
1912
+ const useRecyclingState2 = __DEV__ ? () => {
1913
+ warnDevOnce(
1914
+ "useRecyclingState",
1915
+ `useRecyclingState has been moved from a render prop to a regular import: import { useRecyclingState } from "@legendapp/list";`
1855
1916
  );
1856
1917
  } : void 0;
1857
1918
  const renderedItem = (_b = (_a = refState.current).renderItem) == null ? void 0 : _b.call(_a, {
@@ -1904,7 +1965,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1904
1965
  });
1905
1966
  const updateItemSize = React6.useCallback((itemKey, size, fromFixGaps) => {
1906
1967
  const state = refState.current;
1907
- const { sizes, indexByKey, sizesKnown, data, rowHeights, startBuffered, endBuffered } = state;
1968
+ const { sizes, indexByKey, sizesKnown, data, rowHeights, startBuffered, endBuffered, averageSizes } = state;
1908
1969
  if (!data) {
1909
1970
  return;
1910
1971
  }
@@ -1922,7 +1983,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1922
1983
  needsUpdateContainersDidLayout = true;
1923
1984
  }
1924
1985
  }
1925
- sizesKnown == null ? void 0 : sizesKnown.set(itemKey, size);
1986
+ sizesKnown.set(itemKey, size);
1987
+ const itemType = "";
1988
+ let averages = averageSizes[itemType];
1989
+ if (!averages) {
1990
+ averages = averageSizes[itemType] = {
1991
+ num: 0,
1992
+ avg: 0
1993
+ };
1994
+ }
1995
+ averages.avg = (averages.avg * averages.num + size) / (averages.num + 1);
1996
+ averages.num++;
1926
1997
  if (!prevSize || Math.abs(prevSize - size) > 0.5) {
1927
1998
  let diff;
1928
1999
  needsCalculate = true;
@@ -1937,19 +2008,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1937
2008
  sizes.set(itemKey, size);
1938
2009
  diff = size - prevSize;
1939
2010
  }
1940
- if (__DEV__ && !estimatedItemSize && !getEstimatedItemSize) {
2011
+ if (__DEV__ && suggestEstimatedItemSize) {
1941
2012
  if (state.timeoutSizeMessage) {
1942
2013
  clearTimeout(state.timeoutSizeMessage);
1943
2014
  }
1944
2015
  state.timeoutSizeMessage = setTimeout(() => {
1945
2016
  state.timeoutSizeMessage = void 0;
1946
- let total = 0;
1947
- let num = 0;
1948
- for (const [_, size2] of sizesKnown) {
1949
- num++;
1950
- total += size2;
1951
- }
1952
- const avg = Math.round(total / num);
2017
+ const num = sizesKnown.size;
2018
+ const avg = state.averageSizes[""].avg;
1953
2019
  console.warn(
1954
2020
  `[legend-list] estimatedItemSize or getEstimatedItemSize are not defined. Based on the ${num} items rendered so far, the optimal estimated size is ${avg}.`
1955
2021
  );
@@ -2009,7 +2075,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2009
2075
  const isWidthZero = event.nativeEvent.layout.width === 0;
2010
2076
  const isHeightZero = event.nativeEvent.layout.height === 0;
2011
2077
  if (isWidthZero || isHeightZero) {
2012
- console.warn(
2078
+ warnDevOnce(
2079
+ "height0",
2013
2080
  `[legend-list] List ${isWidthZero ? "width" : "height"} is 0. You may need to set a style or \`flex: \` for the list, because children are absolutely positioned.`
2014
2081
  );
2015
2082
  }
@@ -2176,7 +2243,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2176
2243
  alignItemsAtEnd,
2177
2244
  ListEmptyComponent: dataProp.length === 0 ? ListEmptyComponent : void 0,
2178
2245
  maintainVisibleContentPosition,
2179
- scrollEventThrottle: scrollEventThrottle != null ? scrollEventThrottle : reactNative.Platform.OS === "web" ? 16 : void 0,
2246
+ scrollEventThrottle: reactNative.Platform.OS === "web" ? 16 : void 0,
2180
2247
  waitForInitialLayout,
2181
2248
  refreshControl: refreshControl != null ? refreshControl : onRefresh && /* @__PURE__ */ React6__namespace.createElement(
2182
2249
  reactNative.RefreshControl,