@legendapp/list 0.3.7 → 0.4.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
@@ -18,7 +18,7 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
18
18
  distanceFromEnd: number;
19
19
  }) => void) | null | undefined;
20
20
  keyExtractor?: (item: T, index: number) => string;
21
- renderItem?: (props: LegendListRenderItemInfo<T>) => ReactNode;
21
+ renderItem?: (props: LegendListRenderItemProps<T>) => ReactNode;
22
22
  onViewableRangeChanged?: (range: ViewableRange<T>) => void;
23
23
  ListHeaderComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
24
24
  ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
@@ -59,9 +59,12 @@ interface ViewableRange<T> {
59
59
  end: number;
60
60
  items: T[];
61
61
  }
62
- interface LegendListRenderItemInfo<ItemT> {
62
+ interface LegendListRenderItemProps<ItemT> {
63
63
  item: ItemT;
64
64
  index: number;
65
+ useViewability: (configId: string, callback: ViewabilityCallback) => void;
66
+ useRecyclingEffect: (effect: (info: LegendListRecyclingState<ItemT>) => void | (() => void)) => void;
67
+ useRecyclingState: <T>(updateState: (info: LegendListRecyclingState<ItemT>) => T) => [T, React.Dispatch<T>];
65
68
  }
66
69
  type LegendListRef = {
67
70
  /**
@@ -106,7 +109,7 @@ interface ViewToken<ItemT = any> {
106
109
  }
107
110
  interface ViewabilityConfigCallbackPair {
108
111
  viewabilityConfig: ViewabilityConfig;
109
- onViewableItemsChanged: OnViewableItemsChanged;
112
+ onViewableItemsChanged?: OnViewableItemsChanged;
110
113
  }
111
114
  type ViewabilityConfigCallbackPairs = ViewabilityConfigCallbackPair[];
112
115
  type OnViewableItemsChanged = ((info: {
@@ -114,6 +117,10 @@ type OnViewableItemsChanged = ((info: {
114
117
  changed: Array<ViewToken>;
115
118
  }) => void) | null;
116
119
  interface ViewabilityConfig {
120
+ /**
121
+ * A unique ID to identify this viewability config
122
+ */
123
+ id: string;
117
124
  /**
118
125
  * Minimum amount of time (in milliseconds) that an item must be physically viewable before the
119
126
  * viewability callback will be fired. A high number means that scrolling through content without
@@ -138,9 +145,16 @@ interface ViewabilityConfig {
138
145
  */
139
146
  waitForInteraction?: boolean | undefined;
140
147
  }
148
+ type ViewabilityCallback = (viewToken: ViewToken) => void;
149
+ interface LegendListRecyclingState<T> {
150
+ item: T;
151
+ prevItem: T | undefined;
152
+ index: number;
153
+ prevIndex: number | undefined;
154
+ }
141
155
 
142
156
  declare const LegendList: <T>(props: LegendListProps<T> & {
143
157
  ref?: ForwardedRef<LegendListRef>;
144
158
  }) => ReactElement;
145
159
 
146
- export { type InternalState, LegendList, type LegendListProps, type LegendListRef, type LegendListRenderItemInfo, type OnViewableItemsChanged, type ViewToken, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, type ViewableRange };
160
+ export { type InternalState, LegendList, type LegendListProps, type LegendListRecyclingState, type LegendListRef, type LegendListRenderItemProps, type OnViewableItemsChanged, type ViewToken, type ViewabilityCallback, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, type ViewableRange };
package/index.d.ts CHANGED
@@ -18,7 +18,7 @@ type LegendListProps<T> = Omit<ComponentProps<typeof ScrollView>, 'contentOffset
18
18
  distanceFromEnd: number;
19
19
  }) => void) | null | undefined;
20
20
  keyExtractor?: (item: T, index: number) => string;
21
- renderItem?: (props: LegendListRenderItemInfo<T>) => ReactNode;
21
+ renderItem?: (props: LegendListRenderItemProps<T>) => ReactNode;
22
22
  onViewableRangeChanged?: (range: ViewableRange<T>) => void;
23
23
  ListHeaderComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
24
24
  ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
@@ -59,9 +59,12 @@ interface ViewableRange<T> {
59
59
  end: number;
60
60
  items: T[];
61
61
  }
