@legendapp/list 0.1.1 → 0.2.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
@@ -70,3 +70,11 @@ There's not a lot of code here so hopefully it's easy to contribute. If you want
70
70
  - Other important missing features from FlatList or other lists libraries
71
71
  - Optimizations:
72
72
  - Loop over only potentially changed items when adjusting heights rather than data array
73
+
74
+ ## Community
75
+
76
+ Join us on [Discord](https://discord.gg/tuW2pAffjA) to get involved with the Legend community.
77
+
78
+ ## 👩‍⚖️ License
79
+
80
+ [MIT](LICENSE)
package/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ComponentProps, ReactNode } from 'react';
3
- import { ScrollView } from 'react-native';
3
+ import * as react_native from 'react-native';
4
+ import { ScrollView, StyleProp, ViewStyle } from 'react-native';
4
5
 
5
6
  type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset'> & {
6
7
  data: ArrayLike<any> & T[];
@@ -12,6 +13,7 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
12
13
  onEndReachedThreshold?: number | null | undefined;
13
14
  autoScrollToBottom?: boolean;
14
15
  autoScrollToBottomThreshold?: number;
16
+ startAtBottom?: boolean;
15
17
  estimatedItemLength: (index: number) => number;
16
18
  onEndReached?: ((info: {
17
19
  distanceFromEnd: number;
@@ -19,6 +21,10 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
19
21
  keyExtractor?: (item: T, index: number) => string;
20
22
  renderItem?: (props: LegendListRenderItemInfo<T>) => ReactNode;
21
23
  onViewableRangeChanged?: (range: ViewableRange<T>) => void;
24
+ ListHeaderComponent?: ReactNode;
25
+ ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
26
+ ListFooterComponent?: ReactNode;
27
+ ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
22
28
  };
23
29
  interface ViewableRange<T> {
24
30
  startBuffered: number;
@@ -32,6 +38,28 @@ interface LegendListRenderItemInfo<ItemT> {
32
38
  index: number;
33
39
  }
34
40
 
35
- declare function LegendList<T>(props: LegendListProps<T>): React.JSX.Element;
41
+ declare const LegendList: React.ForwardRefExoticComponent<Omit<react_native.ScrollViewProps, "contentOffset"> & {
42
+ data: ArrayLike<any> & unknown[];
43
+ initialScrollOffset?: number;
44
+ initialScrollIndex?: number;
45
+ drawDistance?: number;
46
+ initialContainers?: number;
47
+ recycleItems?: boolean;
48
+ onEndReachedThreshold?: number | null | undefined;
49
+ autoScrollToBottom?: boolean;
50
+ autoScrollToBottomThreshold?: number;
51
+ startAtBottom?: boolean;
52
+ estimatedItemLength: (index: number) => number;
53
+ onEndReached?: ((info: {
54
+ distanceFromEnd: number;
55
+ }) => void) | null | undefined;
56
+ keyExtractor?: ((item: unknown, index: number) => string) | undefined;
57
+ renderItem?: ((props: LegendListRenderItemInfo<unknown>) => React.ReactNode) | undefined;
58
+ onViewableRangeChanged?: ((range: ViewableRange<unknown>) => void) | undefined;
59
+ ListHeaderComponent?: React.ReactNode;
60
+ ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
61
+ ListFooterComponent?: React.ReactNode;
62
+ ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
63
+ } & React.RefAttributes<ScrollView>>;
36
64
 
37
65
  export { LegendList, type LegendListProps, type LegendListRenderItemInfo, type ViewableRange };
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ComponentProps, ReactNode } from 'react';
3
- import { ScrollView } from 'react-native';
3
+ import * as react_native from 'react-native';
4
+ import { ScrollView, StyleProp, ViewStyle } from 'react-native';
4
5
 
5
6
  type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset'> & {
6
7
  data: ArrayLike<any> & T[];
@@ -12,6 +13,7 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
12
13
  onEndReachedThreshold?: number | null | undefined;
13
14
  autoScrollToBottom?: boolean;
14
15
  autoScrollToBottomThreshold?: number;
16
+ startAtBottom?: boolean;
15
17
  estimatedItemLength: (index: number) => number;
16
18
  onEndReached?: ((info: {
17
19
  distanceFromEnd: number;
@@ -19,6 +21,10 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
19
21
  keyExtractor?: (item: T, index: number) => string;
20
22
  renderItem?: (props: LegendListRenderItemInfo<T>) => ReactNode;
21
23
  onViewableRangeChanged?: (range: ViewableRange<T>) => void;
24
+ ListHeaderComponent?: ReactNode;
25
+ ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
26
+ ListFooterComponent?: ReactNode;
27
+ ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
22
28
  };
23
29
  interface ViewableRange<T> {
24
30
  startBuffered: number;
@@ -32,6 +38,28 @@ interface LegendListRenderItemInfo<ItemT> {
32
38
  index: number;
33
39
  }
34
40
 
35
- declare function LegendList<T>(props: LegendListProps<T>): React.JSX.Element;
41
+ declare const LegendList: React.ForwardRefExoticComponent<Omit<react_native.ScrollViewProps, "contentOffset"> & {
42
+ data: ArrayLike<any> & unknown[];
43
+ initialScrollOffset?: number;
44
+ initialScrollIndex?: number;
45
+ drawDistance?: number;
46
+ initialContainers?: number;
47
+ recycleItems?: boolean;
48
+ onEndReachedThreshold?: number | null | undefined;
49
+ autoScrollToBottom?: boolean;
50
+ autoScrollToBottomThreshold?: number;
51
+ startAtBottom?: boolean;
52
+ estimatedItemLength: (index: number) => number;
53
+ onEndReached?: ((info: {
54
+ distanceFromEnd: number;
55
+ }) => void) | null | undefined;
56
+ keyExtractor?: ((item: unknown, index: number) => string) | undefined;
57
+ renderItem?: ((props: LegendListRenderItemInfo<unknown>) => React.ReactNode) | undefined;
58
+ onViewableRangeChanged?: ((range: ViewableRange<unknown>) => void) | undefined;
59
+ ListHeaderComponent?: React.ReactNode;
60
+ ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
61
+ ListFooterComponent?: React.ReactNode;
62
+ ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
63
+ } & React.RefAttributes<ScrollView>>;
36
64
 
37
65
  export { LegendList, type LegendListProps, type LegendListRenderItemInfo, type ViewableRange };
package/index.js CHANGED
@@ -71,7 +71,7 @@ var Container = ({
71
71
  enableReactNativeComponents.enableReactNativeComponents();
72
72
  var DEFAULT_SCROLL_BUFFER = 0;
73
73
  var POSITION_OUT_OF_VIEW = -1e4;
74
- function LegendList(props) {
74
+ var LegendList = React2.forwardRef(function LegendList2(props, forwardedRef) {
75
75
  const {
76
76
  data,
77
77
  initialScrollIndex,
@@ -81,21 +81,26 @@ function LegendList(props) {
81
81
  contentContainerStyle,
82
82
  initialContainers,
83
83
  drawDistance,
84
- recycleItems = false,
84
+ recycleItems = true,
85
85
  onEndReachedThreshold,
86
86
  autoScrollToBottom = false,
87
87
  autoScrollToBottomThreshold = 0.1,
88
+ startAtBottom = false,
88
89
  keyExtractor,
89
90
  renderItem,
90
91
  estimatedItemLength,
91
92
  onEndReached,
92
93
  onViewableRangeChanged,
93
- children,
94
+ ListHeaderComponent,
95
+ ListHeaderComponentStyle,
96
+ ListFooterComponent,
97
+ ListFooterComponentStyle,
94
98
  ...rest
95
99
  } = props;
96
- const childrenArray = children && !data ? React2__namespace.Children.toArray(children) : void 0;
97
- const refScroller = React2.useRef(null);
100
+ const internalRef = React2.useRef(null);
101
+ const refScroller = forwardedRef || internalRef;
98
102
  const containers$ = react.useObservable(() => []);
103
+ const paddingTop$ = react.useObservable(0);
99
104
  const visibleRange$ = react.useObservable(() => ({
100
105
  start: 0,
101
106
  end: 0,
@@ -111,7 +116,7 @@ function LegendList(props) {
111
116
  if (!data2) {
112
117
  return "";
113
118
  }
114
- const ret = index < (data2 || childrenArray).length ? data2 ? keyExtractor ? keyExtractor(data2[index], index) : index : index : null;
119
+ const ret = index < data2.length ? keyExtractor ? keyExtractor(data2[index], index) : index : null;
115
120
  return ret + "";
116
121
  };
117
122
  if (!refPositions.current) {
@@ -126,16 +131,23 @@ function LegendList(props) {
126
131
  data,
127
132
  idsInFirstRender: void 0,
128
133
  hasScrolled: false,
129
- childrenArray
134
+ scrollLength: reactNative.Dimensions.get("window")[horizontal ? "width" : "height"]
130
135
  };
131
- refPositions.current.idsInFirstRender = new Set((data || childrenArray).map((_, i) => getId(i)));
136
+ refPositions.current.idsInFirstRender = new Set(data.map((_, i) => getId(i)));
132
137
  }
133
138
  refPositions.current.data = data;
134
- refPositions.current.childrenArray = childrenArray;
135
- const SCREEN_LENGTH = reactNative.Dimensions.get("window")[horizontal ? "width" : "height"];
136
139
  const initialContentOffset = initialScrollOffset != null ? initialScrollOffset : initialScrollIndex ? initialScrollIndex * estimatedItemLength(initialScrollIndex) : void 0;
140
+ const setTotalLength = (length) => {
141
+ visibleRange$.totalLength.set(length);
142
+ const screenLength = refPositions.current.scrollLength;
143
+ if (startAtBottom) {
144
+ const listPaddingTop = ((style == null ? void 0 : style.paddingTop) || 0) + ((contentContainerStyle == null ? void 0 : contentContainerStyle.paddingTop) || 0);
145
+ paddingTop$.set(Math.max(0, screenLength - length - listPaddingTop));
146
+ }
147
+ };
137
148
  const allocateContainers = React2.useCallback(() => {
138
- const numContainers = initialContainers || Math.ceil((SCREEN_LENGTH + scrollBuffer * 2) / estimatedItemLength(0)) + 4;
149
+ const scrollLength = refPositions.current.scrollLength;
150
+ const numContainers = initialContainers || Math.ceil((scrollLength + scrollBuffer * 2) / estimatedItemLength(0)) + 4;
139
151
  const containers2 = [];
140
152
  for (let i = 0; i < numContainers; i++) {
141
153
  containers2.push({
@@ -148,15 +160,10 @@ function LegendList(props) {
148
160
  }, []);
149
161
  const getRenderedItem = React2.useCallback(
150
162
  (index) => {
151
- var _a, _b;
163
+ var _a;
152
164
  const data2 = (_a = refPositions.current) == null ? void 0 : _a.data;
153
165
  if (!data2) {
154
- const childrenArray2 = (_b = refPositions.current) == null ? void 0 : _b.childrenArray;
155
- if (childrenArray2) {
156
- return childrenArray2[index];
157
- } else {
158
- return null;
159
- }
166
+ return null;
160
167
  }
161
168
  const renderedItem = renderItem == null ? void 0 : renderItem({
162
169
  item: data2[index],
@@ -168,7 +175,7 @@ function LegendList(props) {
168
175
  );
169
176
  const calculateItemsInView = React2.useCallback(() => {
170
177
  var _a, _b;
171
- const data2 = refPositions.current.data;
178
+ const { data: data2, scrollLength } = refPositions.current;
172
179
  if (!data2) {
173
180
  return;
174
181
  }
@@ -194,10 +201,10 @@ function LegendList(props) {
194
201
  startBuffered = i;
195
202
  }
196
203
  if (startNoBuffer !== null) {
197
- if (top <= scroll + SCREEN_LENGTH) {
204
+ if (top <= scroll + scrollLength) {
198
205
  endNoBuffer = i;
199
206
  }
200
- if (top <= scroll + SCREEN_LENGTH + scrollBuffer) {
207
+ if (top <= scroll + scrollLength + scrollBuffer) {
201
208
  endBuffered = i;
202
209
  } else {
203
210
  break;
@@ -286,12 +293,34 @@ function LegendList(props) {
286
293
  const id = getId(i);
287
294
  totalLength += (_b = lengths.get(id)) != null ? _b : estimatedItemLength(i);
288
295
  }
289
- visibleRange$.totalLength.set(totalLength);
296
+ setTotalLength(totalLength);
290
297
  }, []);
298
+ const checkAtBottom = () => {
299
+ var _a;
300
+ const scrollLength = refPositions.current.scrollLength;
301
+ const newScroll = visibleRange$.scroll.peek();
302
+ const distanceFromEnd = visibleRange$.totalLength.peek() - newScroll - scrollLength;
303
+ if (refPositions.current) {
304
+ refPositions.current.isAtBottom = distanceFromEnd < scrollLength * autoScrollToBottomThreshold;
305
+ }
306
+ if (onEndReached && !((_a = refPositions.current) == null ? void 0 : _a.isEndReached)) {
307
+ if (distanceFromEnd < (onEndReachedThreshold || 0.5) * scrollLength) {
308
+ if (refPositions.current) {
309
+ refPositions.current.isEndReached = true;
310
+ }
311
+ onEndReached({ distanceFromEnd });
312
+ }
313
+ }
314
+ };
291
315
  React2.useMemo(() => {
316
+ var _a;
292
317
  if (refPositions.current) {
293
- refPositions.current.isEndReached = false;
318
+ if (!((_a = refPositions.current) == null ? void 0 : _a.isAtBottom)) {
319
+ refPositions.current.isEndReached = false;
320
+ }
294
321
  }
322
+ calculateItemsInView();
323
+ checkAtBottom();
295
324
  }, [data]);
296
325
  const containers = react.use$(containers$, { shallow: true });
297
326
  const updateItemLength = React2.useCallback((index, length) => {
@@ -307,7 +336,7 @@ function LegendList(props) {
307
336
  if (!prevLength || prevLength !== length) {
308
337
  state.beginBatch();
309
338
  lengths.set(id, length);
310
- visibleRange$.totalLength.set((prevTotal) => prevTotal + (length - prevLength));
339
+ setTotalLength(visibleRange$.totalLength.peek() + (length - prevLength));
311
340
  if (((_d = refPositions.current) == null ? void 0 : _d.isAtBottom) && autoScrollToBottom) {
312
341
  requestAnimationFrame(() => {
313
342
  var _a2;
@@ -323,25 +352,16 @@ function LegendList(props) {
323
352
  }
324
353
  }, []);
325
354
  const handleScrollDebounced = React2.useCallback(() => {
326
- var _a;
327
- const newScroll = visibleRange$.scroll.peek();
328
355
  calculateItemsInView();
329
- const distanceFromEnd = visibleRange$.totalLength.peek() - newScroll - SCREEN_LENGTH;
330
- if (refPositions.current) {
331
- refPositions.current.isAtBottom = distanceFromEnd < SCREEN_LENGTH * autoScrollToBottomThreshold;
332
- }
333
- if (onEndReached && !((_a = refPositions.current) == null ? void 0 : _a.isEndReached)) {
334
- if (distanceFromEnd < (onEndReachedThreshold || 0.5) * SCREEN_LENGTH) {
335
- if (refPositions.current) {
336
- refPositions.current.isEndReached = true;
337
- }
338
- onEndReached({ distanceFromEnd });
339
- }
340
- }
356
+ checkAtBottom();
341
357
  if (refPositions.current) {
342
358
  refPositions.current.animFrame = null;
343
359
  }
344
360
  }, []);
361
+ const onLayout = (event) => {
362
+ const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
363
+ refPositions.current.scrollLength = scrollLength;
364
+ };
345
365
  const handleScroll = React2.useCallback((event) => {
346
366
  refPositions.current.hasScrolled = true;
347
367
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
@@ -369,12 +389,15 @@ function LegendList(props) {
369
389
  } : {}
370
390
  ],
371
391
  onScroll: handleScroll,
392
+ onLayout,
372
393
  scrollEventThrottle: 32,
373
394
  horizontal,
374
395
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
375
396
  ...rest,
376
397
  ref: refScroller
377
398
  },
399
+ startAtBottom && /* @__PURE__ */ React2__namespace.createElement(react.Reactive.View, { $style: () => ({ height: paddingTop$.get() }) }),
400
+ ListHeaderComponent && /* @__PURE__ */ React2__namespace.createElement(react.Reactive.View, { $style: ListHeaderComponentStyle }, ListHeaderComponent),
378
401
  /* @__PURE__ */ React2__namespace.createElement(
379
402
  react.Reactive.View,
380
403
  {
@@ -395,8 +418,9 @@ function LegendList(props) {
395
418
  onLayout: updateItemLength
396
419
  }
397
420
  ))
398
- )
421
+ ),
422
+ ListFooterComponent && /* @__PURE__ */ React2__namespace.createElement(react.Reactive.View, { $style: ListFooterComponentStyle }, ListFooterComponent)
399
423
  );
400
- }
424
+ });
401
425
 
402
426
  exports.LegendList = LegendList;
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React2 from 'react';
2
- import { useRef, useCallback, useMemo, useEffect } from 'react';
2
+ import { forwardRef, useRef, useCallback, useMemo, useEffect } from 'react';
3
3
  import { beginBatch, endBatch } from '@legendapp/state';
4
4
  import { enableReactNativeComponents } from '@legendapp/state/config/enableReactNativeComponents';
5
5
  import { useObservable, use$, Reactive } from '@legendapp/state/react';
@@ -50,7 +50,7 @@ var Container = ({
50
50
  enableReactNativeComponents();
51
51
  var DEFAULT_SCROLL_BUFFER = 0;
52
52
  var POSITION_OUT_OF_VIEW = -1e4;
53
- function LegendList(props) {
53
+ var LegendList = forwardRef(function LegendList2(props, forwardedRef) {
54
54
  const {
55
55
  data,
56
56
  initialScrollIndex,
@@ -60,21 +60,26 @@ function LegendList(props) {
60
60
  contentContainerStyle,
61
61
  initialContainers,
62
62
  drawDistance,
63
- recycleItems = false,
63
+ recycleItems = true,
64
64
  onEndReachedThreshold,
65
65
  autoScrollToBottom = false,
66
66
  autoScrollToBottomThreshold = 0.1,
67
+ startAtBottom = false,
67
68
  keyExtractor,
68
69
  renderItem,
69
70
  estimatedItemLength,
70
71
  onEndReached,
71
72
  onViewableRangeChanged,
72
- children,
73
+ ListHeaderComponent,
74
+ ListHeaderComponentStyle,
75
+ ListFooterComponent,
76
+ ListFooterComponentStyle,
73
77
  ...rest
74
78
  } = props;
75
- const childrenArray = children && !data ? React2.Children.toArray(children) : void 0;
76
- const refScroller = useRef(null);
79
+ const internalRef = useRef(null);
80
+ const refScroller = forwardedRef || internalRef;
77
81
  const containers$ = useObservable(() => []);
82
+ const paddingTop$ = useObservable(0);
78
83
  const visibleRange$ = useObservable(() => ({
79
84
  start: 0,
80
85
  end: 0,
@@ -90,7 +95,7 @@ function LegendList(props) {
90
95
  if (!data2) {
91
96
  return "";
92
97
  }
93
- const ret = index < (data2 || childrenArray).length ? data2 ? keyExtractor ? keyExtractor(data2[index], index) : index : index : null;
98
+ const ret = index < data2.length ? keyExtractor ? keyExtractor(data2[index], index) : index : null;
94
99
  return ret + "";
95
100
  };
96
101
  if (!refPositions.current) {
@@ -105,16 +110,23 @@ function LegendList(props) {
105
110
  data,
106
111
  idsInFirstRender: void 0,
107
112
  hasScrolled: false,
108
- childrenArray
113
+ scrollLength: Dimensions.get("window")[horizontal ? "width" : "height"]
109
114
  };
110
- refPositions.current.idsInFirstRender = new Set((data || childrenArray).map((_, i) => getId(i)));
115
+ refPositions.current.idsInFirstRender = new Set(data.map((_, i) => getId(i)));
111
116
  }
112
117
  refPositions.current.data = data;
113
- refPositions.current.childrenArray = childrenArray;
114
- const SCREEN_LENGTH = Dimensions.get("window")[horizontal ? "width" : "height"];
115
118
  const initialContentOffset = initialScrollOffset != null ? initialScrollOffset : initialScrollIndex ? initialScrollIndex * estimatedItemLength(initialScrollIndex) : void 0;
119
+ const setTotalLength = (length) => {
120
+ visibleRange$.totalLength.set(length);
121
+ const screenLength = refPositions.current.scrollLength;
122
+ if (startAtBottom) {
123
+ const listPaddingTop = ((style == null ? void 0 : style.paddingTop) || 0) + ((contentContainerStyle == null ? void 0 : contentContainerStyle.paddingTop) || 0);
124
+ paddingTop$.set(Math.max(0, screenLength - length - listPaddingTop));
125
+ }
126
+ };
116
127
  const allocateContainers = useCallback(() => {
117
- const numContainers = initialContainers || Math.ceil((SCREEN_LENGTH + scrollBuffer * 2) / estimatedItemLength(0)) + 4;
128
+ const scrollLength = refPositions.current.scrollLength;
129
+ const numContainers = initialContainers || Math.ceil((scrollLength + scrollBuffer * 2) / estimatedItemLength(0)) + 4;
118
130
  const containers2 = [];
119
131
  for (let i = 0; i < numContainers; i++) {
120
132
  containers2.push({
@@ -127,15 +139,10 @@ function LegendList(props) {
127
139
  }, []);
128
140
  const getRenderedItem = useCallback(
129
141
  (index) => {
130
- var _a, _b;
142
+ var _a;
131
143
  const data2 = (_a = refPositions.current) == null ? void 0 : _a.data;
132
144
  if (!data2) {
133
- const childrenArray2 = (_b = refPositions.current) == null ? void 0 : _b.childrenArray;
134
- if (childrenArray2) {
135
- return childrenArray2[index];
136
- } else {
137
- return null;
138
- }
145
+ return null;
139
146
  }
140
147
  const renderedItem = renderItem == null ? void 0 : renderItem({
141
148
  item: data2[index],
@@ -147,7 +154,7 @@ function LegendList(props) {
147
154
  );
148
155
  const calculateItemsInView = useCallback(() => {
149
156
  var _a, _b;
150
- const data2 = refPositions.current.data;
157
+ const { data: data2, scrollLength } = refPositions.current;
151
158
  if (!data2) {
152
159
  return;
153
160
  }
@@ -173,10 +180,10 @@ function LegendList(props) {
173
180
  startBuffered = i;
174
181
  }
175
182
  if (startNoBuffer !== null) {
176
- if (top <= scroll + SCREEN_LENGTH) {
183
+ if (top <= scroll + scrollLength) {
177
184
  endNoBuffer = i;
178
185
  }
179
- if (top <= scroll + SCREEN_LENGTH + scrollBuffer) {
186
+ if (top <= scroll + scrollLength + scrollBuffer) {
180
187
  endBuffered = i;
181
188
  } else {
182
189
  break;
@@ -265,12 +272,34 @@ function LegendList(props) {
265
272
  const id = getId(i);
266
273
  totalLength += (_b = lengths.get(id)) != null ? _b : estimatedItemLength(i);
267
274
  }
268
- visibleRange$.totalLength.set(totalLength);
275
+ setTotalLength(totalLength);
269
276
  }, []);
277
+ const checkAtBottom = () => {
278
+ var _a;
279
+ const scrollLength = refPositions.current.scrollLength;
280
+ const newScroll = visibleRange$.scroll.peek();
281
+ const distanceFromEnd = visibleRange$.totalLength.peek() - newScroll - scrollLength;
282
+ if (refPositions.current) {
283
+ refPositions.current.isAtBottom = distanceFromEnd < scrollLength * autoScrollToBottomThreshold;
284
+ }
285
+ if (onEndReached && !((_a = refPositions.current) == null ? void 0 : _a.isEndReached)) {
286
+ if (distanceFromEnd < (onEndReachedThreshold || 0.5) * scrollLength) {
287
+ if (refPositions.current) {
288
+ refPositions.current.isEndReached = true;
289
+ }
290
+ onEndReached({ distanceFromEnd });
291
+ }
292
+ }
293
+ };
270
294
  useMemo(() => {
295
+ var _a;
271
296
  if (refPositions.current) {
272
- refPositions.current.isEndReached = false;
297
+ if (!((_a = refPositions.current) == null ? void 0 : _a.isAtBottom)) {
298
+ refPositions.current.isEndReached = false;
299
+ }
273
300
  }
301
+ calculateItemsInView();
302
+ checkAtBottom();
274
303
  }, [data]);
275
304
  const containers = use$(containers$, { shallow: true });
276
305
  const updateItemLength = useCallback((index, length) => {
@@ -286,7 +315,7 @@ function LegendList(props) {
286
315
  if (!prevLength || prevLength !== length) {
287
316
  beginBatch();
288
317
  lengths.set(id, length);
289
- visibleRange$.totalLength.set((prevTotal) => prevTotal + (length - prevLength));
318
+ setTotalLength(visibleRange$.totalLength.peek() + (length - prevLength));
290
319
  if (((_d = refPositions.current) == null ? void 0 : _d.isAtBottom) && autoScrollToBottom) {
291
320
  requestAnimationFrame(() => {
292
321
  var _a2;
@@ -302,25 +331,16 @@ function LegendList(props) {
302
331
  }
303
332
  }, []);
304
333
  const handleScrollDebounced = useCallback(() => {
305
- var _a;
306
- const newScroll = visibleRange$.scroll.peek();
307
334
  calculateItemsInView();
308
- const distanceFromEnd = visibleRange$.totalLength.peek() - newScroll - SCREEN_LENGTH;
309
- if (refPositions.current) {
310
- refPositions.current.isAtBottom = distanceFromEnd < SCREEN_LENGTH * autoScrollToBottomThreshold;
311
- }
312
- if (onEndReached && !((_a = refPositions.current) == null ? void 0 : _a.isEndReached)) {
313
- if (distanceFromEnd < (onEndReachedThreshold || 0.5) * SCREEN_LENGTH) {
314
- if (refPositions.current) {
315
- refPositions.current.isEndReached = true;
316
- }
317
- onEndReached({ distanceFromEnd });
318
- }
319
- }
335
+ checkAtBottom();
320
336
  if (refPositions.current) {
321
337
  refPositions.current.animFrame = null;
322
338
  }
323
339
  }, []);
340
+ const onLayout = (event) => {
341
+ const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
342
+ refPositions.current.scrollLength = scrollLength;
343
+ };
324
344
  const handleScroll = useCallback((event) => {
325
345
  refPositions.current.hasScrolled = true;
326
346
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
@@ -348,12 +368,15 @@ function LegendList(props) {
348
368
  } : {}
349
369
  ],
350
370
  onScroll: handleScroll,
371
+ onLayout,
351
372
  scrollEventThrottle: 32,
352
373
  horizontal,
353
374
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
354
375
  ...rest,
355
376
  ref: refScroller
356
377
  },
378
+ startAtBottom && /* @__PURE__ */ React2.createElement(Reactive.View, { $style: () => ({ height: paddingTop$.get() }) }),
379
+ ListHeaderComponent && /* @__PURE__ */ React2.createElement(Reactive.View, { $style: ListHeaderComponentStyle }, ListHeaderComponent),
357
380
  /* @__PURE__ */ React2.createElement(
358
381
  Reactive.View,
359
382
  {
@@ -374,8 +397,9 @@ function LegendList(props) {
374
397
  onLayout: updateItemLength
375
398
  }
376
399
  ))
377
- )
400
+ ),
401
+ ListFooterComponent && /* @__PURE__ */ React2.createElement(Reactive.View, { $style: ListFooterComponentStyle }, ListFooterComponent)
378
402
  );
379
- }
403
+ });
380
404
 
381
405
  export { LegendList };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "legend-list",
5
5
  "sideEffects": false,
6
6
  "private": false,