@legendapp/list 3.4.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/react-native.mjs CHANGED
@@ -914,8 +914,14 @@ function scheduleFullDrawDistancePrewarm(ctx) {
914
914
  return;
915
915
  }
916
916
  state.scheduledWork.frame(() => {
917
- var _a3;
918
- return (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
917
+ var _a3, _b, _c;
918
+ if (state.props.hasExternalScroll && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust) {
919
+ const offset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getRawScrollOffset) == null ? void 0 : _b.call(_a3);
920
+ if (offset !== void 0) {
921
+ state.scroll = state.scrollPending = state.props.horizontal ? toLogicalHorizontalOffset(state, offset, state.totalSize) : offset;
922
+ }
923
+ }
924
+ (_c = state.triggerCalculateItemsInView) == null ? void 0 : _c.call(state);
919
925
  }, "fullDrawDistancePrewarm");
920
926
  }
921
927
 
@@ -928,6 +934,9 @@ function resetInitialRenderState(ctx, {
928
934
  if (resetLayout) {
929
935
  state.didContainersLayout = false;
930
936
  state.queuedInitialLayout = false;
937
+ state.scrollBufferDirection = void 0;
938
+ state.scrollHistory.length = 0;
939
+ state.scheduledWork.cancel("renderRangeProjection");
931
940
  state.edgeReachedGate = void 0;
932
941
  state.isStartReached = null;
933
942
  state.isEndReached = null;
@@ -1156,6 +1165,7 @@ function setSize(ctx, itemKey, size, notifyTotalSize = true) {
1156
1165
  const previousSize = sizes.get(itemKey);
1157
1166
  const diff = previousSize !== void 0 ? size - previousSize : size;
1158
1167
  if (diff !== 0) {
1168
+ state.positionsAreCurrent = false;
1159
1169
  addTotalSize(ctx, itemKey, diff, notifyTotalSize);
1160
1170
  }
1161
1171
  sizes.set(itemKey, size);
@@ -1216,6 +1226,7 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
1216
1226
  const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
1217
1227
  if (fixedSize !== void 0) {
1218
1228
  size = fixedSize + ctx.scrollAxisGap;
1229
+ state.positionsAreCurrent = false;
1219
1230
  state.sizesKnown.set(key, size);
1220
1231
  }
1221
1232
  }
@@ -1289,10 +1300,17 @@ function getItemSizeAtIndex(ctx, index) {
1289
1300
  }
1290
1301
 
1291
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
+ }
1292
1310
  function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1293
1311
  var _a3;
1294
1312
  const state = ctx.state;
1295
- const { index, viewOffset, viewPosition } = params;
1313
+ const { index, viewOffset, viewPosition, viewPositionFallback } = params;
1296
1314
  let offset = offsetParam;
1297
1315
  if (viewOffset) {
1298
1316
  offset -= viewOffset;
@@ -1313,7 +1331,8 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1313
1331
  const measuredItemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1314
1332
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1315
1333
  const trailingInset = getContentInsetEnd(ctx);
1316
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1334
+ const availableSpace = state.scrollLength - trailingInset - itemSize;
1335
+ offset += getViewPositionOffset(availableSpace, viewPosition, viewPositionFallback);
1317
1336
  }
1318
1337
  return offset;
1319
1338
  }
@@ -1700,15 +1719,27 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
1700
1719
  anchorPositionForLock = newPosition;
1701
1720
  }
1702
1721
  }