62
- interface LegendListRenderItemInfo<ItemT> {
62
+ interface LegendListRenderItemProps<ItemT> {
63
63
  item: ItemT;
64
64
  index: number;
65
+ useViewability: (configId: string, callback: ViewabilityCallback) => void;
66
+ useRecyclingEffect: (effect: (info: LegendListRecyclingState<ItemT>) => void | (() => void)) => void;
67
+ useRecyclingState: <T>(updateState: (info: LegendListRecyclingState<ItemT>) => T) => [T, React.Dispatch<T>];
65
68
  }
66
69
  type LegendListRef = {
67
70
  /**
@@ -106,7 +109,7 @@ interface ViewToken<ItemT = any> {
106
109
  }
107
110
  interface ViewabilityConfigCallbackPair {
108
111
  viewabilityConfig: ViewabilityConfig;
109
- onViewableItemsChanged: OnViewableItemsChanged;
112
+ onViewableItemsChanged?: OnViewableItemsChanged;
110
113
  }
111
114
  type ViewabilityConfigCallbackPairs = ViewabilityConfigCallbackPair[];
112
115
  type OnViewableItemsChanged = ((info: {
@@ -114,6 +117,10 @@ type OnViewableItemsChanged = ((info: {
114
117
  changed: Array<ViewToken>;
115
118
  }) => void) | null;
116
119
  interface ViewabilityConfig {
120
+ /**
121
+ * A unique ID to identify this viewability config
122
+ */
123
+ id: string;
117
124
  /**
118
125
  * Minimum amount of time (in milliseconds) that an item must be physically viewable before the
119
126
  * viewability callback will be fired. A high number means that scrolling through content without
@@ -138,9 +145,16 @@ interface ViewabilityConfig {
138
145
  */
139
146
  waitForInteraction?: boolean | undefined;
140
147
  }
148
+ type ViewabilityCallback = (viewToken: ViewToken) => void;
149
+ interface LegendListRecyclingState<T> {
150
+ item: T;
151
+ prevItem: T | undefined;
152
+ index: number;
153
+ prevIndex: number | undefined;
154
+ }
141
155
 
142
156
  declare const LegendList: <T>(props: LegendListProps<T> & {
143
157
  ref?: ForwardedRef<LegendListRef>;
144
158
  }) => ReactElement;
145
159
 
146
- export { type InternalState, LegendList, type LegendListProps, type LegendListRef, type LegendListRenderItemInfo, type OnViewableItemsChanged, type ViewToken, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, type ViewableRange };
160
+ export { type InternalState, LegendList, type LegendListProps, type LegendListRecyclingState, type LegendListRef, type LegendListRenderItemProps, type OnViewableItemsChanged, type ViewToken, type ViewabilityCallback, type ViewabilityConfig, type ViewabilityConfigCallbackPair, type ViewabilityConfigCallbackPairs, type ViewableRange };
package/index.js CHANGED
@@ -31,6 +31,7 @@ LeanView.displayName = "RCTView";
31
31
  var ContextState = React7__namespace.createContext(null);
32
32
  function StateProvider({ children }) {
33
33
  const [value] = React7__namespace.useState(() => ({
34
+ hooks: /* @__PURE__ */ new Map(),
34
35
  listeners: /* @__PURE__ */ new Map(),
35
36
  values: /* @__PURE__ */ new Map()
36
37
  }));
@@ -40,21 +41,37 @@ function useStateContext() {
40
41
  return React7__namespace.useContext(ContextState);
41
42
  }
42
43
  function use$(signalName) {
43
- const { listeners, values } = React7__namespace.useContext(ContextState);
44
+ const { hooks, values } = React7__namespace.useContext(ContextState);
44
45
  const [, forceUpdate] = React7__namespace.useReducer((x) => x + 1, 0);
45
- listeners.set(signalName, forceUpdate);
46
+ hooks.set(signalName, forceUpdate);
46
47
  return values.get(signalName);
47
48
  }
49
+ function listen$(ctx, signalName, cb) {
50
+ const { listeners } = ctx;
51
+ let setListeners = listeners.get(signalName);
52
+ if (!setListeners) {
53
+ setListeners = /* @__PURE__ */ new Set();
54
+ listeners.set(signalName, setListeners);
55
+ }
56
+ setListeners.add(cb);
57
+ return () => setListeners.delete(cb);
58
+ }
48
59
  function peek$(ctx, signalName) {
49
60
  const { values } = ctx;
50
61
  return values.get(signalName);
51
62
  }
52
63
  function set$(ctx, signalName, value) {
53
64
  var _a;
54
- const { listeners, values } = ctx;
65
+ const { listeners, hooks, values } = ctx;
55
66
  if (values.get(signalName) !== value) {
56
67
  values.set(signalName, value);
57
- (_a = listeners.get(signalName)) == null ? void 0 : _a();
68
+ (_a = hooks.get(signalName)) == null ? void 0 : _a();
69
+ const setListeners = listeners.get(signalName);
70
+ if (setListeners) {
71
+ for (const listener of setListeners) {
72
+ listener(value);
73
+ }
74
+ }
58
75
  }
59
76
  }
60
77
 
@@ -70,8 +87,15 @@ function InnerContainer({ id, getRenderedItem, recycleItems, ItemSeparatorCompon
70
87
  if (itemIndex < 0) {
71
88
  return null;
72
89
  }
73
- const renderedItem = getRenderedItem(itemIndex);
74
- return /* @__PURE__ */ React7__namespace.createElement(React7__namespace.Fragment, { key: recycleItems ? void 0 : itemIndex }, renderedItem, ItemSeparatorComponent && itemIndex < numItems - 1 && ItemSeparatorComponent);
90
+ return /* @__PURE__ */ React7__namespace.createElement(React7__namespace.Fragment, { key: recycleItems ? void 0 : itemIndex }, /* @__PURE__ */ React7__namespace.createElement(RenderedItem, { itemIndex, id, getRenderedItem }), ItemSeparatorComponent && itemIndex < numItems - 1 && ItemSeparatorComponent);
91
+ }
92
+ function RenderedItem({
93
+ itemIndex,
94
+ id,
95
+ getRenderedItem
96
+ }) {
97
+ const renderedItem = getRenderedItem(itemIndex, id);
98
+ return renderedItem;
75
99
  }
76
100
  var Container = ({
77
101
  id,
@@ -191,6 +215,7 @@ var ListComponent = React7__namespace.memo(function ListComponent2({
191
215
  ListFooterComponentStyle,
192
216
  getRenderedItem,
193
217
  updateItemSize,
218
+ addTotalSize,
194
219
  refScroller,
195
220
  ...rest
196
221
  }) {
@@ -214,7 +239,21 @@ var ListComponent = React7__namespace.memo(function ListComponent2({
214
239
  ref: refScroller
215
240
  },
216
241
  alignItemsAtEnd && /* @__PURE__ */ React7__namespace.createElement($View, { $key: "paddingTop", $style: () => ({ height: peek$(ctx, "paddingTop") }) }),
217
- ListHeaderComponent && /* @__PURE__ */ React7__namespace.createElement(reactNative.View, { style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
242
+ ListHeaderComponent && /* @__PURE__ */ React7__namespace.createElement(
243
+ reactNative.View,
244
+ {
245
+ style: ListHeaderComponentStyle,
246
+ onLayout: (event) => {
247
+ const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
248
+ const prevSize = peek$(ctx, "headerSize") || 0;
249
+ if (size !== prevSize) {
250
+ set$(ctx, "headerSize", size);
251
+ addTotalSize(size - prevSize);
252
+ }
253
+ }
254
+ },
255
+ getComponent(ListHeaderComponent)
256
+ ),
218
257
  /* @__PURE__ */ React7__namespace.createElement(
219
258
  Containers,
220
259
  {
@@ -230,15 +269,16 @@ var ListComponent = React7__namespace.memo(function ListComponent2({
230
269
  });
231
270
 
232
271
  // src/viewability.ts
233
- var mapViewabilityConfigCallbackPairs = /* @__PURE__ */ new WeakMap();
272
+ var mapViewabilityConfigCallbackPairs = /* @__PURE__ */ new Map();
273
+ var mapViewabilityCallbacks = /* @__PURE__ */ new Map();
234
274
  function setupViewability(props) {
235
275
  let { viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged } = props;
236
- viewabilityConfigCallbackPairs = viewabilityConfigCallbackPairs || onViewableItemsChanged && [
276
+ viewabilityConfigCallbackPairs = viewabilityConfigCallbackPairs || [
237
277
  { viewabilityConfig: viewabilityConfig || { viewAreaCoveragePercentThreshold: 0 }, onViewableItemsChanged }
238
278
  ];
239
279
  if (viewabilityConfigCallbackPairs) {
240
280
  for (const pair of viewabilityConfigCallbackPairs) {
241
- mapViewabilityConfigCallbackPairs.set(pair, {
281
+ mapViewabilityConfigCallbackPairs.set(pair.viewabilityConfig.id, {
242
282
  viewableItems: [],
243
283
  start: -1,
244
284
  end: -1,
@@ -251,7 +291,9 @@ function setupViewability(props) {
251
291
  }
252
292
  function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, getId, scrollSize, start, end) {
253
293
  for (const viewabilityConfigCallbackPair of viewabilityConfigCallbackPairs) {
254
- const viewabilityState = mapViewabilityConfigCallbackPairs.get(viewabilityConfigCallbackPair);
294
+ const viewabilityState = mapViewabilityConfigCallbackPairs.get(
295
+ viewabilityConfigCallbackPair.viewabilityConfig.id
296
+ );
255
297
  viewabilityState.start = start;
256
298
  viewabilityState.end = end;
257
299
  if (viewabilityConfigCallbackPair.viewabilityConfig.minimumViewTime) {
@@ -266,13 +308,14 @@ function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, getId,
266
308
  }
267
309
  }
268
310
  function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getId, state, ctx, scrollSize) {
269
- var _a;
270
- const viewabilityState = mapViewabilityConfigCallbackPairs.get(viewabilityConfigCallbackPair);
311
+ const { viewabilityConfig, onViewableItemsChanged } = viewabilityConfigCallbackPair;
312
+ const configId = viewabilityConfig.id;
313
+ const viewabilityState = mapViewabilityConfigCallbackPairs.get(configId);
271
314
  const { viewableItems: previousViewableItems, start, previousStart, end, previousEnd } = viewabilityState;
272
315
  const changed = [];
273
316
  if (previousViewableItems) {
274
317
  for (const viewToken of previousViewableItems) {
275
- if (viewToken.index < start || viewToken.index > end) {
318
+ if (!isViewable(state, ctx, viewabilityConfig, viewToken.key, scrollSize)) {
276
319
  viewToken.isViewable = false;
277
320
  changed.push(viewToken);
278
321
  }
@@ -283,7 +326,7 @@ function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getI
283
326
  const item = data[i];
284
327
  if (item) {
285
328
  const key = getId(i);
286
- if (isViewable(state, ctx, viewabilityConfigCallbackPair.viewabilityConfig, key, scrollSize)) {
329
+ if (isViewable(state, ctx, viewabilityConfig, key, scrollSize)) {
287
330
  const viewToken = {
288
331
  item,
289
332
  key,
@@ -299,7 +342,15 @@ function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getI
299
342
  }
300
343
  Object.assign(viewabilityState, { viewableItems, previousStart: start, previousEnd: end });
301
344
  if (changed.length > 0) {
302
- (_a = viewabilityConfigCallbackPair.onViewableItemsChanged) == null ? void 0 : _a.call(viewabilityConfigCallbackPair, { viewableItems, changed });
345
+ console.log("changed", changed);
346
+ viewabilityState.viewableItems = viewableItems;
347
+ for (let i = 0; i < changed.length; i++) {
348
+ const change = changed[i];
349
+ maybeUpdateViewabilityCallback(configId, change);
350
+ }
351
+ if (onViewableItemsChanged) {
352
+ onViewableItemsChanged({ viewableItems, changed });
353
+ }
303
354
  }
304
355
  }
305
356
  function isViewable(state, ctx, viewabilityConfig, key, scrollSize) {
@@ -319,6 +370,18 @@ function isViewable(state, ctx, viewabilityConfig, key, scrollSize) {
319
370
  const percent = 100 * (visibleHeight / (viewAreaMode ? scrollSize : size));
320
371
  return percent >= viewablePercentThreshold;
321
372
  }
373
+ function maybeUpdateViewabilityCallback(configId, viewToken) {
374
+ const key = viewToken.key + configId;
375
+ const cb = mapViewabilityCallbacks.get(key);
376
+ cb == null ? void 0 : cb(viewToken);
377
+ }
378
+ function registerViewabilityCallback(itemKey, configId, callback) {
379
+ const key = itemKey + configId;
380
+ mapViewabilityCallbacks.set(key, callback);
381
+ return () => {
382
+ mapViewabilityCallbacks.delete(key);
383
+ };
384
+ }
322
385
 
323
386
  // src/LegendList.tsx
324
387
  var DEFAULT_SCROLL_BUFFER = 0;
@@ -420,7 +483,7 @@ var LegendListInner = React7.forwardRef(function LegendListInner2(props, forward
420
483
  refState.current.data = data;
421
484
  set$(ctx, "numItems", data.length);
422
485
  set$(ctx, "stylePaddingTop", (_b = (_a = styleFlattened == null ? void 0 : styleFlattened.paddingTop) != null ? _a : contentContainerStyleFlattened == null ? void 0 : contentContainerStyleFlattened.paddingTop) != null ? _b : 0);
423
- const addTotalSize = (add) => {
486
+ const addTotalSize = React7.useCallback((add) => {
424
487
  const prev = refState.current.totalSize;
425
488
  refState.current.totalSize += add;
426
489
  const totalSize = refState.current.totalSize;
@@ -438,17 +501,65 @@ var LegendListInner = React7.forwardRef(function LegendListInner2(props, forward
438
501
  } else if (!refState.current.animFrameTotalSize) {
439
502
  refState.current.animFrameTotalSize = requestAnimationFrame(doAdd);
440
503
  }
441
- };
504
+ }, []);
442
505
  const getRenderedItem = React7.useCallback(
443
- (index) => {
506
+ (index, containerIndex) => {
444
507
  var _a2;
445
508
  const data2 = (_a2 = refState.current) == null ? void 0 : _a2.data;
446
509
  if (!data2) {
447
510
  return null;
448
511
  }
512
+ const itemKey = getId(index);
513
+ const useViewability = (configId, callback) => {
514
+ React7.useEffect(() => registerViewabilityCallback(itemKey, configId, callback), []);
515
+ };
516
+ const useRecyclingEffect = (effect) => {
517
+ React7.useEffect(() => {
518
+ let prevIndex = index;
519
+ let prevItem = data2[index];
520
+ const signal = `containerIndex${containerIndex}`;
521
+ listen$(ctx, signal, () => {
522
+ var _a3;
523
+ const data3 = (_a3 = refState.current) == null ? void 0 : _a3.data;
524
+ if (data3) {
525
+ const newIndex = peek$(ctx, signal);
526
+ const newItem = data3[newIndex];
527
+ if (newItem) {
528
+ effect({
529
+ index: newIndex,
530
+ item: newItem,
531
+ prevIndex,
532
+ prevItem
533
+ });
534
+ }
535
+ prevIndex = newIndex;
536
+ prevItem = newItem;
537
+ }
538
+ });
539
+ }, []);
540
+ };
541
+ const useRecyclingState = (updateState) => {
542
+ const stateInfo = React7.useState(
543
+ () => updateState({
544
+ index,
545
+ item: data2[index],
546
+ prevIndex: void 0,
547
+ prevItem: void 0
548
+ })
549
+ );
550
+ useRecyclingEffect((state) => {
551
+ const newState = updateState(state);
552
+ console.log("setting state", newState);
553
+ stateInfo[1](newState);
554
+ });
555
+ return stateInfo;
556
+ };
449
557
  const renderedItem = renderItem == null ? void 0 : renderItem({
450
558
  item: data2[index],
451
- index
559
+ index,
560
+ useViewability,
561
+ useRecyclingEffect,
562
+ useRecyclingState
452
563
  });
453
564
  return renderedItem;
454
565
  },
@@ -757,7 +868,8 @@ var LegendListInner = React7.forwardRef(function LegendListInner2(props, forward
757
868
  handleScroll,
758
869
  onLayout,
759
870
  recycleItems,
760
- alignItemsAtEnd
871
+ alignItemsAtEnd,
872
+ addTotalSize
761
873
  }
762
874
  );
763
875
  });
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React7 from 'react';
2
- import { forwardRef, useRef, useMemo, useCallback, useEffect, useImperativeHandle } from 'react';
2
+ import { forwardRef, useRef, useMemo, useCallback, useEffect, useImperativeHandle, useState } from 'react';
3
3
  import { ScrollView, View, StyleSheet, Dimensions, unstable_batchedUpdates } from 'react-native';
4
4
 
5
5
  // src/LegendList.tsx
@@ -10,6 +10,7 @@ LeanView.displayName = "RCTView";
10
10
  var ContextState = React7.createContext(null);
11
11
  function StateProvider({ children }) {
12
12
  const [value] = React7.useState(() => ({
13
+ hooks: /* @__PURE__ */ new Map(),
13
14
  listeners: /* @__PURE__ */ new Map(),
14
15
  values: /* @__PURE__ */ new Map()
15
16
  }));
@@ -19,21 +20,37 @@ function useStateContext() {
19
20
  return React7.useContext(ContextState);
20
21
  }
21
22
  function use$(signalName) {
22
- const { listeners, values } = React7.useContext(ContextState);
23
+ const { hooks, values } = React7.useContext(ContextState);
23
24
  const [, forceUpdate] = React7.useReducer((x) => x + 1, 0);
24
- listeners.set(signalName, forceUpdate);
25
+ hooks.set(signalName, forceUpdate);
25
26
  return values.get(signalName);
26
27
  }
28
+ function listen$(ctx, signalName, cb) {
29
+ const { listeners } = ctx;
30
+ let setListeners = listeners.get(signalName);
31
+ if (!setListeners) {
32
+ setListeners = /* @__PURE__ */ new Set();
33
+ listeners.set(signalName, setListeners);
34
+ }
35
+ setListeners.add(cb);
36
+ return () => setListeners.delete(cb);
37
+ }
27
38
  function peek$(ctx, signalName) {
28
39
  const { values } = ctx;
29
40
  return values.get(signalName);
30
41
  }
31
42
  function set$(ctx, signalName, value) {
32
43
  var _a;
33
- const { listeners, values } = ctx;
44
+ const { listeners, hooks, values } = ctx;
34
45
  if (values.get(signalName) !== value) {
35
46
  values.set(signalName, value);
36
- (_a = listeners.get(signalName)) == null ? void 0 : _a();
47
+ (_a = hooks.get(signalName)) == null ? void 0 : _a();
48
+ const setListeners = listeners.get(signalName);
49
+ if (setListeners) {
50
+ for (const listener of setListeners) {
51
+ listener(value);
52
+ }
53
+ }
37
54
  }
38
55
  }
39
56
 
@@ -49,8 +66,15 @@ function InnerContainer({ id, getRenderedItem, recycleItems, ItemSeparatorCompon
49
66
  if (itemIndex < 0) {
50
67
  return null;
51
68
  }
52
- const renderedItem = getRenderedItem(itemIndex);
53
- return /* @__PURE__ */ React7.createElement(React7.Fragment, { key: recycleItems ? void 0 : itemIndex }, renderedItem, ItemSeparatorComponent && itemIndex < numItems - 1 && ItemSeparatorComponent);
69
+ return /* @__PURE__ */ React7.createElement(React7.Fragment, { key: recycleItems ? void 0 : itemIndex }, /* @__PURE__ */ React7.createElement(RenderedItem, { itemIndex, id, getRenderedItem }), ItemSeparatorComponent && itemIndex < numItems - 1 && ItemSeparatorComponent);
70
+ }
71
+ function RenderedItem({
72
+ itemIndex,
73
+ id,
74
+ getRenderedItem
75
+ }) {
76
+ const renderedItem = getRenderedItem(itemIndex, id);
77
+ return renderedItem;
54
78
  }
55
79
  var Container = ({
56
80
  id,
@@ -170,6 +194,7 @@ var ListComponent = React7.memo(function ListComponent2({
170
194
  ListFooterComponentStyle,
171
195
  getRenderedItem,
172
196
  updateItemSize,
197
+ addTotalSize,
173
198
  refScroller,
174
199
  ...rest
175
200
  }) {
@@ -193,7 +218,21 @@ var ListComponent = React7.memo(function ListComponent2({
193
218
  ref: refScroller
194
219
  },
195
220
  alignItemsAtEnd && /* @__PURE__ */ React7.createElement($View, { $key: "paddingTop", $style: () => ({ height: peek$(ctx, "paddingTop") }) }),
196
- ListHeaderComponent && /* @__PURE__ */ React7.createElement(View, { style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
221
+ ListHeaderComponent && /* @__PURE__ */ React7.createElement(
222
+ View,
223
+ {
224
+ style: ListHeaderComponentStyle,
225
+ onLayout: (event) => {
226
+ const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
227
+ const prevSize = peek$(ctx, "headerSize") || 0;
228
+ if (size !== prevSize) {
229
+ set$(ctx, "headerSize", size);
230
+ addTotalSize(size - prevSize);
231
+ }
232
+ }
233
+ },
234
+ getComponent(ListHeaderComponent)
235
+ ),
197
236
  /* @__PURE__ */ React7.createElement(
198
237
  Containers,
199
238
  {
@@ -209,15 +248,16 @@ var ListComponent = React7.memo(function ListComponent2({
209
248
  });
210
249
 
211
250
  // src/viewability.ts
212
- var mapViewabilityConfigCallbackPairs = /* @__PURE__ */ new WeakMap();
251
+ var mapViewabilityConfigCallbackPairs = /* @__PURE__ */ new Map();
252
+ var mapViewabilityCallbacks = /* @__PURE__ */ new Map();
213
253
  function setupViewability(props) {
214
254
  let { viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged } = props;
215
- viewabilityConfigCallbackPairs = viewabilityConfigCallbackPairs || onViewableItemsChanged && [
255
+ viewabilityConfigCallbackPairs = viewabilityConfigCallbackPairs || [
216
256
  { viewabilityConfig: viewabilityConfig || { viewAreaCoveragePercentThreshold: 0 }, onViewableItemsChanged }
217
257
  ];
218
258
  if (viewabilityConfigCallbackPairs) {
219
259
  for (const pair of viewabilityConfigCallbackPairs) {
220
- mapViewabilityConfigCallbackPairs.set(pair, {
260
+ mapViewabilityConfigCallbackPairs.set(pair.viewabilityConfig.id, {
221
261
  viewableItems: [],
222
262
  start: -1,
223
263
  end: -1,
@@ -230,7 +270,9 @@ function setupViewability(props) {
230
270
  }
231
271
  function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, getId, scrollSize, start, end) {
232
272
  for (const viewabilityConfigCallbackPair of viewabilityConfigCallbackPairs) {
233
- const viewabilityState = mapViewabilityConfigCallbackPairs.get(viewabilityConfigCallbackPair);
273
+ const viewabilityState = mapViewabilityConfigCallbackPairs.get(
274
+ viewabilityConfigCallbackPair.viewabilityConfig.id
275
+ );
234
276
  viewabilityState.start = start;
235
277
  viewabilityState.end = end;
236
278
  if (viewabilityConfigCallbackPair.viewabilityConfig.minimumViewTime) {
@@ -245,13 +287,14 @@ function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, getId,
245
287
  }
246
288
  }
247
289
  function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getId, state, ctx, scrollSize) {
248
- var _a;
249
- const viewabilityState = mapViewabilityConfigCallbackPairs.get(viewabilityConfigCallbackPair);
290
+ const { viewabilityConfig, onViewableItemsChanged } = viewabilityConfigCallbackPair;
291
+ const configId = viewabilityConfig.id;
292
+ const viewabilityState = mapViewabilityConfigCallbackPairs.get(configId);
250
293
  const { viewableItems: previousViewableItems, start, previousStart, end, previousEnd } = viewabilityState;
251
294
  const changed = [];
252
295
  if (previousViewableItems) {
253
296
  for (const viewToken of previousViewableItems) {
254
- if (viewToken.index < start || viewToken.index > end) {
297
+ if (!isViewable(state, ctx, viewabilityConfig, viewToken.key, scrollSize)) {
255
298
  viewToken.isViewable = false;
256
299
  changed.push(viewToken);
257
300
  }
@@ -262,7 +305,7 @@ function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getI
262
305
  const item = data[i];
263
306
  if (item) {
264
307
  const key = getId(i);
265
- if (isViewable(state, ctx, viewabilityConfigCallbackPair.viewabilityConfig, key, scrollSize)) {
308
+ if (isViewable(state, ctx, viewabilityConfig, key, scrollSize)) {
266
309
  const viewToken = {
267
310
  item,
268
311
  key,
@@ -278,7 +321,15 @@ function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getI
278
321
  }
279
322
  Object.assign(viewabilityState, { viewableItems, previousStart: start, previousEnd: end });
280
323
  if (changed.length > 0) {
281
- (_a = viewabilityConfigCallbackPair.onViewableItemsChanged) == null ? void 0 : _a.call(viewabilityConfigCallbackPair, { viewableItems, changed });
324
+ console.log("changed", changed);
325
+ viewabilityState.viewableItems = viewableItems;
326
+ for (let i = 0; i < changed.length; i++) {
327
+ const change = changed[i];
328
+ maybeUpdateViewabilityCallback(configId, change);
329
+ }
330
+ if (onViewableItemsChanged) {
331
+ onViewableItemsChanged({ viewableItems, changed });
332
+ }
282
333
  }
283
334
  }
284
335
  function isViewable(state, ctx, viewabilityConfig, key, scrollSize) {
@@ -298,6 +349,18 @@ function isViewable(state, ctx, viewabilityConfig, key, scrollSize) {
298
349
  const percent = 100 * (visibleHeight / (viewAreaMode ? scrollSize : size));
299
350
  return percent >= viewablePercentThreshold;
300
351
  }
352
+ function maybeUpdateViewabilityCallback(configId, viewToken) {
353
+ const key = viewToken.key + configId;
354
+ const cb = mapViewabilityCallbacks.get(key);
355
+ cb == null ? void 0 : cb(viewToken);
356
+ }
357
+ function registerViewabilityCallback(itemKey, configId, callback) {
358
+ const key = itemKey + configId;
359
+ mapViewabilityCallbacks.set(key, callback);
360
+ return () => {
361
+ mapViewabilityCallbacks.delete(key);
362
+ };
363
+ }
301
364
 
302
365
  // src/LegendList.tsx
303
366
  var DEFAULT_SCROLL_BUFFER = 0;
@@ -399,7 +462,7 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
399
462
  refState.current.data = data;
400
463
  set$(ctx, "numItems", data.length);
401
464
  set$(ctx, "stylePaddingTop", (_b = (_a = styleFlattened == null ? void 0 : styleFlattened.paddingTop) != null ? _a : contentContainerStyleFlattened == null ? void 0 : contentContainerStyleFlattened.paddingTop) != null ? _b : 0);
402
- const addTotalSize = (add) => {
465
+ const addTotalSize = useCallback((add) => {
403
466
  const prev = refState.current.totalSize;
404
467
  refState.current.totalSize += add;
405
468
  const totalSize = refState.current.totalSize;
@@ -417,17 +480,65 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
417
480
  } else if (!refState.current.animFrameTotalSize) {
418
481
  refState.current.animFrameTotalSize = requestAnimationFrame(doAdd);
419
482
  }
420
- };
483
+ }, []);
421
484
  const getRenderedItem = useCallback(
422
- (index) => {
485
+ (index, containerIndex) => {
423
486
  var _a2;
424
487
  const data2 = (_a2 = refState.current) == null ? void 0 : _a2.data;
425
488
  if (!data2) {
426
489
  return null;
427
490
  }
491
+ const itemKey = getId(index);
492
+ const useViewability = (configId, callback) => {
493
+ useEffect(() => registerViewabilityCallback(itemKey, configId, callback), []);
494
+ };
495
+ const useRecyclingEffect = (effect) => {
496
+ useEffect(() => {
497
+ let prevIndex = index;
498
+ let prevItem = data2[index];
499
+ const signal = `containerIndex${containerIndex}`;
500
+ listen$(ctx, signal, () => {
501
+ var _a3;
502
+ const data3 = (_a3 = refState.current) == null ? void 0 : _a3.data;
503
+ if (data3) {
504
+ const newIndex = peek$(ctx, signal);
505
+ const newItem = data3[newIndex];
506
+ if (newItem) {
507
+ effect({
508
+ index: newIndex,
509
+ item: newItem,
510
+ prevIndex,
511
+ prevItem
512
+ });
513
+ }
514
+ prevIndex = newIndex;
515
+ prevItem = newItem;
516
+ }
517
+ });
518
+ }, []);
519
+ };
520
+ const useRecyclingState = (updateState) => {
521
+ const stateInfo = useState(
522
+ () => updateState({
523
+ index,
524
+ item: data2[index],
525
+ prevIndex: void 0,
526
+ prevItem: void 0
527
+ })
528
+ );
529
+ useRecyclingEffect((state) => {
530
+ const newState = updateState(state);
531
+ console.log("setting state", newState);
532
+ stateInfo[1](newState);
533
+ });
534
+ return stateInfo;
535
+ };
428
536
  const renderedItem = renderItem == null ? void 0 : renderItem({
429
537
  item: data2[index],
430
- index
538
+ index,
539
+ useViewability,
540
+ useRecyclingEffect,
541
+ useRecyclingState
431
542
  });
432
543
  return renderedItem;
433
544
  },
@@ -736,7 +847,8 @@ var LegendListInner = forwardRef(function LegendListInner2(props, forwardedRef)
736
847
  handleScroll,
737
848
  onLayout,
738
849
  recycleItems,
739
- alignItemsAtEnd
850
+ alignItemsAtEnd,
851
+ addTotalSize
740
852
  }
741
853
  );
742
854
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "legend-list",
5
5
  "sideEffects": false,
6
6
  "private": false,