@legendapp/list 0.6.3 → 1.0.0-beta.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/README.md CHANGED
@@ -14,6 +14,10 @@ In addition to normal FlatList features:
14
14
  - `maintainScrollAtEnd`: If true and scroll is within `maintainScrollAtEndThreshold * screen height` then changing items or heights will scroll to the bottom. This can be useful for chat interfaces.
15
15
  - `recycleItems` prop enables toggling recycling of list items. If enabled it will reuse item components for improved performance, but it will reuse any local state in items. So if you have local state in items you likely want this disabled.
16
16
 
17
+ ## Documentation
18
+
19
+ See the [documentation site](https://www.legendapp.com/open-source/list) for the full documentation.
20
+
17
21
  ## Usage
18
22
 
19
23
  ## Install
@@ -41,7 +45,6 @@ interface PropsOptional {
41
45
  initialScrollOffset?: number;
42
46
  initialScrollIndex?: number;
43
47
  drawDistance?: number;
44
- initialContainers?: number;
45
48
  recycleItems?: boolean;
46
49
  onEndReachedThreshold?: number | null | undefined;
47
50
  maintainScrollAtEnd?: boolean;
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ComponentProps, ReactNode, ForwardedRef, ReactElement } from 'react';
2
- import { ScrollView, StyleProp, ViewStyle, ScrollViewComponent, ScrollResponderMixin } from 'react-native';
2
+ import { ScrollView, StyleProp, ViewStyle, ScrollViewProps, ScrollViewComponent, ScrollResponderMixin } from 'react-native';
3
3
  import Animated from 'react-native-reanimated';
4
4
 
5
5
  declare class ScrollAdjustHandler {
@@ -19,7 +19,6 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
19
19
  initialScrollOffset?: number;
20
20
  initialScrollIndex?: number;
21
21
  drawDistance?: number;
22
- initialNumContainers?: number;
23
22
  recycleItems?: boolean;
24
23
  onEndReachedThreshold?: number | null | undefined;
25
24
  onStartReachedThreshold?: number | null | undefined;
@@ -29,6 +28,7 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
29
28
  maintainVisibleContentPosition?: boolean;
30
29
  numColumns?: number;
31
30
  refScrollView?: React.Ref<ScrollView>;
31
+ waitForInitialLayout?: boolean;
32
32
  estimatedItemSize?: number;
33
33
  getEstimatedItemSize?: (index: number, item: ItemT) => number;
34
34
  onStartReached?: ((info: {
@@ -49,6 +49,18 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
49
49
  viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs | undefined;
50
50
  viewabilityConfig?: ViewabilityConfig;
51
51
  onViewableItemsChanged?: OnViewableItemsChanged | undefined;
52
+ onItemSizeChanged?: (info: {
53
+ size: number;
54
+ previous: number;
55
+ index: number;
56
+ itemKey: string;
57
+ itemData: ItemT;
58
+ }) => void;
59
+ /**
60
+ * Render custom ScrollView component.
61
+ * @default (props) => <ScrollView {...props} />
62
+ */
63
+ renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement<ScrollViewProps>;
52
64
  };
53
65
  type LegendListProps<ItemT> = LegendListPropsBase<ItemT, ComponentProps<typeof ScrollView>>;
54
66
  interface InternalState {
@@ -89,10 +101,6 @@ interface InternalState {
89
101
  timeoutSizeMessage: any;
90
102
  nativeMarginTop: number;
91
103
  indexByKey: Map<string, number>;
92
- contentSize: {
93
- width: number;
94
- height: number;
95
- };
96
104
  viewabilityConfigCallbackPairs: ViewabilityConfigCallbackPairs | undefined;
97
105
  renderItem: (props: LegendListRenderItemProps<any>) => ReactNode;
98
106
  scrollHistory: Array<{
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ComponentProps, ReactNode, ForwardedRef, ReactElement } from 'react';
2
- import { ScrollView, StyleProp, ViewStyle, ScrollViewComponent, ScrollResponderMixin } from 'react-native';
2
+ import { ScrollView, StyleProp, ViewStyle, ScrollViewProps, ScrollViewComponent, ScrollResponderMixin } from 'react-native';
3
3
  import Animated from 'react-native-reanimated';
4
4
 
5
5
  declare class ScrollAdjustHandler {
@@ -19,7 +19,6 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
19
19
  initialScrollOffset?: number;
20
20
  initialScrollIndex?: number;
21
21
  drawDistance?: number;
22
- initialNumContainers?: number;
23
22
  recycleItems?: boolean;
24
23
  onEndReachedThreshold?: number | null | undefined;
25
24
  onStartReachedThreshold?: number | null | undefined;
@@ -29,6 +28,7 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
29
28
  maintainVisibleContentPosition?: boolean;
30
29
  numColumns?: number;
31
30
  refScrollView?: React.Ref<ScrollView>;
31
+ waitForInitialLayout?: boolean;
32
32
  estimatedItemSize?: number;
33
33
  getEstimatedItemSize?: (index: number, item: ItemT) => number;
34
34
  onStartReached?: ((info: {
@@ -49,6 +49,18 @@ type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof Scroll
49
49
  viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs | undefined;
50
50
  viewabilityConfig?: ViewabilityConfig;
51
51
  onViewableItemsChanged?: OnViewableItemsChanged | undefined;
52
+ onItemSizeChanged?: (info: {
53
+ size: number;
54
+ previous: number;
55
+ index: number;
56
+ itemKey: string;
57
+ itemData: ItemT;
58
+ }) => void;
59
+ /**
60
+ * Render custom ScrollView component.
61
+ * @default (props) => <ScrollView {...props} />
62
+ */
63
+ renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement<ScrollViewProps>;
52
64
  };
53
65
  type LegendListProps<ItemT> = LegendListPropsBase<ItemT, ComponentProps<typeof ScrollView>>;
54
66
  interface InternalState {
@@ -89,10 +101,6 @@ interface InternalState {
89
101
  timeoutSizeMessage: any;
90
102
  nativeMarginTop: number;
91
103
  indexByKey: Map<string, number>;
92
- contentSize: {
93
- width: number;
94
- height: number;
95
- };
96
104
  viewabilityConfigCallbackPairs: ViewabilityConfigCallbackPairs | undefined;
97
105
  renderItem: (props: LegendListRenderItemProps<any>) => ReactNode;
98
106
  scrollHistory: Array<{
package/index.js CHANGED
@@ -77,10 +77,13 @@ function set$(ctx, signalName, value) {
77
77
  }
78
78
  }
79
79
  }
80
+
81
+ // src/Container.tsx
80
82
  var Container = ({
81
83
  id,
82
84
  recycleItems,
83
85
  horizontal,
86
+ waitForInitialLayout,
84
87
  getRenderedItem,
85
88
  updateItemSize,
86
89
  ItemSeparatorComponent
@@ -88,11 +91,10 @@ var Container = ({
88
91
  const ctx = useStateContext();
89
92
  const position = use$(`containerPosition${id}`);
90
93
  const column = use$(`containerColumn${id}`) || 0;
91
- const visible = use$(`containerDidLayout${id}`);
92
94
  const numColumns = use$("numColumns");
93
95
  const otherAxisPos = numColumns > 1 ? `${(column - 1) / numColumns * 100}%` : 0;
94
96
  const otherAxisSize = numColumns > 1 ? `${1 / numColumns * 100}%` : void 0;
95
- let style = horizontal ? {
97
+ const style = horizontal ? {
96
98
  flexDirection: "row",
97
99
  position: "absolute",
98
100
  top: otherAxisPos,
@@ -106,7 +108,8 @@ var Container = ({
106
108
  width: otherAxisSize,
107
109
  top: position
108
110
  };
109
- {
111
+ if (waitForInitialLayout) {
112
+ const visible = use$(`containerDidLayout${id}`);
110
113
  style.opacity = visible ? 1 : 0;
111
114
  }
112
115
  const lastItemKey = use$("lastItemKey");
@@ -146,6 +149,7 @@ var Containers = React4__namespace.memo(function Containers2({
146
149
  horizontal,
147
150
  recycleItems,
148
151
  ItemSeparatorComponent,
152
+ waitForInitialLayout,
149
153
  updateItemSize,
150
154
  getRenderedItem
151
155
  }) {
@@ -161,6 +165,7 @@ var Containers = React4__namespace.memo(function Containers2({
161
165
  key: i,
162
166
  recycleItems,
163
167
  horizontal,
168
+ waitForInitialLayout,
164
169
  getRenderedItem,
165
170
  updateItemSize,
166
171
  ItemSeparatorComponent
@@ -190,6 +195,7 @@ var ListComponent = React4__namespace.memo(function ListComponent2({
190
195
  recycleItems,
191
196
  ItemSeparatorComponent,
192
197
  alignItemsAtEnd,
198
+ waitForInitialLayout,
193
199
  handleScroll,
194
200
  onLayout,
195
201
  ListHeaderComponent,
@@ -202,14 +208,19 @@ var ListComponent = React4__namespace.memo(function ListComponent2({
202
208
  updateItemSize,
203
209
  refScrollView,
204
210
  maintainVisibleContentPosition,
211
+ renderScrollComponent,
205
212
  ...rest
206
213
  }) {
207
214
  const ctx = useStateContext();
208
215
  const animPaddingTop = useValue$("paddingTop");
209
216
  const animScrollAdjust = useValue$("scrollAdjust");
217
+ const ScrollComponent = renderScrollComponent ? React4.useMemo(
218
+ () => React4__namespace.forwardRef((props, ref) => renderScrollComponent({ ...props, ref })),
219
+ [renderScrollComponent]
220
+ ) : reactNative.ScrollView;
210
221
  const additionalSize = { marginTop: animScrollAdjust, paddingTop: animPaddingTop };
211
222
  return /* @__PURE__ */ React4__namespace.createElement(
212
- reactNative.ScrollView,
223
+ ScrollComponent,
213
224
  {
214
225
  ...rest,
215
226
  style,
@@ -247,6 +258,7 @@ var ListComponent = React4__namespace.memo(function ListComponent2({
247
258
  {
248
259
  horizontal,
249
260
  recycleItems,
261
+ waitForInitialLayout,
250
262
  getRenderedItem,
251
263
  ItemSeparatorComponent: ItemSeparatorComponent && getComponent(ItemSeparatorComponent),
252
264
  updateItemSize
@@ -462,7 +474,6 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
462
474
  initialScrollIndex,
463
475
  initialScrollOffset,
464
476
  horizontal,
465
- initialNumContainers,
466
477
  drawDistance = 250,
467
478
  recycleItems = false,
468
479
  onEndReachedThreshold = 0.5,
@@ -480,6 +491,8 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
480
491
  onEndReached,
481
492
  onStartReached,
482
493
  ListEmptyComponent,
494
+ onItemSizeChanged,
495
+ scrollEventThrottle,
483
496
  refScrollView,
484
497
  ...rest
485
498
  } = props;
@@ -557,7 +570,6 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
557
570
  indexByKey: /* @__PURE__ */ new Map(),
558
571
  scrollHistory: [],
559
572
  scrollVelocity: 0,
560
- contentSize: { width: 0, height: 0 },
561
573
  sizesLaidOut: __DEV__ ? /* @__PURE__ */ new Map() : void 0,
562
574
  timeoutSizeMessage: 0,
563
575
  scrollTimer: void 0,
@@ -619,21 +631,18 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
619
631
  state.belowAnchorElementPositions = buildElementPositionsBelowAnchor();
620
632
  state.rowHeights.clear();
621
633
  }
622
- const doAdd = () => {
623
- const totalSize = state.totalSize;
624
- let resultSize = totalSize;
625
- if (applyAdjustValue !== void 0) {
626
- resultSize -= applyAdjustValue;
627
- refState.current.scrollAdjustHandler.requestAdjust(applyAdjustValue, (diff) => {
628
- state.scroll -= diff;
629
- });
630
- }
631
- set$(ctx, "totalSize", resultSize);
632
- if (alignItemsAtEnd) {
633
- doUpdatePaddingTop();
634
- }
635
- };
636
- doAdd();
634
+ const totalSize = state.totalSize;
635
+ let resultSize = totalSize;
636
+ if (applyAdjustValue !== void 0) {
637
+ resultSize -= applyAdjustValue;
638
+ refState.current.scrollAdjustHandler.requestAdjust(applyAdjustValue, (diff) => {
639
+ state.scroll -= diff;
640
+ });
641
+ }
642
+ set$(ctx, "totalSize", resultSize);
643
+ if (alignItemsAtEnd) {
644
+ doUpdatePaddingTop();
645
+ }
637
646
  }, []);
638
647
  const getRowHeight = (n) => {
639
648
  const { rowHeights } = refState.current;
@@ -864,7 +873,7 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
864
873
  set$(ctx, `containerColumn${containerId}`, -1);
865
874
  if (__DEV__ && numContainers > peek$(ctx, "numContainersPooled")) {
866
875
  console.warn(
867
- "[legend-list] No container to recycle, consider increasing initialContainers or estimatedItemSize. numContainers:",
876
+ "[legend-list] No container to recycle, so creating one on demand. This can be a performance issue and is likely caused by the estimatedItemSize being too small. Consider increasing estimatedItemSize. numContainers:",
868
877
  numContainers
869
878
  );
870
879
  }
@@ -936,25 +945,25 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
936
945
  }
937
946
  };
938
947
  const doMaintainScrollAtEnd = (animated) => {
939
- var _a2;
940
- if (((_a2 = refState.current) == null ? void 0 : _a2.isAtBottom) && maintainScrollAtEnd) {
941
- refState.current.scroll = refState.current.totalSize - refState.current.scrollLength;
948
+ const state = refState.current;
949
+ if ((state == null ? void 0 : state.isAtBottom) && maintainScrollAtEnd) {
950
+ state.scroll = state.totalSize - state.scrollLength + peek$(ctx, "paddingTop");
942
951
  requestAnimationFrame(() => {
943
- var _a3;
944
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
952
+ var _a2;
953
+ (_a2 = refScroller.current) == null ? void 0 : _a2.scrollToEnd({
945
954
  animated
946
955
  });
947
956
  });
957
+ return true;
948
958
  }
949
959
  };
950
960
  const checkAtBottom = () => {
951
961
  if (!refState.current) {
952
962
  return;
953
963
  }
954
- const { scrollLength, scroll, contentSize } = refState.current;
955
- const contentLength = contentSize[horizontal ? "width" : "height"];
956
- if (scroll > 0 && contentLength > 0) {
957
- const distanceFromEnd = contentLength - scroll - scrollLength;
964
+ const { scrollLength, scroll, totalSize } = refState.current;
965
+ if (totalSize > 0) {
966
+ const distanceFromEnd = totalSize - scroll - scrollLength;
958
967
  if (refState.current) {
959
968
  refState.current.isAtBottom = distanceFromEnd < scrollLength * maintainScrollAtEndThreshold;
960
969
  }
@@ -996,29 +1005,31 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
996
1005
  }
997
1006
  }
998
1007
  };
999
- const resetContainers = () => {
1008
+ const checkResetContainers = (reset) => {
1000
1009
  const state = refState.current;
1001
1010
  if (state) {
1002
- if (data.length > state.data.length) {
1003
- state.isEndReached = false;
1004
- }
1005
- refState.current.scrollForNextCalculateItemsInView = void 0;
1006
1011
  state.data = data;
1007
- const numContainers = peek$(ctx, "numContainers");
1008
- for (let i = 0; i < numContainers; i++) {
1009
- const itemKey = peek$(ctx, `containerItemKey${i}`);
1010
- if (!keyExtractorProp || itemKey && state.indexByKey.get(itemKey) === void 0) {
1011
- set$(ctx, `containerItemKey${i}`, void 0);
1012
- set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
1013
- set$(ctx, `containerColumn${i}`, -1);
1012
+ if (reset) {
1013
+ refState.current.scrollForNextCalculateItemsInView = void 0;
1014
+ const numContainers = peek$(ctx, "numContainers");
1015
+ for (let i = 0; i < numContainers; i++) {
1016
+ const itemKey = peek$(ctx, `containerItemKey${i}`);
1017
+ if (!keyExtractorProp || itemKey && state.indexByKey.get(itemKey) === void 0) {
1018
+ set$(ctx, `containerItemKey${i}`, void 0);
1019
+ set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
1020
+ set$(ctx, `containerColumn${i}`, -1);
1021
+ }
1022
+ }
1023
+ if (!keyExtractorProp) {
1024
+ state.sizes.clear();
1025
+ state.positions;
1014
1026
  }
1027
+ calculateItemsInView(state.scrollVelocity);
1015
1028
  }
1016
- if (!keyExtractorProp) {
1017
- state.sizes.clear();
1018
- state.positions;
1029
+ const didMaintainScrollAtEnd = doMaintainScrollAtEnd(false);
1030
+ if (!didMaintainScrollAtEnd && data.length > state.data.length) {
1031
+ state.isEndReached = false;
1019
1032
  }
1020
- calculateItemsInView(state.scrollVelocity);
1021
- doMaintainScrollAtEnd(false);
1022
1033
  checkAtTop();
1023
1034
  checkAtBottom();
1024
1035
  }
@@ -1058,9 +1069,10 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
1058
1069
  addTotalSize(null, totalSize, totalSizeBelowIndex);
1059
1070
  }
1060
1071
  React4.useEffect(() => {
1061
- if (!isFirst) {
1062
- resetContainers();
1063
- }
1072
+ checkResetContainers(
1073
+ /*reset*/
1074
+ !isFirst
1075
+ );
1064
1076
  }, [isFirst, data, numColumnsProp]);
1065
1077
  refState.current.renderItem = renderItem;
1066
1078
  const lastItemKey = getId(data[data.length - 1]);
@@ -1174,7 +1186,7 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
1174
1186
  refState.current.viewabilityConfigCallbackPairs = setupViewability(props);
1175
1187
  const scrollLength = refState.current.scrollLength;
1176
1188
  const averageItemSize = (_a2 = estimatedItemSize != null ? estimatedItemSize : getEstimatedItemSize == null ? void 0 : getEstimatedItemSize(0, data[0])) != null ? _a2 : DEFAULT_ITEM_SIZE;
1177
- const numContainers = (initialNumContainers || Math.ceil((scrollLength + scrollBuffer * 2) / averageItemSize)) * numColumnsProp;
1189
+ const numContainers = Math.ceil((scrollLength + scrollBuffer * 2) / averageItemSize) * numColumnsProp;
1178
1190
  for (let i = 0; i < numContainers; i++) {
1179
1191
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
1180
1192
  set$(ctx, `containerColumn${i}`, -1);
@@ -1250,6 +1262,9 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
1250
1262
  calculateItemsInView(state.scrollVelocity);
1251
1263
  }
1252
1264
  }
1265
+ if (onItemSizeChanged) {
1266
+ onItemSizeChanged({ size, previous: prevSize, index, itemKey, itemData: data2[index] });
1267
+ }
1253
1268
  } else {
1254
1269
  set$(ctx, `containerDidLayout${containerId}`, true);
1255
1270
  }
@@ -1262,12 +1277,10 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
1262
1277
  const onLayout = React4.useCallback((event) => {
1263
1278
  const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
1264
1279
  refState.current.scrollLength = scrollLength;
1265
- if (refState.current.hasScrolled) {
1266
- doMaintainScrollAtEnd(false);
1267
- doUpdatePaddingTop();
1268
- checkAtBottom();
1269
- checkAtTop();
1270
- }
1280
+ doMaintainScrollAtEnd(false);
1281
+ doUpdatePaddingTop();
1282
+ checkAtBottom();
1283
+ checkAtTop();
1271
1284
  if (__DEV__) {
1272
1285
  const isWidthZero = event.nativeEvent.layout.width === 0;
1273
1286
  const isHeightZero = event.nativeEvent.layout.height === 0;
@@ -1286,7 +1299,6 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
1286
1299
  }
1287
1300
  const state = refState.current;
1288
1301
  state.hasScrolled = true;
1289
- state.contentSize = event.nativeEvent.contentSize;
1290
1302
  const currentTime = performance.now();
1291
1303
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
1292
1304
  if (!(state.scrollHistory.length === 0 && newScroll === initialContentOffset)) {
@@ -1374,6 +1386,7 @@ var LegendListInner = React4.forwardRef(function LegendListInner2(props, forward
1374
1386
  alignItemsAtEnd,
1375
1387
  ListEmptyComponent: data.length === 0 ? ListEmptyComponent : void 0,
1376
1388
  maintainVisibleContentPosition,
1389
+ scrollEventThrottle: scrollEventThrottle != null ? scrollEventThrottle : reactNative.Platform.OS === "web" ? 16 : void 0,
1377
1390
  style
1378
1391
  }
1379
1392
  );
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React4 from 'react';
2
- import React4__default, { forwardRef, useRef, useMemo, useCallback, useEffect, useImperativeHandle, useSyncExternalStore, useState } from 'react';
3
- import { Animated, ScrollView, View, Dimensions, StyleSheet, useAnimatedValue as useAnimatedValue$1 } from 'react-native';
2
+ import React4__default, { useMemo, forwardRef, useRef, useCallback, useEffect, useImperativeHandle, useSyncExternalStore, useState } from 'react';
3
+ import { Animated, ScrollView, View, Dimensions, StyleSheet, Platform, useAnimatedValue as useAnimatedValue$1 } from 'react-native';
4
4
 
5
5
  // src/LegendList.tsx
6
6
  var ContextState = React4.createContext(null);
@@ -56,10 +56,13 @@ function set$(ctx, signalName, value) {
56
56
  }
57
57
  }
58
58
  }
59
+
60
+ // src/Container.tsx
59
61
  var Container = ({
60
62
  id,
61
63
  recycleItems,
62
64
  horizontal,
65
+ waitForInitialLayout,
63
66
  getRenderedItem,
64
67
  updateItemSize,
65
68
  ItemSeparatorComponent
@@ -67,11 +70,10 @@ var Container = ({
67
70
  const ctx = useStateContext();
68
71
  const position = use$(`containerPosition${id}`);
69
72
  const column = use$(`containerColumn${id}`) || 0;
70
- const visible = use$(`containerDidLayout${id}`);
71
73
  const numColumns = use$("numColumns");
72
74
  const otherAxisPos = numColumns > 1 ? `${(column - 1) / numColumns * 100}%` : 0;
73
75
  const otherAxisSize = numColumns > 1 ? `${1 / numColumns * 100}%` : void 0;
74
- let style = horizontal ? {
76
+ const style = horizontal ? {
75
77
  flexDirection: "row",
76
78
  position: "absolute",
77
79
  top: otherAxisPos,
@@ -85,7 +87,8 @@ var Container = ({
85
87
  width: otherAxisSize,
86
88
  top: position
87
89
  };
88
- {
90
+ if (waitForInitialLayout) {
91
+ const visible = use$(`containerDidLayout${id}`);
89
92
  style.opacity = visible ? 1 : 0;
90
93
  }
91
94
  const lastItemKey = use$("lastItemKey");
@@ -125,6 +128,7 @@ var Containers = React4.memo(function Containers2({
125
128
  horizontal,
126
129
  recycleItems,
127
130
  ItemSeparatorComponent,
131
+ waitForInitialLayout,
128
132
  updateItemSize,
129
133
  getRenderedItem
130
134
  }) {
@@ -140,6 +144,7 @@ var Containers = React4.memo(function Containers2({
140
144
  key: i,
141
145
  recycleItems,
142
146
  horizontal,
147
+ waitForInitialLayout,
143
148
  getRenderedItem,
144
149
  updateItemSize,
145
150
  ItemSeparatorComponent
@@ -169,6 +174,7 @@ var ListComponent = React4.memo(function ListComponent2({
169
174
  recycleItems,
170
175
  ItemSeparatorComponent,
171
176
  alignItemsAtEnd,
177
+ waitForInitialLayout,
172
178
  handleScroll,
173
179
  onLayout,
174
180
  ListHeaderComponent,
@@ -181,14 +187,19 @@ var ListComponent = React4.memo(function ListComponent2({
181
187
  updateItemSize,
182
188
  refScrollView,
183
189
  maintainVisibleContentPosition,
190
+ renderScrollComponent,
184
191
  ...rest
185
192
  }) {
186
193
  const ctx = useStateContext();
187
194
  const animPaddingTop = useValue$("paddingTop");
188
195
  const animScrollAdjust = useValue$("scrollAdjust");
196
+ const ScrollComponent = renderScrollComponent ? useMemo(
197
+ () => React4.forwardRef((props, ref) => renderScrollComponent({ ...props, ref })),
198
+ [renderScrollComponent]
199
+ ) : ScrollView;
189
200
  const additionalSize = { marginTop: animScrollAdjust, paddingTop: animPaddingTop };
190
201
  return /* @__PURE__ */ React4.createElement(
191
- ScrollView,
202
+ ScrollComponent,
192
203
  {
193
204
  ...rest,
194
205
  style,
@@ -226,6 +237,7 @@ var ListComponent = React4.memo(function ListComponent2({
226
237
  {
227
238
  horizontal,
228
239
  recycleItems,
240
+ waitForInitialLayout,
229
241
  getRenderedItem,
230
242
  ItemSeparatorComponent: ItemSeparatorComponent && getComponent(ItemSeparatorComponent),
231
243
  updateItemSize
@@ -441,7 +453,6 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
441
453
  initialScrollIndex,
442
454
  initialScrollOffset,
443
455
  horizontal,
444
- initialNumContainers,
445
456
  drawDistance = 250,
446
457
  recycleItems = false,
447
458
  onEndReachedThreshold = 0.5,
@@ -459,6 +470,8 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
459
470
  onEndReached,
460
471
  onStartReached,
461
472
  ListEmptyComponent,
473
+ onItemSizeChanged,
474
+ scrollEventThrottle,
462
475
  refScrollView,
463
476
  ...rest
464
477
  } = props;
@@ -536,7 +549,6 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
536
549
  indexByKey: /* @__PURE__ */ new Map(),
537
550
  scrollHistory: [],
538
551
  scrollVelocity: 0,
539
- contentSize: { width: 0, height: 0 },
540
552
  sizesLaidOut: __DEV__ ? /* @__PURE__ */ new Map() : void 0,
541
553
  timeoutSizeMessage: 0,
542
554
  scrollTimer: void 0,
@@ -598,21 +610,18 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
598
610
  state.belowAnchorElementPositions = buildElementPositionsBelowAnchor();
599
611
  state.rowHeights.clear();
600
612
  }
601
- const doAdd = () => {
602
- const totalSize = state.totalSize;
603
- let resultSize = totalSize;
604
- if (applyAdjustValue !== void 0) {
605
- resultSize -= applyAdjustValue;
606
- refState.current.scrollAdjustHandler.requestAdjust(applyAdjustValue, (diff) => {
607
- state.scroll -= diff;
608
- });
609
- }
610
- set$(ctx, "totalSize", resultSize);
611
- if (alignItemsAtEnd) {
612
- doUpdatePaddingTop();
613
- }
614
- };
615
- doAdd();
613
+ const totalSize = state.totalSize;
614
+ let resultSize = totalSize;
615
+ if (applyAdjustValue !== void 0) {
616
+ resultSize -= applyAdjustValue;
617
+ refState.current.scrollAdjustHandler.requestAdjust(applyAdjustValue, (diff) => {
618
+ state.scroll -= diff;
619
+ });
620
+ }
621
+ set$(ctx, "totalSize", resultSize);
622
+ if (alignItemsAtEnd) {
623
+ doUpdatePaddingTop();
624
+ }
616
625
  }, []);
617
626
  const getRowHeight = (n) => {
618
627
  const { rowHeights } = refState.current;
@@ -843,7 +852,7 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
843
852
  set$(ctx, `containerColumn${containerId}`, -1);
844
853
  if (__DEV__ && numContainers > peek$(ctx, "numContainersPooled")) {
845
854
  console.warn(
846
- "[legend-list] No container to recycle, consider increasing initialContainers or estimatedItemSize. numContainers:",
855
+ "[legend-list] No container to recycle, so creating one on demand. This can be a performance issue and is likely caused by the estimatedItemSize being too small. Consider increasing estimatedItemSize. numContainers:",
847
856
  numContainers
848
857
  );
849
858
  }
@@ -915,25 +924,25 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
915
924
  }
916
925
  };
917
926
  const doMaintainScrollAtEnd = (animated) => {
918
- var _a2;
919
- if (((_a2 = refState.current) == null ? void 0 : _a2.isAtBottom) && maintainScrollAtEnd) {
920
- refState.current.scroll = refState.current.totalSize - refState.current.scrollLength;
927
+ const state = refState.current;
928
+ if ((state == null ? void 0 : state.isAtBottom) && maintainScrollAtEnd) {
929
+ state.scroll = state.totalSize - state.scrollLength + peek$(ctx, "paddingTop");
921
930
  requestAnimationFrame(() => {
922
- var _a3;
923
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
931
+ var _a2;
932
+ (_a2 = refScroller.current) == null ? void 0 : _a2.scrollToEnd({
924
933
  animated
925
934
  });
926
935
  });
936
+ return true;
927
937
  }
928
938
  };
929
939
  const checkAtBottom = () => {
930
940
  if (!refState.current) {
931
941
  return;
932
942
  }
933
- const { scrollLength, scroll, contentSize } = refState.current;
934
- const contentLength = contentSize[horizontal ? "width" : "height"];
935
- if (scroll > 0 && contentLength > 0) {
936
- const distanceFromEnd = contentLength - scroll - scrollLength;
943
+ const { scrollLength, scroll, totalSize } = refState.current;
944
+ if (totalSize > 0) {
945
+ const distanceFromEnd = totalSize - scroll - scrollLength;
937
946
  if (refState.current) {
938
947
  refState.current.isAtBottom = distanceFromEnd < scrollLength * maintainScrollAtEndThreshold;
939
948
  }
@@ -975,29 +984,31 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
975
984
  }
976
985
  }
977
986
  };
978
- const resetContainers = () => {
987
+ const checkResetContainers = (reset) => {
979
988
  const state = refState.current;
980
989
  if (state) {
981
- if (data.length > state.data.length) {
982
- state.isEndReached = false;
983
- }
984
- refState.current.scrollForNextCalculateItemsInView = void 0;
985
990
  state.data = data;
986
- const numContainers = peek$(ctx, "numContainers");
987
- for (let i = 0; i < numContainers; i++) {
988
- const itemKey = peek$(ctx, `containerItemKey${i}`);
989
- if (!keyExtractorProp || itemKey && state.indexByKey.get(itemKey) === void 0) {
990
- set$(ctx, `containerItemKey${i}`, void 0);
991
- set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
992
- set$(ctx, `containerColumn${i}`, -1);
991
+ if (reset) {
992
+ refState.current.scrollForNextCalculateItemsInView = void 0;
993
+ const numContainers = peek$(ctx, "numContainers");
994
+ for (let i = 0; i < numContainers; i++) {
995
+ const itemKey = peek$(ctx, `containerItemKey${i}`);
996
+ if (!keyExtractorProp || itemKey && state.indexByKey.get(itemKey) === void 0) {
997
+ set$(ctx, `containerItemKey${i}`, void 0);
998
+ set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
999
+ set$(ctx, `containerColumn${i}`, -1);
1000
+ }
1001
+ }
1002
+ if (!keyExtractorProp) {
1003
+ state.sizes.clear();
1004
+ state.positions;
993
1005
  }
1006
+ calculateItemsInView(state.scrollVelocity);
994
1007
  }
995
- if (!keyExtractorProp) {
996
- state.sizes.clear();
997
- state.positions;
1008
+ const didMaintainScrollAtEnd = doMaintainScrollAtEnd(false);
1009
+ if (!didMaintainScrollAtEnd && data.length > state.data.length) {
1010
+ state.isEndReached = false;
998
1011
  }
999
- calculateItemsInView(state.scrollVelocity);
1000
- doMaintainScrollAtEnd(false);
1001
1012
  checkAtTop();
1002
1013
  checkAtBottom();
1003
1014
  }
@@ -1037,9 +1048,10 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
1037
1048
  addTotalSize(null, totalSize, totalSizeBelowIndex);
1038
1049
  }
1039
1050
  useEffect(() => {
1040
- if (!isFirst) {
1041
- resetContainers();
1042
- }
1051
+ checkResetContainers(
1052
+ /*reset*/
1053
+ !isFirst
1054
+ );
1043
1055
  }, [isFirst, data, numColumnsProp]);
1044
1056
  refState.current.renderItem = renderItem;
1045
1057
  const lastItemKey = getId(data[data.length - 1]);
@@ -1153,7 +1165,7 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
1153
1165
  refState.current.viewabilityConfigCallbackPairs = setupViewability(props);
1154
1166
  const scrollLength = refState.current.scrollLength;
1155
1167
  const averageItemSize = (_a2 = estimatedItemSize != null ? estimatedItemSize : getEstimatedItemSize == null ? void 0 : getEstimatedItemSize(0, data[0])) != null ? _a2 : DEFAULT_ITEM_SIZE;
1156
- const numContainers = (initialNumContainers || Math.ceil((scrollLength + scrollBuffer * 2) / averageItemSize)) * numColumnsProp;
1168
+ const numContainers = Math.ceil((scrollLength + scrollBuffer * 2) / averageItemSize) * numColumnsProp;
1157
1169
  for (let i = 0; i < numContainers; i++) {
1158
1170
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
1159
1171
  set$(ctx, `containerColumn${i}`, -1);
@@ -1229,6 +1241,9 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
1229
1241
  calculateItemsInView(state.scrollVelocity);
1230
1242
  }
1231
1243
  }
1244
+ if (onItemSizeChanged) {
1245
+ onItemSizeChanged({ size, previous: prevSize, index, itemKey, itemData: data2[index] });
1246
+ }
1232
1247
  } else {
1233
1248
  set$(ctx, `containerDidLayout${containerId}`, true);
1234
1249
  }
@@ -1241,12 +1256,10 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
1241
1256
  const onLayout = useCallback((event) => {
1242
1257
  const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
1243
1258
  refState.current.scrollLength = scrollLength;
1244
- if (refState.current.hasScrolled) {
1245
- doMaintainScrollAtEnd(false);
1246
- doUpdatePaddingTop();
1247
- checkAtBottom();
1248
- checkAtTop();
1249
- }
1259
+ doMaintainScrollAtEnd(false);
1260
+ doUpdatePaddingTop();
1261
+ checkAtBottom();
1262
+ checkAtTop();
1250
1263
  if (__DEV__) {
1251
1264
  const isWidthZero = event.nativeEvent.layout.width === 0;
1252
1265
  const isHeightZero = event.nativeEvent.layout.height === 0;
@@ -1265,7 +1278,6 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
1265
1278
  }
1266
1279
  const state = refState.current;
1267
1280
  state.hasScrolled = true;
1268
- state.contentSize = event.nativeEvent.contentSize;
1269
1281
  const currentTime = performance.now();
1270
1282
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
1271
1283
  if (!(state.scrollHistory.length === 0 && newScroll === initialContentOffset)) {
@@ -1353,6 +1365,7 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
1353
1365
  alignItemsAtEnd,
1354
1366
  ListEmptyComponent: data.length === 0 ? ListEmptyComponent : void 0,
1355
1367
  maintainVisibleContentPosition,
1368
+ scrollEventThrottle: scrollEventThrottle != null ? scrollEventThrottle : Platform.OS === "web" ? 16 : void 0,
1356
1369
  style
1357
1370
  }
1358
1371
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "0.6.3",
3
+ "version": "1.0.0-beta.0",
4
4
  "description": "legend-list",
5
5
  "sideEffects": false,
6
6
  "private": false,
package/reanimated.d.mts CHANGED
@@ -2,7 +2,7 @@ import { LegendListRef, LegendListPropsBase } from '@legendapp/list';
2
2
  import React__default, { ComponentProps } from 'react';
3
3
  import Animated from 'react-native-reanimated';
4
4
 
5
- type KeysToOmit = "getEstimatedItemSize" | "keyExtractor" | "animatedProps" | "renderItem";
5
+ type KeysToOmit = "getEstimatedItemSize" | "keyExtractor" | "animatedProps" | "renderItem" | "onItemSizeChanged";
6
6
  type PropsBase<ItemT> = LegendListPropsBase<ItemT, ComponentProps<typeof Animated.ScrollView>>;
7
7
  interface AnimatedLegendListProps<ItemT> extends Omit<PropsBase<ItemT>, KeysToOmit> {
8
8
  refScrollView?: React__default.Ref<Animated.ScrollView>;
package/reanimated.d.ts CHANGED
@@ -2,7 +2,7 @@ import { LegendListRef, LegendListPropsBase } from '@legendapp/list';
2
2
  import React__default, { ComponentProps } from 'react';
3
3
  import Animated from 'react-native-reanimated';
4
4
 
5
- type KeysToOmit = "getEstimatedItemSize" | "keyExtractor" | "animatedProps" | "renderItem";
5
+ type KeysToOmit = "getEstimatedItemSize" | "keyExtractor" | "animatedProps" | "renderItem" | "onItemSizeChanged";
6
6
  type PropsBase<ItemT> = LegendListPropsBase<ItemT, ComponentProps<typeof Animated.ScrollView>>;
7
7
  interface AnimatedLegendListProps<ItemT> extends Omit<PropsBase<ItemT>, KeysToOmit> {
8
8
  refScrollView?: React__default.Ref<Animated.ScrollView>;