@legendapp/list 0.3.0 → 0.3.2

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.mjs CHANGED
@@ -1,69 +1,372 @@
1
- import * as React2 from 'react';
2
- import { forwardRef, useRef, useMemo, useCallback, useEffect } from 'react';
3
- import { ScrollView, View, StyleSheet, Dimensions } from 'react-native';
1
+ import * as React6 from 'react';
2
+ import { forwardRef, useRef, useMemo } from 'react';
3
+ import { ScrollView, View, Dimensions, StyleSheet, unstable_batchedUpdates } from 'react-native';
4
4
 
5
5
  // src/LegendList.tsx
6
- var LeanView = React2.forwardRef((props, ref) => {
7
- return React2.createElement("RCTView", { ...props, ref });
8
- });
9
- LeanView.displayName = "RCTView";
10
- var ContextListener = React2.createContext(null);
6
+
7
+ // src/constants.ts
8
+ var DEFAULT_SCROLL_BUFFER = 0;
9
+ var POSITION_OUT_OF_VIEW = -1e4;
10
+ var ContextListener = React6.createContext(null);
11
11
  function StateProvider({ children }) {
12
- const [value] = React2.useState(() => ({
12
+ const [value] = React6.useState(() => ({
13
13
  listeners: /* @__PURE__ */ new Map(),
14
14
  values: /* @__PURE__ */ new Map()
15
15
  }));
16
- return /* @__PURE__ */ React2.createElement(ContextListener.Provider, { value }, children);
16
+ return /* @__PURE__ */ React6.createElement(ContextListener.Provider, { value }, children);
17
17
  }
18
18
  function useStateContext() {
19
- return React2.useContext(ContextListener);
19
+ return React6.useContext(ContextListener);
20
20
  }
21
21
  function use$(signalName) {
22
- const { listeners, values } = React2.useContext(ContextListener);
23
- const [_, setState] = React2.useState(0);
24
- React2.useMemo(() => {
25
- const render = () => setState((prev) => prev > 1e4 ? 0 : prev + 1);
26
- listeners.set(signalName, render);
27
- }, []);
22
+ const { listeners, values } = React6.useContext(ContextListener);
23
+ const [, forceUpdate] = React6.useReducer((x) => x + 1, 0);
24
+ listeners.set(signalName, forceUpdate);
28
25
  return values.get(signalName);
29
26
  }
30
- function peek$(signalName, ctx) {
31
- const { values } = ctx || React2.useContext(ContextListener);
27
+ function peek$(ctx, signalName) {
28
+ const { values } = ctx;
32
29
  return values.get(signalName);
33
30
  }
34
- function set$(signalName, ctx, value) {
31
+ function set$(ctx, signalName, value) {
35
32
  var _a;
36
- const { listeners, values } = ctx || React2.useContext(ContextListener);
33
+ const { listeners, values } = ctx;
37
34
  if (values.get(signalName) !== value) {
38
35
  values.set(signalName, value);
39
36
  (_a = listeners.get(signalName)) == null ? void 0 : _a();
40
37
  }
41
38
  }
39
+ function applyDefaultProps(props) {
40
+ return {
41
+ ...props,
42
+ recycleItems: props.recycleItems || false,
43
+ onEndReachedThreshold: props.onEndReachedThreshold || 0.5,
44
+ maintainScrollAtEndThreshold: props.maintainScrollAtEndThreshold || 0.1,
45
+ maintainScrollAtEnd: props.maintainScrollAtEnd || false,
46
+ alignItemsAtEnd: props.alignItemsAtEnd || false
47
+ };
48
+ }
49
+ function allocateContainers(state) {
50
+ var _a, _b;
51
+ const { scrollLength, props, ctx, scrollBuffer } = state;
52
+ const averageItemSize = (_b = props.estimatedItemSize) != null ? _b : (_a = props.getEstimatedItemSize) == null ? void 0 : _a.call(props, 0, props.data[0]);
53
+ const numContainers = props.initialNumContainers || Math.ceil((scrollLength + scrollBuffer * 2) / averageItemSize) + 4;
54
+ for (let i = 0; i < numContainers; i++) {
55
+ set$(ctx, `containerIndex${i}`, -1);
56
+ set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
57
+ }
58
+ set$(ctx, `numContainers`, numContainers);
59
+ }
60
+ function getId(state, index) {
61
+ const { data, keyExtractor } = state.props;
62
+ if (!data) {
63
+ return "";
64
+ }
65
+ const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
66
+ return ret + "";
67
+ }
68
+ function calculateInitialOffset(props) {
69
+ const { data, initialScrollIndex, estimatedItemSize, getEstimatedItemSize } = props;
70
+ if (initialScrollIndex) {
71
+ if (getEstimatedItemSize) {
72
+ let offset = 0;
73
+ for (let i = 0; i < initialScrollIndex; i++) {
74
+ offset += getEstimatedItemSize(i, data[i]);
75
+ }
76
+ return offset;
77
+ } else if (estimatedItemSize) {
78
+ return initialScrollIndex * estimatedItemSize;
79
+ }
80
+ }
81
+ return void 0;
82
+ }
83
+ function addTotalLength(state, add) {
84
+ const { ctx, props } = state;
85
+ const totalLength = (peek$(ctx, `totalLength`) || 0) + add;
86
+ set$(ctx, `totalLength`, totalLength);
87
+ const screenLength = state.scrollLength;
88
+ if (props.alignItemsAtEnd) {
89
+ const listPaddingTop = peek$(ctx, `stylePaddingTop`);
90
+ set$(ctx, `paddingTop`, Math.max(0, screenLength - totalLength - listPaddingTop));
91
+ }
92
+ }
93
+ function getRenderedItem(state, index) {
94
+ const { data, renderItem } = state.props;
95
+ if (!data) {
96
+ return null;
97
+ }
98
+ const renderedItem = renderItem == null ? void 0 : renderItem({
99
+ item: data[index],
100
+ index
101
+ });
102
+ return renderedItem;
103
+ }
104
+ var getItemSize = (state, index, data) => {
105
+ const { getEstimatedItemSize, estimatedItemSize } = state.props;
106
+ return getEstimatedItemSize ? getEstimatedItemSize(index, data) : estimatedItemSize;
107
+ };
108
+ function calculateItemsInView(state) {
109
+ unstable_batchedUpdates(() => {
110
+ var _a, _b, _c;
111
+ const {
112
+ props: { data, onViewableRangeChanged },
113
+ scrollLength,
114
+ scroll: scrollState,
115
+ startNoBuffer: startNoBufferState,
116
+ startBuffered: startBufferedState,
117
+ endNoBuffer: endNoBufferState,
118
+ endBuffered: endBufferedState,
119
+ lengths,
120
+ positions,
121
+ scrollBuffer,
122
+ ctx
123
+ } = state;
124
+ if (!data) {
125
+ return;
126
+ }
127
+ const topPad = (peek$(ctx, `stylePaddingTop`) || 0) + (peek$(ctx, `headerSize`) || 0);
128
+ const scroll = scrollState - topPad;
129
+ scroll > state.scrollPrevious ? 1 : -1;
130
+ const scrollBufferTop = scrollBuffer;
131
+ const scrollBufferBottom = scrollBuffer;
132
+ let startNoBuffer = null;
133
+ let startBuffered = null;
134
+ let endNoBuffer = null;
135
+ let endBuffered = null;
136
+ let loopStart = startBufferedState || 0;
137
+ if (startBufferedState) {
138
+ for (let i = startBufferedState; i >= 0; i--) {
139
+ const id = getId(state, i);
140
+ const top2 = positions.get(id);
141
+ if (top2 !== void 0) {
142
+ const length = (_a = lengths.get(id)) != null ? _a : getItemSize(state, i, data[i]);
143
+ const bottom = top2 + length;
144
+ if (bottom > scroll - scrollBufferTop) {
145
+ loopStart = i;
146
+ } else {
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ }
152
+ let top = loopStart > 0 ? positions.get(getId(state, loopStart)) : 0;
153
+ for (let i = loopStart; i < data.length; i++) {
154
+ const id = getId(state, i);
155
+ const length = (_b = lengths.get(id)) != null ? _b : getItemSize(state, i, data[i]);
156
+ if (positions.get(id) !== top) {
157
+ positions.set(id, top);
158
+ }
159
+ if (startNoBuffer === null && top + length > scroll) {
160
+ startNoBuffer = i;
161
+ }
162
+ if (startBuffered === null && top + length > scroll - scrollBufferTop) {
163
+ startBuffered = i;
164
+ }
165
+ if (startNoBuffer !== null) {
166
+ if (top <= scroll + scrollLength) {
167
+ endNoBuffer = i;
168
+ }
169
+ if (top <= scroll + scrollLength + scrollBufferBottom) {
170
+ endBuffered = i;
171
+ } else {
172
+ break;
173
+ }
174
+ }
175
+ top += length;
176
+ }
177
+ Object.assign(state, {
178
+ startBuffered,
179
+ startNoBuffer,
180
+ endBuffered,
181
+ endNoBuffer
182
+ });
183
+ if (startBuffered !== null && endBuffered !== null) {
184
+ const prevNumContainers = ctx.values.get("numContainers");
185
+ let numContainers = prevNumContainers;
186
+ for (let i = startBuffered; i <= endBuffered; i++) {
187
+ let isContained = false;
188
+ for (let j = 0; j < numContainers; j++) {
189
+ const index = peek$(ctx, `containerIndex${j}`);
190
+ if (index === i) {
191
+ isContained = true;
192
+ break;
193
+ }
194
+ }
195
+ if (!isContained) {
196
+ const id = getId(state, i);
197
+ const top2 = positions.get(id) || 0;
198
+ let furthestIndex = -1;
199
+ let furthestDistance = 0;
200
+ for (let u = 0; u < numContainers; u++) {
201
+ const index = peek$(ctx, `containerIndex${u}`);
202
+ if (index < 0) {
203
+ furthestIndex = u;
204
+ break;
205
+ }
206
+ const pos = peek$(ctx, `containerPosition${u}`);
207
+ if (index < startBuffered || index > endBuffered) {
208
+ const distance = Math.abs(pos - top2);
209
+ if (index < 0 || distance > furthestDistance) {
210
+ furthestDistance = distance;
211
+ furthestIndex = u;
212
+ }
213
+ }
214
+ }
215
+ if (furthestIndex >= 0) {
216
+ set$(ctx, `containerIndex${furthestIndex}`, i);
217
+ } else {
218
+ if (__DEV__) {
219
+ console.warn(
220
+ "[legend-list] No container to recycle, consider increasing initialContainers or estimatedItemLength",
221
+ i
222
+ );
223
+ }
224
+ const containerId = numContainers;
225
+ numContainers++;
226
+ set$(ctx, `containerIndex${containerId}`, i);
227
+ set$(ctx, `containerPosition${containerId}`, POSITION_OUT_OF_VIEW);
228
+ }
229
+ }
230
+ }
231
+ if (numContainers !== prevNumContainers) {
232
+ set$(ctx, `numContainers`, numContainers);
233
+ }
234
+ for (let i = 0; i < numContainers; i++) {
235
+ const itemIndex = peek$(ctx, `containerIndex${i}`);
236
+ const item = data[itemIndex];
237
+ if (item) {
238
+ const id = getId(state, itemIndex);
239
+ if (itemIndex < startBuffered || itemIndex > endBuffered) ; else {
240
+ const pos = (_c = positions.get(id)) != null ? _c : -1;
241
+ const prevPos = peek$(ctx, `containerPosition${i}`);
242
+ if (pos >= 0 && pos !== prevPos) {
243
+ set$(ctx, `containerPosition${i}`, pos);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ if (onViewableRangeChanged) {
249
+ if (startNoBuffer !== startNoBufferState || startBuffered !== startBufferedState || endNoBuffer !== endNoBufferState || endBuffered !== endBufferedState) {
250
+ onViewableRangeChanged({
251
+ start: startNoBuffer,
252
+ startBuffered,
253
+ end: endNoBuffer,
254
+ endBuffered,
255
+ items: data.slice(startNoBuffer, endNoBuffer + 1)
256
+ });
257
+ }
258
+ }
259
+ }
260
+ });
261
+ }
262
+ function updateItemSize(state, refScroller, index, length) {
263
+ const {
264
+ props: { data, maintainScrollAtEnd },
265
+ ctx,
266
+ lengths,
267
+ idsInFirstRender,
268
+ isAtBottom
269
+ } = state;
270
+ if (!data) {
271
+ return;
272
+ }
273
+ const id = getId(state, index);
274
+ const wasInFirstRender = idsInFirstRender.has(id);
275
+ const prevLength = lengths.get(id) || (wasInFirstRender ? getItemSize(state, index, data[index]) : 0);
276
+ if (!prevLength || prevLength !== length) {
277
+ lengths.set(id, length);
278
+ addTotalLength(state, length - prevLength);
279
+ if (isAtBottom && maintainScrollAtEnd) {
280
+ requestAnimationFrame(() => {
281
+ var _a;
282
+ (_a = refScroller.current) == null ? void 0 : _a.scrollToEnd({
283
+ animated: true
284
+ });
285
+ });
286
+ }
287
+ if (!state.animFrameScroll && !state.animFrameLayout) {
288
+ state.animFrameLayout = requestAnimationFrame(() => {
289
+ state.animFrameLayout = null;
290
+ if (!state.animFrameScroll) {
291
+ calculateItemsInView(state);
292
+ }
293
+ });
294
+ }
295
+ }
296
+ }
297
+ function checkAtBottom(state) {
298
+ const {
299
+ ctx,
300
+ scrollLength,
301
+ scroll,
302
+ props: { maintainScrollAtEndThreshold, onEndReached, onEndReachedThreshold }
303
+ } = state;
304
+ const totalLength = peek$(ctx, "totalLength");
305
+ const distanceFromEnd = totalLength - scroll - scrollLength;
306
+ state.isAtBottom = distanceFromEnd < scrollLength * maintainScrollAtEndThreshold;
307
+ if (onEndReached && !state.isEndReached) {
308
+ if (distanceFromEnd < onEndReachedThreshold * scrollLength) {
309
+ state.isEndReached = true;
310
+ onEndReached({ distanceFromEnd });
311
+ }
312
+ }
313
+ }
314
+ function handleScrollDebounced(state) {
315
+ calculateItemsInView(state);
316
+ checkAtBottom(state);
317
+ state.animFrameScroll = null;
318
+ }
319
+ function handleScroll(state, onScrollDebounced, event) {
320
+ var _a, _b, _c;
321
+ if (((_b = (_a = event.nativeEvent) == null ? void 0 : _a.contentSize) == null ? void 0 : _b.height) === 0 && ((_c = event.nativeEvent.contentSize) == null ? void 0 : _c.width) === 0) {
322
+ return;
323
+ }
324
+ const { horizontal, onScroll } = state.props;
325
+ state.hasScrolled = true;
326
+ const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
327
+ state.scrollPrevious = state.scroll;
328
+ state.scroll = newScroll;
329
+ if (state && !state.animFrameScroll) {
330
+ state.animFrameScroll = requestAnimationFrame(onScrollDebounced);
331
+ }
332
+ onScroll == null ? void 0 : onScroll(event);
333
+ }
334
+ function onLayout(state, event) {
335
+ const scrollLength = event.nativeEvent.layout[state.props.horizontal ? "width" : "height"];
336
+ state.scrollLength = scrollLength;
337
+ }
338
+ var LeanView = React6.forwardRef((props, ref) => {
339
+ return React6.createElement("RCTView", { ...props, ref });
340
+ });
341
+ LeanView.displayName = "RCTView";
42
342
 
43
343
  // src/$View.tsx
44
344
  function $View({ $key, $style, ...rest }) {
45
345
  use$($key);
46
346
  const style = $style();
47
- return /* @__PURE__ */ React2.createElement(LeanView, { style, ...rest });
347
+ return /* @__PURE__ */ React6.createElement(LeanView, { style, ...rest });
348
+ }
349
+ function InnerContainer({ id, getRenderedItem: getRenderedItem2, recycleItems, ItemSeparatorComponent }) {
350
+ const itemIndex = use$(`containerIndex${id}`);
351
+ const numItems = ItemSeparatorComponent ? use$("numItems") : 0;
352
+ if (itemIndex < 0) {
353
+ return null;
354
+ }
355
+ const renderedItem = getRenderedItem2(itemIndex);
356
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, { key: recycleItems ? void 0 : itemIndex }, renderedItem, ItemSeparatorComponent && itemIndex < numItems - 1 && ItemSeparatorComponent);
48
357
  }
49
358
  var Container = ({
50
359
  id,
51
360
  recycleItems,
52
361
  horizontal,
53
- getRenderedItem,
54
- onLayout,
362
+ getRenderedItem: getRenderedItem2,
363
+ onLayout: onLayout2,
55
364
  ItemSeparatorComponent
56
365
  }) => {
57
366
  const ctx = useStateContext();
58
- const numItems = ItemSeparatorComponent ? use$("numItems") : 0;
59
- const itemIndex = use$(`containerIndex${id}`);
60
- if (itemIndex < 0) {
61
- return null;
62
- }
63
367
  const createStyle = () => {
64
- const position = peek$(`containerPosition${id}`, ctx);
368
+ const position = peek$(ctx, `containerPosition${id}`);
65
369
  return horizontal ? {
66
- flexDirection: "row",
67
370
  position: "absolute",
68
371
  top: 0,
69
372
  bottom: 0,
@@ -77,58 +380,66 @@ var Container = ({
77
380
  opacity: position < 0 ? 0 : 1
78
381
  };
79
382
  };
80
- const renderedItem = getRenderedItem(itemIndex);
81
- return /* @__PURE__ */ React2.createElement(
383
+ return /* @__PURE__ */ React6.createElement(
82
384
  $View,
83
385
  {
84
386
  $key: `containerPosition${id}`,
85
387
  $style: createStyle,
86
388
  onLayout: (event) => {
87
- const index = peek$(`containerIndex${id}`, ctx);
88
- const length = Math.round(event.nativeEvent.layout[horizontal ? "width" : "height"]);
89
- onLayout(index, length);
389
+ const index = peek$(ctx, `containerIndex${id}`);
390
+ if (index >= 0) {
391
+ const length = Math.round(event.nativeEvent.layout[horizontal ? "width" : "height"]);
392
+ onLayout2(index, length);
393
+ }
90
394
  }
91
395
  },
92
- recycleItems ? renderedItem : /* @__PURE__ */ React2.createElement(React2.Fragment, { key: itemIndex }, renderedItem),
93
- ItemSeparatorComponent && itemIndex < numItems - 1 && ItemSeparatorComponent
396
+ /* @__PURE__ */ React6.createElement(
397
+ InnerContainer,
398
+ {
399
+ id,
400
+ getRenderedItem: getRenderedItem2,
401
+ recycleItems,
402
+ ItemSeparatorComponent
403
+ }
404
+ )
94
405
  );
95
406
  };
96
407
 
97
408
  // src/Containers.tsx
98
- var Containers = React2.memo(function Containers2({
409
+ var Containers = React6.memo(function Containers2({
99
410
  horizontal,
100
411
  recycleItems,
101
412
  ItemSeparatorComponent,
102
- updateItemLength,
103
- getRenderedItem
413
+ updateItemSize: updateItemSize2,
414
+ getRenderedItem: getRenderedItem2
104
415
  }) {
105
416
  const ctx = useStateContext();
106
417
  const numContainers = use$("numContainers");
107
418
  const containers = [];
108
419
  for (let i = 0; i < numContainers; i++) {
109
420
  containers.push(
110
- /* @__PURE__ */ React2.createElement(
421
+ /* @__PURE__ */ React6.createElement(
111
422
  Container,
112
423
  {
113
424
  id: i,
114
425
  key: i,
115
426
  recycleItems,
116
427
  horizontal,
117
- getRenderedItem,
118
- onLayout: updateItemLength,
428
+ getRenderedItem: getRenderedItem2,
429
+ onLayout: updateItemSize2,
119
430
  ItemSeparatorComponent
120
431
  }
121
432
  )
122
433
  );
123
434
  }
124
- return /* @__PURE__ */ React2.createElement(
435
+ return /* @__PURE__ */ React6.createElement(
125
436
  $View,
126
437
  {
127
438
  $key: "totalLength",
128
439
  $style: () => horizontal ? {
129
- width: peek$("totalLength", ctx)
440
+ width: peek$(ctx, "totalLength")
130
441
  } : {
131
- height: peek$("totalLength", ctx)
442
+ height: peek$(ctx, "totalLength")
132
443
  }
133
444
  },
134
445
  containers
@@ -137,15 +448,15 @@ var Containers = React2.memo(function Containers2({
137
448
 
138
449
  // src/ListComponent.tsx
139
450
  var getComponent = (Component) => {
140
- if (React2.isValidElement(Component)) {
451
+ if (React6.isValidElement(Component)) {
141
452
  return Component;
142
453
  }
143
454
  if (Component) {
144
- return /* @__PURE__ */ React2.createElement(Component, null);
455
+ return /* @__PURE__ */ React6.createElement(Component, null);
145
456
  }
146
457
  return null;
147
458
  };
148
- var ListComponent = React2.memo(function ListComponent2({
459
+ var ListComponent = React6.memo(function ListComponent2({
149
460
  style,
150
461
  contentContainerStyle,
151
462
  horizontal,
@@ -153,21 +464,23 @@ var ListComponent = React2.memo(function ListComponent2({
153
464
  recycleItems,
154
465
  ItemSeparatorComponent,
155
466
  alignItemsAtEnd,
156
- handleScroll,
157
- onLayout,
467
+ handleScroll: handleScroll2,
468
+ onLayout: onLayout2,
158
469
  ListHeaderComponent,
159
470
  ListHeaderComponentStyle,
160
471
  ListFooterComponent,
161
472
  ListFooterComponentStyle,
162
- getRenderedItem,
163
- updateItemLength,
473
+ getRenderedItem: getRenderedItem2,
474
+ updateItemSize: updateItemSize2,
475
+ addTotalLength: addTotalLength2,
164
476
  refScroller,
165
477
  ...rest
166
478
  }) {
167
479
  const ctx = useStateContext();
168
- return /* @__PURE__ */ React2.createElement(
480
+ return /* @__PURE__ */ React6.createElement(
169
481
  ScrollView,
170
482
  {
483
+ ...rest,
171
484
  style,
172
485
  contentContainerStyle: [
173
486
  contentContainerStyle,
@@ -175,38 +488,64 @@ var ListComponent = React2.memo(function ListComponent2({
175
488
  height: "100%"
176
489
  } : {}
177
490
  ],
178
- onScroll: handleScroll,
179
- onLayout,
180
- scrollEventThrottle: 32,
491
+ onScroll: handleScroll2,
492
+ onLayout: onLayout2,
181
493
  horizontal,
182
494
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
183
- ...rest,
184
495
  ref: refScroller
185
496
  },
186
- alignItemsAtEnd && /* @__PURE__ */ React2.createElement($View, { $key: "paddingTop", $style: () => ({ height: peek$("paddingTop", ctx) }) }),
187
- ListHeaderComponent && /* @__PURE__ */ React2.createElement(View, { style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
188
- /* @__PURE__ */ React2.createElement(
497
+ alignItemsAtEnd && /* @__PURE__ */ React6.createElement($View, { $key: "paddingTop", $style: () => ({ height: peek$(ctx, "paddingTop") }) }),
498
+ ListHeaderComponent && /* @__PURE__ */ React6.createElement(
499
+ View,
500
+ {
501
+ style: ListHeaderComponentStyle,
502
+ onLayout: (event) => {
503
+ const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
504
+ const prevSize = peek$(ctx, "headerSize") || 0;
505
+ if (size !== prevSize) {
506
+ set$(ctx, "headerSize", size);
507
+ addTotalLength2(size - prevSize);
508
+ }
509
+ }
510
+ },
511
+ getComponent(ListHeaderComponent)
512
+ ),
513
+ /* @__PURE__ */ React6.createElement(
189
514
  Containers,
190
515
  {
191
516
  horizontal,
192
517
  recycleItems,
193
- getRenderedItem,
518
+ getRenderedItem: getRenderedItem2,
194
519
  ItemSeparatorComponent: ItemSeparatorComponent && getComponent(ItemSeparatorComponent),
195
- updateItemLength
520
+ updateItemSize: updateItemSize2
196
521
  }
197
522
  ),
198
- ListFooterComponent && /* @__PURE__ */ React2.createElement(View, { style: ListFooterComponentStyle }, getComponent(ListFooterComponent))
523
+ ListFooterComponent && /* @__PURE__ */ React6.createElement(
524
+ View,
525
+ {
526
+ style: ListFooterComponentStyle,
527
+ onLayout: (event) => {
528
+ const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
529
+ const prevSize = peek$(ctx, "footerSize") || 0;
530
+ if (size !== prevSize) {
531
+ set$(ctx, "footerSize", size);
532
+ addTotalLength2(size - prevSize);
533
+ }
534
+ }
535
+ },
536
+ getComponent(ListFooterComponent)
537
+ )
199
538
  );
200
539
  });
201
540
 
202
541
  // src/LegendList.tsx
203
- var DEFAULT_SCROLL_BUFFER = 0;
204
- var POSITION_OUT_OF_VIEW = -1e4;
205
542
  var LegendList = forwardRef(function LegendList2(props, forwardedRef) {
206
- return /* @__PURE__ */ React2.createElement(StateProvider, null, /* @__PURE__ */ React2.createElement(LegendListInner, { ...props, ref: forwardedRef }));
543
+ return /* @__PURE__ */ React6.createElement(StateProvider, null, /* @__PURE__ */ React6.createElement(LegendListInner, { ...props, ref: forwardedRef }));
207
544
  });
208
545
  var LegendListInner = forwardRef(
209
- function LegendListInner2(props, forwardedRef) {
546
+ function LegendListInner2(props_, forwardedRef) {
547
+ var _a, _b;
548
+ const props = applyDefaultProps(props_);
210
549
  const {
211
550
  data,
212
551
  initialScrollIndex,
@@ -214,51 +553,24 @@ var LegendListInner = forwardRef(
214
553
  horizontal,
215
554
  style: styleProp,
216
555
  contentContainerStyle: contentContainerStyleProp,
217
- initialContainers,
218
556
  drawDistance,
219
- recycleItems = true,
220
- onEndReachedThreshold = 0.5,
221
- maintainScrollAtEnd = false,
222
- maintainScrollAtEndThreshold = 0.1,
223
- alignItemsAtEnd = false,
224
- keyExtractor,
225
- renderItem,
226
- estimatedItemLength,
227
- onEndReached,
228
- onViewableRangeChanged,
229
557
  ...rest
230
558
  } = props;
231
559
  const ctx = useStateContext();
232
- const internalRef = useRef(null);
233
- const refScroller = forwardedRef || internalRef;
560
+ const refScroller = forwardedRef || useRef(null);
234
561
  const scrollBuffer = drawDistance != null ? drawDistance : DEFAULT_SCROLL_BUFFER;
235
- const styleFlattened = StyleSheet.flatten(styleProp);
236
- const style = useMemo(() => styleFlattened, [JSON.stringify(styleProp)]);
237
- const contentContainerStyleFlattened = StyleSheet.flatten(contentContainerStyleProp);
238
- const contentContainerStyle = useMemo(
239
- () => contentContainerStyleFlattened,
240
- [JSON.stringify(contentContainerStyleProp)]
241
- );
242
562
  const refState = useRef();
243
- const getId = (index) => {
244
- var _a;
245
- const data2 = (_a = refState.current) == null ? void 0 : _a.data;
246
- if (!data2) {
247
- return "";
248
- }
249
- const ret = index < data2.length ? keyExtractor ? keyExtractor(data2[index], index) : index : null;
250
- return ret + "";
251
- };
563
+ const initialContentOffset = initialScrollOffset != null ? initialScrollOffset : useMemo(calculateInitialOffset.bind(void 0, props), []);
252
564
  if (!refState.current) {
253
565
  refState.current = {
254
566
  lengths: /* @__PURE__ */ new Map(),
255
567
  positions: /* @__PURE__ */ new Map(),
256
568
  pendingAdjust: 0,
257
- animFrame: null,
569
+ animFrameScroll: null,
570
+ animFrameLayout: null,
258
571
  isStartReached: false,
259
572
  isEndReached: false,
260
573
  isAtBottom: false,
261
- data,
262
574
  idsInFirstRender: void 0,
263
575
  hasScrolled: false,
264
576
  scrollLength: Dimensions.get("window")[horizontal ? "width" : "height"],
@@ -266,271 +578,91 @@ var LegendListInner = forwardRef(
266
578
  startNoBuffer: 0,
267
579
  endBuffered: 0,
268
580
  endNoBuffer: 0,
269
- scroll: 0,
270
- topPad: 0
581
+ scroll: initialContentOffset || 0,
582
+ scrollPrevious: initialContentOffset || 0,
583
+ previousViewableItems: /* @__PURE__ */ new Set(),
584
+ props,
585
+ ctx,
586
+ scrollBuffer
271
587
  };
272
- refState.current.idsInFirstRender = new Set(data.map((_, i) => getId(i)));
588
+ refState.current.idsInFirstRender = new Set(
589
+ data.map((_, i) => getId(refState.current, i))
590
+ );
273
591
  }
274
- refState.current.data = data;
275
- set$(`numItems`, ctx, data.length);
276
- const initialContentOffset = initialScrollOffset != null ? initialScrollOffset : initialScrollIndex ? initialScrollIndex * estimatedItemLength(initialScrollIndex) : void 0;
277
- const setTotalLength = (length) => {
278
- set$(`totalLength`, ctx, length);
279
- const screenLength = refState.current.scrollLength;
280
- if (alignItemsAtEnd) {
281
- const listPaddingTop = ((style == null ? void 0 : style.paddingTop) || 0) + ((contentContainerStyle == null ? void 0 : contentContainerStyle.paddingTop) || 0);
282
- set$(`paddingTop`, ctx, Math.max(0, screenLength - length - listPaddingTop));
283
- }
284
- };
285
- const allocateContainers = useCallback(() => {
286
- const scrollLength = refState.current.scrollLength;
287
- const numContainers = initialContainers || Math.ceil((scrollLength + scrollBuffer * 2) / estimatedItemLength(0)) + 4;
288
- for (let i = 0; i < numContainers; i++) {
289
- set$(`containerIndex${i}`, ctx, -1);
290
- set$(`containerPosition${i}`, ctx, POSITION_OUT_OF_VIEW);
291
- }
292
- set$(`numContainers`, ctx, numContainers);
293
- }, []);
294
- const getRenderedItem = useCallback(
295
- (index) => {
296
- var _a;
297
- const data2 = (_a = refState.current) == null ? void 0 : _a.data;
298
- if (!data2) {
299
- return null;
300
- }
301
- const renderedItem = renderItem == null ? void 0 : renderItem({
302
- item: data2[index],
303
- index
304
- });
305
- return renderedItem;
306
- },
307
- [renderItem]
592
+ refState.current.props = props;
593
+ refState.current.ctx = ctx;
594
+ const styleFlattened = StyleSheet.flatten(styleProp);
595
+ const style = useMemo(() => styleFlattened, [JSON.stringify(styleProp)]);
596
+ const contentContainerStyleFlattened = StyleSheet.flatten(contentContainerStyleProp);
597
+ const contentContainerStyle = useMemo(
598
+ () => contentContainerStyleFlattened,
599
+ [JSON.stringify(contentContainerStyleProp)]
308
600
  );
309
- const calculateItemsInView = useCallback(() => {
310
- var _a, _b;
311
- const {
312
- data: data2,
313
- scrollLength,
314
- scroll: scrollState,
315
- topPad,
316
- startNoBuffer: startNoBufferState,
317
- startBuffered: startBufferedState,
318
- endNoBuffer: endNoBufferState,
319
- endBuffered: endBufferedState
320
- } = refState.current;
321
- if (!data2) {
322
- return;
323
- }
324
- const scroll = scrollState - topPad;
325
- const { lengths, positions } = refState.current;
326
- let top = 0;
327
- let startNoBuffer = null;
328
- let startBuffered = null;
329
- let endNoBuffer = null;
330
- let endBuffered = null;
331
- for (let i = 0; i < data2.length; i++) {
332
- const id = getId(i);
333
- const length = (_a = lengths.get(id)) != null ? _a : estimatedItemLength(i);
334
- if (positions.get(id) !== top) {
335
- positions.set(id, top);
336
- }
337
- if (startNoBuffer === null && top + length > scroll) {
338
- startNoBuffer = i;
339
- }
340
- if (startBuffered === null && top + length > scroll - scrollBuffer) {
341
- startBuffered = i;
342
- }
343
- if (startNoBuffer !== null) {
344
- if (top <= scroll + scrollLength) {
345
- endNoBuffer = i;
346
- }
347
- if (top <= scroll + scrollLength + scrollBuffer) {
348
- endBuffered = i;
349
- } else {
350
- break;
351
- }
352
- }
353
- top += length;
354
- }
355
- Object.assign(refState.current, {
356
- startBuffered,
357
- startNoBuffer,
358
- endBuffered,
359
- endNoBuffer
360
- });
361
- if (startBuffered !== null && endBuffered !== null) {
362
- const prevNumContainers = ctx.values.get("numContainers");
363
- let numContainers = prevNumContainers;
364
- for (let i = startBuffered; i <= endBuffered; i++) {
365
- let isContained = false;
366
- for (let j = 0; j < numContainers; j++) {
367
- const index = peek$(`containerIndex${j}`, ctx);
368
- if (index === i) {
369
- isContained = true;
370
- break;
371
- }
372
- }
373
- if (!isContained) {
374
- let didRecycle = false;
375
- for (let u = 0; u < numContainers; u++) {
376
- const index = peek$(`containerIndex${u}`, ctx);
377
- if (index < startBuffered || index > endBuffered) {
378
- set$(`containerIndex${u}`, ctx, i);
379
- didRecycle = true;
380
- break;
381
- }
382
- }
383
- if (!didRecycle) {
384
- if (__DEV__) {
385
- console.warn(
386
- "[legend-list] No container to recycle, consider increasing initialContainers or estimatedItemLength",
387
- i
388
- );
389
- }
390
- const id = numContainers;
391
- numContainers++;
392
- set$(`containerIndex${id}`, ctx, i);
393
- set$(`containerPosition${id}`, ctx, POSITION_OUT_OF_VIEW);
394
- }
395
- }
396
- }
397
- if (numContainers !== prevNumContainers) {
398
- set$(`numContainers`, ctx, numContainers);
399
- }
400
- for (let i = 0; i < numContainers; i++) {
401
- const itemIndex = peek$(`containerIndex${i}`, ctx);
402
- const item = data2[itemIndex];
403
- if (item) {
404
- const id = getId(itemIndex);
405
- if (itemIndex < startBuffered || itemIndex > endBuffered) {
406
- set$(`containerPosition${i}`, ctx, POSITION_OUT_OF_VIEW);
407
- } else {
408
- const pos = (_b = positions.get(id)) != null ? _b : -1;
409
- const prevPos = peek$(`containerPosition${i}`, ctx);
410
- if (pos >= 0 && pos !== prevPos) {
411
- set$(`containerPosition${i}`, ctx, pos);
412
- }
413
- }
414
- }
415
- }
416
- if (onViewableRangeChanged) {
417
- if (startNoBuffer !== startNoBufferState || startBuffered !== startBufferedState || endNoBuffer !== endNoBufferState || endBuffered !== endBufferedState) {
418
- onViewableRangeChanged({
419
- start: startNoBuffer,
420
- startBuffered,
421
- end: endNoBuffer,
422
- endBuffered,
423
- items: data2.slice(startNoBuffer, endNoBuffer + 1)
424
- });
425
- }
426
- }
427
- }
428
- }, [data]);
601
+ const fns = useMemo(
602
+ () => ({
603
+ getRenderedItem: getRenderedItem.bind(void 0, refState.current),
604
+ getId: getId.bind(void 0, refState.current),
605
+ getItemSize: getItemSize.bind(void 0, refState.current),
606
+ calculateItemsInView: calculateItemsInView.bind(void 0, refState.current),
607
+ updateItemSize: updateItemSize.bind(void 0, refState.current, refScroller),
608
+ checkAtBottom: checkAtBottom.bind(void 0, refState.current),
609
+ handleScrollDebounced: handleScrollDebounced.bind(void 0, refState.current),
610
+ onLayout: onLayout.bind(void 0, refState.current),
611
+ addTotalLength: addTotalLength.bind(void 0, refState.current),
612
+ handleScroll: handleScroll.bind(
613
+ void 0,
614
+ refState.current,
615
+ handleScrollDebounced.bind(void 0, refState.current)
616
+ )
617
+ }),
618
+ []
619
+ );
620
+ const {
621
+ calculateItemsInView: calculateItemsInView2,
622
+ getId: getId2,
623
+ getItemSize: getItemSize2,
624
+ checkAtBottom: checkAtBottom2,
625
+ updateItemSize: updateItemSize2,
626
+ getRenderedItem: getRenderedItem2,
627
+ onLayout: onLayout2,
628
+ handleScroll: handleScroll2,
629
+ addTotalLength: addTotalLength2
630
+ } = fns;
631
+ set$(ctx, `numItems`, data.length);
632
+ set$(ctx, `stylePaddingTop`, (_b = (_a = styleFlattened == null ? void 0 : styleFlattened.paddingTop) != null ? _a : contentContainerStyleFlattened == null ? void 0 : contentContainerStyleFlattened.paddingTop) != null ? _b : 0);
429
633
  useMemo(() => {
430
- var _a, _b;
431
- allocateContainers();
432
- calculateItemsInView();
433
- const lengths = (_a = refState.current) == null ? void 0 : _a.lengths;
434
- let totalLength = 0;
634
+ var _a2, _b2;
635
+ allocateContainers(refState.current);
636
+ calculateItemsInView2();
637
+ const lengths = (_a2 = refState.current) == null ? void 0 : _a2.lengths;
638
+ let totalLength = (peek$(ctx, `headerSize`) || 0) + (peek$(ctx, `footerSize`) || 0);
435
639
  for (let i = 0; i < data.length; i++) {
436
- const id = getId(i);
437
- totalLength += (_b = lengths.get(id)) != null ? _b : estimatedItemLength(i);
640
+ const id = getId2(i);
641
+ totalLength += (_b2 = lengths.get(id)) != null ? _b2 : getItemSize2(i, data[i]);
438
642
  }
439
- setTotalLength(totalLength);
643
+ addTotalLength2(totalLength);
440
644
  }, []);
441
- const checkAtBottom = () => {
442
- var _a;
443
- const { scrollLength, scroll } = refState.current;
444
- const totalLength = peek$("totalLength", ctx);
445
- const distanceFromEnd = totalLength - scroll - scrollLength;
446
- if (refState.current) {
447
- refState.current.isAtBottom = distanceFromEnd < scrollLength * maintainScrollAtEndThreshold;
448
- }
449
- if (onEndReached && !((_a = refState.current) == null ? void 0 : _a.isEndReached)) {
450
- if (distanceFromEnd < onEndReachedThreshold * scrollLength) {
451
- if (refState.current) {
452
- refState.current.isEndReached = true;
453
- }
454
- onEndReached({ distanceFromEnd });
455
- }
456
- }
457
- };
458
645
  useMemo(() => {
459
646
  if (refState.current) {
460
647
  refState.current.isEndReached = false;
461
648
  }
462
- calculateItemsInView();
463
- checkAtBottom();
649
+ calculateItemsInView2();
650
+ checkAtBottom2();
464
651
  }, [data]);
465
- const updateItemLength = useCallback((index, length) => {
466
- var _a, _b, _c, _d, _e;
467
- const data2 = (_a = refState.current) == null ? void 0 : _a.data;
468
- if (!data2) {
469
- return;
470
- }
471
- const lengths = (_b = refState.current) == null ? void 0 : _b.lengths;
472
- const id = getId(index);
473
- const wasInFirstRender = (_c = refState.current) == null ? void 0 : _c.idsInFirstRender.has(id);
474
- const prevLength = lengths.get(id) || (wasInFirstRender ? estimatedItemLength(index) : 0);
475
- if (!prevLength || prevLength !== length) {
476
- lengths.set(id, length);
477
- const totalLength = peek$("totalLength", ctx);
478
- setTotalLength(totalLength + (length - prevLength));
479
- if (((_d = refState.current) == null ? void 0 : _d.isAtBottom) && maintainScrollAtEnd) {
480
- requestAnimationFrame(() => {
481
- var _a2;
482
- (_a2 = refScroller.current) == null ? void 0 : _a2.scrollToEnd({
483
- animated: true
484
- });
485
- });
486
- }
487
- if (!((_e = refState.current) == null ? void 0 : _e.animFrame)) {
488
- calculateItemsInView();
489
- }
490
- }
491
- }, []);
492
- const handleScrollDebounced = useCallback(() => {
493
- calculateItemsInView();
494
- checkAtBottom();
495
- if (refState.current) {
496
- refState.current.animFrame = null;
497
- }
498
- }, []);
499
- const onLayout = useCallback((event) => {
500
- const scrollLength = event.nativeEvent.layout[horizontal ? "width" : "height"];
501
- refState.current.scrollLength = scrollLength;
502
- }, []);
503
- const handleScroll = useCallback((event) => {
504
- refState.current.hasScrolled = true;
505
- const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
506
- refState.current.scroll = newScroll;
507
- if (refState.current && !refState.current.animFrame) {
508
- refState.current.animFrame = requestAnimationFrame(handleScrollDebounced);
509
- }
510
- }, []);
511
- useEffect(() => {
512
- if (initialContentOffset) {
513
- handleScroll({
514
- nativeEvent: { contentOffset: { y: initialContentOffset } }
515
- });
516
- calculateItemsInView();
517
- }
518
- }, []);
519
- return /* @__PURE__ */ React2.createElement(
652
+ return /* @__PURE__ */ React6.createElement(
520
653
  ListComponent,
521
654
  {
522
655
  ...rest,
656
+ addTotalLength: addTotalLength2,
523
657
  contentContainerStyle,
524
658
  style,
525
659
  horizontal,
526
660
  refScroller,
527
661
  initialContentOffset,
528
- getRenderedItem,
529
- updateItemLength,
530
- handleScroll,
531
- onLayout,
532
- recycleItems,
533
- alignItemsAtEnd
662
+ getRenderedItem: getRenderedItem2,
663
+ updateItemSize: updateItemSize2,
664
+ handleScroll: handleScroll2,
665
+ onLayout: onLayout2
534
666
  }
535
667
  );
536
668
  }