@legendapp/list 0.1.2 → 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/index.d.mts CHANGED
@@ -13,6 +13,7 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
13
13
  onEndReachedThreshold?: number | null | undefined;
14
14
  autoScrollToBottom?: boolean;
15
15
  autoScrollToBottomThreshold?: number;
16
+ startAtBottom?: boolean;
16
17
  estimatedItemLength: (index: number) => number;
17
18
  onEndReached?: ((info: {
18
19
  distanceFromEnd: number;
@@ -47,6 +48,7 @@ declare const LegendList: React.ForwardRefExoticComponent<Omit<react_native.Scro
47
48
  onEndReachedThreshold?: number | null | undefined;
48
49
  autoScrollToBottom?: boolean;
49
50
  autoScrollToBottomThreshold?: number;
51
+ startAtBottom?: boolean;
50
52
  estimatedItemLength: (index: number) => number;
51
53
  onEndReached?: ((info: {
52
54
  distanceFromEnd: number;
@@ -55,9 +57,9 @@ declare const LegendList: React.ForwardRefExoticComponent<Omit<react_native.Scro
55
57
  renderItem?: ((props: LegendListRenderItemInfo<unknown>) => React.ReactNode) | undefined;
56
58
  onViewableRangeChanged?: ((range: ViewableRange<unknown>) => void) | undefined;
57
59
  ListHeaderComponent?: React.ReactNode;
58
- ListHeaderComponentStyle?: react_native.StyleProp<react_native.ViewStyle> | undefined;
60
+ ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
59
61
  ListFooterComponent?: React.ReactNode;
60
- ListFooterComponentStyle?: react_native.StyleProp<react_native.ViewStyle> | undefined;
62
+ ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
61
63
  } & React.RefAttributes<ScrollView>>;
62
64
 
63
65
  export { LegendList, type LegendListProps, type LegendListRenderItemInfo, type ViewableRange };
package/index.d.ts CHANGED
@@ -13,6 +13,7 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
13
13
  onEndReachedThreshold?: number | null | undefined;
14
14
  autoScrollToBottom?: boolean;
15
15
  autoScrollToBottomThreshold?: number;
16
+ startAtBottom?: boolean;
16
17
  estimatedItemLength: (index: number) => number;
17
18
  onEndReached?: ((info: {
18
19
  distanceFromEnd: number;
@@ -47,6 +48,7 @@ declare const LegendList: React.ForwardRefExoticComponent<Omit<react_native.Scro
47
48
  onEndReachedThreshold?: number | null | undefined;
48
49
  autoScrollToBottom?: boolean;
49
50
  autoScrollToBottomThreshold?: number;
51
+ startAtBottom?: boolean;
50
52
  estimatedItemLength: (index: number) => number;
51
53
  onEndReached?: ((info: {
52
54
  distanceFromEnd: number;
@@ -55,9 +57,9 @@ declare const LegendList: React.ForwardRefExoticComponent<Omit<react_native.Scro
55
57
  renderItem?: ((props: LegendListRenderItemInfo<unknown>) => React.ReactNode) | undefined;
56
58
  onViewableRangeChanged?: ((range: ViewableRange<unknown>) => void) | undefined;
57
59
  ListHeaderComponent?: React.ReactNode;
58
- ListHeaderComponentStyle?: react_native.StyleProp<react_native.ViewStyle> | undefined;
60
+ ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
59
61
  ListFooterComponent?: React.ReactNode;
60
- ListFooterComponentStyle?: react_native.StyleProp<react_native.ViewStyle> | undefined;
62
+ ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
61
63
  } & React.RefAttributes<ScrollView>>;
62
64
 
63
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
- var LegendList = React2.forwardRef((props, forwardedRef) => {
74
+ var LegendList = React2.forwardRef(function LegendList2(props, forwardedRef) {
75
75
  const {
76
76
  data,
77
77
  initialScrollIndex,
@@ -85,6 +85,7 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
85
85
  onEndReachedThreshold,
86
86
  autoScrollToBottom = false,
87
87
  autoScrollToBottomThreshold = 0.1,
88
+ startAtBottom = false,
88
89
  keyExtractor,
89
90
  renderItem,
90
91
  estimatedItemLength,
@@ -99,6 +100,7 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
99
100
  const internalRef = React2.useRef(null);
100
101
  const refScroller = forwardedRef || internalRef;
101
102
  const containers$ = react.useObservable(() => []);
103
+ const paddingTop$ = react.useObservable(0);
102
104
  const visibleRange$ = react.useObservable(() => ({
103
105
  start: 0,
104
106
  end: 0,
@@ -128,15 +130,24 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
128
130
  isAtBottom: false,
129
131
  data,
130
132
  idsInFirstRender: void 0,
131
- hasScrolled: false
133
+ hasScrolled: false,
134
+ scrollLength: reactNative.Dimensions.get("window")[horizontal ? "width" : "height"]
132
135
  };
133
136
  refPositions.current.idsInFirstRender = new Set(data.map((_, i) => getId(i)));
134
137
  }
135
138
  refPositions.current.data = data;
136
- const SCREEN_LENGTH = reactNative.Dimensions.get("window")[horizontal ? "width" : "height"];
137
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
+ };
138
148
  const allocateContainers = React2.useCallback(() => {
139
- 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;
140
151
  const containers2 = [];
141
152
  for (let i = 0; i < numContainers; i++) {
142
153
  containers2.push({
@@ -164,7 +175,7 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
164
175
  );
165
176
  const calculateItemsInView = React2.useCallback(() => {
166
177
  var _a, _b;
167
- const data2 = refPositions.current.data;
178
+ const { data: data2, scrollLength } = refPositions.current;
168
179
  if (!data2) {
169
180
  return;
170
181
  }
@@ -190,10 +201,10 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
190
201
  startBuffered = i;
191
202
  }
192
203
  if (startNoBuffer !== null) {
193
- if (top <= scroll + SCREEN_LENGTH) {
204
+ if (top <= scroll + scrollLength) {
194
205
  endNoBuffer = i;
195
206
  }
196
- if (top <= scroll + SCREEN_LENGTH + scrollBuffer) {
207
+ if (top <= scroll + scrollLength + scrollBuffer) {
197
208
  endBuffered = i;
198
209
  } else {
199
210
  break;
@@ -282,12 +293,34 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
282
293
  const id = getId(i);
283
294
  totalLength += (_b = lengths.get(id)) != null ? _b : estimatedItemLength(i);
284
295
  }
285
- visibleRange$.totalLength.set(totalLength);
296
+ setTotalLength(totalLength);
286
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
+ };
287
315
  React2.useMemo(() => {
316
+ var _a;
288
317
  if (refPositions.current) {
289
- refPositions.current.isEndReached = false;
318
+ if (!((_a = refPositions.current) == null ? void 0 : _a.isAtBottom)) {
319
+ refPositions.current.isEndReached = false;
320
+ }
290
321
  }
322
+ calculateItemsInView();
323
+ checkAtBottom();
291
324
  }, [data]);
292
325
  const containers = react.use$(containers$, { shallow: true });
293
326
  const updateItemLength = React2.useCallback((index, length) => {
@@ -303,7 +336,7 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
303
336
  if (!prevLength || prevLength !== length) {
304
337
  state.beginBatch();
305
338
  lengths.set(id, length);
306
- visibleRange$.totalLength.set((prevTotal) => prevTotal + (length - prevLength));
339
+ setTotalLength(visibleRange$.totalLength.peek() + (length - prevLength));
307
340
  if (((_d = refPositions.current) == null ? void 0 : _d.isAtBottom) && autoScrollToBottom) {
308
341
  requestAnimationFrame(() => {
309
342
  var _a2;
@@ -319,25 +352,16 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
319
352
  }
320
353
  }, []);
321
354
  const handleScrollDebounced = React2.useCallback(() => {
322
- var _a;
323
- const newScroll = visibleRange$.scroll.peek();
324
355
  calculateItemsInView();
325
- const distanceFromEnd = visibleRange$.totalLength.peek() - newScroll - SCREEN_LENGTH;
326
- if (refPositions.current) {
327
- refPositions.current.isAtBottom = distanceFromEnd < SCREEN_LENGTH * autoScrollToBottomThreshold;
328
- }
329
- if (onEndReached && !((_a = refPositions.current) == null ? void 0 : _a.isEndReached)) {
330
- if (distanceFromEnd < (onEndReachedThreshold || 0.5) * SCREEN_LENGTH) {
331
- if (refPositions.current) {
332
- refPositions.current.isEndReached = true;
333
- }
334
- onEndReached({ distanceFromEnd });
335
- }
336
- }
356
+ checkAtBottom();
337
357
  if (refPositions.current) {
338
358
  refPositions.current.animFrame = null;
339
359
  }
340
360
  }, []);
361
+ const onLayout = (event) => {
362
+ const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
363
+ refPositions.current.scrollLength = scrollLength;
364
+ };
341
365
  const handleScroll = React2.useCallback((event) => {
342
366
  refPositions.current.hasScrolled = true;
343
367
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
@@ -365,12 +389,14 @@ var LegendList = React2.forwardRef((props, forwardedRef) => {
365
389
  } : {}
366
390
  ],
367
391
  onScroll: handleScroll,
392
+ onLayout,
368
393
  scrollEventThrottle: 32,
369
394
  horizontal,
370
395
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
371
396
  ...rest,
372
397
  ref: refScroller
373
398
  },
399
+ startAtBottom && /* @__PURE__ */ React2__namespace.createElement(react.Reactive.View, { $style: () => ({ height: paddingTop$.get() }) }),
374
400
  ListHeaderComponent && /* @__PURE__ */ React2__namespace.createElement(react.Reactive.View, { $style: ListHeaderComponentStyle }, ListHeaderComponent),
375
401
  /* @__PURE__ */ React2__namespace.createElement(
376
402
  react.Reactive.View,
package/index.mjs CHANGED
@@ -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
- var LegendList = forwardRef((props, forwardedRef) => {
53
+ var LegendList = forwardRef(function LegendList2(props, forwardedRef) {
54
54
  const {
55
55
  data,
56
56
  initialScrollIndex,
@@ -64,6 +64,7 @@ var LegendList = forwardRef((props, forwardedRef) => {
64
64
  onEndReachedThreshold,
65
65
  autoScrollToBottom = false,
66
66
  autoScrollToBottomThreshold = 0.1,
67
+ startAtBottom = false,
67
68
  keyExtractor,
68
69
  renderItem,
69
70
  estimatedItemLength,
@@ -78,6 +79,7 @@ var LegendList = forwardRef((props, forwardedRef) => {
78
79
  const internalRef = useRef(null);
79
80
  const refScroller = forwardedRef || internalRef;
80
81
  const containers$ = useObservable(() => []);
82
+ const paddingTop$ = useObservable(0);
81
83
  const visibleRange$ = useObservable(() => ({
82
84
  start: 0,
83
85
  end: 0,
@@ -107,15 +109,24 @@ var LegendList = forwardRef((props, forwardedRef) => {
107
109
  isAtBottom: false,
108
110
  data,
109
111
  idsInFirstRender: void 0,
110
- hasScrolled: false
112
+ hasScrolled: false,
113
+ scrollLength: Dimensions.get("window")[horizontal ? "width" : "height"]
111
114
  };
112
115
  refPositions.current.idsInFirstRender = new Set(data.map((_, i) => getId(i)));
113
116
  }
114
117
  refPositions.current.data = data;
115
- const SCREEN_LENGTH = Dimensions.get("window")[horizontal ? "width" : "height"];
116
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
+ };
117
127
  const allocateContainers = useCallback(() => {
118
- 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;
119
130
  const containers2 = [];
120
131
  for (let i = 0; i < numContainers; i++) {
121
132
  containers2.push({
@@ -143,7 +154,7 @@ var LegendList = forwardRef((props, forwardedRef) => {
143
154
  );
144
155
  const calculateItemsInView = useCallback(() => {
145
156
  var _a, _b;
146
- const data2 = refPositions.current.data;
157
+ const { data: data2, scrollLength } = refPositions.current;
147
158
  if (!data2) {
148
159
  return;
149
160
  }
@@ -169,10 +180,10 @@ var LegendList = forwardRef((props, forwardedRef) => {
169
180
  startBuffered = i;
170
181
  }
171
182
  if (startNoBuffer !== null) {
172
- if (top <= scroll + SCREEN_LENGTH) {
183
+ if (top <= scroll + scrollLength) {
173
184
  endNoBuffer = i;
174
185
  }
175
- if (top <= scroll + SCREEN_LENGTH + scrollBuffer) {
186
+ if (top <= scroll + scrollLength + scrollBuffer) {
176
187
  endBuffered = i;
177
188
  } else {
178
189
  break;
@@ -261,12 +272,34 @@ var LegendList = forwardRef((props, forwardedRef) => {
261
272
  const id = getId(i);
262
273
  totalLength += (_b = lengths.get(id)) != null ? _b : estimatedItemLength(i);
263
274
  }
264
- visibleRange$.totalLength.set(totalLength);
275
+ setTotalLength(totalLength);
265
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
+ };
266
294
  useMemo(() => {
295
+ var _a;
267
296
  if (refPositions.current) {
268
- refPositions.current.isEndReached = false;
297
+ if (!((_a = refPositions.current) == null ? void 0 : _a.isAtBottom)) {
298
+ refPositions.current.isEndReached = false;
299
+ }
269
300
  }
301
+ calculateItemsInView();
302
+ checkAtBottom();
270
303
  }, [data]);
271
304
  const containers = use$(containers$, { shallow: true });
272
305
  const updateItemLength = useCallback((index, length) => {
@@ -282,7 +315,7 @@ var LegendList = forwardRef((props, forwardedRef) => {
282
315
  if (!prevLength || prevLength !== length) {
283
316
  beginBatch();
284
317
  lengths.set(id, length);
285
- visibleRange$.totalLength.set((prevTotal) => prevTotal + (length - prevLength));
318
+ setTotalLength(visibleRange$.totalLength.peek() + (length - prevLength));
286
319
  if (((_d = refPositions.current) == null ? void 0 : _d.isAtBottom) && autoScrollToBottom) {
287
320
  requestAnimationFrame(() => {
288
321
  var _a2;
@@ -298,25 +331,16 @@ var LegendList = forwardRef((props, forwardedRef) => {
298
331
  }
299
332
  }, []);
300
333
  const handleScrollDebounced = useCallback(() => {
301
- var _a;
302
- const newScroll = visibleRange$.scroll.peek();
303
334
  calculateItemsInView();
304
- const distanceFromEnd = visibleRange$.totalLength.peek() - newScroll - SCREEN_LENGTH;
305
- if (refPositions.current) {
306
- refPositions.current.isAtBottom = distanceFromEnd < SCREEN_LENGTH * autoScrollToBottomThreshold;
307
- }
308
- if (onEndReached && !((_a = refPositions.current) == null ? void 0 : _a.isEndReached)) {
309
- if (distanceFromEnd < (onEndReachedThreshold || 0.5) * SCREEN_LENGTH) {
310
- if (refPositions.current) {
311
- refPositions.current.isEndReached = true;
312
- }
313
- onEndReached({ distanceFromEnd });
314
- }
315
- }
335
+ checkAtBottom();
316
336
  if (refPositions.current) {
317
337
  refPositions.current.animFrame = null;
318
338
  }
319
339
  }, []);
340
+ const onLayout = (event) => {
341
+ const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
342
+ refPositions.current.scrollLength = scrollLength;
343
+ };
320
344
  const handleScroll = useCallback((event) => {
321
345
  refPositions.current.hasScrolled = true;
322
346
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
@@ -344,12 +368,14 @@ var LegendList = forwardRef((props, forwardedRef) => {
344
368
  } : {}
345
369
  ],
346
370
  onScroll: handleScroll,
371
+ onLayout,
347
372
  scrollEventThrottle: 32,
348
373
  horizontal,
349
374
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
350
375
  ...rest,
351
376
  ref: refScroller
352
377
  },
378
+ startAtBottom && /* @__PURE__ */ React2.createElement(Reactive.View, { $style: () => ({ height: paddingTop$.get() }) }),
353
379
  ListHeaderComponent && /* @__PURE__ */ React2.createElement(Reactive.View, { $style: ListHeaderComponentStyle }, ListHeaderComponent),
354
380
  /* @__PURE__ */ React2.createElement(
355
381
  Reactive.View,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "legend-list",
5
5
  "sideEffects": false,
6
6
  "private": false,