@legendapp/list 3.5.0 → 3.6.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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 3.6.0
2
+
3
+ - Feat: Add `viewPositionFallback` to align oversized items to the start or end of the viewport during initial or programmatic scrolling, while keeping `viewPosition` alignment for items that fit.
4
+
1
5
  ## 3.5.0
2
6
 
3
7
  - Feat: Add `scrollElement` so web lists can share an ancestor element's scrollbar
package/animated.d.ts CHANGED
@@ -225,6 +225,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
225
225
  index: number;
226
226
  viewOffset?: number | undefined;
227
227
  viewPosition?: number | undefined;
228
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
229
+ viewPositionFallback?: "start" | "end";
228
230
  };
229
231
  /**
230
232
  * Initial scroll position in pixels.
@@ -651,6 +653,8 @@ type LegendListRef$1 = {
651
653
  index: number;
652
654
  viewOffset?: number | undefined;
653
655
  viewPosition?: number | undefined;
656
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
657
+ viewPositionFallback?: "start" | "end";
654
658
  }): Promise<void>;
655
659
  /**
656
660
  * Scrolls to a specific item in the list.
@@ -665,6 +669,8 @@ type LegendListRef$1 = {
665
669
  item: any;
666
670
  viewOffset?: number | undefined;
667
671
  viewPosition?: number | undefined;
672
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
673
+ viewPositionFallback?: "start" | "end";
668
674
  }): Promise<void>;
669
675
  /**
670
676
  * Scrolls to a specific offset in pixels.
@@ -190,6 +190,8 @@ type LegendListRef$1 = {
190
190
  index: number;
191
191
  viewOffset?: number | undefined;
192
192
  viewPosition?: number | undefined;
193
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
194
+ viewPositionFallback?: "start" | "end";
193
195
  }): Promise<void>;
194
196
  /**
195
197
  * Scrolls to a specific item in the list.
@@ -204,6 +206,8 @@ type LegendListRef$1 = {
204
206
  item: any;
205
207
  viewOffset?: number | undefined;
206
208
  viewPosition?: number | undefined;
209
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
210
+ viewPositionFallback?: "start" | "end";
207
211
  }): Promise<void>;
208
212
  /**
209
213
  * Scrolls to a specific offset in pixels.
package/keyboard.d.ts CHANGED
@@ -208,6 +208,8 @@ type LegendListRef$1 = {
208
208
  index: number;
209
209
  viewOffset?: number | undefined;
210
210
  viewPosition?: number | undefined;
211
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
212
+ viewPositionFallback?: "start" | "end";
211
213
  }): Promise<void>;
212
214
  /**
213
215
  * Scrolls to a specific item in the list.
@@ -222,6 +224,8 @@ type LegendListRef$1 = {
222
224
  item: any;
223
225
  viewOffset?: number | undefined;
224
226
  viewPosition?: number | undefined;
227
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
228
+ viewPositionFallback?: "start" | "end";
225
229
  }): Promise<void>;
226
230
  /**
227
231
  * Scrolls to a specific offset in pixels.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
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,
package/react-native.d.ts CHANGED
@@ -225,6 +225,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
225
225
  index: number;
226
226
  viewOffset?: number | undefined;
227
227
  viewPosition?: number | undefined;
228
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
229
+ viewPositionFallback?: "start" | "end";
228
230
  };
229
231
  /**
230
232
  * Initial scroll position in pixels.
@@ -651,6 +653,8 @@ type LegendListRef$1 = {
651
653
  index: number;
652
654
  viewOffset?: number | undefined;
653
655
  viewPosition?: number | undefined;
656
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
657
+ viewPositionFallback?: "start" | "end";
654
658
  }): Promise<void>;
655
659
  /**
656
660
  * Scrolls to a specific item in the list.
@@ -665,6 +669,8 @@ type LegendListRef$1 = {
665
669
  item: any;
666
670
  viewOffset?: number | undefined;
667
671
  viewPosition?: number | undefined;
672
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
673
+ viewPositionFallback?: "start" | "end";
668
674
  }): Promise<void>;
669
675
  /**
670
676
  * Scrolls to a specific offset in pixels.
@@ -763,6 +769,8 @@ interface ScrollIndexWithOffset {
763
769
  index: number;
764
770
  viewOffset?: number;
765
771
  viewPosition?: number;
772
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
773
+ viewPositionFallback?: "start" | "end";
766
774
  }
767
775
  interface ScrollIndexWithOffsetPosition extends ScrollIndexWithOffset {
768
776
  viewPosition?: number;
package/react-native.js CHANGED
@@ -1321,10 +1321,17 @@ function getItemSizeAtIndex(ctx, index) {
1321
1321
  }
1322
1322
 
1323
1323
  // src/core/calculateOffsetWithOffsetPosition.ts
1324
+ function getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback) {
1325
+ let alignment = viewPosition;
1326
+ if (availableSpace < 0 && viewPositionFallback !== void 0) {
1327
+ alignment = viewPositionFallback === "start" ? 0 : 1;
1328
+ }
1329
+ return -alignment * availableSpace;
1330
+ }
1324
1331
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1325
1332
  var _a3;
1326
1333
  const state = ctx.state;
1327
- const { index, viewOffset, viewPosition } = params;
1334
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1328
1335
  let offset = offsetParam;
1329
1336
  if (viewOffset) {
1330
1337
  offset -= viewOffset;
@@ -1345,7 +1352,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1345
1352
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1346
1353
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1347
1354
  const trailingInset = getContentInsetEnd(ctx);
1348
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1355
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1356
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1349
1357
  }
1350
1358
  return offset;
1351
1359
  }
@@ -1732,15 +1740,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1732
1740
  anchorPositionForLock = newPosition;
1733
1741
  }
1734
1742
  }
1735
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1743
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1736
1744
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1737
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1745
+ const prevSize = scrollingTo.itemSize;
1738
1746
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1739
- const diff = newSize - prevSize;
1740
- if (diff !== 0) {
1741
- positionDiff += diff * scrollingToViewPosition;
1742
- scrollingTo.itemSize = newSize;
1747
+ if (scrollingTo.viewPositionFallback) {
1748
+ const viewport = state.scrollLength - getContentInsetEnd(ctx);
1749
+ const previousSpace = viewport - Math.max(0, prevSize - ctx.scrollAxisGap);
1750
+ const nextSpace = viewport - Math.max(0, newSize - ctx.scrollAxisGap);
1751
+ positionDiff += getViewPositionOffset(
1752
+ nextSpace,
1753
+ scrollingToViewPosition,
1754
+ scrollingTo.viewPositionFallback
1755
+ ) - getViewPositionOffset(
1756
+ previousSpace,
1757
+ scrollingToViewPosition,
1758
+ scrollingTo.viewPositionFallback
1759
+ );
1760
+ } else {
1761
+ positionDiff += (newSize - prevSize) * scrollingToViewPosition;
1743
1762
  }
1763
+ scrollingTo.itemSize = newSize;
1744
1764
  }
1745
1765
  }
1746
1766
  updateAnchorLock(state, {
@@ -2467,7 +2487,8 @@ function scrollToIndex(ctx, {
2467
2487
  animated = true,
2468
2488
  forceScroll,
2469
2489
  isInitialScroll,
2470
- viewPosition
2490
+ viewPosition,
2491
+ viewPositionFallback
2471
2492
  }) {
2472
2493
  const state = ctx.state;
2473
2494
  const { data } = state.props;
@@ -2487,7 +2508,8 @@ function scrollToIndex(ctx, {
2487
2508
  itemSize,
2488
2509
  offset: firstIndexOffset,
2489
2510
  viewOffset,
2490
- viewPosition: viewPosition != null ? viewPosition : 0
2511
+ viewPosition: viewPosition != null ? viewPosition : 0,
2512
+ viewPositionFallback
2491
2513
  });
2492
2514
  }
2493
2515
 
@@ -2507,6 +2529,7 @@ function dispatchInitialScroll(ctx, params) {
2507
2529
  precomputedWithViewOffset: true,
2508
2530
  viewOffset: target.viewOffset,
2509
2531
  viewPosition: target.viewPosition,
2532
+ viewPositionFallback: target.viewPositionFallback,
2510
2533
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2511
2534
  });
2512
2535
  }
@@ -2841,7 +2864,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2841
2864
  };
2842
2865
  }
2843
2866
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2844
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2867
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition && current.viewPositionFallback === next.viewPositionFallback;
2845
2868
  }
2846
2869
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2847
2870
  const { dataLength, stylePaddingEnd, target } = options;
@@ -3056,7 +3079,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3056
3079
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3057
3080
  stylePaddingEnd
3058
3081
  });
3059
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3082
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewPositionFallback !== updatedInitialScroll.viewPositionFallback || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3060
3083
  if (!didTargetChange) {
3061
3084
  clearPendingInitialScrollFooterLayout(ctx, {
3062
3085
  dataLength,
@@ -6652,6 +6675,10 @@ function handleLayout(ctx, layoutParam, setCanRender) {
6652
6675
  if (needsCalculate && (!didAllocate || state.initialScroll)) {
6653
6676
  calculateItemsInView(ctx, { doMVCP: true });
6654
6677
  }
6678
+ const scrollTarget = state.scrollingTo;
6679
+ if (scrollLength !== previousLength && (scrollTarget == null ? void 0 : scrollTarget.viewPositionFallback) && !scrollTarget.isInitialScroll && scrollTarget.index !== void 0) {
6680
+ scrollToIndex(ctx, { ...scrollTarget, forceScroll: true, index: scrollTarget.index });
6681
+ }
6655
6682
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
6656
6683
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
6657
6684
  }
@@ -7570,9 +7597,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7570
7597
  viewPosition: 1
7571
7598
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7572
7599
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7573
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7574
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7575
- viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7600
+ preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? true : void 0,
7601
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7602
+ viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0,
7603
+ viewPositionFallback: initialScrollIndexProp.viewPositionFallback
7576
7604
  } : {
7577
7605
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7578
7606
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
package/react-native.mjs CHANGED
@@ -1300,10 +1300,17 @@ function getItemSizeAtIndex(ctx, index) {
1300
1300
  }
1301
1301
 
1302
1302
  // src/core/calculateOffsetWithOffsetPosition.ts
1303
+ function getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback) {
1304
+ let alignment = viewPosition;
1305
+ if (availableSpace < 0 && viewPositionFallback !== void 0) {
1306
+ alignment = viewPositionFallback === "start" ? 0 : 1;
1307
+ }
1308
+ return -alignment * availableSpace;
1309
+ }
1303
1310
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1304
1311
  var _a3;
1305
1312
  const state = ctx.state;
1306
- const { index, viewOffset, viewPosition } = params;
1313
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1307
1314
  let offset = offsetParam;
1308
1315
  if (viewOffset) {
1309
1316
  offset -= viewOffset;
@@ -1324,7 +1331,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1324
1331
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1325
1332
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1326
1333
  const trailingInset = getContentInsetEnd(ctx);
1327
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1334
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1335
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1328
1336
  }
1329
1337
  return offset;
1330
1338
  }
@@ -1711,15 +1719,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1711
1719
  anchorPositionForLock = newPosition;
1712
1720
  }
1713
1721
  }
1714
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1722
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1715
1723
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1716
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1724
+ const prevSize = scrollingTo.itemSize;
1717
1725
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1718
- const diff = newSize - prevSize;
1719
- if (diff !== 0) {
1720
- positionDiff += diff * scrollingToViewPosition;
1721
- scrollingTo.itemSize = newSize;
1726
+ if (scrollingTo.viewPositionFallback) {
1727
+ const viewport = state.scrollLength - getContentInsetEnd(ctx);
1728
+ const previousSpace = viewport - Math.max(0, prevSize - ctx.scrollAxisGap);
1729
+ const nextSpace = viewport - Math.max(0, newSize - ctx.scrollAxisGap);
1730
+ positionDiff += getViewPositionOffset(
1731
+ nextSpace,
1732
+ scrollingToViewPosition,
1733
+ scrollingTo.viewPositionFallback
1734
+ ) - getViewPositionOffset(
1735
+ previousSpace,
1736
+ scrollingToViewPosition,
1737
+ scrollingTo.viewPositionFallback
1738
+ );
1739
+ } else {
1740
+ positionDiff += (newSize - prevSize) * scrollingToViewPosition;
1722
1741
  }
1742
+ scrollingTo.itemSize = newSize;
1723
1743
  }
1724
1744
  }
1725
1745
  updateAnchorLock(state, {
@@ -2446,7 +2466,8 @@ function scrollToIndex(ctx, {
2446
2466
  animated = true,
2447
2467
  forceScroll,
2448
2468
  isInitialScroll,
2449
- viewPosition
2469
+ viewPosition,
2470
+ viewPositionFallback
2450
2471
  }) {
2451
2472
  const state = ctx.state;
2452
2473
  const { data } = state.props;
@@ -2466,7 +2487,8 @@ function scrollToIndex(ctx, {
2466
2487
  itemSize,
2467
2488
  offset: firstIndexOffset,
2468
2489
  viewOffset,
2469
- viewPosition: viewPosition != null ? viewPosition : 0
2490
+ viewPosition: viewPosition != null ? viewPosition : 0,
2491
+ viewPositionFallback
2470
2492
  });
2471
2493
  }
2472
2494
 
@@ -2486,6 +2508,7 @@ function dispatchInitialScroll(ctx, params) {
2486
2508
  precomputedWithViewOffset: true,
2487
2509
  viewOffset: target.viewOffset,
2488
2510
  viewPosition: target.viewPosition,
2511
+ viewPositionFallback: target.viewPositionFallback,
2489
2512
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2490
2513
  });
2491
2514
  }
@@ -2820,7 +2843,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2820
2843
  };
2821
2844
  }
2822
2845
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2823
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2846
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition && current.viewPositionFallback === next.viewPositionFallback;
2824
2847
  }
2825
2848
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2826
2849
  const { dataLength, stylePaddingEnd, target } = options;
@@ -3035,7 +3058,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3035
3058
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3036
3059
  stylePaddingEnd
3037
3060
  });
3038
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3061
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewPositionFallback !== updatedInitialScroll.viewPositionFallback || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
3039
3062
  if (!didTargetChange) {
3040
3063
  clearPendingInitialScrollFooterLayout(ctx, {
3041
3064
  dataLength,
@@ -6631,6 +6654,10 @@ function handleLayout(ctx, layoutParam, setCanRender) {
6631
6654
  if (needsCalculate && (!didAllocate || state.initialScroll)) {
6632
6655
  calculateItemsInView(ctx, { doMVCP: true });
6633
6656
  }
6657
+ const scrollTarget = state.scrollingTo;
6658
+ if (scrollLength !== previousLength && (scrollTarget == null ? void 0 : scrollTarget.viewPositionFallback) && !scrollTarget.isInitialScroll && scrollTarget.index !== void 0) {
6659
+ scrollToIndex(ctx, { ...scrollTarget, forceScroll: true, index: scrollTarget.index });
6660
+ }
6634
6661
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
6635
6662
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
6636
6663
  }
@@ -7549,9 +7576,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7549
7576
  viewPosition: 1
7550
7577
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7551
7578
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7552
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7553
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7554
- viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7579
+ preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? true : void 0,
7580
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7581
+ viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0,
7582
+ viewPositionFallback: initialScrollIndexProp.viewPositionFallback
7555
7583
  } : {
7556
7584
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7557
7585
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
@@ -252,6 +252,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
252
252
  index: number;
253
253
  viewOffset?: number | undefined;
254
254
  viewPosition?: number | undefined;
255
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
256
+ viewPositionFallback?: "start" | "end";
255
257
  };
256
258
  /**
257
259
  * Initial scroll position in pixels.
@@ -678,6 +680,8 @@ type LegendListRef$1 = {
678
680
  index: number;
679
681
  viewOffset?: number | undefined;
680
682
  viewPosition?: number | undefined;
683
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
684
+ viewPositionFallback?: "start" | "end";
681
685
  }): Promise<void>;
682
686
  /**
683
687
  * Scrolls to a specific item in the list.
@@ -692,6 +696,8 @@ type LegendListRef$1 = {
692
696
  item: any;
693
697
  viewOffset?: number | undefined;
694
698
  viewPosition?: number | undefined;
699
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
700
+ viewPositionFallback?: "start" | "end";
695
701
  }): Promise<void>;
696
702
  /**
697
703
  * Scrolls to a specific offset in pixels.
@@ -790,6 +796,8 @@ interface ScrollIndexWithOffset {
790
796
  index: number;
791
797
  viewOffset?: number;
792
798
  viewPosition?: number;
799
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
800
+ viewPositionFallback?: "start" | "end";
793
801
  }
794
802
  interface ScrollIndexWithOffsetPosition extends ScrollIndexWithOffset {
795
803
  viewPosition?: number;
@@ -1285,10 +1285,17 @@ function getItemSizeAtIndex(ctx, index) {
1285
1285
  }
1286
1286
 
1287
1287
  // src/core/calculateOffsetWithOffsetPosition.ts
1288
+ function getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback) {
1289
+ let alignment = viewPosition;
1290
+ if (availableSpace < 0 && viewPositionFallback !== void 0) {
1291
+ alignment = viewPositionFallback === "start" ? 0 : 1;
1292
+ }
1293
+ return -alignment * availableSpace;
1294
+ }
1288
1295
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1289
1296
  var _a3;
1290
1297
  const state = ctx.state;
1291
- const { index, viewOffset, viewPosition } = params;
1298
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1292
1299
  let offset = offsetParam;
1293
1300
  if (viewOffset) {
1294
1301
  offset -= viewOffset;
@@ -1309,7 +1316,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1309
1316
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1310
1317
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1311
1318
  const trailingInset = getContentInsetEnd(ctx);
1312
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1319
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1320
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1313
1321
  }
1314
1322
  return offset;
1315
1323
  }
@@ -1656,15 +1664,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1656
1664
  anchorPositionForLock = newPosition;
1657
1665
  }
1658
1666
  }
1659
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1667
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1660
1668
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1661
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1669
+ const prevSize = scrollingTo.itemSize;
1662
1670
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1663
- const diff = newSize - prevSize;
1664
- if (diff !== 0) {
1665
- positionDiff += diff * scrollingToViewPosition;
1666
- scrollingTo.itemSize = newSize;
1671
+ if (scrollingTo.viewPositionFallback) {
1672
+ const viewport = state.scrollLength - getContentInsetEnd(ctx);
1673
+ const previousSpace = viewport - Math.max(0, prevSize - ctx.scrollAxisGap);
1674
+ const nextSpace = viewport - Math.max(0, newSize - ctx.scrollAxisGap);
1675
+ positionDiff += getViewPositionOffset(
1676
+ nextSpace,
1677
+ scrollingToViewPosition,
1678
+ scrollingTo.viewPositionFallback
1679
+ ) - getViewPositionOffset(
1680
+ previousSpace,
1681
+ scrollingToViewPosition,
1682
+ scrollingTo.viewPositionFallback
1683
+ );
1684
+ } else {
1685
+ positionDiff += (newSize - prevSize) * scrollingToViewPosition;
1667
1686
  }
1687
+ scrollingTo.itemSize = newSize;
1668
1688
  }
1669
1689
  }
1670
1690
  updateAnchorLock(state, {
@@ -2314,7 +2334,8 @@ function scrollToIndex(ctx, {
2314
2334
  animated = true,
2315
2335
  forceScroll,
2316
2336
  isInitialScroll,
2317
- viewPosition
2337
+ viewPosition,
2338
+ viewPositionFallback
2318
2339
  }) {
2319
2340
  const state = ctx.state;
2320
2341
  const { data } = state.props;
@@ -2334,7 +2355,8 @@ function scrollToIndex(ctx, {
2334
2355
  itemSize,
2335
2356
  offset: firstIndexOffset,
2336
2357
  viewOffset,
2337
- viewPosition: viewPosition != null ? viewPosition : 0
2358
+ viewPosition: viewPosition != null ? viewPosition : 0,
2359
+ viewPositionFallback
2338
2360
  });
2339
2361
  }
2340
2362
 
@@ -2354,6 +2376,7 @@ function dispatchInitialScroll(ctx, params) {
2354
2376
  precomputedWithViewOffset: true,
2355
2377
  viewOffset: target.viewOffset,
2356
2378
  viewPosition: target.viewPosition,
2379
+ viewPositionFallback: target.viewPositionFallback,
2357
2380
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2358
2381
  });
2359
2382
  }
@@ -2688,7 +2711,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2688
2711
  };
2689
2712
  }
2690
2713
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2691
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2714
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition && current.viewPositionFallback === next.viewPositionFallback;
2692
2715
  }
2693
2716
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2694
2717
  const { dataLength, stylePaddingEnd, target } = options;
@@ -2903,7 +2926,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2903
2926
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2904
2927
  stylePaddingEnd
2905
2928
  });
2906
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2929
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewPositionFallback !== updatedInitialScroll.viewPositionFallback || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2907
2930
  if (!didTargetChange) {
2908
2931
  clearPendingInitialScrollFooterLayout(ctx, {
2909
2932
  dataLength,
@@ -7348,6 +7371,10 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7348
7371
  if (needsCalculate && (!didAllocate || state.initialScroll)) {
7349
7372
  calculateItemsInView(ctx, { doMVCP: true });
7350
7373
  }
7374
+ const scrollTarget = state.scrollingTo;
7375
+ if (scrollLength !== previousLength && (scrollTarget == null ? void 0 : scrollTarget.viewPositionFallback) && !scrollTarget.isInitialScroll && scrollTarget.index !== void 0) {
7376
+ scrollToIndex(ctx, { ...scrollTarget, forceScroll: true, index: scrollTarget.index });
7377
+ }
7351
7378
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
7352
7379
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7353
7380
  }
@@ -8246,9 +8273,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8246
8273
  viewPosition: 1
8247
8274
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
8248
8275
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
8249
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
8250
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8251
- viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
8276
+ preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? true : void 0,
8277
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8278
+ viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0,
8279
+ viewPositionFallback: initialScrollIndexProp.viewPositionFallback
8252
8280
  } : {
8253
8281
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
8254
8282
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
@@ -1264,10 +1264,17 @@ function getItemSizeAtIndex(ctx, index) {
1264
1264
  }
1265
1265
 
1266
1266
  // src/core/calculateOffsetWithOffsetPosition.ts
1267
+ function getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback) {
1268
+ let alignment = viewPosition;
1269
+ if (availableSpace < 0 && viewPositionFallback !== void 0) {
1270
+ alignment = viewPositionFallback === "start" ? 0 : 1;
1271
+ }
1272
+ return -alignment * availableSpace;
1273
+ }
1267
1274
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1268
1275
  var _a3;
1269
1276
  const state = ctx.state;
1270
- const { index, viewOffset, viewPosition } = params;
1277
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1271
1278
  let offset = offsetParam;
1272
1279
  if (viewOffset) {
1273
1280
  offset -= viewOffset;
@@ -1288,7 +1295,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1288
1295
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1289
1296
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1290
1297
  const trailingInset = getContentInsetEnd(ctx);
1291
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1298
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1299
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1292
1300
  }
1293
1301
  return offset;
1294
1302
  }
@@ -1635,15 +1643,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1635
1643
  anchorPositionForLock = newPosition;
1636
1644
  }
1637
1645
  }
1638
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1646
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1639
1647
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1640
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1648
+ const prevSize = scrollingTo.itemSize;
1641
1649
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1642
- const diff = newSize - prevSize;
1643
- if (diff !== 0) {
1644
- positionDiff += diff * scrollingToViewPosition;
1645
- scrollingTo.itemSize = newSize;
1650
+ if (scrollingTo.viewPositionFallback) {
1651
+ const viewport = state.scrollLength - getContentInsetEnd(ctx);
1652
+ const previousSpace = viewport - Math.max(0, prevSize - ctx.scrollAxisGap);
1653
+ const nextSpace = viewport - Math.max(0, newSize - ctx.scrollAxisGap);
1654
+ positionDiff += getViewPositionOffset(
1655
+ nextSpace,
1656
+ scrollingToViewPosition,
1657
+ scrollingTo.viewPositionFallback
1658
+ ) - getViewPositionOffset(
1659
+ previousSpace,
1660
+ scrollingToViewPosition,
1661
+ scrollingTo.viewPositionFallback
1662
+ );
1663
+ } else {
1664
+ positionDiff += (newSize - prevSize) * scrollingToViewPosition;
1646
1665
  }
1666
+ scrollingTo.itemSize = newSize;
1647
1667
  }
1648
1668
  }
1649
1669
  updateAnchorLock(state, {
@@ -2293,7 +2313,8 @@ function scrollToIndex(ctx, {
2293
2313
  animated = true,
2294
2314
  forceScroll,
2295
2315
  isInitialScroll,
2296
- viewPosition
2316
+ viewPosition,
2317
+ viewPositionFallback
2297
2318
  }) {
2298
2319
  const state = ctx.state;
2299
2320
  const { data } = state.props;
@@ -2313,7 +2334,8 @@ function scrollToIndex(ctx, {
2313
2334
  itemSize,
2314
2335
  offset: firstIndexOffset,
2315
2336
  viewOffset,
2316
- viewPosition: viewPosition != null ? viewPosition : 0
2337
+ viewPosition: viewPosition != null ? viewPosition : 0,
2338
+ viewPositionFallback
2317
2339
  });
2318
2340
  }
2319
2341
 
@@ -2333,6 +2355,7 @@ function dispatchInitialScroll(ctx, params) {
2333
2355
  precomputedWithViewOffset: true,
2334
2356
  viewOffset: target.viewOffset,
2335
2357
  viewPosition: target.viewPosition,
2358
+ viewPositionFallback: target.viewPositionFallback,
2336
2359
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2337
2360
  });
2338
2361
  }
@@ -2667,7 +2690,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2667
2690
  };
2668
2691
  }
2669
2692
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2670
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2693
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition && current.viewPositionFallback === next.viewPositionFallback;
2671
2694
  }
2672
2695
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2673
2696
  const { dataLength, stylePaddingEnd, target } = options;
@@ -2882,7 +2905,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2882
2905
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2883
2906
  stylePaddingEnd
2884
2907
  });
2885
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2908
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewPositionFallback !== updatedInitialScroll.viewPositionFallback || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2886
2909
  if (!didTargetChange) {
2887
2910
  clearPendingInitialScrollFooterLayout(ctx, {
2888
2911
  dataLength,
@@ -7327,6 +7350,10 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7327
7350
  if (needsCalculate && (!didAllocate || state.initialScroll)) {
7328
7351
  calculateItemsInView(ctx, { doMVCP: true });
7329
7352
  }
7353
+ const scrollTarget = state.scrollingTo;
7354
+ if (scrollLength !== previousLength && (scrollTarget == null ? void 0 : scrollTarget.viewPositionFallback) && !scrollTarget.isInitialScroll && scrollTarget.index !== void 0) {
7355
+ scrollToIndex(ctx, { ...scrollTarget, forceScroll: true, index: scrollTarget.index });
7356
+ }
7330
7357
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
7331
7358
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7332
7359
  }
@@ -8225,9 +8252,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8225
8252
  viewPosition: 1
8226
8253
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
8227
8254
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
8228
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
8229
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8230
- viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
8255
+ preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? true : void 0,
8256
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8257
+ viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0,
8258
+ viewPositionFallback: initialScrollIndexProp.viewPositionFallback
8231
8259
  } : {
8232
8260
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
8233
8261
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
package/react.d.ts CHANGED
@@ -252,6 +252,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
252
252
  index: number;
253
253
  viewOffset?: number | undefined;
254
254
  viewPosition?: number | undefined;
255
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
256
+ viewPositionFallback?: "start" | "end";
255
257
  };
256
258
  /**
257
259
  * Initial scroll position in pixels.
@@ -678,6 +680,8 @@ type LegendListRef$1 = {
678
680
  index: number;
679
681
  viewOffset?: number | undefined;
680
682
  viewPosition?: number | undefined;
683
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
684
+ viewPositionFallback?: "start" | "end";
681
685
  }): Promise<void>;
682
686
  /**
683
687
  * Scrolls to a specific item in the list.
@@ -692,6 +696,8 @@ type LegendListRef$1 = {
692
696
  item: any;
693
697
  viewOffset?: number | undefined;
694
698
  viewPosition?: number | undefined;
699
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
700
+ viewPositionFallback?: "start" | "end";
695
701
  }): Promise<void>;
696
702
  /**
697
703
  * Scrolls to a specific offset in pixels.
@@ -790,6 +796,8 @@ interface ScrollIndexWithOffset {
790
796
  index: number;
791
797
  viewOffset?: number;
792
798
  viewPosition?: number;
799
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
800
+ viewPositionFallback?: "start" | "end";
793
801
  }
794
802
  interface ScrollIndexWithOffsetPosition extends ScrollIndexWithOffset {
795
803
  viewPosition?: number;
package/react.js CHANGED
@@ -1285,10 +1285,17 @@ function getItemSizeAtIndex(ctx, index) {
1285
1285
  }
1286
1286
 
1287
1287
  // src/core/calculateOffsetWithOffsetPosition.ts
1288
+ function getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback) {
1289
+ let alignment = viewPosition;
1290
+ if (availableSpace < 0 && viewPositionFallback !== void 0) {
1291
+ alignment = viewPositionFallback === "start" ? 0 : 1;
1292
+ }
1293
+ return -alignment * availableSpace;
1294
+ }
1288
1295
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1289
1296
  var _a3;
1290
1297
  const state = ctx.state;
1291
- const { index, viewOffset, viewPosition } = params;
1298
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1292
1299
  let offset = offsetParam;
1293
1300
  if (viewOffset) {
1294
1301
  offset -= viewOffset;
@@ -1309,7 +1316,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1309
1316
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1310
1317
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1311
1318
  const trailingInset = getContentInsetEnd(ctx);
1312
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1319
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1320
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1313
1321
  }
1314
1322
  return offset;
1315
1323
  }
@@ -1656,15 +1664,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1656
1664
  anchorPositionForLock = newPosition;
1657
1665
  }
1658
1666
  }
1659
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1667
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1660
1668
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1661
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1669
+ const prevSize = scrollingTo.itemSize;
1662
1670
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1663
- const diff = newSize - prevSize;
1664
- if (diff !== 0) {
1665
- positionDiff += diff * scrollingToViewPosition;
1666
- scrollingTo.itemSize = newSize;
1671
+ if (scrollingTo.viewPositionFallback) {
1672
+ const viewport = state.scrollLength - getContentInsetEnd(ctx);
1673
+ const previousSpace = viewport - Math.max(0, prevSize - ctx.scrollAxisGap);
1674
+ const nextSpace = viewport - Math.max(0, newSize - ctx.scrollAxisGap);
1675
+ positionDiff += getViewPositionOffset(
1676
+ nextSpace,
1677
+ scrollingToViewPosition,
1678
+ scrollingTo.viewPositionFallback
1679
+ ) - getViewPositionOffset(
1680
+ previousSpace,
1681
+ scrollingToViewPosition,
1682
+ scrollingTo.viewPositionFallback
1683
+ );
1684
+ } else {
1685
+ positionDiff += (newSize - prevSize) * scrollingToViewPosition;
1667
1686
  }
1687
+ scrollingTo.itemSize = newSize;
1668
1688
  }
1669
1689
  }
1670
1690
  updateAnchorLock(state, {
@@ -2314,7 +2334,8 @@ function scrollToIndex(ctx, {
2314
2334
  animated = true,
2315
2335
  forceScroll,
2316
2336
  isInitialScroll,
2317
- viewPosition
2337
+ viewPosition,
2338
+ viewPositionFallback
2318
2339
  }) {
2319
2340
  const state = ctx.state;
2320
2341
  const { data } = state.props;
@@ -2334,7 +2355,8 @@ function scrollToIndex(ctx, {
2334
2355
  itemSize,
2335
2356
  offset: firstIndexOffset,
2336
2357
  viewOffset,
2337
- viewPosition: viewPosition != null ? viewPosition : 0
2358
+ viewPosition: viewPosition != null ? viewPosition : 0,
2359
+ viewPositionFallback
2338
2360
  });
2339
2361
  }
2340
2362
 
@@ -2354,6 +2376,7 @@ function dispatchInitialScroll(ctx, params) {
2354
2376
  precomputedWithViewOffset: true,
2355
2377
  viewOffset: target.viewOffset,
2356
2378
  viewPosition: target.viewPosition,
2379
+ viewPositionFallback: target.viewPositionFallback,
2357
2380
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2358
2381
  });
2359
2382
  }
@@ -2688,7 +2711,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2688
2711
  };
2689
2712
  }
2690
2713
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2691
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2714
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition && current.viewPositionFallback === next.viewPositionFallback;
2692
2715
  }
2693
2716
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2694
2717
  const { dataLength, stylePaddingEnd, target } = options;
@@ -2903,7 +2926,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2903
2926
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2904
2927
  stylePaddingEnd
2905
2928
  });
2906
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2929
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewPositionFallback !== updatedInitialScroll.viewPositionFallback || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2907
2930
  if (!didTargetChange) {
2908
2931
  clearPendingInitialScrollFooterLayout(ctx, {
2909
2932
  dataLength,
@@ -7348,6 +7371,10 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7348
7371
  if (needsCalculate && (!didAllocate || state.initialScroll)) {
7349
7372
  calculateItemsInView(ctx, { doMVCP: true });
7350
7373
  }
7374
+ const scrollTarget = state.scrollingTo;
7375
+ if (scrollLength !== previousLength && (scrollTarget == null ? void 0 : scrollTarget.viewPositionFallback) && !scrollTarget.isInitialScroll && scrollTarget.index !== void 0) {
7376
+ scrollToIndex(ctx, { ...scrollTarget, forceScroll: true, index: scrollTarget.index });
7377
+ }
7351
7378
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
7352
7379
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7353
7380
  }
@@ -8246,9 +8273,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8246
8273
  viewPosition: 1
8247
8274
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
8248
8275
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
8249
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
8250
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8251
- viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
8276
+ preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? true : void 0,
8277
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8278
+ viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0,
8279
+ viewPositionFallback: initialScrollIndexProp.viewPositionFallback
8252
8280
  } : {
8253
8281
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
8254
8282
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
package/react.mjs CHANGED
@@ -1264,10 +1264,17 @@ function getItemSizeAtIndex(ctx, index) {
1264
1264
  }
1265
1265
 
1266
1266
  // src/core/calculateOffsetWithOffsetPosition.ts
1267
+ function getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback) {
1268
+ let alignment = viewPosition;
1269
+ if (availableSpace < 0 && viewPositionFallback !== void 0) {
1270
+ alignment = viewPositionFallback === "start" ? 0 : 1;
1271
+ }
1272
+ return -alignment * availableSpace;
1273
+ }
1267
1274
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1268
1275
  var _a3;
1269
1276
  const state = ctx.state;
1270
- const { index, viewOffset, viewPosition } = params;
1277
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1271
1278
  let offset = offsetParam;
1272
1279
  if (viewOffset) {
1273
1280
  offset -= viewOffset;
@@ -1288,7 +1295,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1288
1295
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1289
1296
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1290
1297
  const trailingInset = getContentInsetEnd(ctx);
1291
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1298
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1299
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1292
1300
  }
1293
1301
  return offset;
1294
1302
  }
@@ -1635,15 +1643,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1635
1643
  anchorPositionForLock = newPosition;
1636
1644
  }
1637
1645
  }
1638
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1646
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1639
1647
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1640
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1648
+ const prevSize = scrollingTo.itemSize;
1641
1649
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1642
- const diff = newSize - prevSize;
1643
- if (diff !== 0) {
1644
- positionDiff += diff * scrollingToViewPosition;
1645
- scrollingTo.itemSize = newSize;
1650
+ if (scrollingTo.viewPositionFallback) {
1651
+ const viewport = state.scrollLength - getContentInsetEnd(ctx);
1652
+ const previousSpace = viewport - Math.max(0, prevSize - ctx.scrollAxisGap);
1653
+ const nextSpace = viewport - Math.max(0, newSize - ctx.scrollAxisGap);
1654
+ positionDiff += getViewPositionOffset(
1655
+ nextSpace,
1656
+ scrollingToViewPosition,
1657
+ scrollingTo.viewPositionFallback
1658
+ ) - getViewPositionOffset(
1659
+ previousSpace,
1660
+ scrollingToViewPosition,
1661
+ scrollingTo.viewPositionFallback
1662
+ );
1663
+ } else {
1664
+ positionDiff += (newSize - prevSize) * scrollingToViewPosition;
1646
1665
  }
1666
+ scrollingTo.itemSize = newSize;
1647
1667
  }
1648
1668
  }
1649
1669
  updateAnchorLock(state, {
@@ -2293,7 +2313,8 @@ function scrollToIndex(ctx, {
2293
2313
  animated = true,
2294
2314
  forceScroll,
2295
2315
  isInitialScroll,
2296
- viewPosition
2316
+ viewPosition,
2317
+ viewPositionFallback
2297
2318
  }) {
2298
2319
  const state = ctx.state;
2299
2320
  const { data } = state.props;
@@ -2313,7 +2334,8 @@ function scrollToIndex(ctx, {
2313
2334
  itemSize,
2314
2335
  offset: firstIndexOffset,
2315
2336
  viewOffset,
2316
- viewPosition: viewPosition != null ? viewPosition : 0
2337
+ viewPosition: viewPosition != null ? viewPosition : 0,
2338
+ viewPositionFallback
2317
2339
  });
2318
2340
  }
2319
2341
 
@@ -2333,6 +2355,7 @@ function dispatchInitialScroll(ctx, params) {
2333
2355
  precomputedWithViewOffset: true,
2334
2356
  viewOffset: target.viewOffset,
2335
2357
  viewPosition: target.viewPosition,
2358
+ viewPositionFallback: target.viewPositionFallback,
2336
2359
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2337
2360
  });
2338
2361
  }
@@ -2667,7 +2690,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2667
2690
  };
2668
2691
  }
2669
2692
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2670
- return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition;
2693
+ return current.index === next.index && current.preserveForBottomPadding === next.preserveForBottomPadding && current.preserveForFooterLayout === next.preserveForFooterLayout && current.viewOffset === next.viewOffset && current.viewPosition === next.viewPosition && current.viewPositionFallback === next.viewPositionFallback;
2671
2694
  }
2672
2695
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2673
2696
  const { dataLength, stylePaddingEnd, target } = options;
@@ -2882,7 +2905,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2882
2905
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2883
2906
  stylePaddingEnd
2884
2907
  });
2885
- const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2908
+ const didTargetChange = initialScroll.index !== updatedInitialScroll.index || initialScroll.viewPosition !== updatedInitialScroll.viewPosition || initialScroll.viewPositionFallback !== updatedInitialScroll.viewPositionFallback || initialScroll.viewOffset !== updatedInitialScroll.viewOffset;
2886
2909
  if (!didTargetChange) {
2887
2910
  clearPendingInitialScrollFooterLayout(ctx, {
2888
2911
  dataLength,
@@ -7327,6 +7350,10 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7327
7350
  if (needsCalculate && (!didAllocate || state.initialScroll)) {
7328
7351
  calculateItemsInView(ctx, { doMVCP: true });
7329
7352
  }
7353
+ const scrollTarget = state.scrollingTo;
7354
+ if (scrollLength !== previousLength && (scrollTarget == null ? void 0 : scrollTarget.viewPositionFallback) && !scrollTarget.isInitialScroll && scrollTarget.index !== void 0) {
7355
+ scrollToIndex(ctx, { ...scrollTarget, forceScroll: true, index: scrollTarget.index });
7356
+ }
7330
7357
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
7331
7358
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7332
7359
  }
@@ -8225,9 +8252,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8225
8252
  viewPosition: 1
8226
8253
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
8227
8254
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
8228
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
8229
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8230
- viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
8255
+ preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? true : void 0,
8256
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 && initialScrollIndexProp.viewPositionFallback === void 0 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8257
+ viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0,
8258
+ viewPositionFallback: initialScrollIndexProp.viewPositionFallback
8231
8259
  } : {
8232
8260
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
8233
8261
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
package/reanimated.d.ts CHANGED
@@ -226,6 +226,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
226
226
  index: number;
227
227
  viewOffset?: number | undefined;
228
228
  viewPosition?: number | undefined;
229
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
230
+ viewPositionFallback?: "start" | "end";
229
231
  };
230
232
  /**
231
233
  * Initial scroll position in pixels.
@@ -652,6 +654,8 @@ type LegendListRef$1 = {
652
654
  index: number;
653
655
  viewOffset?: number | undefined;
654
656
  viewPosition?: number | undefined;
657
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
658
+ viewPositionFallback?: "start" | "end";
655
659
  }): Promise<void>;
656
660
  /**
657
661
  * Scrolls to a specific item in the list.
@@ -666,6 +670,8 @@ type LegendListRef$1 = {
666
670
  item: any;
667
671
  viewOffset?: number | undefined;
668
672
  viewPosition?: number | undefined;
673
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
674
+ viewPositionFallback?: "start" | "end";
669
675
  }): Promise<void>;
670
676
  /**
671
677
  * Scrolls to a specific offset in pixels.
package/section-list.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_native from 'react-native';
2
- import { SectionListData, ScrollViewProps, NativeSyntheticEvent as NativeSyntheticEvent$1, NativeScrollEvent as NativeScrollEvent$1, ScrollView, StyleProp as StyleProp$1, ViewStyle as ViewStyle$1, ScrollViewComponent, ScrollResponderMixin, Insets as Insets$1, SectionBase, SectionListRenderItemInfo, SectionListScrollParams } from 'react-native';
2
+ import { SectionListData, ScrollViewProps, NativeSyntheticEvent as NativeSyntheticEvent$1, NativeScrollEvent as NativeScrollEvent$1, ScrollView, StyleProp as StyleProp$1, ViewStyle as ViewStyle$1, ScrollViewComponent, ScrollResponderMixin, Insets as Insets$1, SectionBase, SectionListRenderItemInfo, SectionListScrollParams as SectionListScrollParams$1 } from 'react-native';
3
3
  import * as React from 'react';
4
4
  import { Key } from 'react';
5
5
 
@@ -282,6 +282,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
282
282
  index: number;
283
283
  viewOffset?: number | undefined;
284
284
  viewPosition?: number | undefined;
285
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
286
+ viewPositionFallback?: "start" | "end";
285
287
  };
286
288
  /**
287
289
  * Initial scroll position in pixels.
@@ -708,6 +710,8 @@ type LegendListRef$1 = {
708
710
  index: number;
709
711
  viewOffset?: number | undefined;
710
712
  viewPosition?: number | undefined;
713
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
714
+ viewPositionFallback?: "start" | "end";
711
715
  }): Promise<void>;
712
716
  /**
713
717
  * Scrolls to a specific item in the list.
@@ -722,6 +726,8 @@ type LegendListRef$1 = {
722
726
  item: any;
723
727
  viewOffset?: number | undefined;
724
728
  viewPosition?: number | undefined;
729
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
730
+ viewPositionFallback?: "start" | "end";
725
731
  }): Promise<void>;
726
732
  /**
727
733
  * Scrolls to a specific offset in pixels.
@@ -818,6 +824,9 @@ type LegendListRef = Omit<LegendListRef$1, "getAnimatableRef" | "getNativeScroll
818
824
  reportContentInset(inset?: Partial<Insets$1> | null): void;
819
825
  };
820
826
 
827
+ type SectionListScrollParams = SectionListScrollParams$1 & {
828
+ viewPositionFallback?: "start" | "end";
829
+ };
821
830
  type SectionListViewToken<ItemT, SectionT> = {
822
831
  item: ItemT;
823
832
  key: string;
package/section-list.js CHANGED
@@ -281,7 +281,14 @@ var SectionList = typedMemo(
281
281
  ]
282
282
  );
283
283
  const scrollToLocation = React__namespace.useCallback(
284
- ({ sectionIndex, itemIndex, viewOffset, viewPosition, animated }) => {
284
+ ({
285
+ sectionIndex,
286
+ itemIndex,
287
+ viewOffset,
288
+ viewPosition,
289
+ viewPositionFallback,
290
+ animated
291
+ }) => {
285
292
  var _a, _b, _c;
286
293
  const meta = sectionMeta[sectionIndex];
287
294
  if (!meta) return;
@@ -291,7 +298,8 @@ var SectionList = typedMemo(
291
298
  animated,
292
299
  index: target,
293
300
  viewOffset,
294
- viewPosition
301
+ viewPosition,
302
+ viewPositionFallback
295
303
  });
296
304
  },
297
305
  [sectionMeta]
package/section-list.mjs CHANGED
@@ -259,7 +259,14 @@ var SectionList = typedMemo(
259
259
  ]
260
260
  );
261
261
  const scrollToLocation = React.useCallback(
262
- ({ sectionIndex, itemIndex, viewOffset, viewPosition, animated }) => {
262
+ ({
263
+ sectionIndex,
264
+ itemIndex,
265
+ viewOffset,
266
+ viewPosition,
267
+ viewPositionFallback,
268
+ animated
269
+ }) => {
263
270
  var _a, _b, _c;
264
271
  const meta = sectionMeta[sectionIndex];
265
272
  if (!meta) return;
@@ -269,7 +276,8 @@ var SectionList = typedMemo(
269
276
  animated,
270
277
  index: target,
271
278
  viewOffset,
272
- viewPosition
279
+ viewPosition,
280
+ viewPositionFallback
273
281
  });
274
282
  },
275
283
  [sectionMeta]