@legendapp/list 3.4.0 → 3.5.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.mjs CHANGED
@@ -880,8 +880,14 @@ function scheduleFullDrawDistancePrewarm(ctx) {
880
880
  return;
881
881
  }
882
882
  state.scheduledWork.frame(() => {
883
- var _a3;
884
- return (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
883
+ var _a3, _b, _c;
884
+ if (state.props.hasExternalScroll && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust) {
885
+ const offset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getRawScrollOffset) == null ? void 0 : _b.call(_a3);
886
+ if (offset !== void 0) {
887
+ state.scroll = state.scrollPending = state.props.horizontal ? toLogicalHorizontalOffset(state, offset, state.totalSize) : offset;
888
+ }
889
+ }
890
+ (_c = state.triggerCalculateItemsInView) == null ? void 0 : _c.call(state);
885
891
  }, "fullDrawDistancePrewarm");
886
892
  }
887
893
 
@@ -894,6 +900,9 @@ function resetInitialRenderState(ctx, {
894
900
  if (resetLayout) {
895
901
  state.didContainersLayout = false;
896
902
  state.queuedInitialLayout = false;
903
+ state.scrollBufferDirection = void 0;
904
+ state.scrollHistory.length = 0;
905
+ state.scheduledWork.cancel("renderRangeProjection");
897
906
  state.edgeReachedGate = void 0;
898
907
  state.isStartReached = null;
899
908
  state.isEndReached = null;
@@ -1120,6 +1129,7 @@ function setSize(ctx, itemKey, size, notifyTotalSize = true) {
1120
1129
  const previousSize = sizes.get(itemKey);
1121
1130
  const diff = previousSize !== void 0 ? size - previousSize : size;
1122
1131
  if (diff !== 0) {
1132
+ state.positionsAreCurrent = false;
1123
1133
  addTotalSize(ctx, itemKey, diff, notifyTotalSize);
1124
1134
  }
1125
1135
  sizes.set(itemKey, size);
@@ -1180,6 +1190,7 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
1180
1190
  const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
1181
1191
  if (fixedSize !== void 0) {
1182
1192
  size = fixedSize + ctx.scrollAxisGap;
1193
+ state.positionsAreCurrent = false;
1183
1194
  state.sizesKnown.set(key, size);
1184
1195
  }
1185
1196
  }
@@ -1761,6 +1772,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1761
1772
  state.scrollTime = currentTime;
1762
1773
  const scrollDelta = Math.abs(newScroll - prevScroll);
1763
1774
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1775
+ if (isUserScrollEvent) {
1776
+ state.scrollBufferDirection = newScroll > prevScroll ? 1 : -1;
1777
+ }
1764
1778
  const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1765
1779
  if (didCancelMaintainScrollAtEnd) {
1766
1780
  finishMaintainScrollAtEnd(ctx);
@@ -1840,10 +1854,10 @@ function finishMaintainScrollAtEnd(ctx) {
1840
1854
  }
1841
1855
  }
1842
1856
  function doMaintainScrollAtEnd(ctx, options) {
1843
- var _a3, _b, _c, _d, _e, _f;
1857
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
1844
1858
  const state = ctx.state;
1845
1859
  let didMaintain = false;
1846
- if (state.isDragging) {
1860
+ if (state.isDragging || ((_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.isScrollInRange) == null ? void 0 : _b.call(_a3)) === false) {
1847
1861
  finishMaintainScrollAtEnd(ctx);
1848
1862
  } else {
1849
1863
  const {
@@ -1855,11 +1869,11 @@ function doMaintainScrollAtEnd(ctx, options) {
1855
1869
  const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1856
1870
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1857
1871
  const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1858
- 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);
1872
+ 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);
1859
1873
  if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1860
1874
  const scroller = state.refScroller.current;
1861
- const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1862
- const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1875
+ const nativeMax = (_e = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _e.call(scroller);
1876
+ const observed = (_f = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _f.call(scroller);
1863
1877
  if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1864
1878
  state.pendingMaintainScrollAtEnd = true;
1865
1879
  state.scrollPending = observed;
@@ -1871,13 +1885,13 @@ function doMaintainScrollAtEnd(ctx, options) {
1871
1885
  } else if (pendingNativeMVCPAdjust) {
1872
1886
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1873
1887
  } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1874
- if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1888
+ if (immediate && ((_g = state.scrollingTo) == null ? void 0 : _g.isScrollToEnd) && !state.scrollingTo.animated) {
1875
1889
  state.pendingMaintainScrollAtEnd = false;
1876
1890
  ctx.scrollToEnd({ animated: false });
1877
1891
  didMaintain = true;
1878
1892
  } else {
1879
1893
  state.pendingMaintainScrollAtEnd = true;
1880
- (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1894
+ (_h = state.maintainingScrollAtEnd) != null ? _h : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1881
1895
  }
1882
1896
  } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1883
1897
  state.pendingMaintainScrollAtEnd = false;
@@ -1891,8 +1905,9 @@ function doMaintainScrollAtEnd(ctx, options) {
1891
1905
  const scrollAtRequest = state.scroll;
1892
1906
  state.maintainingScrollAtEnd = pendingState;
1893
1907
  const run = () => {
1908
+ var _a4, _b2;
1894
1909
  if (state.maintainingScrollAtEnd === pendingState) {
1895
- if (state.isDragging) {
1910
+ if (state.isDragging || ((_b2 = (_a4 = state.refScroller.current) == null ? void 0 : _a4.isScrollInRange) == null ? void 0 : _b2.call(_a4)) === false) {
1896
1911
  finishMaintainScrollAtEnd(ctx);
1897
1912
  } else {
1898
1913
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
@@ -3335,6 +3350,7 @@ function handleInitialScrollDataChange(ctx, options) {
3335
3350
 
3336
3351
  // src/core/resetLayoutCachesForDataChange.ts
3337
3352
  function resetLayoutCachesForDataChange(state) {
3353
+ state.positionsAreCurrent = false;
3338
3354
  state.indexByKey.clear();
3339
3355
  state.idCache.length = 0;
3340
3356
  state.positions.length = 0;
@@ -3615,7 +3631,7 @@ function updateItemPositions(ctx, dataChanged, {
3615
3631
  scrollBottomBuffered: -1,
3616
3632
  startIndex: 0
3617
3633
  }) {
3618
- var _a3, _b, _c, _d, _e;
3634
+ var _a3, _b, _c, _d, _e, _f;
3619
3635
  const state = ctx.state;
3620
3636
  const hasPositionListeners = ctx.positionListeners.size > 0;
3621
3637
  const {
@@ -3631,6 +3647,11 @@ function updateItemPositions(ctx, dataChanged, {
3631
3647
  const dataLength = data.length;
3632
3648
  const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
3633
3649
  const hasColumns = numColumns > 1;
3650
+ const canReuseEstimates = !doMVCP || !state.didContainersLayout && Object.keys(state.averageSizes).length === 0;
3651
+ if (state.positionsAreCurrent && !dataChanged && !forceFullUpdate && state.positionRecalculationStartIndex === void 0 && !scrollingTo && !hasColumns && !snapToIndices && positions.length === dataLength && canReuseEstimates) {
3652
+ return;
3653
+ }
3654
+ state.positionsAreCurrent = false;
3634
3655
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
3635
3656
  const extraData = peek$(ctx, "extraData");
3636
3657
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
@@ -3745,6 +3766,10 @@ function updateItemPositions(ctx, dataChanged, {
3745
3766
  }
3746
3767
  if (!didBreakEarly) {
3747
3768
  updateTotalSize(ctx);
3769
+ state.positionsAreCurrent = !hasColumns;
3770
+ if (startIndex <= ((_f = state.positionRecalculationStartIndex) != null ? _f : -1)) {
3771
+ state.positionRecalculationStartIndex = void 0;
3772
+ }
3748
3773
  }
3749
3774
  if (snapToIndices) {
3750
3775
  updateSnapToOffsets(ctx);
@@ -4307,7 +4332,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4307
4332
  }
4308
4333
  function trackVisibleRange(range, i, top, size, scroll, scrollBottom) {
4309
4334
  let didPassVisibleEnd = false;
4310
- if (range.startNoBuffer === null && top + size > scroll) {
4335
+ if (range.startNoBuffer === null && top + size > scroll && top <= scrollBottom) {
4311
4336
  range.startNoBuffer = i;
4312
4337
  }
4313
4338
  if (range.firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
@@ -4373,7 +4398,7 @@ function findFirstVisibleIndexInCachedRange(ctx, scroll) {
4373
4398
  return null;
4374
4399
  }
4375
4400
  function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, scrollLength, scroll, scrollBottom) {
4376
- var _a3, _b;
4401
+ var _a3, _b, _c, _d;
4377
4402
  const state = ctx.state;
4378
4403
  const {
4379
4404
  endBuffered,
@@ -4407,14 +4432,14 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4407
4432
  startNoBuffer: visibleRange.startNoBuffer
4408
4433
  });
4409
4434
  maybeEmitFirstVisibleItemChanged(state, visibleRange.startNoBuffer);
4410
- if (visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4435
+ if (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4411
4436
  updateViewableItems(
4412
4437
  state,
4413
4438
  ctx,
4414
4439
  viewabilityConfigCallbackPairs,
4415
4440
  scrollLength,
4416
- visibleRange.startNoBuffer,
4417
- visibleRange.endNoBuffer,
4441
+ (_c = visibleRange.startNoBuffer) != null ? _c : 0,
4442
+ (_d = visibleRange.endNoBuffer) != null ? _d : -1,
4418
4443
  startBuffered,
4419
4444
  endBuffered
4420
4445
  );
@@ -4423,7 +4448,7 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4423
4448
  function calculateItemsInView(ctx, params = {}) {
4424
4449
  const state = ctx.state;
4425
4450
  batchedUpdates(() => {
4426
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
4451
+ 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;
4427
4452
  const {
4428
4453
  columns,
4429
4454
  containerItemKeys,
@@ -4475,7 +4500,7 @@ function calculateItemsInView(ctx, params = {}) {
4475
4500
  scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4476
4501
  scrollAdjustPad = scrollAdjustPending - topPad;
4477
4502
  scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4478
- if (scroll + scrollLength > totalSize) {
4503
+ if (!state.props.hasExternalScroll && scroll + scrollLength > totalSize) {
4479
4504
  scroll = Math.max(0, totalSize - scrollLength);
4480
4505
  }
4481
4506
  };
@@ -4502,7 +4527,11 @@ function calculateItemsInView(ctx, params = {}) {
4502
4527
  let stickyState = dataChanged ? void 0 : resolveStickyState();
4503
4528
  let scrollBufferTop = drawDistance;
4504
4529
  let scrollBufferBottom = drawDistance;
4505
- if (speed > 0 || speed === 0 && scroll < Math.max(50, drawDistance)) {
4530
+ if (speed !== 0) {
4531
+ state.scrollBufferDirection = speed > 0 ? 1 : -1;
4532
+ }
4533
+ const bufferForward = state.scrollBufferDirection === 1 || state.scrollBufferDirection === void 0 && scroll < Math.max(50, drawDistance);
4534
+ if (bufferForward) {
4506
4535
  scrollBufferTop = drawDistance * 0.5;
4507
4536
  scrollBufferBottom = drawDistance * 1.5;
4508
4537
  } else {
@@ -4512,8 +4541,8 @@ function calculateItemsInView(ctx, params = {}) {
4512
4541
  const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4513
4542
  const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4514
4543
  const updateScrollRange = () => {
4515
- const scrollStart = Math.max(0, scroll);
4516
- const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4544
+ const scrollStart = state.props.hasExternalScroll ? scroll : Math.max(0, scroll);
4545
+ const overscrollBeforeContent = state.props.hasExternalScroll ? 0 : Math.max(0, -nativeScrollState);
4517
4546
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4518
4547
  scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4519
4548
  scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
@@ -4642,7 +4671,7 @@ function calculateItemsInView(ctx, params = {}) {
4642
4671
  nextTop = top;
4643
4672
  }
4644
4673
  }
4645
- if (visibleRange.startNoBuffer !== null) {
4674
+ if (startBuffered !== null) {
4646
4675
  if (top <= scrollBottomBuffered) {
4647
4676
  endBuffered = i;
4648
4677
  if (scrollBottomBuffered > totalSize) {
@@ -4653,6 +4682,8 @@ function calculateItemsInView(ctx, params = {}) {
4653
4682
  } else {
4654
4683
  foundEnd = true;
4655
4684
  }
4685
+ } else if (top > scrollBottomBuffered) {
4686
+ foundEnd = true;
4656
4687
  }
4657
4688
  }
4658
4689
  }
@@ -4670,6 +4701,12 @@ function calculateItemsInView(ctx, params = {}) {
4670
4701
  bottom: nextBottom,
4671
4702
  top: nextTop
4672
4703
  };
4704
+ } else if (enableScrollForNextCalculateItemsInView && state.props.hasExternalScroll) {
4705
+ if (scrollBottomBuffered < 0) {
4706
+ state.scrollForNextCalculateItemsInView = { bottom: 0, top: null };
4707
+ } else if (scrollTopBuffered > totalSize) {
4708
+ state.scrollForNextCalculateItemsInView = { bottom: null, top: totalSize };
4709
+ }
4673
4710
  }
4674
4711
  let numContainers = prevNumContainers;
4675
4712
  const pendingRemoval = [];
@@ -4693,7 +4730,9 @@ function calculateItemsInView(ctx, params = {}) {
4693
4730
  }
4694
4731
  const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4695
4732
  const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4696
- if (startBuffered !== null && endBuffered !== null) {
4733
+ if (startBuffered !== null && endBuffered !== null || alwaysRenderIndicesArr.length || hasScrollTargetPinnedRange) {
4734
+ const allocationStart = startBuffered != null ? startBuffered : 0;
4735
+ const allocationEnd = endBuffered != null ? endBuffered : -1;
4697
4736
  const needNewContainers = [];
4698
4737
  const needNewContainersSet = /* @__PURE__ */ new Set();
4699
4738
  const addPinnedIndex = (index) => {
@@ -4709,7 +4748,7 @@ function calculateItemsInView(ctx, params = {}) {
4709
4748
  }
4710
4749
  }
4711
4750
  };
4712
- for (let i = startBuffered; i <= endBuffered; i++) {
4751
+ for (let i = allocationStart; i <= allocationEnd; i++) {
4713
4752
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4714
4753
  if (!containerItemKeys.has(id)) {
4715
4754
  needNewContainersSet.add(i);
@@ -4731,8 +4770,8 @@ function calculateItemsInView(ctx, params = {}) {
4731
4770
  (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4732
4771
  needNewContainers,
4733
4772
  needNewContainersSet,
4734
- startBuffered,
4735
- endBuffered
4773
+ allocationStart,
4774
+ allocationEnd
4736
4775
  );
4737
4776
  } else if (previousStickyIndex !== -1) {
4738
4777
  set$(ctx, "activeStickyIndex", -1);
@@ -4741,8 +4780,8 @@ function calculateItemsInView(ctx, params = {}) {
4741
4780
  const availableContainerAllocations = findAvailableContainers(
4742
4781
  ctx,
4743
4782
  needNewContainers,
4744
- startBuffered,
4745
- endBuffered,
4783
+ allocationStart,
4784
+ allocationEnd,
4746
4785
  pendingRemoval,
4747
4786
  protectedContainerKeys,
4748
4787
  // Initial end alignment has no scroll velocity yet. Keep its
@@ -4869,19 +4908,19 @@ function calculateItemsInView(ctx, params = {}) {
4869
4908
  handleInitialScrollLayoutReady(ctx);
4870
4909
  }
4871
4910
  }
4872
- if (viewabilityConfigCallbackPairs && visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4911
+ if (viewabilityConfigCallbackPairs && (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null)) {
4873
4912
  updateViewableItems(
4874
4913
  ctx.state,
4875
4914
  ctx,
4876
4915
  viewabilityConfigCallbackPairs,
4877
4916
  scrollLength,
4878
- visibleRange.startNoBuffer,
4879
- visibleRange.endNoBuffer,
4880
- startBuffered != null ? startBuffered : visibleRange.startNoBuffer,
4881
- endBuffered != null ? endBuffered : visibleRange.endNoBuffer
4917
+ (_u = visibleRange.startNoBuffer) != null ? _u : 0,
4918
+ (_v = visibleRange.endNoBuffer) != null ? _v : -1,
4919
+ (_w = startBuffered != null ? startBuffered : visibleRange.startNoBuffer) != null ? _w : 0,
4920
+ (_x = endBuffered != null ? endBuffered : visibleRange.endNoBuffer) != null ? _x : -1
4882
4921
  );
4883
4922
  }
4884
- (_u = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _u.call(stickyState);
4923
+ (_y = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _y.call(stickyState);
4885
4924
  });
4886
4925
  }
4887
4926
 
@@ -5160,6 +5199,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notif
5160
5199
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
5161
5200
  const prevSizeKnown = sizesKnown.get(itemKey);
5162
5201
  const size = Math.round(rawSize) ;
5202
+ if (prevSizeKnown !== size) state.positionsAreCurrent = false;
5163
5203
  sizesKnown.set(itemKey, size);
5164
5204
  if (fixedItemSize === void 0 && size > 0) {
5165
5205
  itemType != null ? itemType : itemType = getItemType ? (_b = getItemType(itemData, index)) != null ? _b : "" : "";
@@ -5580,10 +5620,19 @@ function useOnLayoutSync({
5580
5620
  measureInLayoutEffect = true,
5581
5621
  onLayoutProp,
5582
5622
  onLayoutChange,
5583
- webLayoutResync
5623
+ webLayoutResync,
5624
+ webExternalScroll = false
5584
5625
  }, deps) {
5626
+ const onExternalLayout = useCallback(
5627
+ (event) => {
5628
+ onLayoutChange(event.nativeEvent.layout, false);
5629
+ onLayoutProp == null ? void 0 : onLayoutProp(event);
5630
+ },
5631
+ [onLayoutChange, onLayoutProp]
5632
+ );
5585
5633
  useLayoutEffect(() => {
5586
5634
  var _a3, _b;
5635
+ if (webExternalScroll) return;
5587
5636
  const current = ref.current;
5588
5637
  const scrollableNode = (_b = (_a3 = current == null ? void 0 : current.getScrollableNode) == null ? void 0 : _a3.call(current)) != null ? _b : null;
5589
5638
  const element = scrollableNode || current;
@@ -5621,8 +5670,8 @@ function useOnLayoutSync({
5621
5670
  emit(toLayout(rectObserved), false);
5622
5671
  }
5623
5672
  });
5624
- }, [measureInLayoutEffect, ...deps || []]);
5625
- return {};
5673
+ }, [measureInLayoutEffect, webExternalScroll, ...deps || []]);
5674
+ return webExternalScroll ? { onLayout: onExternalLayout } : {};
5626
5675
  }
5627
5676
  function toLayout(rect) {
5628
5677
  if (!rect) {
@@ -6184,94 +6233,121 @@ function useRafCoalescer(callback) {
6184
6233
  return coalescer;
6185
6234
  }
6186
6235
 
6187
- // src/components/webConstants.ts
6188
- var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
6189
- var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
6190
- var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
6191
-
6192
6236
  // src/components/webScrollUtils.ts
6193
- function getDocumentScrollerNode() {
6194
- if (typeof document === "undefined") {
6195
- return null;
6196
- }
6197
- return document.scrollingElement || document.documentElement || document.body;
6237
+ function isWindowTarget(target) {
6238
+ return typeof window !== "undefined" && target === window;
6198
6239
  }
6199
- function getWindowScrollPosition() {
6200
- var _a3, _b, _c, _d;
6201
- if (typeof window === "undefined") {
6202
- return { x: 0, y: 0 };
6240
+ function getScrollPosition(target) {
6241
+ var _a3, _b, _c, _d, _e, _f;
6242
+ if (isWindowTarget(target)) {
6243
+ return { x: (_b = (_a3 = target.scrollX) != null ? _a3 : target.pageXOffset) != null ? _b : 0, y: (_d = (_c = target.scrollY) != null ? _c : target.pageYOffset) != null ? _d : 0 };
6203
6244
  }
6204
- return {
6205
- x: (_b = (_a3 = window.scrollX) != null ? _a3 : window.pageXOffset) != null ? _b : 0,
6206
- y: (_d = (_c = window.scrollY) != null ? _c : window.pageYOffset) != null ? _d : 0
6207
- };
6245
+ return { x: (_e = target == null ? void 0 : target.scrollLeft) != null ? _e : 0, y: (_f = target == null ? void 0 : target.scrollTop) != null ? _f : 0 };
6208
6246
  }
6209
- function getElementDocumentPosition(element, scroll) {
6210
- var _a3, _b;
6247
+ function getElementScrollPosition(element, target) {
6248
+ var _a3, _b, _c, _d, _e, _f;
6211
6249
  const rect = element == null ? void 0 : element.getBoundingClientRect();
6250
+ const scroll = getScrollPosition(target);
6251
+ const parent = target && !isWindowTarget(target) ? target : null;
6252
+ const parentRect = parent == null ? void 0 : parent.getBoundingClientRect();
6212
6253
  return {
6213
- left: ((_a3 = rect == null ? void 0 : rect.left) != null ? _a3 : 0) + scroll.x,
6214
- top: ((_b = rect == null ? void 0 : rect.top) != null ? _b : 0) + scroll.y
6254
+ left: ((_a3 = rect == null ? void 0 : rect.left) != null ? _a3 : 0) - ((_b = parentRect == null ? void 0 : parentRect.left) != null ? _b : 0) - ((_c = parent == null ? void 0 : parent.clientLeft) != null ? _c : 0) + scroll.x,
6255
+ top: ((_d = rect == null ? void 0 : rect.top) != null ? _d : 0) - ((_e = parentRect == null ? void 0 : parentRect.top) != null ? _e : 0) - ((_f = parent == null ? void 0 : parent.clientTop) != null ? _f : 0) + scroll.y
6215
6256
  };
6216
6257
  }
6217
6258
  function getContentSize2(content) {
6218
6259
  var _a3, _b;
6219
- return {
6220
- height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0,
6221
- width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0
6222
- };
6260
+ return { height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0, width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0 };
6223
6261
  }
6224
- function getScrollContentSize(scrollElement, contentElement, isWindowScroll) {
6225
- return getContentSize2(isWindowScroll ? contentElement : scrollElement);
6262
+ function getScrollContentSize(scrollElement, contentElement, externalTarget) {
6263
+ return getContentSize2(externalTarget !== void 0 ? contentElement : scrollElement);
6226
6264
  }
6227
- function getLayoutMeasurement(scrollElement, isWindowScroll, horizontal) {
6228
- var _a3, _b, _c, _d, _e, _f;
6229
- if (isWindowScroll && typeof window !== "undefined") {
6265
+ function getLayoutMeasurement(scrollElement, externalTarget, horizontal) {
6266
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
6267
+ if (externalTarget !== void 0) {
6230
6268
  const rect = scrollElement == null ? void 0 : scrollElement.getBoundingClientRect();
6269
+ const viewport = isWindowTarget(externalTarget) ? { height: externalTarget.innerHeight, width: externalTarget.innerWidth } : { height: (_a3 = externalTarget == null ? void 0 : externalTarget.clientHeight) != null ? _a3 : 0, width: (_b = externalTarget == null ? void 0 : externalTarget.clientWidth) != null ? _b : 0 };
6231
6270
  return {
6232
- // In window-scroll mode, use viewport size on the scroll axis.
6233
- height: horizontal ? (_b = (_a3 = rect == null ? void 0 : rect.height) != null ? _a3 : scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _b : window.innerHeight : window.innerHeight,
6234
- // Keep the cross-axis size list-relative to avoid inflating container measurements.
6235
- width: horizontal ? window.innerWidth : (_d = (_c = rect == null ? void 0 : rect.width) != null ? _c : scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _d : window.innerWidth
6271
+ height: horizontal ? (_d = (_c = rect == null ? void 0 : rect.height) != null ? _c : scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _d : 0 : viewport.height,
6272
+ width: horizontal ? viewport.width : (_f = (_e = rect == null ? void 0 : rect.width) != null ? _e : scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _f : 0
6236
6273
  };
6237
6274
  }
6238
- return {
6239
- height: (_e = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _e : 0,
6240
- width: (_f = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _f : 0
6241
- };
6275
+ return { height: (_g = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _g : 0, width: (_h = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _h : 0 };
6242
6276
  }
6243
6277
  function clampOffset(offset, maxOffset) {
6244
6278
  return Math.max(0, Math.min(offset, maxOffset));
6245
6279
  }
6246
- function getAxisSize(size, horizontal) {
6247
- return horizontal ? size.width : size.height;
6248
- }
6249
6280
  function getMaxOffset(contentSize, layoutMeasurement, horizontal) {
6250
- return Math.max(0, getAxisSize(contentSize, horizontal) - getAxisSize(layoutMeasurement, horizontal));
6281
+ const axis = horizontal ? "width" : "height";
6282
+ return Math.max(0, contentSize[axis] - layoutMeasurement[axis]);
6251
6283
  }
6252
- function resolveScrollableNode(scrollElement, isWindowScroll) {
6253
- return isWindowScroll ? getDocumentScrollerNode() || scrollElement : scrollElement;
6284
+ function resolveScrollableNode(scrollElement, externalTarget) {
6285
+ if (isWindowTarget(externalTarget)) {
6286
+ return document.scrollingElement || document.documentElement || document.body;
6287
+ }
6288
+ return externalTarget === void 0 ? scrollElement : externalTarget;
6254
6289
  }
6255
- function resolveScrollEventTarget(scrollElement, isWindowScroll) {
6256
- return isWindowScroll && typeof window !== "undefined" ? window : scrollElement;
6290
+ function resolveScrollEventTarget(scrollElement, externalTarget) {
6291
+ return externalTarget === void 0 ? scrollElement : externalTarget;
6257
6292
  }
6258
- function getLayoutRectangle(element, isWindowScroll, horizontal) {
6259
- const rect = element.getBoundingClientRect();
6260
- const scroll = getWindowScrollPosition();
6261
- return {
6262
- height: isWindowScroll && typeof window !== "undefined" && !horizontal ? window.innerHeight : rect.height,
6263
- width: isWindowScroll && typeof window !== "undefined" && horizontal ? window.innerWidth : rect.width,
6264
- x: isWindowScroll ? rect.left + scroll.x : rect.left,
6265
- y: isWindowScroll ? rect.top + scroll.y : rect.top
6266
- };
6293
+ function getLayoutRectangle(element, externalTarget, horizontal) {
6294
+ if (externalTarget === void 0) {
6295
+ const rect = element.getBoundingClientRect();
6296
+ return { height: rect.height, width: rect.width, x: rect.left, y: rect.top };
6297
+ }
6298
+ const { left, top } = getElementScrollPosition(element, externalTarget);
6299
+ const { height, width } = getLayoutMeasurement(element, externalTarget, horizontal);
6300
+ return { height, width, x: left, y: top };
6267
6301
  }
6268
- function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll }) {
6302
+ function resolveExternalScrollOffset({
6303
+ clampedOffset,
6304
+ horizontal,
6305
+ listPos,
6306
+ scroll
6307
+ }) {
6269
6308
  return {
6270
6309
  left: horizontal ? listPos.left + clampedOffset : scroll.x,
6271
6310
  top: horizontal ? scroll.y : listPos.top + clampedOffset
6272
6311
  };
6273
6312
  }
6274
6313
 
6314
+ // src/components/observeScrollLayout.ts
6315
+ function observeScrollLayout(element, owner, onLayout) {
6316
+ if (owner === null) return;
6317
+ const resizeObserver = new ResizeObserver(onLayout);
6318
+ const mutationObserver = owner === void 0 ? void 0 : new MutationObserver(observe);
6319
+ function observe() {
6320
+ resizeObserver.disconnect();
6321
+ mutationObserver == null ? void 0 : mutationObserver.disconnect();
6322
+ resizeObserver.observe(element);
6323
+ if (owner !== void 0) {
6324
+ for (let node = element; node && node !== owner; node = node.parentElement) {
6325
+ const parent = node.parentElement;
6326
+ if (parent) {
6327
+ resizeObserver.observe(parent);
6328
+ mutationObserver == null ? void 0 : mutationObserver.observe(parent, { childList: true });
6329
+ }
6330
+ for (let sibling = node.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
6331
+ resizeObserver.observe(sibling);
6332
+ }
6333
+ }
6334
+ }
6335
+ onLayout();
6336
+ }
6337
+ observe();
6338
+ if (isWindowTarget(owner)) owner.addEventListener("resize", onLayout);
6339
+ return () => {
6340
+ resizeObserver.disconnect();
6341
+ mutationObserver == null ? void 0 : mutationObserver.disconnect();
6342
+ if (isWindowTarget(owner)) owner.removeEventListener("resize", onLayout);
6343
+ };
6344
+ }
6345
+
6346
+ // src/components/webConstants.ts
6347
+ var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
6348
+ var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
6349
+ var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
6350
+
6275
6351
  // src/components/ListComponentScrollView.tsx
6276
6352
  var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
6277
6353
  var SCROLL_END_FALLBACK_MS = 200;
@@ -6330,6 +6406,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6330
6406
  showsVerticalScrollIndicator = true,
6331
6407
  refreshControl,
6332
6408
  useWindowScroll = false,
6409
+ scrollElement: externalScrollElement,
6333
6410
  onLayout,
6334
6411
  ...props
6335
6412
  }, ref) {
@@ -6338,31 +6415,38 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6338
6415
  const [anchoredEndSpaceSize] = useArr$(["anchoredEndSpaceSize"]);
6339
6416
  const scrollRef = useRef(null);
6340
6417
  const contentRef = useRef(null);
6341
- const isWindowScroll = useWindowScroll;
6418
+ const externalTarget = externalScrollElement === void 0 && useWindowScroll ? typeof window === "undefined" ? null : window : externalScrollElement;
6419
+ const isWindowScroll = isWindowTarget(externalTarget);
6420
+ const isExternalScroll = externalTarget !== void 0;
6342
6421
  const getScrollTarget = useCallback(
6343
- () => resolveScrollEventTarget(scrollRef.current, isWindowScroll),
6344
- [isWindowScroll]
6422
+ () => resolveScrollEventTarget(scrollRef.current, externalTarget),
6423
+ [externalTarget]
6345
6424
  );
6346
6425
  const getMaxScrollOffset = useCallback(() => {
6347
6426
  const scrollElement = scrollRef.current;
6348
- const contentSize = getScrollContentSize(scrollElement, contentRef.current, isWindowScroll);
6349
- const layoutMeasurement = getLayoutMeasurement(scrollElement, isWindowScroll, horizontal);
6427
+ const contentSize = getScrollContentSize(scrollElement, contentRef.current, externalTarget);
6428
+ const layoutMeasurement = getLayoutMeasurement(scrollElement, externalTarget, horizontal);
6350
6429
  return getMaxOffset(contentSize, layoutMeasurement, horizontal);
6351
- }, [horizontal, isWindowScroll]);
6430
+ }, [externalTarget, horizontal]);
6431
+ const getRawScrollOffset = useCallback(() => {
6432
+ var _a4;
6433
+ const element = scrollRef.current;
6434
+ if (isExternalScroll) {
6435
+ const scroll = getScrollPosition(externalTarget);
6436
+ const listPos = getElementScrollPosition(element, externalTarget);
6437
+ return horizontal ? scroll.x - listPos.left : scroll.y - listPos.top;
6438
+ }
6439
+ return (_a4 = horizontal ? element == null ? void 0 : element.scrollLeft : element == null ? void 0 : element.scrollTop) != null ? _a4 : 0;
6440
+ }, [externalTarget, horizontal, isExternalScroll]);
6441
+ const isScrollInRange = useCallback(() => {
6442
+ if (!isExternalScroll) return true;
6443
+ const offset = getRawScrollOffset();
6444
+ return offset >= -1 && offset <= getMaxScrollOffset() + 1;
6445
+ }, [getMaxScrollOffset, getRawScrollOffset, isExternalScroll]);
6352
6446
  const getCurrentScrollOffset = useCallback(() => {
6353
- const scrollElement = scrollRef.current;
6354
- if (isWindowScroll) {
6355
- const maxOffset = getMaxScrollOffset();
6356
- const scroll = getWindowScrollPosition();
6357
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6358
- const rawOffset = horizontal ? scroll.x - listPos.left : scroll.y - listPos.top;
6359
- return clampOffset(rawOffset, maxOffset);
6360
- }
6361
- if (!scrollElement) {
6362
- return 0;
6363
- }
6364
- return horizontal ? scrollElement.scrollLeft : scrollElement.scrollTop;
6365
- }, [getMaxScrollOffset, horizontal, isWindowScroll]);
6447
+ const offset = getRawScrollOffset();
6448
+ return isExternalScroll ? clampOffset(offset, getMaxScrollOffset()) : offset;
6449
+ }, [getMaxScrollOffset, getRawScrollOffset, isExternalScroll]);
6366
6450
  const scrollToLocalOffset = useCallback(
6367
6451
  (offset, animated) => {
6368
6452
  const scrollElement = scrollRef.current;
@@ -6374,10 +6458,10 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6374
6458
  const clampedOffset = clampOffset(offset, maxOffset);
6375
6459
  const behavior = animated ? "smooth" : "auto";
6376
6460
  const options = { behavior };
6377
- if (isWindowScroll) {
6378
- const scroll = getWindowScrollPosition();
6379
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6380
- const { left, top } = resolveWindowScrollTarget({
6461
+ if (isExternalScroll) {
6462
+ const scroll = getScrollPosition(externalTarget);
6463
+ const listPos = getElementScrollPosition(scrollElement, externalTarget);
6464
+ const { left, top } = resolveExternalScrollOffset({
6381
6465
  clampedOffset,
6382
6466
  horizontal,
6383
6467
  listPos,
@@ -6392,7 +6476,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6392
6476
  }
6393
6477
  target.scrollTo(options);
6394
6478
  },
6395
- [getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll]
6479
+ [externalTarget, getMaxScrollOffset, getScrollTarget, horizontal, isExternalScroll]
6396
6480
  );
6397
6481
  useImperativeHandle(ref, () => {
6398
6482
  const api = {
@@ -6400,11 +6484,14 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6400
6484
  var _a4;
6401
6485
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6402
6486
  },
6487
+ getContentNode: () => contentRef.current,
6403
6488
  getCurrentScrollOffset,
6404
6489
  getMaxScrollOffset,
6405
- getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6406
- getScrollEventTarget: () => getScrollTarget(),
6407
- getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6490
+ getRawScrollOffset,
6491
+ getScrollableNode: () => resolveScrollableNode(scrollRef.current, externalTarget),
6492
+ getScrollEventTarget: getScrollTarget,
6493
+ getScrollResponder: () => resolveScrollableNode(scrollRef.current, externalTarget),
6494
+ isScrollInRange,
6408
6495
  isWindowScroll: () => isWindowScroll,
6409
6496
  scrollBy: (x, y) => {
6410
6497
  const target = getScrollTarget();
@@ -6428,14 +6515,24 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6428
6515
  }
6429
6516
  };
6430
6517
  return api;
6431
- }, [getCurrentScrollOffset, getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll, scrollToLocalOffset]);
6518
+ }, [
6519
+ externalTarget,
6520
+ getCurrentScrollOffset,
6521
+ getRawScrollOffset,
6522
+ getMaxScrollOffset,
6523
+ isScrollInRange,
6524
+ getScrollTarget,
6525
+ horizontal,
6526
+ isWindowScroll,
6527
+ scrollToLocalOffset
6528
+ ]);
6432
6529
  const emitScroll = useCallback(() => {
6433
6530
  if (!onScroll2 || !scrollRef.current) {
6434
6531
  return;
6435
6532
  }
6436
6533
  const contentSize = getContentSize2(contentRef.current);
6437
- const layoutMeasurement = getLayoutMeasurement(scrollRef.current, isWindowScroll, horizontal);
6438
- const offset = getCurrentScrollOffset();
6534
+ const layoutMeasurement = getLayoutMeasurement(scrollRef.current, externalTarget, horizontal);
6535
+ const offset = getRawScrollOffset();
6439
6536
  const scrollEvent = {
6440
6537
  nativeEvent: {
6441
6538
  contentOffset: {
@@ -6453,7 +6550,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6453
6550
  }
6454
6551
  };
6455
6552
  onScroll2(scrollEvent);
6456
- }, [getCurrentScrollOffset, horizontal, isWindowScroll, onScroll2]);
6553
+ }, [externalTarget, getRawScrollOffset, horizontal, onScroll2]);
6457
6554
  const scrollEventCoalescer = useRafCoalescer(emitScroll);
6458
6555
  const scrollEndFallbackRef = useRef(void 0);
6459
6556
  const emitScrollEnd = useCallback(() => {
@@ -6515,14 +6612,14 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6515
6612
  const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6516
6613
  if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6517
6614
  const offset = getCurrentScrollOffset();
6518
- if (interruptMaintainScrollAtEnd(ctx)) {
6615
+ if (interruptMaintainScrollAtEnd(ctx) && isScrollInRange()) {
6519
6616
  scrollToLocalOffset(offset, false);
6520
6617
  }
6521
6618
  }
6522
6619
  };
6523
6620
  target.addEventListener("wheel", onWheel, { passive: true });
6524
6621
  return () => target.removeEventListener("wheel", onWheel);
6525
- }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6622
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, isScrollInRange, scrollToLocalOffset]);
6526
6623
  useEffect(() => {
6527
6624
  const doScroll = () => {
6528
6625
  if (contentOffset) {
@@ -6530,44 +6627,28 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6530
6627
  }
6531
6628
  };
6532
6629
  doScroll();
6533
- requestAnimationFrame(doScroll);
6630
+ const frame = requestAnimationFrame(doScroll);
6631
+ return () => cancelAnimationFrame(frame);
6534
6632
  }, [contentOffset == null ? void 0 : contentOffset.x, contentOffset == null ? void 0 : contentOffset.y, horizontal, scrollToLocalOffset]);
6535
6633
  useLayoutEffect(() => {
6536
- if (!onLayout || !scrollRef.current) return;
6537
6634
  const element = scrollRef.current;
6538
- const fireLayout = () => {
6539
- onLayout({
6540
- nativeEvent: {
6541
- layout: getLayoutRectangle(element, isWindowScroll, horizontal)
6542
- }
6543
- });
6544
- };
6545
- fireLayout();
6546
- const resizeObserver = new ResizeObserver(() => {
6547
- fireLayout();
6548
- });
6549
- resizeObserver.observe(element);
6550
- const onWindowResize = () => {
6551
- fireLayout();
6552
- };
6553
- if (isWindowScroll && typeof window !== "undefined" && typeof window.addEventListener === "function") {
6554
- window.addEventListener("resize", onWindowResize);
6555
- }
6556
- return () => {
6557
- resizeObserver.disconnect();
6558
- if (isWindowScroll && typeof window !== "undefined" && typeof window.removeEventListener === "function") {
6559
- window.removeEventListener("resize", onWindowResize);
6635
+ if (!onLayout || !element) return;
6636
+ return observeScrollLayout(element, externalTarget, () => {
6637
+ if (isExternalScroll && !ctx.state.lastLayout && !ctx.state.initialScroll) {
6638
+ ctx.state.scroll = ctx.state.scrollPending = getRawScrollOffset();
6560
6639
  }
6561
- };
6562
- }, [isWindowScroll, onLayout]);
6563
- const hiddenScrollIndicatorClassName = !isWindowScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
6640
+ onLayout({ nativeEvent: { layout: getLayoutRectangle(element, externalTarget, horizontal) } });
6641
+ if (isExternalScroll) emitScroll();
6642
+ });
6643
+ }, [ctx, emitScroll, externalTarget, getRawScrollOffset, horizontal, isExternalScroll, onLayout]);
6644
+ const hiddenScrollIndicatorClassName = !isExternalScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
6564
6645
  useLayoutEffect(() => {
6565
6646
  if (hiddenScrollIndicatorClassName) {
6566
6647
  ensureScrollbarHiddenStyle();
6567
6648
  }
6568
6649
  }, [hiddenScrollIndicatorClassName]);
6569
6650
  const scrollViewStyle = {
6570
- ...isWindowScroll ? {} : {
6651
+ ...isExternalScroll ? {} : {
6571
6652
  overflow: "auto",
6572
6653
  overflowX: horizontal ? "auto" : showsHorizontalScrollIndicator ? "auto" : "hidden",
6573
6654
  overflowY: horizontal ? showsVerticalScrollIndicator ? "auto" : "hidden" : "auto",
@@ -6602,11 +6683,10 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6602
6683
  scrollEventThrottle: _scrollEventThrottle,
6603
6684
  ScrollComponent: _ScrollComponent,
6604
6685
  snapToOffsets,
6605
- useWindowScroll: _useWindowScroll,
6606
6686
  className: scrollViewClassNameProp,
6607
6687
  ...webProps
6608
6688
  } = props;
6609
- const snapOffsets = !isWindowScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6689
+ const snapOffsets = !isExternalScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6610
6690
  if (snapOffsets.length > 0) {
6611
6691
  scrollViewStyle.scrollSnapType = horizontal ? "x mandatory" : "y mandatory";
6612
6692
  contentStyle.position = (_c = contentStyle.position) != null ? _c : "relative";
@@ -6681,12 +6761,12 @@ function getScrollAdjustAxis(horizontal) {
6681
6761
  };
6682
6762
  }
6683
6763
  function getScrollAdjustTarget(ctx, contentNode) {
6684
- var _a3, _b, _c;
6764
+ var _a3, _b, _c, _d, _e;
6685
6765
  const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
6686
6766
  const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
6687
6767
  let resolvedContentNode = null;
6688
6768
  if (scrollElement) {
6689
- resolvedContentNode = (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
6769
+ resolvedContentNode = (_e = (_d = scrollView == null ? void 0 : scrollView.getContentNode) == null ? void 0 : _d.call(scrollView)) != null ? _e : (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
6690
6770
  }
6691
6771
  return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
6692
6772
  }
@@ -6701,6 +6781,7 @@ function ScrollAdjust() {
6701
6781
  const temporaryPaddingRef = React3.useRef(void 0);
6702
6782
  const contentNodeRef = React3.useRef(null);
6703
6783
  const callback = React3.useCallback(() => {
6784
+ var _a3, _b, _c;
6704
6785
  const scrollAdjust = peek$(ctx, "scrollAdjust");
6705
6786
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
6706
6787
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
@@ -6711,17 +6792,21 @@ function ScrollAdjust() {
6711
6792
  const horizontal = !!ctx.state.props.horizontal;
6712
6793
  const axis = getScrollAdjustAxis(horizontal);
6713
6794
  const { contentNode, scrollElement: el } = target;
6714
- const currentScroll = horizontal ? el.scrollLeft : el.scrollTop;
6795
+ const scroller = ctx.state.refScroller.current;
6796
+ const parentScroll = horizontal ? el.scrollLeft : el.scrollTop;
6797
+ const isExternal = ctx.state.props.hasExternalScroll;
6798
+ const currentScroll = (_b = isExternal ? (_a3 = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _a3.call(scroller) : void 0) != null ? _b : parentScroll;
6799
+ const isOutsideList = ((_c = scroller == null ? void 0 : scroller.isScrollInRange) == null ? void 0 : _c.call(scroller)) === false;
6715
6800
  const userOffsetDelta = (scrollAdjustUserOffset || 0) - lastScrollAdjustUserOffsetRef.current;
6716
6801
  const intendedScroll = userOffsetDelta !== 0 ? currentScroll + userOffsetDelta : ctx.state.scroll;
6717
6802
  const scrollDelta = intendedScroll - currentScroll;
6718
- const shouldScroll = Math.abs(scrollDelta) > 0.01;
6803
+ const shouldScroll = !isOutsideList && Math.abs(scrollDelta) > 0.01;
6719
6804
  const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
6720
6805
  contentNodeRef.current = contentNode;
6721
6806
  if (shouldScroll && contentNode) {
6722
- const totalSize = contentNode[axis.contentSizeKey];
6807
+ const totalSize = isExternal ? el[axis.contentSizeKey] : contentNode[axis.contentSizeKey];
6723
6808
  const viewportSize = el[axis.viewportSizeKey];
6724
- const nextScroll = currentScroll + scrollDelta;
6809
+ const nextScroll = parentScroll + scrollDelta;
6725
6810
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
6726
6811
  if (needsTemporaryPadding) {
6727
6812
  const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp];
@@ -6886,6 +6971,7 @@ var ListComponent = typedMemo(function ListComponent2({
6886
6971
  stickyHeaderConfig,
6887
6972
  stickyHeaderIndices,
6888
6973
  useWindowScroll = false,
6974
+ scrollElement,
6889
6975
  ...rest
6890
6976
  }) {
6891
6977
  const ctx = useStateContext();
@@ -6951,7 +7037,7 @@ var ListComponent = typedMemo(function ListComponent2({
6951
7037
  SnapOrScroll,
6952
7038
  {
6953
7039
  ...rest,
6954
- ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} ,
7040
+ ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, scrollElement, useWindowScroll } : {} ,
6955
7041
  contentContainerStyle: [
6956
7042
  horizontal ? { height: "100%" } : {},
6957
7043
  contentContainerStyle,
@@ -6991,7 +7077,15 @@ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
6991
7077
  var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
6992
7078
  function useDevChecksImpl(props) {
6993
7079
  const ctx = useStateContext();
6994
- const { anchoredEndSpace, childrenMode, keyExtractor, numColumns, renderScrollComponent, useWindowScroll } = props;
7080
+ const {
7081
+ anchoredEndSpace,
7082
+ childrenMode,
7083
+ keyExtractor,
7084
+ numColumns,
7085
+ renderScrollComponent,
7086
+ scrollElement,
7087
+ useWindowScroll
7088
+ } = props;
6995
7089
  const hasAnchoredEndSpace = !!anchoredEndSpace;
6996
7090
  useEffect(() => {
6997
7091
  if (hasAnchoredEndSpace && (numColumns != null ? numColumns : 1) > 1) {
@@ -7002,13 +7096,13 @@ function useDevChecksImpl(props) {
7002
7096
  }
7003
7097
  }, [hasAnchoredEndSpace, numColumns]);
7004
7098
  useEffect(() => {
7005
- if (useWindowScroll && renderScrollComponent) {
7099
+ if ((useWindowScroll || scrollElement !== void 0) && renderScrollComponent) {
7006
7100
  warnDevOnce(
7007
7101
  "useWindowScrollRenderScrollComponent",
7008
- "useWindowScroll is not supported when renderScrollComponent is provided."
7102
+ "useWindowScroll and scrollElement are not supported when renderScrollComponent is provided."
7009
7103
  );
7010
7104
  }
7011
- }, [renderScrollComponent, useWindowScroll]);
7105
+ }, [renderScrollComponent, scrollElement, useWindowScroll]);
7012
7106
  useEffect(() => {
7013
7107
  if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
7014
7108
  warnDevOnce(
@@ -7020,8 +7114,8 @@ function useDevChecksImpl(props) {
7020
7114
  useEffect(() => {
7021
7115
  const state = ctx.state;
7022
7116
  const dataLength = state.props.data.length;
7023
- const useWindowScrollResolved = state.props.useWindowScroll;
7024
- if (useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
7117
+ const usesExternalScroll = state.props.hasExternalScroll;
7118
+ if (usesExternalScroll || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
7025
7119
  return;
7026
7120
  }
7027
7121
  const warnIfUnboundedOuterSize = () => {
@@ -7185,7 +7279,8 @@ function doInitialAllocateContainers(ctx) {
7185
7279
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7186
7280
  });
7187
7281
  } else {
7188
- calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7282
+ const reuseInitialPositions = !state.didLoad && state.positionsAreCurrent && !state.didDataChange && !state.didColumnsChange;
7283
+ calculateItemsInView(ctx, { dataChanged: !reuseInitialPositions, doMVCP: true });
7189
7284
  }
7190
7285
  }
7191
7286
  return true;
@@ -7228,10 +7323,8 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7228
7323
  updateContentMetricsState(ctx);
7229
7324
  state.lastBatchingAction = Date.now();
7230
7325
  state.scrollForNextCalculateItemsInView = void 0;
7231
- if (scrollLength > 0) {
7232
- doInitialAllocateContainers(ctx);
7233
- }
7234
- if (needsCalculate) {
7326
+ const didAllocate = scrollLength > 0 && doInitialAllocateContainers(ctx);
7327
+ if (needsCalculate && (!didAllocate || state.initialScroll)) {
7235
7328
  calculateItemsInView(ctx, { doMVCP: true });
7236
7329
  }
7237
7330
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
@@ -7309,7 +7402,7 @@ function onScroll(ctx, event) {
7309
7402
  state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
7310
7403
  state.lastNativeScroll = newScroll;
7311
7404
  state.lastNativeScrollTime = Date.now();
7312
- if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7405
+ if (!state.props.hasExternalScroll && state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7313
7406
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
7314
7407
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
7315
7408
  newScroll = maxOffset;
@@ -8076,6 +8169,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8076
8169
  stickyHeaderIndices: stickyHeaderIndicesProp,
8077
8170
  style: styleProp,
8078
8171
  useWindowScroll = false,
8172
+ scrollElement,
8079
8173
  viewabilityConfig,
8080
8174
  viewabilityConfigCallbackPairs,
8081
8175
  ...rest
@@ -8170,7 +8264,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8170
8264
  dataVersion,
8171
8265
  keyExtractor
8172
8266
  ]);
8173
- const useWindowScrollResolved = !!useWindowScroll && !renderScrollComponent;
8267
+ const scrollElementResolved = !renderScrollComponent ? scrollElement : void 0;
8268
+ const useWindowScrollResolved = !!useWindowScroll && scrollElementResolved === void 0 && !renderScrollComponent;
8269
+ const hasExternalScroll = useWindowScrollResolved || scrollElementResolved !== void 0;
8174
8270
  const refState = useRef(void 0);
8175
8271
  const hasOverrideItemLayout = !!overrideItemLayout;
8176
8272
  const prevHasOverrideItemLayout = useRef(hasOverrideItemLayout);
@@ -8308,6 +8404,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8308
8404
  estimatedItemSize,
8309
8405
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8310
8406
  getItemType: useWrapIfItem(getItemType),
8407
+ hasExternalScroll,
8311
8408
  hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8312
8409
  horizontal: !!horizontal,
8313
8410
  itemsAreEqual,
@@ -8499,8 +8596,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8499
8596
  const { onLayout } = useOnLayoutSync({
8500
8597
  onLayoutChange,
8501
8598
  onLayoutProp,
8502
- ref: refScroller
8599
+ ref: refScroller,
8503
8600
  // the type of ScrollView doesn't include measure?
8601
+ webExternalScroll: hasExternalScroll
8504
8602
  });
8505
8603
  useLayoutEffect(() => {
8506
8604
  if (snapToIndices) {
@@ -8666,6 +8764,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8666
8764
  refScrollView: combinedRef,
8667
8765
  renderScrollComponent,
8668
8766
  scrollAdjustHandler: (_s = refState.current) == null ? void 0 : _s.scrollAdjustHandler,
8767
+ scrollElement: scrollElementResolved,
8669
8768
  scrollEventThrottle: 0,
8670
8769
  snapToIndices,
8671
8770
  stickyHeaderIndices,