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