@legendapp/list 2.0.7 → 2.0.9

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,10 @@
1
+ ## 2.0.9
2
+ - Fix: Improve initialScrollIndex accuracy and reliability
3
+
4
+ ## 2.0.8
5
+ - Fix: Data changing sometimes left blank spaces because it was ignoring scroll
6
+ - Fix: Toggling between empty and non-empty causing maintainVisibleContentPosition issues
7
+
1
8
  ## 2.0.7
2
9
  - Fix: Layout not working on react-native-macos because of transform instead of position
3
10
 
package/index.d.mts CHANGED
@@ -5,6 +5,7 @@ import Animated$1 from 'react-native-reanimated';
5
5
 
6
6
  type ListenerType = "numContainers" | "numContainersPooled" | `containerItemKey${number}` | `containerItemData${number}` | `containerPosition${number}` | `containerColumn${number}` | `containerSticky${number}` | `containerStickyOffset${number}` | "containersDidLayout" | "extraData" | "numColumns" | "lastItemKeys" | "totalSize" | "alignItemsPaddingTop" | "stylePaddingTop" | "scrollAdjust" | "scrollAdjustUserOffset" | "headerSize" | "footerSize" | "maintainVisibleContentPosition" | "debugRawScroll" | "debugComputedScroll" | "otherAxisSize" | "snapToOffsets" | "scrollSize";