1703
- if (scrollingToViewPosition && scrollingToViewPosition > 0) {
1722
+ if (scrollingTo && scrollingToViewPosition !== void 0 && (scrollingToViewPosition > 0 || scrollingTo.viewPositionFallback)) {
1704
1723
  const newSize = getItemSize(ctx, targetId, scrollTarget, state.props.data[scrollTarget]);
1705
- const prevSize = scrollingTo == null ? void 0 : scrollingTo.itemSize;
1724
+ const prevSize = scrollingTo.itemSize;
1706
1725
  if (newSize !== void 0 && prevSize !== void 0 && newSize !== prevSize) {
1707
- const diff = newSize - prevSize;
1708
- if (diff !== 0) {
1709
- positionDiff += diff * scrollingToViewPosition;
1710
- 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;
1711
1741
  }
1742
+ scrollingTo.itemSize = newSize;
1712
1743
  }
1713
1744
  }
1714
1745
  updateAnchorLock(state, {
@@ -1829,6 +1860,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1829
1860
  state.scrollTime = currentTime;
1830
1861
  const scrollDelta = Math.abs(newScroll - prevScroll);
1831
1862
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1863
+ if (isUserScrollEvent) {
1864
+ state.scrollBufferDirection = newScroll > prevScroll ? 1 : -1;
1865
+ }
1832
1866
  const didCancelMaintainScrollAtEnd = Platform.OS === "web" && isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1833
1867
  if (didCancelMaintainScrollAtEnd) {
1834
1868
  finishMaintainScrollAtEnd(ctx);
@@ -1908,10 +1942,10 @@ function finishMaintainScrollAtEnd(ctx) {
1908
1942
  }
1909
1943
  }
1910
1944
  function doMaintainScrollAtEnd(ctx, options) {
1911
- var _a3, _b, _c, _d, _e, _f;
1945
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
1912
1946
  const state = ctx.state;
1913
1947
  let didMaintain = false;
1914
- if (state.isDragging) {
1948
+ if (state.isDragging || ((_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.isScrollInRange) == null ? void 0 : _b.call(_a3)) === false) {
1915
1949
  finishMaintainScrollAtEnd(ctx);
1916
1950
  } else {
1917
1951
  const {
@@ -1923,11 +1957,11 @@ function doMaintainScrollAtEnd(ctx, options) {
1923
1957
  const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1924
1958
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1925
1959
  const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1926
- const shouldMaintainScrollAtEnd = !!(!((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) && (!state.scrollingTo || state.scrollingTo.isScrollToEnd) && (isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain || ((_b = state.scrollingTo) == null ? void 0 : _b.isScrollToEnd)) && maintainScrollAtEnd && didFinishInitialScroll);
1960
+ const shouldMaintainScrollAtEnd = !!(!((_c = ctx.scrollRequestTracker) == null ? void 0 : _c.isWaitingToRun()) && (!state.scrollingTo || state.scrollingTo.isScrollToEnd) && (isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain || ((_d = state.scrollingTo) == null ? void 0 : _d.isScrollToEnd)) && maintainScrollAtEnd && didFinishInitialScroll);
1927
1961
  if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1928
1962
  const scroller = state.refScroller.current;
1929
- const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1930
- const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1963
+ const nativeMax = (_e = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _e.call(scroller);
1964
+ const observed = (_f = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _f.call(scroller);
1931
1965
  if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1932
1966
  state.pendingMaintainScrollAtEnd = true;
1933
1967
  state.scrollPending = observed;
@@ -1939,13 +1973,13 @@ function doMaintainScrollAtEnd(ctx, options) {
1939
1973
  } else if (pendingNativeMVCPAdjust) {
1940
1974
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1941
1975
  } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1942
- if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1976
+ if (immediate && ((_g = state.scrollingTo) == null ? void 0 : _g.isScrollToEnd) && !state.scrollingTo.animated) {
1943
1977
  state.pendingMaintainScrollAtEnd = false;
1944
1978
  ctx.scrollToEnd({ animated: false });
1945
1979
  didMaintain = true;
1946
1980
  } else {
1947
1981
  state.pendingMaintainScrollAtEnd = true;
1948
- (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1982
+ (_h = state.maintainingScrollAtEnd) != null ? _h : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1949
1983
  }
1950
1984
  } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1951
1985
  state.pendingMaintainScrollAtEnd = false;
@@ -1959,8 +1993,9 @@ function doMaintainScrollAtEnd(ctx, options) {
1959
1993
  const scrollAtRequest = state.scroll;
1960
1994
  state.maintainingScrollAtEnd = pendingState;
1961
1995
  const run = () => {
1996
+ var _a4, _b2;
1962
1997
  if (state.maintainingScrollAtEnd === pendingState) {
1963
- if (state.isDragging) {
1998
+ if (state.isDragging || ((_b2 = (_a4 = state.refScroller.current) == null ? void 0 : _a4.isScrollInRange) == null ? void 0 : _b2.call(_a4)) === false) {
1964
1999
  finishMaintainScrollAtEnd(ctx);
1965
2000
  } else {
1966
2001
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
@@ -2431,7 +2466,8 @@ function scrollToIndex(ctx, {
2431
2466
  animated = true,
2432
2467
  forceScroll,
2433
2468
  isInitialScroll,
2434
- viewPosition
2469
+ viewPosition,
2470
+ viewPositionFallback
2435
2471
  }) {
2436
2472
  const state = ctx.state;
2437
2473
  const { data } = state.props;
@@ -2451,7 +2487,8 @@ function scrollToIndex(ctx, {
2451
2487
  itemSize,
2452
2488
  offset: firstIndexOffset,
2453
2489
  viewOffset,
2454
- viewPosition: viewPosition != null ? viewPosition : 0
2490
+ viewPosition: viewPosition != null ? viewPosition : 0,
2491
+ viewPositionFallback
2455
2492
  });
2456
2493
  }
2457
2494
 
@@ -2471,6 +2508,7 @@ function dispatchInitialScroll(ctx, params) {
2471
2508
  precomputedWithViewOffset: true,
2472
2509
  viewOffset: target.viewOffset,
2473
2510
  viewPosition: target.viewPosition,
2511
+ viewPositionFallback: target.viewPositionFallback,
2474
2512
  waitForInitialScrollCompletionFrame: waitForCompletionFrame
2475
2513
  });
2476
2514
  }
@@ -2805,7 +2843,7 @@ function createRetargetedBottomAlignedInitialScroll(options) {
2805
2843
  };
2806
2844
  }
2807
2845
  function areEquivalentBootstrapInitialScrollTargets(current, next) {
2808
- 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;
2809
2847
  }
2810
2848
  function clearPendingInitialScrollFooterLayout(ctx, options) {
2811
2849
  const { dataLength, stylePaddingEnd, target } = options;
@@ -3020,7 +3058,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3020
3058
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3021
3059
  stylePaddingEnd
3022
3060
  });
3023
- 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;
3024
3062
  if (!didTargetChange) {
3025
3063
  clearPendingInitialScrollFooterLayout(ctx, {
3026
3064
  dataLength,
@@ -3298,6 +3336,7 @@ function handleInitialScrollDataChange(ctx, options) {
3298
3336
 
3299
3337
  // src/core/resetLayoutCachesForDataChange.ts
3300
3338
  function resetLayoutCachesForDataChange(state) {
3339
+ state.positionsAreCurrent = false;
3301
3340
  state.indexByKey.clear();
3302
3341
  state.idCache.length = 0;
3303
3342
  state.positions.length = 0;
@@ -3578,7 +3617,7 @@ function updateItemPositions(ctx, dataChanged, {
3578
3617
  scrollBottomBuffered: -1,
3579
3618
  startIndex: 0
3580
3619
  }) {
3581
- var _a3, _b, _c, _d, _e;
3620
+ var _a3, _b, _c, _d, _e, _f;
3582
3621
  const state = ctx.state;
3583
3622
  const hasPositionListeners = ctx.positionListeners.size > 0;
3584
3623
  const {
@@ -3594,6 +3633,11 @@ function updateItemPositions(ctx, dataChanged, {
3594
3633
  const dataLength = data.length;
3595
3634
  const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
3596
3635
  const hasColumns = numColumns > 1;
3636
+ const canReuseEstimates = !doMVCP || !state.didContainersLayout && Object.keys(state.averageSizes).length === 0;
3637
+ if (state.positionsAreCurrent && !dataChanged && !forceFullUpdate && state.positionRecalculationStartIndex === void 0 && !scrollingTo && !hasColumns && !snapToIndices && positions.length === dataLength && canReuseEstimates) {
3638
+ return;
3639
+ }
3640
+ state.positionsAreCurrent = false;
3597
3641
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
3598
3642
  const extraData = peek$(ctx, "extraData");
3599
3643
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
@@ -3708,6 +3752,10 @@ function updateItemPositions(ctx, dataChanged, {
3708
3752
  }
3709
3753
  if (!didBreakEarly) {
3710
3754
  updateTotalSize(ctx);
3755
+ state.positionsAreCurrent = !hasColumns;
3756
+ if (startIndex <= ((_f = state.positionRecalculationStartIndex) != null ? _f : -1)) {
3757
+ state.positionRecalculationStartIndex = void 0;
3758
+ }
3711
3759
  }
3712
3760
  if (snapToIndices) {
3713
3761
  updateSnapToOffsets(ctx);
@@ -4270,7 +4318,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4270
4318
  }
4271
4319
  function trackVisibleRange(range, i, top, size, scroll, scrollBottom) {
4272
4320
  let didPassVisibleEnd = false;
4273
- if (range.startNoBuffer === null && top + size > scroll) {
4321
+ if (range.startNoBuffer === null && top + size > scroll && top <= scrollBottom) {
4274
4322
  range.startNoBuffer = i;
4275
4323
  }
4276
4324
  if (range.firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
@@ -4336,7 +4384,7 @@ function findFirstVisibleIndexInCachedRange(ctx, scroll) {
4336
4384
  return null;
4337
4385
  }
4338
4386
  function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, scrollLength, scroll, scrollBottom) {
4339
- var _a3, _b;
4387
+ var _a3, _b, _c, _d;
4340
4388
  const state = ctx.state;
4341
4389
  const {
4342
4390
  endBuffered,
@@ -4370,14 +4418,14 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4370
4418
  startNoBuffer: visibleRange.startNoBuffer
4371
4419
  });
4372
4420
  maybeEmitFirstVisibleItemChanged(state, visibleRange.startNoBuffer);
4373
- if (visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4421
+ if (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4374
4422
  updateViewableItems(
4375
4423
  state,
4376
4424
  ctx,
4377
4425
  viewabilityConfigCallbackPairs,
4378
4426
  scrollLength,
4379
- visibleRange.startNoBuffer,
4380
- visibleRange.endNoBuffer,
4427
+ (_c = visibleRange.startNoBuffer) != null ? _c : 0,
4428
+ (_d = visibleRange.endNoBuffer) != null ? _d : -1,
4381
4429
  startBuffered,
4382
4430
  endBuffered
4383
4431
  );
@@ -4386,7 +4434,7 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4386
4434
  function calculateItemsInView(ctx, params = {}) {
4387
4435
  const state = ctx.state;
4388
4436
  batchedUpdates(() => {
4389
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
4437
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
4390
4438
  const {
4391
4439
  columns,
4392
4440
  containerItemKeys,
@@ -4438,7 +4486,7 @@ function calculateItemsInView(ctx, params = {}) {
4438
4486
  scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4439
4487
  scrollAdjustPad = scrollAdjustPending - topPad;
4440
4488
  scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4441
- if (scroll + scrollLength > totalSize) {
4489
+ if (!state.props.hasExternalScroll && scroll + scrollLength > totalSize) {
4442
4490
  scroll = Math.max(0, totalSize - scrollLength);
4443
4491
  }
4444
4492
  };
@@ -4465,7 +4513,11 @@ function calculateItemsInView(ctx, params = {}) {
4465
4513
  let stickyState = dataChanged ? void 0 : resolveStickyState();
4466
4514
  let scrollBufferTop = drawDistance;
4467
4515
  let scrollBufferBottom = drawDistance;
4468
- if (speed > 0 || speed === 0 && scroll < Math.max(50, drawDistance)) {
4516
+ if (speed !== 0) {
4517
+ state.scrollBufferDirection = speed > 0 ? 1 : -1;
4518
+ }
4519
+ const bufferForward = state.scrollBufferDirection === 1 || state.scrollBufferDirection === void 0 && scroll < Math.max(50, drawDistance);
4520
+ if (bufferForward) {
4469
4521
  scrollBufferTop = drawDistance * 0.5;
4470
4522
  scrollBufferBottom = drawDistance * 1.5;
4471
4523
  } else {
@@ -4475,8 +4527,8 @@ function calculateItemsInView(ctx, params = {}) {
4475
4527
  const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4476
4528
  const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4477
4529
  const updateScrollRange = () => {
4478
- const scrollStart = Math.max(0, scroll);
4479
- const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4530
+ const scrollStart = state.props.hasExternalScroll ? scroll : Math.max(0, scroll);
4531
+ const overscrollBeforeContent = state.props.hasExternalScroll ? 0 : Math.max(0, -nativeScrollState);
4480
4532
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4481
4533
  scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4482
4534
  scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
@@ -4605,7 +4657,7 @@ function calculateItemsInView(ctx, params = {}) {
4605
4657
  nextTop = top;
4606
4658
  }
4607
4659
  }
4608
- if (visibleRange.startNoBuffer !== null) {
4660
+ if (startBuffered !== null) {
4609
4661
  if (top <= scrollBottomBuffered) {
4610
4662
  endBuffered = i;
4611
4663
  if (scrollBottomBuffered > totalSize) {
@@ -4616,6 +4668,8 @@ function calculateItemsInView(ctx, params = {}) {
4616
4668
  } else {
4617
4669
  foundEnd = true;
4618
4670
  }
4671
+ } else if (top > scrollBottomBuffered) {
4672
+ foundEnd = true;
4619
4673
  }
4620
4674
  }
4621
4675
  }
@@ -4633,6 +4687,12 @@ function calculateItemsInView(ctx, params = {}) {
4633
4687
  bottom: nextBottom,
4634
4688
  top: nextTop
4635
4689
  };
4690
+ } else if (enableScrollForNextCalculateItemsInView && state.props.hasExternalScroll) {
4691
+ if (scrollBottomBuffered < 0) {
4692
+ state.scrollForNextCalculateItemsInView = { bottom: 0, top: null };
4693
+ } else if (scrollTopBuffered > totalSize) {
4694
+ state.scrollForNextCalculateItemsInView = { bottom: null, top: totalSize };
4695
+ }
4636
4696
  }
4637
4697
  let numContainers = prevNumContainers;
4638
4698
  const pendingRemoval = [];
@@ -4656,7 +4716,9 @@ function calculateItemsInView(ctx, params = {}) {
4656
4716
  }
4657
4717
  const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4658
4718
  const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4659
- if (startBuffered !== null && endBuffered !== null) {
4719
+ if (startBuffered !== null && endBuffered !== null || alwaysRenderIndicesArr.length || hasScrollTargetPinnedRange) {
4720
+ const allocationStart = startBuffered != null ? startBuffered : 0;
4721
+ const allocationEnd = endBuffered != null ? endBuffered : -1;
4660
4722
  const needNewContainers = [];
4661
4723
  const needNewContainersSet = /* @__PURE__ */ new Set();
4662
4724
  const addPinnedIndex = (index) => {
@@ -4672,7 +4734,7 @@ function calculateItemsInView(ctx, params = {}) {
4672
4734
  }
4673
4735
  }
4674
4736
  };
4675
- for (let i = startBuffered; i <= endBuffered; i++) {
4737
+ for (let i = allocationStart; i <= allocationEnd; i++) {
4676
4738
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4677
4739
  if (!containerItemKeys.has(id)) {
4678
4740
  needNewContainersSet.add(i);
@@ -4694,8 +4756,8 @@ function calculateItemsInView(ctx, params = {}) {
4694
4756
  (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4695
4757
  needNewContainers,
4696
4758
  needNewContainersSet,
4697
- startBuffered,
4698
- endBuffered
4759
+ allocationStart,
4760
+ allocationEnd
4699
4761
  );
4700
4762
  } else if (previousStickyIndex !== -1) {
4701
4763
  set$(ctx, "activeStickyIndex", -1);
@@ -4704,8 +4766,8 @@ function calculateItemsInView(ctx, params = {}) {
4704
4766
  const availableContainerAllocations = findAvailableContainers(
4705
4767
  ctx,
4706
4768
  needNewContainers,
4707
- startBuffered,
4708
- endBuffered,
4769
+ allocationStart,
4770
+ allocationEnd,
4709
4771
  pendingRemoval,
4710
4772
  protectedContainerKeys,
4711
4773
  // Initial end alignment has no scroll velocity yet. Keep its
@@ -4832,19 +4894,19 @@ function calculateItemsInView(ctx, params = {}) {
4832
4894
  handleInitialScrollLayoutReady(ctx);
4833
4895
  }
4834
4896
  }
4835
- if (viewabilityConfigCallbackPairs && visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4897
+ if (viewabilityConfigCallbackPairs && (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null)) {
4836
4898
  updateViewableItems(
4837
4899
  ctx.state,
4838
4900
  ctx,
4839
4901
  viewabilityConfigCallbackPairs,
4840
4902
  scrollLength,
4841
- visibleRange.startNoBuffer,
4842
- visibleRange.endNoBuffer,
4843
- startBuffered != null ? startBuffered : visibleRange.startNoBuffer,
4844
- endBuffered != null ? endBuffered : visibleRange.endNoBuffer
4903
+ (_u = visibleRange.startNoBuffer) != null ? _u : 0,
4904
+ (_v = visibleRange.endNoBuffer) != null ? _v : -1,
4905
+ (_w = startBuffered != null ? startBuffered : visibleRange.startNoBuffer) != null ? _w : 0,
4906
+ (_x = endBuffered != null ? endBuffered : visibleRange.endNoBuffer) != null ? _x : -1
4845
4907
  );
4846
4908
  }
4847
- (_u = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _u.call(stickyState);
4909
+ (_y = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _y.call(stickyState);
4848
4910
  });
4849
4911
  }
4850
4912
 
@@ -5139,6 +5201,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notif
5139
5201
  return 0;
5140
5202
  }
5141
5203
  const size = Platform.OS === "web" ? Math.round(rawSize) : roundSize(rawSize);
5204
+ if (prevSizeKnown !== size) state.positionsAreCurrent = false;
5142
5205
  sizesKnown.set(itemKey, size);
5143
5206
  if (fixedItemSize === void 0 && size > 0) {
5144
5207
  itemType != null ? itemType : itemType = getItemType ? (_b = getItemType(itemData, index)) != null ? _b : "" : "";
@@ -6239,6 +6302,7 @@ var ListComponent = typedMemo(function ListComponent2({
6239
6302
  stickyHeaderConfig,
6240
6303
  stickyHeaderIndices,
6241
6304
  useWindowScroll = false,
6305
+ scrollElement,
6242
6306
  ...rest
6243
6307
  }) {
6244
6308
  const ctx = useStateContext();
@@ -6304,7 +6368,7 @@ var ListComponent = typedMemo(function ListComponent2({
6304
6368
  SnapOrScroll,
6305
6369
  {
6306
6370
  ...rest,
6307
- ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag, onScrollEndDrag: onInternalScrollEndDrag },
6371
+ ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, scrollElement, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag, onScrollEndDrag: onInternalScrollEndDrag },
6308
6372
  contentContainerStyle: [
6309
6373
  horizontal ? { height: "100%" } : {},
6310
6374
  contentContainerStyle,
@@ -6344,7 +6408,15 @@ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
6344
6408
  var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
6345
6409
  function useDevChecksImpl(props) {
6346
6410
  const ctx = useStateContext();
6347
- const { anchoredEndSpace, childrenMode, keyExtractor, numColumns, renderScrollComponent, useWindowScroll } = props;
6411
+ const {
6412
+ anchoredEndSpace,
6413
+ childrenMode,
6414
+ keyExtractor,
6415
+ numColumns,
6416
+ renderScrollComponent,
6417
+ scrollElement,
6418
+ useWindowScroll
6419
+ } = props;
6348
6420
  const hasAnchoredEndSpace = !!anchoredEndSpace;
6349
6421
  useEffect(() => {
6350
6422
  if (hasAnchoredEndSpace && (numColumns != null ? numColumns : 1) > 1) {
@@ -6355,13 +6427,13 @@ function useDevChecksImpl(props) {
6355
6427
  }
6356
6428
  }, [hasAnchoredEndSpace, numColumns]);
6357
6429
  useEffect(() => {
6358
- if (useWindowScroll && renderScrollComponent) {
6430
+ if ((useWindowScroll || scrollElement !== void 0) && renderScrollComponent) {
6359
6431
  warnDevOnce(
6360
6432
  "useWindowScrollRenderScrollComponent",
6361
- "useWindowScroll is not supported when renderScrollComponent is provided."
6433
+ "useWindowScroll and scrollElement are not supported when renderScrollComponent is provided."
6362
6434
  );
6363
6435
  }
6364
- }, [renderScrollComponent, useWindowScroll]);
6436
+ }, [renderScrollComponent, scrollElement, useWindowScroll]);
6365
6437
  useEffect(() => {
6366
6438
  if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
6367
6439
  warnDevOnce(
@@ -6373,8 +6445,8 @@ function useDevChecksImpl(props) {
6373
6445
  useEffect(() => {
6374
6446
  const state = ctx.state;
6375
6447
  const dataLength = state.props.data.length;
6376
- const useWindowScrollResolved = state.props.useWindowScroll;
6377
- if (Platform.OS !== "web" || useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
6448
+ const usesExternalScroll = state.props.hasExternalScroll;
6449
+ if (Platform.OS !== "web" || usesExternalScroll || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
6378
6450
  return;
6379
6451
  }
6380
6452
  const warnIfUnboundedOuterSize = () => {
@@ -6538,7 +6610,8 @@ function doInitialAllocateContainers(ctx) {
6538
6610
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
6539
6611
  });
6540
6612
  } else {
6541
- calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
6613
+ const reuseInitialPositions = !state.didLoad && state.positionsAreCurrent && !state.didDataChange && !state.didColumnsChange;
6614
+ calculateItemsInView(ctx, { dataChanged: !reuseInitialPositions, doMVCP: true });
6542
6615
  }
6543
6616
  }
6544
6617
  return true;
@@ -6577,12 +6650,14 @@ function handleLayout(ctx, layoutParam, setCanRender) {
6577
6650
  updateContentMetricsState(ctx);
6578
6651
  state.lastBatchingAction = Date.now();
6579
6652
  state.scrollForNextCalculateItemsInView = void 0;
6580
- if (scrollLength > 0) {
6581
- doInitialAllocateContainers(ctx);
6582
- }
6583
- if (needsCalculate) {
6653
+ const didAllocate = scrollLength > 0 && doInitialAllocateContainers(ctx);
6654
+ if (needsCalculate && (!didAllocate || state.initialScroll)) {
6584
6655
  calculateItemsInView(ctx, { doMVCP: true });
6585
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
+ }
6586
6661
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
6587
6662
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
6588
6663
  }
@@ -6662,7 +6737,7 @@ function onScroll(ctx, event) {
6662
6737
  }
6663
6738
  state.lastNativeScroll = newScroll;
6664
6739
  state.lastNativeScrollTime = Date.now();
6665
- if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
6740
+ if (!state.props.hasExternalScroll && state.scrollingTo && state.scrollingTo.offset >= newScroll) {
6666
6741
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
6667
6742
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
6668
6743
  newScroll = maxOffset;
@@ -7445,6 +7520,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7445
7520
  stickyHeaderIndices: stickyHeaderIndicesProp,
7446
7521
  style: styleProp,
7447
7522
  useWindowScroll = false,
7523
+ scrollElement,
7448
7524
  viewabilityConfig,
7449
7525
  viewabilityConfigCallbackPairs,
7450
7526
  ...rest
@@ -7500,9 +7576,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7500
7576
  viewPosition: 1
7501
7577
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7502
7578
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7503
- preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7504
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7505
- 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
7506
7583
  } : {
7507
7584
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7508
7585
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
@@ -7539,7 +7616,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7539
7616
  dataVersion,
7540
7617
  keyExtractor
7541
7618
  ]);
7542
- const useWindowScrollResolved = Platform.OS === "web" && !!useWindowScroll && !renderScrollComponent;
7619
+ const scrollElementResolved = Platform.OS === "web" && !renderScrollComponent ? scrollElement : void 0;
7620
+ const useWindowScrollResolved = Platform.OS === "web" && !!useWindowScroll && scrollElementResolved === void 0 && !renderScrollComponent;
7621
+ const hasExternalScroll = useWindowScrollResolved || scrollElementResolved !== void 0;
7543
7622
  const refState = useRef(void 0);
7544
7623
  const hasOverrideItemLayout = !!overrideItemLayout;
7545
7624
  const prevHasOverrideItemLayout = useRef(hasOverrideItemLayout);
@@ -7677,6 +7756,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7677
7756
  estimatedItemSize,
7678
7757
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7679
7758
  getItemType: useWrapIfItem(getItemType),
7759
+ hasExternalScroll,
7680
7760
  hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7681
7761
  horizontal: !!horizontal,
7682
7762
  itemsAreEqual,
@@ -7873,9 +7953,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7873
7953
  const { onLayout } = useOnLayoutSync({
7874
7954
  onLayoutChange,
7875
7955
  onLayoutProp,
7876
- ref: refScroller
7877
- // the type of ScrollView doesn't include measure?
7878
- });
7956
+ ref: refScroller});
7879
7957
  useLayoutEffect(() => {
7880
7958
  if (snapToIndices) {
7881
7959
  updateSnapToOffsets(ctx);
@@ -8043,6 +8121,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8043
8121
  refScrollView: combinedRef,
8044
8122
  renderScrollComponent,
8045
8123
  scrollAdjustHandler: (_s = refState.current) == null ? void 0 : _s.scrollAdjustHandler,
8124
+ scrollElement: scrollElementResolved,
8046
8125
  scrollEventThrottle: 0,
8047
8126
  snapToIndices,
8048
8127
  stickyHeaderIndices,
@@ -6,11 +6,14 @@ type ScrollEventTarget = Window | HTMLElement;
6
6
  interface ScrollViewMethods {
7
7
  getBoundingClientRect(): DOMRect | null | undefined;
8
8
  getCurrentScrollOffset(): number;
9
+ getRawScrollOffset(): number;
9
10
  getMaxScrollOffset(): number;
10
- getScrollableNode(): HTMLElement;
11
+ getScrollableNode(): HTMLElement | null;
11
12
  getScrollEventTarget(): ScrollEventTarget | null;
12
13
  getScrollResponder(): HTMLElement | null;
13
14
  isWindowScroll?(): boolean;
15
+ getContentNode?(): HTMLElement | null;
16
+ isScrollInRange?(): boolean;
14
17
  scrollBy(x: number, y: number): void;
15
18
  scrollTo(options: {
16
19
  x?: number;
@@ -249,6 +252,8 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
249
252
  index: number;
250
253
  viewOffset?: number | undefined;
251
254
  viewPosition?: number | undefined;
255
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
256
+ viewPositionFallback?: "start" | "end";
252
257
  };
253
258
  /**
254
259
  * Initial scroll position in pixels.
@@ -675,6 +680,8 @@ type LegendListRef$1 = {
675
680
  index: number;
676
681
  viewOffset?: number | undefined;
677
682
  viewPosition?: number | undefined;
683
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
684
+ viewPositionFallback?: "start" | "end";
678
685
  }): Promise<void>;
679
686
  /**
680
687
  * Scrolls to a specific item in the list.
@@ -689,6 +696,8 @@ type LegendListRef$1 = {
689
696
  item: any;
690
697
  viewOffset?: number | undefined;
691
698
  viewPosition?: number | undefined;
699
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
700
+ viewPositionFallback?: "start" | "end";
692
701
  }): Promise<void>;
693
702
  /**
694
703
  * Scrolls to a specific offset in pixels.
@@ -787,6 +796,8 @@ interface ScrollIndexWithOffset {
787
796
  index: number;
788
797
  viewOffset?: number;
789
798
  viewPosition?: number;
799
+ /** Alignment used only when the item exceeds the viewport. Omit to preserve viewPosition. */
800
+ viewPositionFallback?: "start" | "end";
790
801
  }
791
802
  interface ScrollIndexWithOffsetPosition extends ScrollIndexWithOffset {
792
803
  viewPosition?: number;
@@ -836,6 +847,13 @@ type ScrollViewPropsWeb = Omit<LooseScrollViewProps, "style" | "contentContainer
836
847
  onLayout?: (event: LooseLayoutChangeEvent) => void;
837
848
  };
838
849
  type LegendListPropsOverrides<ItemT, TItemType extends string | undefined> = Omit<LegendListPropsBase<ItemT, ScrollViewPropsWeb, TItemType>, "anchoredEndSpace" | "refScrollView" | "renderScrollComponent" | "ListHeaderComponentStyle" | "ListFooterComponentStyle" | "onRefresh" | "progressViewOffset" | "refreshing"> & {
850
+ /**
851
+ * Web only: use an ancestor element's scrollbar instead of creating a scrollable list.
852
+ * Pass a state-backed callback ref value so changes to the element are observed.
853
+ * null waits for the element to mount; undefined uses the list's own scrollbar.
854
+ * Takes precedence over useWindowScroll. Not supported with renderScrollComponent.
855
+ */
856
+ scrollElement?: HTMLElement | null;
839
857
  anchoredEndSpace?: AnchoredEndSpaceConfig;
840
858
  refScrollView?: Ref<HTMLElement | ScrollViewMethods>;
841
859
  ListHeaderComponentStyle?: CSSProperties | undefined;
@@ -845,7 +863,7 @@ type LegendListProps<ItemT = any, TItemType extends string | undefined = string
845
863
  type LegendListRef = Omit<LegendListRef$1, "getAnimatableRef" | "getNativeScrollRef" | "getScrollableNode" | "getScrollResponder"> & {
846
864
  getAnimatableRef(): HTMLElement | ScrollViewMethods;
847
865
  getNativeScrollRef(): HTMLElement | ScrollViewMethods;
848
- getScrollableNode(): HTMLElement;
866
+ getScrollableNode(): HTMLElement | null;
849
867
  getScrollResponder(): HTMLElement | null;
850
868
  };
851
869
  type LegendListState = Omit<LegendListState$1, "elementAtIndex"> & {