@legendapp/list 2.0.12 → 2.0.14

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 2.0.14
2
+ - Feat: Add dataVersion prop to trigger re-render when mutating the data array in place
3
+
4
+ ## 2.0.13
5
+ - Feat: Allow returning undefined in getFixedItemSize to fall back to estimated size
6
+ - Fix: scrollToIndex viewOffset was being subtracted twice, causing incorrect scroll positioning
7
+ - Fix: Initial container allocation was not applying maintainVisibleContentPosition calculations
8
+ - Fix: updateItemSize was providing full data array to getEstimatedItemSize and getFixedItemSize instead of individual item
9
+
1
10
  ## 2.0.12
2
11
  - Fix: Scroll velocity calculation was sometimes incorrect when item sizes were very different from estimate
3
12
  - Fix: onScroll while scrolling was updating positions without maintainVisibleContentPosition calculations, which was breaking scroll position maintenance
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React$1 from 'react';
2
- import { ComponentProps, ReactNode, Dispatch, SetStateAction } from 'react';
2
+ import { ComponentProps, Key, ReactNode, Dispatch, SetStateAction } from 'react';
3
3
  import { View, Animated, ScrollView, LayoutRectangle, ScrollViewComponent, ScrollResponderMixin, StyleProp, ViewStyle, NativeSyntheticEvent, NativeScrollEvent, ScrollViewProps } from 'react-native';
4
4
  import Animated$1 from 'react-native-reanimated';
5
5
 
@@ -99,6 +99,11 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
99
99
  * Extra data to trigger re-rendering when changed.
100
100
  */
101
101
  extraData?: any;
102
+ /**
103
+ * Version token that forces the list to treat data as updated even when the array reference is stable.
104
+ * Increment or change this when mutating the data array in place.
105
+ */
106
+ dataVersion?: Key;
102
107
  /**
103
108
  * In case you have distinct item sizes, you can provide a function to get the size of an item.
104
109
  * Use instead of FlatList's getItemLayout or FlashList overrideItemLayout if you want to have accurate initialScrollOffset, you should provide this function
@@ -275,7 +280,7 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
275
280
  */
276
281
  stickyIndices?: number[];
277
282
  getItemType?: (item: ItemT, index: number) => TItemType;
278
- getFixedItemSize?: (index: number, item: ItemT, type: TItemType) => number;
283
+ getFixedItemSize?: (index: number, item: ItemT, type: TItemType) => number | undefined;
279
284
  itemsAreEqual?: (itemPrevious: ItemT, item: ItemT, index: number, data: readonly ItemT[]) => boolean;
280
285
  }
281
286
  type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof ScrollView> | ComponentProps<typeof Animated.ScrollView> | ComponentProps<typeof Animated$1.ScrollView>, TItemType extends string | undefined = string | undefined> = BaseScrollViewProps<TScrollView> & LegendListSpecificProps<ItemT, TItemType> & (DataModeProps<ItemT, TItemType> | ChildrenModeProps);