7
7
  interface StateContext {
8
+ internalState: InternalState | undefined;
8
9
  listeners: Map<ListenerType, Set<(value: any) => void>>;
9
10
  values: Map<ListenerType, any>;
10
11
  mapViewabilityCallbacks: Map<string, ViewabilityCallback>;
@@ -331,6 +332,7 @@ interface InternalState {
331
332
  minIndexSizeChanged: number | undefined;
332
333
  queuedInitialLayout?: boolean | undefined;
333
334
  queuedCalculateItemsInView: number | undefined;
335
+ dataChangeNeedsScrollUpdate: boolean;
334
336
  lastBatchingAction: number;
335
337
  ignoreScrollFromMVCP?: {
336
338
  lt?: number;
@@ -343,6 +345,7 @@ interface InternalState {
343
345
  viewOffset?: number;
344
346
  viewPosition?: number;
345
347
  animated?: boolean;
348
+ isInitialScroll?: boolean;
346
349
  } | undefined;
347
350
  needsOtherAxisSize?: boolean;
348
351
  averageSizes: Record<string, {
package/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import Animated$1 from 'react-native-reanimated';
5
5
 
6
6
  type ListenerType = "numContainers" | "numContainersPooled" | `containerItemKey${number}` | `containerItemData${number}` | `containerPosition${number}` | `containerColumn${number}` | `containerSticky${number}` | `containerStickyOffset${number}` | "containersDidLayout" | "extraData" | "numColumns" | "lastItemKeys" | "totalSize" | "alignItemsPaddingTop" | "stylePaddingTop" | "scrollAdjust" | "scrollAdjustUserOffset" | "headerSize" | "footerSize" | "maintainVisibleContentPosition" | "debugRawScroll" | "debugComputedScroll" | "otherAxisSize" | "snapToOffsets" | "scrollSize";
7
7
  interface StateContext {
8
+ internalState: InternalState | undefined;
8
9
  listeners: Map<ListenerType, Set<(value: any) => void>>;
9
10
  values: Map<ListenerType, any>;
10
11
  mapViewabilityCallbacks: Map<string, ViewabilityCallback>;
@@ -331,6 +332,7 @@ interface InternalState {
331
332
  minIndexSizeChanged: number | undefined;
332
333
  queuedInitialLayout?: boolean | undefined;
333
334
  queuedCalculateItemsInView: number | undefined;
335
+ dataChangeNeedsScrollUpdate: boolean;
334
336
  lastBatchingAction: number;
335
337
  ignoreScrollFromMVCP?: {
336
338
  lt?: number;
@@ -343,6 +345,7 @@ interface InternalState {
343
345
  viewOffset?: number;
344
346
  viewPosition?: number;
345
347
  animated?: boolean;
348
+ isInitialScroll?: boolean;
346
349
  } | undefined;
347
350
  needsOtherAxisSize?: boolean;
348
351
  averageSizes: Record<string, {
package/index.js CHANGED
@@ -30,6 +30,7 @@ function StateProvider({ children }) {
30
30
  const [value] = React2__namespace.useState(() => ({
31
31
  animatedScrollY: new reactNative.Animated.Value(0),
32
32
  columnWrapperStyle: void 0,
33
+ internalState: void 0,
33
34
  listeners: /* @__PURE__ */ new Map(),
34
35
  mapViewabilityAmountCallbacks: /* @__PURE__ */ new Map(),
35
36
  mapViewabilityAmountValues: /* @__PURE__ */ new Map(),
@@ -219,7 +220,9 @@ function useValue$(key, params) {
219
220
  prevValue = newValue;
220
221
  if (!didQueueTask) {
221
222
  didQueueTask = true;
222
- if (delayValue === 0) {
223
+ if (delayValue === void 0) {
224
+ fn();
225
+ } else if (delayValue === 0) {
223
226
  queueMicrotask(fn);
224
227
  } else {
225
228
  setTimeout(fn, delayValue);
@@ -606,7 +609,11 @@ var Containers = typedMemo(function Containers2({
606
609
  const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
607
610
  const animSize = useValue$("totalSize", {
608
611
  // Use a microtask if increasing the size significantly, otherwise use a timeout
609
- delay: (value, prevValue) => !prevValue || value - prevValue > 20 ? 0 : 200
612
+ // If this is the initial scroll, we don't want to delay because we want to update the size immediately
613
+ delay: (value, prevValue) => {
614
+ var _a;
615
+ return !((_a = ctx.internalState) == null ? void 0 : _a.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
616
+ }
610
617
  });
611
618
  const animOpacity = waitForInitialLayout && !IsNewArchitecture ? useValue$("containersDidLayout", { getValue: (value) => value ? 1 : 0 }) : void 0;
612
619
  const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
@@ -820,7 +827,7 @@ var ListComponent = typedMemo(function ListComponent2({
820
827
  ],
821
828
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
822
829
  horizontal,
823
- maintainVisibleContentPosition: maintainVisibleContentPosition && !ListEmptyComponent ? { minIndexForVisible: 0 } : void 0,
830
+ maintainVisibleContentPosition: maintainVisibleContentPosition ? { minIndexForVisible: 0 } : void 0,
824
831
  onLayout,
825
832
  onScroll: onScroll2,
826
833
  ref: refScrollView,
@@ -831,7 +838,7 @@ var ListComponent = typedMemo(function ListComponent2({
831
838
  ENABLE_DEVMODE ? /* @__PURE__ */ React2__namespace.createElement(PaddingDevMode, null) : /* @__PURE__ */ React2__namespace.createElement(Padding, null),
832
839
  ListHeaderComponent && /* @__PURE__ */ React2__namespace.createElement(reactNative.View, { onLayout: onLayoutHeaderSync, ref: refHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
833
840
  ListEmptyComponent && getComponent(ListEmptyComponent),
834
- canRender && /* @__PURE__ */ React2__namespace.createElement(
841
+ canRender && !ListEmptyComponent && /* @__PURE__ */ React2__namespace.createElement(
835
842
  Containers,
836
843
  {
837
844
  getRenderedItem: getRenderedItem2,
@@ -967,7 +974,7 @@ var finishScrollTo = (state) => {
967
974
  // src/core/scrollTo.ts
968
975
  function scrollTo(state, params = {}) {
969
976
  var _a;
970
- const { animated, noScrollingTo } = params;
977
+ const { animated, noScrollingTo, isInitialScroll } = params;
971
978
  const {
972
979
  refScroller,
973
980
  props: { horizontal }
@@ -978,14 +985,21 @@ function scrollTo(state, params = {}) {
978
985
  state.scrollingTo = params;
979
986
  }
980
987
  state.scrollPending = offset;
981
- (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
982
- animated: !!animated,
983
- x: horizontal ? offset : 0,
984
- y: horizontal ? 0 : offset
985
- });
988
+ if (!params.isInitialScroll || reactNative.Platform.OS === "android") {
989
+ (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
990
+ animated: !!animated,
991
+ x: horizontal ? offset : 0,
992
+ y: horizontal ? 0 : offset
993
+ });
994
+ }
986
995
  if (!animated) {
987
996
  state.scroll = offset;
988
997
  setTimeout(() => finishScrollTo(state), 100);
998
+ if (isInitialScroll) {
999
+ setTimeout(() => {
1000
+ state.initialScroll = void 0;
1001
+ }, 500);
1002
+ }
989
1003
  }
990
1004
  }
991
1005
 
@@ -1080,7 +1094,16 @@ function prepareMVCP(ctx, state, dataChanged) {
1080
1094
  if (targetId !== void 0 && prevPosition !== void 0) {
1081
1095
  const newPosition = positions.get(targetId);
1082
1096
  if (newPosition !== void 0) {
1083
- positionDiff = newPosition - prevPosition;
1097
+ const totalSize = peek$(ctx, "totalSize");
1098
+ let diff = newPosition - prevPosition;
1099
+ if (state.scroll + state.scrollLength > totalSize) {
1100
+ if (diff > 0) {
1101
+ diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
1102
+ } else {
1103
+ diff = 0;
1104
+ }
1105
+ }
1106
+ positionDiff = diff;
1084
1107
  }
1085
1108
  }
1086
1109
  if (positionDiff !== void 0 && Math.abs(positionDiff) > 0.1) {
@@ -1148,6 +1171,7 @@ function updateTotalSize(ctx, state) {
1148
1171
  }
1149
1172
  function addTotalSize(ctx, state, key, add) {
1150
1173
  const { alignItemsAtEnd } = state.props;
1174
+ const prevTotalSize = state.totalSize;
1151
1175
  if (key === null) {
1152
1176
  state.totalSize = add;
1153
1177
  if (state.timeoutSetPaddingTop) {
@@ -1157,9 +1181,11 @@ function addTotalSize(ctx, state, key, add) {
1157
1181
  } else {
1158
1182
  state.totalSize += add;
1159
1183
  }
1160
- set$(ctx, "totalSize", state.totalSize);
1161
- if (alignItemsAtEnd) {
1162
- updateAlignItemsPaddingTop(ctx, state);
1184
+ if (prevTotalSize !== state.totalSize) {
1185
+ set$(ctx, "totalSize", state.totalSize);
1186
+ if (alignItemsAtEnd) {
1187
+ updateAlignItemsPaddingTop(ctx, state);
1188
+ }
1163
1189
  }
1164
1190
  }
1165
1191
 
@@ -1721,17 +1747,16 @@ function setDidLayout(ctx, state) {
1721
1747
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1722
1748
  }
1723
1749
  };
1724
- if (reactNative.Platform.OS === "android" || !IsNewArchitecture) {
1725
- if (initialScroll) {
1726
- queueMicrotask(() => {
1750
+ if (reactNative.Platform.OS === "android" && initialScroll) {
1751
+ if (IsNewArchitecture) {
1752
+ scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1753
+ requestAnimationFrame(() => {
1727
1754
  scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1728
- requestAnimationFrame(() => {
1729
- scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1730
- setIt();
1731
- });
1755
+ setIt();
1732
1756
  });
1733
1757
  } else {
1734
- queueMicrotask(setIt);
1758
+ scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1759
+ setIt();
1735
1760
  }
1736
1761
  } else {
1737
1762
  setIt();
@@ -1865,7 +1890,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1865
1890
  const scrollTopBuffered = scroll - scrollBufferTop;
1866
1891
  const scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
1867
1892
  const scrollBottomBuffered = scrollBottom + scrollBufferBottom;
1868
- if (scrollForNextCalculateItemsInView) {
1893
+ if (!dataChanged && scrollForNextCalculateItemsInView) {
1869
1894
  const { top, bottom } = scrollForNextCalculateItemsInView;
1870
1895
  if (scrollTopBuffered > top && scrollBottomBuffered < bottom) {
1871
1896
  return;
@@ -1888,7 +1913,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1888
1913
  let startBufferedId = null;
1889
1914
  let endNoBuffer = null;
1890
1915
  let endBuffered = null;
1891
- let loopStart = startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
1916
+ let loopStart = !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
1892
1917
  for (let i = loopStart; i >= 0; i--) {
1893
1918
  const id = (_b = idCache.get(i)) != null ? _b : getId(state, i);
1894
1919
  const top = positions.get(id);
@@ -2367,10 +2392,11 @@ function updateScroll(ctx, state, newScroll) {
2367
2392
  state.scrollPrevTime = state.scrollTime;
2368
2393
  state.scroll = newScroll;
2369
2394
  state.scrollTime = currentTime;
2370
- if (Math.abs(state.scroll - state.scrollPrev) > 2) {
2395
+ if (state.dataChangeNeedsScrollUpdate || Math.abs(state.scroll - state.scrollPrev) > 2) {
2371
2396
  calculateItemsInView(ctx, state);
2372
2397
  checkAtBottom(ctx, state);
2373
2398
  checkAtTop(state);
2399
+ state.dataChangeNeedsScrollUpdate = false;
2374
2400
  }
2375
2401
  }
2376
2402
 
@@ -2382,7 +2408,7 @@ var ScrollAdjustHandler = class {
2382
2408
  this.context = ctx;
2383
2409
  }
2384
2410
  requestAdjust(add) {
2385
- const oldAdjustTop = peek$(this.context, "scrollAdjust") || 0;
2411
+ const oldAdjustTop = this.appliedAdjust;
2386
2412
  this.appliedAdjust = add + oldAdjustTop;
2387
2413
  const set = () => set$(this.context, "scrollAdjust", this.appliedAdjust);
2388
2414
  if (this.mounted) {
@@ -2401,7 +2427,7 @@ var ScrollAdjustHandler = class {
2401
2427
 
2402
2428
  // src/core/updateItemSize.ts
2403
2429
  function updateItemSize(ctx, state, itemKey, sizeObj) {
2404
- var _a, _b;
2430
+ var _a;
2405
2431
  const {
2406
2432
  sizesKnown,
2407
2433
  props: {
@@ -2416,17 +2442,17 @@ function updateItemSize(ctx, state, itemKey, sizeObj) {
2416
2442
  }
2417
2443
  } = state;
2418
2444
  if (!data) return;
2445
+ const index = state.indexByKey.get(itemKey);
2419
2446
  if (getFixedItemSize) {
2420
- const index2 = state.indexByKey.get(itemKey);
2421
- if (index2 === void 0) {
2447
+ if (index === void 0) {
2422
2448
  return;
2423
2449
  }
2424
- const itemData = state.props.data[index2];
2450
+ const itemData = state.props.data[index];
2425
2451
  if (itemData === void 0) {
2426
2452
  return;
2427
2453
  }
2428
- const type = getItemType ? (_a = getItemType(itemData, index2)) != null ? _a : "" : "";
2429
- const size2 = getFixedItemSize(index2, itemData, type);
2454
+ const type = getItemType ? (_a = getItemType(itemData, index)) != null ? _a : "" : "";
2455
+ const size2 = getFixedItemSize(index, itemData, type);
2430
2456
  if (size2 !== void 0 && size2 === sizesKnown.get(itemKey)) {
2431
2457
  return;
2432
2458
  }
@@ -2436,15 +2462,11 @@ function updateItemSize(ctx, state, itemKey, sizeObj) {
2436
2462
  let shouldMaintainScrollAtEnd = false;
2437
2463
  let minIndexSizeChanged;
2438
2464
  let maxOtherAxisSize = peek$(ctx, "otherAxisSize") || 0;
2439
- const index = state.indexByKey.get(itemKey);
2440
2465
  const prevSizeKnown = state.sizesKnown.get(itemKey);
2441
2466
  const diff = updateOneItemSize(state, itemKey, sizeObj);
2442
2467
  const size = Math.floor((horizontal ? sizeObj.width : sizeObj.height) * 8) / 8;
2443
2468
  if (diff !== 0) {
2444
2469
  minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
2445
- if (((_b = state.scrollingTo) == null ? void 0 : _b.viewPosition) && maintainVisibleContentPosition && index === state.scrollingTo.index && diff > 0) {
2446
- requestAdjust(ctx, state, diff * state.scrollingTo.viewPosition);
2447
- }
2448
2470
  const { startBuffered, endBuffered } = state;
2449
2471
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
2450
2472
  if (!needsRecalculate) {
@@ -2684,63 +2706,70 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2684
2706
  const keyExtractor = keyExtractorProp != null ? keyExtractorProp : (_item, index) => index.toString();
2685
2707
  const refState = React2.useRef();
2686
2708
  if (!refState.current) {
2687
- const initialScrollLength = (estimatedListSize != null ? estimatedListSize : IsNewArchitecture ? { height: 0, width: 0 } : reactNative.Dimensions.get("window"))[horizontal ? "width" : "height"];
2688
- refState.current = {
2689
- activeStickyIndex: void 0,
2690
- averageSizes: {},
2691
- columns: /* @__PURE__ */ new Map(),
2692
- containerItemKeys: /* @__PURE__ */ new Set(),
2693
- containerItemTypes: /* @__PURE__ */ new Map(),
2694
- enableScrollForNextCalculateItemsInView: true,
2695
- endBuffered: -1,
2696
- endNoBuffer: -1,
2697
- endReachedBlockedByTimer: false,
2698
- firstFullyOnScreenIndex: -1,
2699
- idCache: /* @__PURE__ */ new Map(),
2700
- idsInView: [],
2701
- indexByKey: /* @__PURE__ */ new Map(),
2702
- initialScroll,
2703
- isAtEnd: false,
2704
- isAtStart: false,
2705
- isEndReached: false,
2706
- isStartReached: false,
2707
- lastBatchingAction: Date.now(),
2708
- lastLayout: void 0,
2709
- loadStartTime: Date.now(),
2710
- minIndexSizeChanged: 0,
2711
- nativeMarginTop: 0,
2712
- positions: /* @__PURE__ */ new Map(),
2713
- props: {},
2714
- queuedCalculateItemsInView: 0,
2715
- refScroller: void 0,
2716
- scroll: 0,
2717
- scrollAdjustHandler: new ScrollAdjustHandler(ctx),
2718
- scrollForNextCalculateItemsInView: void 0,
2719
- scrollHistory: [],
2720
- scrollLength: initialScrollLength,
2721
- scrollPending: 0,
2722
- scrollPrev: 0,
2723
- scrollPrevTime: 0,
2724
- scrollProcessingEnabled: true,
2725
- scrollTime: 0,
2726
- sizes: /* @__PURE__ */ new Map(),
2727
- sizesKnown: /* @__PURE__ */ new Map(),
2728
- startBuffered: -1,
2729
- startNoBuffer: -1,
2730
- startReachedBlockedByTimer: false,
2731
- stickyContainerPool: /* @__PURE__ */ new Set(),
2732
- stickyContainers: /* @__PURE__ */ new Map(),
2733
- timeoutSizeMessage: 0,
2734
- timeouts: /* @__PURE__ */ new Set(),
2735
- totalSize: 0,
2736
- viewabilityConfigCallbackPairs: void 0
2737
- };
2738
- set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPosition);
2739
- set$(ctx, "extraData", extraData);
2709
+ if (!ctx.internalState) {
2710
+ const initialScrollLength = (estimatedListSize != null ? estimatedListSize : IsNewArchitecture ? { height: 0, width: 0 } : reactNative.Dimensions.get("window"))[horizontal ? "width" : "height"];
2711
+ ctx.internalState = {
2712
+ activeStickyIndex: void 0,
2713
+ averageSizes: {},
2714
+ columns: /* @__PURE__ */ new Map(),
2715
+ containerItemKeys: /* @__PURE__ */ new Set(),
2716
+ containerItemTypes: /* @__PURE__ */ new Map(),
2717
+ dataChangeNeedsScrollUpdate: false,
2718
+ enableScrollForNextCalculateItemsInView: true,
2719
+ endBuffered: -1,
2720
+ endNoBuffer: -1,
2721
+ endReachedBlockedByTimer: false,
2722
+ firstFullyOnScreenIndex: -1,
2723
+ idCache: /* @__PURE__ */ new Map(),
2724
+ idsInView: [],
2725
+ indexByKey: /* @__PURE__ */ new Map(),
2726
+ initialScroll,
2727
+ isAtEnd: false,
2728
+ isAtStart: false,
2729
+ isEndReached: false,
2730
+ isStartReached: false,
2731
+ lastBatchingAction: Date.now(),
2732
+ lastLayout: void 0,
2733
+ loadStartTime: Date.now(),
2734
+ minIndexSizeChanged: 0,
2735
+ nativeMarginTop: 0,
2736
+ positions: /* @__PURE__ */ new Map(),
2737
+ props: {},
2738
+ queuedCalculateItemsInView: 0,
2739
+ refScroller: void 0,
2740
+ scroll: 0,
2741
+ scrollAdjustHandler: new ScrollAdjustHandler(ctx),
2742
+ scrollForNextCalculateItemsInView: void 0,
2743
+ scrollHistory: [],
2744
+ scrollLength: initialScrollLength,
2745
+ scrollPending: 0,
2746
+ scrollPrev: 0,
2747
+ scrollPrevTime: 0,
2748
+ scrollProcessingEnabled: true,
2749
+ scrollTime: 0,
2750
+ sizes: /* @__PURE__ */ new Map(),
2751
+ sizesKnown: /* @__PURE__ */ new Map(),
2752
+ startBuffered: -1,
2753
+ startNoBuffer: -1,
2754
+ startReachedBlockedByTimer: false,
2755
+ stickyContainerPool: /* @__PURE__ */ new Set(),
2756
+ stickyContainers: /* @__PURE__ */ new Map(),
2757
+ timeoutSizeMessage: 0,
2758
+ timeouts: /* @__PURE__ */ new Set(),
2759
+ totalSize: 0,
2760
+ viewabilityConfigCallbackPairs: void 0
2761
+ };
2762
+ set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPosition);
2763
+ set$(ctx, "extraData", extraData);
2764
+ }
2765
+ refState.current = ctx.internalState;
2740
2766
  }
2741
2767
  const state = refState.current;
2742
2768
  const isFirst = !state.props.renderItem;
2743
2769
  const didDataChange = state.props.data !== dataProp;
2770
+ if (didDataChange) {
2771
+ state.dataChangeNeedsScrollUpdate = true;
2772
+ }
2744
2773
  const throttleScrollFn = scrollEventThrottle && onScrollProp ? useThrottledOnScroll(onScrollProp, scrollEventThrottle) : onScrollProp;
2745
2774
  state.props = {
2746
2775
  alignItemsAtEnd,
@@ -2816,7 +2845,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2816
2845
  }
2817
2846
  refState.current.isStartReached = initialContentOffset2 < refState.current.scrollLength * onStartReachedThreshold;
2818
2847
  if (initialContentOffset2 > 0) {
2819
- scrollTo(state, { animated: false, index, offset: initialContentOffset2 });
2848
+ scrollTo(state, {
2849
+ animated: false,
2850
+ index,
2851
+ isInitialScroll: true,
2852
+ offset: initialContentOffset2,
2853
+ viewPosition: index === dataProp.length - 1 ? 1 : 0
2854
+ });
2820
2855
  }
2821
2856
  return initialContentOffset2;
2822
2857
  }
package/index.mjs CHANGED
@@ -9,6 +9,7 @@ function StateProvider({ children }) {
9
9
  const [value] = React2.useState(() => ({
10
10
  animatedScrollY: new Animated.Value(0),
11
11
  columnWrapperStyle: void 0,
12
+ internalState: void 0,
12
13
  listeners: /* @__PURE__ */ new Map(),
13
14
  mapViewabilityAmountCallbacks: /* @__PURE__ */ new Map(),
14
15
  mapViewabilityAmountValues: /* @__PURE__ */ new Map(),
@@ -198,7 +199,9 @@ function useValue$(key, params) {
198
199
  prevValue = newValue;
199
200
  if (!didQueueTask) {
200
201
  didQueueTask = true;
201
- if (delayValue === 0) {
202
+ if (delayValue === void 0) {
203
+ fn();
204
+ } else if (delayValue === 0) {
202
205
  queueMicrotask(fn);
203
206
  } else {
204
207
  setTimeout(fn, delayValue);
@@ -585,7 +588,11 @@ var Containers = typedMemo(function Containers2({
585
588
  const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
586
589
  const animSize = useValue$("totalSize", {
587
590
  // Use a microtask if increasing the size significantly, otherwise use a timeout
588
- delay: (value, prevValue) => !prevValue || value - prevValue > 20 ? 0 : 200
591
+ // If this is the initial scroll, we don't want to delay because we want to update the size immediately
592
+ delay: (value, prevValue) => {
593
+ var _a;
594
+ return !((_a = ctx.internalState) == null ? void 0 : _a.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
595
+ }
589
596
  });
590
597
  const animOpacity = waitForInitialLayout && !IsNewArchitecture ? useValue$("containersDidLayout", { getValue: (value) => value ? 1 : 0 }) : void 0;
591
598
  const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
@@ -799,7 +806,7 @@ var ListComponent = typedMemo(function ListComponent2({
799
806
  ],
800
807
  contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
801
808
  horizontal,
802
- maintainVisibleContentPosition: maintainVisibleContentPosition && !ListEmptyComponent ? { minIndexForVisible: 0 } : void 0,
809
+ maintainVisibleContentPosition: maintainVisibleContentPosition ? { minIndexForVisible: 0 } : void 0,
803
810
  onLayout,
804
811
  onScroll: onScroll2,
805
812
  ref: refScrollView,
@@ -810,7 +817,7 @@ var ListComponent = typedMemo(function ListComponent2({
810
817
  ENABLE_DEVMODE ? /* @__PURE__ */ React2.createElement(PaddingDevMode, null) : /* @__PURE__ */ React2.createElement(Padding, null),
811
818
  ListHeaderComponent && /* @__PURE__ */ React2.createElement(View, { onLayout: onLayoutHeaderSync, ref: refHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
812
819
  ListEmptyComponent && getComponent(ListEmptyComponent),
813
- canRender && /* @__PURE__ */ React2.createElement(
820
+ canRender && !ListEmptyComponent && /* @__PURE__ */ React2.createElement(
814
821
  Containers,
815
822
  {
816
823
  getRenderedItem: getRenderedItem2,
@@ -946,7 +953,7 @@ var finishScrollTo = (state) => {
946
953
  // src/core/scrollTo.ts
947
954
  function scrollTo(state, params = {}) {
948
955
  var _a;
949
- const { animated, noScrollingTo } = params;
956
+ const { animated, noScrollingTo, isInitialScroll } = params;
950
957
  const {
951
958
  refScroller,
952
959
  props: { horizontal }
@@ -957,14 +964,21 @@ function scrollTo(state, params = {}) {
957
964
  state.scrollingTo = params;
958
965
  }
959
966
  state.scrollPending = offset;
960
- (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
961
- animated: !!animated,
962
- x: horizontal ? offset : 0,
963
- y: horizontal ? 0 : offset
964
- });
967
+ if (!params.isInitialScroll || Platform.OS === "android") {
968
+ (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
969
+ animated: !!animated,
970
+ x: horizontal ? offset : 0,
971
+ y: horizontal ? 0 : offset
972
+ });
973
+ }
965
974
  if (!animated) {
966
975
  state.scroll = offset;
967
976
  setTimeout(() => finishScrollTo(state), 100);
977
+ if (isInitialScroll) {
978
+ setTimeout(() => {
979
+ state.initialScroll = void 0;
980
+ }, 500);
981
+ }
968
982
  }
969
983
  }
970
984
 
@@ -1059,7 +1073,16 @@ function prepareMVCP(ctx, state, dataChanged) {
1059
1073
  if (targetId !== void 0 && prevPosition !== void 0) {
1060
1074
  const newPosition = positions.get(targetId);
1061
1075
  if (newPosition !== void 0) {
1062
- positionDiff = newPosition - prevPosition;
1076
+ const totalSize = peek$(ctx, "totalSize");
1077
+ let diff = newPosition - prevPosition;
1078
+ if (state.scroll + state.scrollLength > totalSize) {
1079
+ if (diff > 0) {
1080
+ diff = Math.max(0, totalSize - state.scroll - state.scrollLength);
1081
+ } else {
1082
+ diff = 0;
1083
+ }
1084
+ }
1085
+ positionDiff = diff;
1063
1086
  }
1064
1087
  }
1065
1088
  if (positionDiff !== void 0 && Math.abs(positionDiff) > 0.1) {
@@ -1127,6 +1150,7 @@ function updateTotalSize(ctx, state) {
1127
1150
  }
1128
1151
  function addTotalSize(ctx, state, key, add) {
1129
1152
  const { alignItemsAtEnd } = state.props;
1153
+ const prevTotalSize = state.totalSize;
1130
1154
  if (key === null) {
1131
1155
  state.totalSize = add;
1132
1156
  if (state.timeoutSetPaddingTop) {
@@ -1136,9 +1160,11 @@ function addTotalSize(ctx, state, key, add) {
1136
1160
  } else {
1137
1161
  state.totalSize += add;
1138
1162
  }
1139
- set$(ctx, "totalSize", state.totalSize);
1140
- if (alignItemsAtEnd) {
1141
- updateAlignItemsPaddingTop(ctx, state);
1163
+ if (prevTotalSize !== state.totalSize) {
1164
+ set$(ctx, "totalSize", state.totalSize);
1165
+ if (alignItemsAtEnd) {
1166
+ updateAlignItemsPaddingTop(ctx, state);
1167
+ }
1142
1168
  }
1143
1169
  }
1144
1170
 
@@ -1700,17 +1726,16 @@ function setDidLayout(ctx, state) {
1700
1726
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
1701
1727
  }
1702
1728
  };
1703
- if (Platform.OS === "android" || !IsNewArchitecture) {
1704
- if (initialScroll) {
1705
- queueMicrotask(() => {
1729
+ if (Platform.OS === "android" && initialScroll) {
1730
+ if (IsNewArchitecture) {
1731
+ scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1732
+ requestAnimationFrame(() => {
1706
1733
  scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1707
- requestAnimationFrame(() => {
1708
- scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1709
- setIt();
1710
- });
1734
+ setIt();
1711
1735
  });
1712
1736
  } else {
1713
- queueMicrotask(setIt);
1737
+ scrollToIndex(ctx, state, { ...initialScroll, animated: false });
1738
+ setIt();
1714
1739
  }
1715
1740
  } else {
1716
1741
  setIt();
@@ -1844,7 +1869,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1844
1869
  const scrollTopBuffered = scroll - scrollBufferTop;
1845
1870
  const scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
1846
1871
  const scrollBottomBuffered = scrollBottom + scrollBufferBottom;
1847
- if (scrollForNextCalculateItemsInView) {
1872
+ if (!dataChanged && scrollForNextCalculateItemsInView) {
1848
1873
  const { top, bottom } = scrollForNextCalculateItemsInView;
1849
1874
  if (scrollTopBuffered > top && scrollBottomBuffered < bottom) {
1850
1875
  return;
@@ -1867,7 +1892,7 @@ function calculateItemsInView(ctx, state, params = {}) {
1867
1892
  let startBufferedId = null;
1868
1893
  let endNoBuffer = null;
1869
1894
  let endBuffered = null;
1870
- let loopStart = startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
1895
+ let loopStart = !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
1871
1896
  for (let i = loopStart; i >= 0; i--) {
1872
1897
  const id = (_b = idCache.get(i)) != null ? _b : getId(state, i);
1873
1898
  const top = positions.get(id);
@@ -2346,10 +2371,11 @@ function updateScroll(ctx, state, newScroll) {
2346
2371
  state.scrollPrevTime = state.scrollTime;
2347
2372
  state.scroll = newScroll;
2348
2373
  state.scrollTime = currentTime;
2349
- if (Math.abs(state.scroll - state.scrollPrev) > 2) {
2374
+ if (state.dataChangeNeedsScrollUpdate || Math.abs(state.scroll - state.scrollPrev) > 2) {
2350
2375
  calculateItemsInView(ctx, state);
2351
2376
  checkAtBottom(ctx, state);
2352
2377
  checkAtTop(state);
2378
+ state.dataChangeNeedsScrollUpdate = false;
2353
2379
  }
2354
2380
  }
2355
2381
 
@@ -2361,7 +2387,7 @@ var ScrollAdjustHandler = class {
2361
2387
  this.context = ctx;
2362
2388
  }
2363
2389
  requestAdjust(add) {
2364
- const oldAdjustTop = peek$(this.context, "scrollAdjust") || 0;
2390
+ const oldAdjustTop = this.appliedAdjust;
2365
2391
  this.appliedAdjust = add + oldAdjustTop;
2366
2392
  const set = () => set$(this.context, "scrollAdjust", this.appliedAdjust);
2367
2393
  if (this.mounted) {
@@ -2380,7 +2406,7 @@ var ScrollAdjustHandler = class {
2380
2406
 
2381
2407
  // src/core/updateItemSize.ts
2382
2408
  function updateItemSize(ctx, state, itemKey, sizeObj) {
2383
- var _a, _b;
2409
+ var _a;
2384
2410
  const {
2385
2411
  sizesKnown,
2386
2412
  props: {
@@ -2395,17 +2421,17 @@ function updateItemSize(ctx, state, itemKey, sizeObj) {
2395
2421
  }
2396
2422
  } = state;
2397
2423
  if (!data) return;
2424
+ const index = state.indexByKey.get(itemKey);
2398
2425
  if (getFixedItemSize) {
2399
- const index2 = state.indexByKey.get(itemKey);
2400
- if (index2 === void 0) {
2426
+ if (index === void 0) {
2401
2427
  return;
2402
2428
  }
2403
- const itemData = state.props.data[index2];
2429
+ const itemData = state.props.data[index];
2404
2430
  if (itemData === void 0) {
2405
2431
  return;
2406
2432
  }
2407
- const type = getItemType ? (_a = getItemType(itemData, index2)) != null ? _a : "" : "";
2408
- const size2 = getFixedItemSize(index2, itemData, type);
2433
+ const type = getItemType ? (_a = getItemType(itemData, index)) != null ? _a : "" : "";
2434
+ const size2 = getFixedItemSize(index, itemData, type);
2409
2435
  if (size2 !== void 0 && size2 === sizesKnown.get(itemKey)) {
2410
2436
  return;
2411
2437
  }
@@ -2415,15 +2441,11 @@ function updateItemSize(ctx, state, itemKey, sizeObj) {
2415
2441
  let shouldMaintainScrollAtEnd = false;
2416
2442
  let minIndexSizeChanged;
2417
2443
  let maxOtherAxisSize = peek$(ctx, "otherAxisSize") || 0;
2418
- const index = state.indexByKey.get(itemKey);
2419
2444
  const prevSizeKnown = state.sizesKnown.get(itemKey);
2420
2445
  const diff = updateOneItemSize(state, itemKey, sizeObj);
2421
2446
  const size = Math.floor((horizontal ? sizeObj.width : sizeObj.height) * 8) / 8;
2422
2447
  if (diff !== 0) {
2423
2448
  minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
2424
- if (((_b = state.scrollingTo) == null ? void 0 : _b.viewPosition) && maintainVisibleContentPosition && index === state.scrollingTo.index && diff > 0) {
2425
- requestAdjust(ctx, state, diff * state.scrollingTo.viewPosition);
2426
- }
2427
2449
  const { startBuffered, endBuffered } = state;
2428
2450
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
2429
2451
  if (!needsRecalculate) {
@@ -2663,63 +2685,70 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2663
2685
  const keyExtractor = keyExtractorProp != null ? keyExtractorProp : (_item, index) => index.toString();
2664
2686
  const refState = useRef();
2665
2687
  if (!refState.current) {
2666
- const initialScrollLength = (estimatedListSize != null ? estimatedListSize : IsNewArchitecture ? { height: 0, width: 0 } : Dimensions.get("window"))[horizontal ? "width" : "height"];
2667
- refState.current = {
2668
- activeStickyIndex: void 0,
2669
- averageSizes: {},
2670
- columns: /* @__PURE__ */ new Map(),
2671
- containerItemKeys: /* @__PURE__ */ new Set(),
2672
- containerItemTypes: /* @__PURE__ */ new Map(),
2673
- enableScrollForNextCalculateItemsInView: true,
2674
- endBuffered: -1,
2675
- endNoBuffer: -1,
2676
- endReachedBlockedByTimer: false,
2677
- firstFullyOnScreenIndex: -1,
2678
- idCache: /* @__PURE__ */ new Map(),
2679
- idsInView: [],
2680
- indexByKey: /* @__PURE__ */ new Map(),
2681
- initialScroll,
2682
- isAtEnd: false,
2683
- isAtStart: false,
2684
- isEndReached: false,
2685
- isStartReached: false,
2686
- lastBatchingAction: Date.now(),
2687
- lastLayout: void 0,
2688
- loadStartTime: Date.now(),
2689
- minIndexSizeChanged: 0,
2690
- nativeMarginTop: 0,
2691
- positions: /* @__PURE__ */ new Map(),
2692
- props: {},
2693
- queuedCalculateItemsInView: 0,
2694
- refScroller: void 0,
2695
- scroll: 0,
2696
- scrollAdjustHandler: new ScrollAdjustHandler(ctx),
2697
- scrollForNextCalculateItemsInView: void 0,
2698
- scrollHistory: [],
2699
- scrollLength: initialScrollLength,
2700
- scrollPending: 0,
2701
- scrollPrev: 0,
2702
- scrollPrevTime: 0,
2703
- scrollProcessingEnabled: true,
2704
- scrollTime: 0,
2705
- sizes: /* @__PURE__ */ new Map(),
2706
- sizesKnown: /* @__PURE__ */ new Map(),
2707
- startBuffered: -1,
2708
- startNoBuffer: -1,
2709
- startReachedBlockedByTimer: false,
2710
- stickyContainerPool: /* @__PURE__ */ new Set(),
2711
- stickyContainers: /* @__PURE__ */ new Map(),
2712
- timeoutSizeMessage: 0,
2713
- timeouts: /* @__PURE__ */ new Set(),
2714
- totalSize: 0,
2715
- viewabilityConfigCallbackPairs: void 0
2716
- };
2717
- set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPosition);
2718
- set$(ctx, "extraData", extraData);
2688
+ if (!ctx.internalState) {
2689
+ const initialScrollLength = (estimatedListSize != null ? estimatedListSize : IsNewArchitecture ? { height: 0, width: 0 } : Dimensions.get("window"))[horizontal ? "width" : "height"];
2690
+ ctx.internalState = {
2691
+ activeStickyIndex: void 0,
2692
+ averageSizes: {},
2693
+ columns: /* @__PURE__ */ new Map(),
2694
+ containerItemKeys: /* @__PURE__ */ new Set(),
2695
+ containerItemTypes: /* @__PURE__ */ new Map(),
2696
+ dataChangeNeedsScrollUpdate: false,
2697
+ enableScrollForNextCalculateItemsInView: true,
2698
+ endBuffered: -1,
2699
+ endNoBuffer: -1,
2700
+ endReachedBlockedByTimer: false,
2701
+ firstFullyOnScreenIndex: -1,
2702
+ idCache: /* @__PURE__ */ new Map(),
2703
+ idsInView: [],
2704
+ indexByKey: /* @__PURE__ */ new Map(),
2705
+ initialScroll,
2706
+ isAtEnd: false,
2707
+ isAtStart: false,
2708
+ isEndReached: false,
2709
+ isStartReached: false,
2710
+ lastBatchingAction: Date.now(),
2711
+ lastLayout: void 0,
2712
+ loadStartTime: Date.now(),
2713
+ minIndexSizeChanged: 0,
2714
+ nativeMarginTop: 0,
2715
+ positions: /* @__PURE__ */ new Map(),
2716
+ props: {},
2717
+ queuedCalculateItemsInView: 0,
2718
+ refScroller: void 0,
2719
+ scroll: 0,
2720
+ scrollAdjustHandler: new ScrollAdjustHandler(ctx),
2721
+ scrollForNextCalculateItemsInView: void 0,
2722
+ scrollHistory: [],
2723
+ scrollLength: initialScrollLength,
2724
+ scrollPending: 0,
2725
+ scrollPrev: 0,
2726
+ scrollPrevTime: 0,
2727
+ scrollProcessingEnabled: true,
2728
+ scrollTime: 0,
2729
+ sizes: /* @__PURE__ */ new Map(),
2730
+ sizesKnown: /* @__PURE__ */ new Map(),
2731
+ startBuffered: -1,
2732
+ startNoBuffer: -1,
2733
+ startReachedBlockedByTimer: false,
2734
+ stickyContainerPool: /* @__PURE__ */ new Set(),
2735
+ stickyContainers: /* @__PURE__ */ new Map(),
2736
+ timeoutSizeMessage: 0,
2737
+ timeouts: /* @__PURE__ */ new Set(),
2738
+ totalSize: 0,
2739
+ viewabilityConfigCallbackPairs: void 0
2740
+ };
2741
+ set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPosition);
2742
+ set$(ctx, "extraData", extraData);
2743
+ }
2744
+ refState.current = ctx.internalState;
2719
2745
  }
2720
2746
  const state = refState.current;
2721
2747
  const isFirst = !state.props.renderItem;
2722
2748
  const didDataChange = state.props.data !== dataProp;
2749
+ if (didDataChange) {
2750
+ state.dataChangeNeedsScrollUpdate = true;
2751
+ }
2723
2752
  const throttleScrollFn = scrollEventThrottle && onScrollProp ? useThrottledOnScroll(onScrollProp, scrollEventThrottle) : onScrollProp;
2724
2753
  state.props = {
2725
2754
  alignItemsAtEnd,
@@ -2795,7 +2824,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2795
2824
  }
2796
2825
  refState.current.isStartReached = initialContentOffset2 < refState.current.scrollLength * onStartReachedThreshold;
2797
2826
  if (initialContentOffset2 > 0) {
2798
- scrollTo(state, { animated: false, index, offset: initialContentOffset2 });
2827
+ scrollTo(state, {
2828
+ animated: false,
2829
+ index,
2830
+ isInitialScroll: true,
2831
+ offset: initialContentOffset2,
2832
+ viewPosition: index === dataProp.length - 1 ? 1 : 0
2833
+ });
2799
2834
  }
2800
2835
  return initialContentOffset2;
2801
2836
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
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,