@@ -372,6 +377,7 @@ interface InternalState {
372
377
  props: {
373
378
  alignItemsAtEnd: boolean;
374
379
  data: readonly any[];
380
+ dataVersion: Key | undefined;
375
381
  estimatedItemSize: number | undefined;
376
382
  getEstimatedItemSize: LegendListProps["getEstimatedItemSize"];
377
383
  getFixedItemSize: LegendListProps["getFixedItemSize"];
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React$1 from 'react';
2
- import { ComponentProps, ReactNode, Dispatch, SetStateAction } from 'react';
2
+ import { ComponentProps, Key, ReactNode, Dispatch, SetStateAction } from 'react';
3
3
  import { View, Animated, ScrollView, LayoutRectangle, ScrollViewComponent, ScrollResponderMixin, StyleProp, ViewStyle, NativeSyntheticEvent, NativeScrollEvent, ScrollViewProps } from 'react-native';
4
4
  import Animated$1 from 'react-native-reanimated';
5
5
 
@@ -99,6 +99,11 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
99
99
  * Extra data to trigger re-rendering when changed.
100
100
  */
101
101
  extraData?: any;
102
+ /**
103
+ * Version token that forces the list to treat data as updated even when the array reference is stable.
104
+ * Increment or change this when mutating the data array in place.
105
+ */
106
+ dataVersion?: Key;
102
107
  /**
103
108
  * In case you have distinct item sizes, you can provide a function to get the size of an item.
104
109
  * Use instead of FlatList's getItemLayout or FlashList overrideItemLayout if you want to have accurate initialScrollOffset, you should provide this function
@@ -275,7 +280,7 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
275
280
  */
276
281
  stickyIndices?: number[];
277
282
  getItemType?: (item: ItemT, index: number) => TItemType;
278
- getFixedItemSize?: (index: number, item: ItemT, type: TItemType) => number;
283
+ getFixedItemSize?: (index: number, item: ItemT, type: TItemType) => number | undefined;
279
284
  itemsAreEqual?: (itemPrevious: ItemT, item: ItemT, index: number, data: readonly ItemT[]) => boolean;
280
285
  }
281
286
  type LegendListPropsBase<ItemT, TScrollView extends ComponentProps<typeof ScrollView> | ComponentProps<typeof Animated.ScrollView> | ComponentProps<typeof Animated$1.ScrollView>, TItemType extends string | undefined = string | undefined> = BaseScrollViewProps<TScrollView> & LegendListSpecificProps<ItemT, TItemType> & (DataModeProps<ItemT, TItemType> | ChildrenModeProps);
@@ -372,6 +377,7 @@ interface InternalState {
372
377
  props: {
373
378
  alignItemsAtEnd: boolean;
374
379
  data: readonly any[];
380
+ dataVersion: Key | undefined;
375
381
  estimatedItemSize: number | undefined;
376
382
  getEstimatedItemSize: LegendListProps["getEstimatedItemSize"];
377
383
  getFixedItemSize: LegendListProps["getFixedItemSize"];
package/index.js CHANGED
@@ -1050,7 +1050,7 @@ function prepareMVCP(ctx, state, dataChanged) {
1050
1050
  if (newPosition !== void 0) {
1051
1051
  const totalSize = peek$(ctx, "totalSize");
1052
1052
  let diff = newPosition - prevPosition;
1053
- if (state.scroll + state.scrollLength > totalSize) {
1053
+ if (diff !== 0 && state.scroll + state.scrollLength > totalSize) {
1054
1054
  if (diff > 0) {
1055
1055
  diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
1056
1056
  } else {
@@ -1696,12 +1696,11 @@ function scrollToIndex(ctx, state, { index, viewOffset = 0, animated = true, vie
1696
1696
  if (isLast && viewPosition === void 0) {
1697
1697
  viewPosition = 1;
1698
1698
  }
1699
- const firstIndexScrollPostion = firstIndexOffset - viewOffset;
1700
1699
  state.scrollForNextCalculateItemsInView = void 0;
1701
1700
  scrollTo(state, {
1702
1701
  animated,
1703
1702
  index,
1704
- offset: firstIndexScrollPostion,
1703
+ offset: firstIndexOffset,
1705
1704
  viewOffset,
1706
1705
  viewPosition: viewPosition != null ? viewPosition : 0
1707
1706
  });
@@ -2307,7 +2306,7 @@ function checkResetContainers(ctx, state, isFirst, dataProp) {
2307
2306
 
2308
2307
  // src/core/doInitialAllocateContainers.ts
2309
2308
  function doInitialAllocateContainers(ctx, state) {
2310
- var _a;
2309
+ var _a, _b, _c;
2311
2310
  const {
2312
2311
  scrollLength,
2313
2312
  props: {
@@ -2323,14 +2322,13 @@ function doInitialAllocateContainers(ctx, state) {
2323
2322
  const hasContainers = peek$(ctx, "numContainers");
2324
2323
  if (scrollLength > 0 && data.length > 0 && !hasContainers) {
2325
2324
  let averageItemSize;
2326
- const fn = getFixedItemSize || getEstimatedItemSize;
2327
- if (fn) {
2325
+ if (getFixedItemSize || getEstimatedItemSize) {
2328
2326
  let totalSize = 0;
2329
2327
  const num = Math.min(20, data.length);
2330
2328
  for (let i = 0; i < num; i++) {
2331
2329
  const item = data[i];
2332
2330
  const itemType = getItemType ? (_a = getItemType(item, i)) != null ? _a : "" : "";
2333
- totalSize += fn(i, item, itemType);
2331
+ totalSize += (_c = (_b = getFixedItemSize == null ? void 0 : getFixedItemSize(i, item, itemType)) != null ? _b : getEstimatedItemSize == null ? void 0 : getEstimatedItemSize(i, item, itemType)) != null ? _c : estimatedItemSize;
2334
2332
  }
2335
2333
  averageItemSize = totalSize / num;
2336
2334
  } else {
@@ -2346,10 +2344,10 @@ function doInitialAllocateContainers(ctx, state) {
2346
2344
  if (!IsNewArchitecture || state.lastLayout) {
2347
2345
  if (state.props.initialScroll) {
2348
2346
  requestAnimationFrame(() => {
2349
- calculateItemsInView(ctx, state, { dataChanged: true });
2347
+ calculateItemsInView(ctx, state, { dataChanged: true, doMVCP: true });
2350
2348
  });
2351
2349
  } else {
2352
- calculateItemsInView(ctx, state, { dataChanged: true });
2350
+ calculateItemsInView(ctx, state, { dataChanged: true, doMVCP: true });
2353
2351
  }
2354
2352
  }
2355
2353
  return true;
@@ -2592,7 +2590,7 @@ function updateOneItemSize(state, itemKey, sizeObj) {
2592
2590
  } = state;
2593
2591
  if (!data) return 0;
2594
2592
  const index = indexByKey.get(itemKey);
2595
- const prevSize = getItemSize(state, itemKey, index, data);
2593
+ const prevSize = getItemSize(state, itemKey, index, data[index]);
2596
2594
  const size = Math.floor((horizontal ? sizeObj.width : sizeObj.height) * 8) / 8;
2597
2595
  sizesKnown.set(itemKey, size);
2598
2596
  if (!getEstimatedItemSize && !getFixedItemSize && size > 0) {
@@ -2740,6 +2738,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2740
2738
  columnWrapperStyle,
2741
2739
  contentContainerStyle: contentContainerStyleProp,
2742
2740
  data: dataProp = [],
2741
+ dataVersion,
2743
2742
  drawDistance = 250,
2744
2743
  enableAverages = true,
2745
2744
  estimatedItemSize: estimatedItemSizeProp,
@@ -2864,7 +2863,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2864
2863
  }
2865
2864
  const state = refState.current;
2866
2865
  const isFirst = !state.props.renderItem;
2867
- const didDataChange = state.props.data !== dataProp;
2866
+ const didDataChange = state.props.dataVersion !== dataVersion || state.props.data !== dataProp;
2868
2867
  if (didDataChange) {
2869
2868
  state.dataChangeNeedsScrollUpdate = true;
2870
2869
  }
@@ -2872,6 +2871,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2872
2871
  state.props = {
2873
2872
  alignItemsAtEnd,
2874
2873
  data: dataProp,
2874
+ dataVersion,
2875
2875
  enableAverages,
2876
2876
  estimatedItemSize,
2877
2877
  getEstimatedItemSize,
@@ -2911,7 +2911,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2911
2911
  { length: Math.min(numColumnsProp, dataProp.length) },
2912
2912
  (_, i) => getId(state, dataProp.length - 1 - i)
2913
2913
  );
2914
- }, [dataProp, numColumnsProp]);
2914
+ }, [dataProp, dataVersion, numColumnsProp]);
2915
2915
  const initializeStateVars = () => {
2916
2916
  set$(ctx, "lastItemKeys", memoizedLastItemKeys);
2917
2917
  set$(ctx, "numColumns", numColumnsProp);
@@ -2998,15 +2998,16 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2998
2998
  dataProp
2999
2999
  );
3000
3000
  }
3001
- }, [dataProp, numColumnsProp]);
3001
+ }, [dataProp, dataVersion, numColumnsProp]);
3002
3002
  React2.useLayoutEffect(() => {
3003
3003
  set$(ctx, "extraData", extraData);
3004
3004
  }, [extraData]);
3005
3005
  React2.useLayoutEffect(initializeStateVars, [
3006
+ dataVersion,
3006
3007
  memoizedLastItemKeys.join(","),
3007
3008
  numColumnsProp,
3008
- stylePaddingTopState,
3009
- stylePaddingBottomState
3009
+ stylePaddingBottomState,
3010
+ stylePaddingTopState
3010
3011
  ]);
3011
3012
  React2.useEffect(() => {
3012
3013
  const viewability = setupViewability({
package/index.mjs CHANGED
@@ -1029,7 +1029,7 @@ function prepareMVCP(ctx, state, dataChanged) {
1029
1029
  if (newPosition !== void 0) {
1030
1030
  const totalSize = peek$(ctx, "totalSize");
1031
1031
  let diff = newPosition - prevPosition;
1032
- if (state.scroll + state.scrollLength > totalSize) {
1032
+ if (diff !== 0 && state.scroll + state.scrollLength > totalSize) {
1033
1033
  if (diff > 0) {
1034
1034
  diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
1035
1035
  } else {
@@ -1675,12 +1675,11 @@ function scrollToIndex(ctx, state, { index, viewOffset = 0, animated = true, vie
1675
1675
  if (isLast && viewPosition === void 0) {
1676
1676
  viewPosition = 1;
1677
1677
  }
1678
- const firstIndexScrollPostion = firstIndexOffset - viewOffset;
1679
1678
  state.scrollForNextCalculateItemsInView = void 0;
1680
1679
  scrollTo(state, {
1681
1680
  animated,
1682
1681
  index,
1683
- offset: firstIndexScrollPostion,
1682
+ offset: firstIndexOffset,
1684
1683
  viewOffset,
1685
1684
  viewPosition: viewPosition != null ? viewPosition : 0
1686
1685
  });
@@ -2286,7 +2285,7 @@ function checkResetContainers(ctx, state, isFirst, dataProp) {
2286
2285
 
2287
2286
  // src/core/doInitialAllocateContainers.ts
2288
2287
  function doInitialAllocateContainers(ctx, state) {
2289
- var _a;
2288
+ var _a, _b, _c;
2290
2289
  const {
2291
2290
  scrollLength,
2292
2291
  props: {
@@ -2302,14 +2301,13 @@ function doInitialAllocateContainers(ctx, state) {
2302
2301
  const hasContainers = peek$(ctx, "numContainers");
2303
2302
  if (scrollLength > 0 && data.length > 0 && !hasContainers) {
2304
2303
  let averageItemSize;
2305
- const fn = getFixedItemSize || getEstimatedItemSize;
2306
- if (fn) {
2304
+ if (getFixedItemSize || getEstimatedItemSize) {
2307
2305
  let totalSize = 0;
2308
2306
  const num = Math.min(20, data.length);
2309
2307
  for (let i = 0; i < num; i++) {
2310
2308
  const item = data[i];
2311
2309
  const itemType = getItemType ? (_a = getItemType(item, i)) != null ? _a : "" : "";
2312
- totalSize += fn(i, item, itemType);
2310
+ totalSize += (_c = (_b = getFixedItemSize == null ? void 0 : getFixedItemSize(i, item, itemType)) != null ? _b : getEstimatedItemSize == null ? void 0 : getEstimatedItemSize(i, item, itemType)) != null ? _c : estimatedItemSize;
2313
2311
  }
2314
2312
  averageItemSize = totalSize / num;
2315
2313
  } else {
@@ -2325,10 +2323,10 @@ function doInitialAllocateContainers(ctx, state) {
2325
2323
  if (!IsNewArchitecture || state.lastLayout) {
2326
2324
  if (state.props.initialScroll) {
2327
2325
  requestAnimationFrame(() => {
2328
- calculateItemsInView(ctx, state, { dataChanged: true });
2326
+ calculateItemsInView(ctx, state, { dataChanged: true, doMVCP: true });
2329
2327
  });
2330
2328
  } else {
2331
- calculateItemsInView(ctx, state, { dataChanged: true });
2329
+ calculateItemsInView(ctx, state, { dataChanged: true, doMVCP: true });
2332
2330
  }
2333
2331
  }
2334
2332
  return true;
@@ -2571,7 +2569,7 @@ function updateOneItemSize(state, itemKey, sizeObj) {
2571
2569
  } = state;
2572
2570
  if (!data) return 0;
2573
2571
  const index = indexByKey.get(itemKey);
2574
- const prevSize = getItemSize(state, itemKey, index, data);
2572
+ const prevSize = getItemSize(state, itemKey, index, data[index]);
2575
2573
  const size = Math.floor((horizontal ? sizeObj.width : sizeObj.height) * 8) / 8;
2576
2574
  sizesKnown.set(itemKey, size);
2577
2575
  if (!getEstimatedItemSize && !getFixedItemSize && size > 0) {
@@ -2719,6 +2717,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2719
2717
  columnWrapperStyle,
2720
2718
  contentContainerStyle: contentContainerStyleProp,
2721
2719
  data: dataProp = [],
2720
+ dataVersion,
2722
2721
  drawDistance = 250,
2723
2722
  enableAverages = true,
2724
2723
  estimatedItemSize: estimatedItemSizeProp,
@@ -2843,7 +2842,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2843
2842
  }
2844
2843
  const state = refState.current;
2845
2844
  const isFirst = !state.props.renderItem;
2846
- const didDataChange = state.props.data !== dataProp;
2845
+ const didDataChange = state.props.dataVersion !== dataVersion || state.props.data !== dataProp;
2847
2846
  if (didDataChange) {
2848
2847
  state.dataChangeNeedsScrollUpdate = true;
2849
2848
  }
@@ -2851,6 +2850,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2851
2850
  state.props = {
2852
2851
  alignItemsAtEnd,
2853
2852
  data: dataProp,
2853
+ dataVersion,
2854
2854
  enableAverages,
2855
2855
  estimatedItemSize,
2856
2856
  getEstimatedItemSize,
@@ -2890,7 +2890,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2890
2890
  { length: Math.min(numColumnsProp, dataProp.length) },
2891
2891
  (_, i) => getId(state, dataProp.length - 1 - i)
2892
2892
  );
2893
- }, [dataProp, numColumnsProp]);
2893
+ }, [dataProp, dataVersion, numColumnsProp]);
2894
2894
  const initializeStateVars = () => {
2895
2895
  set$(ctx, "lastItemKeys", memoizedLastItemKeys);
2896
2896
  set$(ctx, "numColumns", numColumnsProp);
@@ -2977,15 +2977,16 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2977
2977
  dataProp
2978
2978
  );
2979
2979
  }
2980
- }, [dataProp, numColumnsProp]);
2980
+ }, [dataProp, dataVersion, numColumnsProp]);
2981
2981
  useLayoutEffect(() => {
2982
2982
  set$(ctx, "extraData", extraData);
2983
2983
  }, [extraData]);
2984
2984
  useLayoutEffect(initializeStateVars, [
2985
+ dataVersion,
2985
2986
  memoizedLastItemKeys.join(","),
2986
2987
  numColumnsProp,
2987
- stylePaddingTopState,
2988
- stylePaddingBottomState
2988
+ stylePaddingBottomState,
2989
+ stylePaddingTopState
2989
2990
  ]);
2990
2991
  useEffect(() => {
2991
2992
  const viewability = setupViewability({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
5
  "sideEffects": false,
6
6
  "private": false,