@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.js CHANGED
@@ -901,8 +901,14 @@ function scheduleFullDrawDistancePrewarm(ctx) {
901
901
  return;
902
902
  }
903
903
  state.scheduledWork.frame(() => {
904
- var _a3;
905
- return (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
904
+ var _a3, _b, _c;
905
+ if (state.props.hasExternalScroll && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust) {
906
+ const offset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getRawScrollOffset) == null ? void 0 : _b.call(_a3);
907
+ if (offset !== void 0) {
908
+ state.scroll = state.scrollPending = state.props.horizontal ? toLogicalHorizontalOffset(state, offset, state.totalSize) : offset;
909
+ }
910
+ }
911
+ (_c = state.triggerCalculateItemsInView) == null ? void 0 : _c.call(state);
906
912
  }, "fullDrawDistancePrewarm");
907
913
  }
908
914
 
@@ -915,6 +921,9 @@ function resetInitialRenderState(ctx, {
915
921
  if (resetLayout) {
916
922
  state.didContainersLayout = false;
917
923
  state.queuedInitialLayout = false;
924
+ state.scrollBufferDirection = void 0;
925
+ state.scrollHistory.length = 0;
926
+ state.scheduledWork.cancel("renderRangeProjection");
918
927
  state.edgeReachedGate = void 0;
919
928
  state.isStartReached = null;
920
929
  state.isEndReached = null;
@@ -1141,6 +1150,7 @@ function setSize(ctx, itemKey, size, notifyTotalSize = true) {
1141
1150
  const previousSize = sizes.get(itemKey);
1142
1151
  const diff = previousSize !== void 0 ? size - previousSize : size;
1143
1152
  if (diff !== 0) {
1153
+ state.positionsAreCurrent = false;
1144
1154
  addTotalSize(ctx, itemKey, diff, notifyTotalSize);
1145
1155
  }
1146
1156
  sizes.set(itemKey, size);
@@ -1201,6 +1211,7 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
1201
1211
  const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
1202
1212
  if (fixedSize !== void 0) {
1203
1213
  size = fixedSize + ctx.scrollAxisGap;
1214
+ state.positionsAreCurrent = false;
1204
1215
  state.sizesKnown.set(key, size);
1205
1216
  }
1206
1217
  }
@@ -1782,6 +1793,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1782
1793
  state.scrollTime = currentTime;
1783
1794
  const scrollDelta = Math.abs(newScroll - prevScroll);
1784
1795
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1796
+ if (isUserScrollEvent) {
1797
+ state.scrollBufferDirection = newScroll > prevScroll ? 1 : -1;
1798
+ }
1785
1799
  const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1786
1800
  if (didCancelMaintainScrollAtEnd) {
1787
1801
  finishMaintainScrollAtEnd(ctx);
@@ -1861,10 +1875,10 @@ function finishMaintainScrollAtEnd(ctx) {
1861
1875
  }
1862
1876
  }
1863
1877
  function doMaintainScrollAtEnd(ctx, options) {
1864
- var _a3, _b, _c, _d, _e, _f;
1878
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
1865
1879
  const state = ctx.state;
1866
1880
  let didMaintain = false;
1867
- if (state.isDragging) {
1881
+ if (state.isDragging || ((_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.isScrollInRange) == null ? void 0 : _b.call(_a3)) === false) {
1868
1882
  finishMaintainScrollAtEnd(ctx);
1869
1883
  } else {
1870
1884
  const {
@@ -1876,11 +1890,11 @@ function doMaintainScrollAtEnd(ctx, options) {
1876
1890
  const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1877
1891
  const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1878
1892
  const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1879
- 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);
1893
+ 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);
1880
1894
  if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1881
1895
  const scroller = state.refScroller.current;
1882
- const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1883
- const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1896
+ const nativeMax = (_e = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _e.call(scroller);
1897
+ const observed = (_f = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _f.call(scroller);
1884
1898
  if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1885
1899
  state.pendingMaintainScrollAtEnd = true;
1886
1900
  state.scrollPending = observed;
@@ -1892,13 +1906,13 @@ function doMaintainScrollAtEnd(ctx, options) {
1892
1906
  } else if (pendingNativeMVCPAdjust) {
1893
1907
  state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1894
1908
  } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1895
- if (immediate && ((_e = state.scrollingTo) == null ? void 0 : _e.isScrollToEnd) && !state.scrollingTo.animated) {
1909
+ if (immediate && ((_g = state.scrollingTo) == null ? void 0 : _g.isScrollToEnd) && !state.scrollingTo.animated) {
1896
1910
  state.pendingMaintainScrollAtEnd = false;
1897
1911
  ctx.scrollToEnd({ animated: false });
1898
1912
  didMaintain = true;
1899
1913
  } else {
1900
1914
  state.pendingMaintainScrollAtEnd = true;
1901
- (_f = state.maintainingScrollAtEnd) != null ? _f : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1915
+ (_h = state.maintainingScrollAtEnd) != null ? _h : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1902
1916
  }
1903
1917
  } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1904
1918
  state.pendingMaintainScrollAtEnd = false;
@@ -1912,8 +1926,9 @@ function doMaintainScrollAtEnd(ctx, options) {
1912
1926
  const scrollAtRequest = state.scroll;
1913
1927
  state.maintainingScrollAtEnd = pendingState;
1914
1928
  const run = () => {
1929
+ var _a4, _b2;
1915
1930
  if (state.maintainingScrollAtEnd === pendingState) {
1916
- if (state.isDragging) {
1931
+ if (state.isDragging || ((_b2 = (_a4 = state.refScroller.current) == null ? void 0 : _a4.isScrollInRange) == null ? void 0 : _b2.call(_a4)) === false) {
1917
1932
  finishMaintainScrollAtEnd(ctx);
1918
1933
  } else {
1919
1934
  const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
@@ -3356,6 +3371,7 @@ function handleInitialScrollDataChange(ctx, options) {
3356
3371
 
3357
3372
  // src/core/resetLayoutCachesForDataChange.ts
3358
3373
  function resetLayoutCachesForDataChange(state) {
3374
+ state.positionsAreCurrent = false;
3359
3375
  state.indexByKey.clear();
3360
3376
  state.idCache.length = 0;
3361
3377
  state.positions.length = 0;
@@ -3636,7 +3652,7 @@ function updateItemPositions(ctx, dataChanged, {
3636
3652
  scrollBottomBuffered: -1,
3637
3653
  startIndex: 0
3638
3654
  }) {
3639
- var _a3, _b, _c, _d, _e;
3655
+ var _a3, _b, _c, _d, _e, _f;
3640
3656
  const state = ctx.state;
3641
3657
  const hasPositionListeners = ctx.positionListeners.size > 0;
3642
3658
  const {
@@ -3652,6 +3668,11 @@ function updateItemPositions(ctx, dataChanged, {
3652
3668
  const dataLength = data.length;
3653
3669
  const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
3654
3670
  const hasColumns = numColumns > 1;
3671
+ const canReuseEstimates = !doMVCP || !state.didContainersLayout && Object.keys(state.averageSizes).length === 0;
3672
+ if (state.positionsAreCurrent && !dataChanged && !forceFullUpdate && state.positionRecalculationStartIndex === void 0 && !scrollingTo && !hasColumns && !snapToIndices && positions.length === dataLength && canReuseEstimates) {
3673
+ return;
3674
+ }
3675
+ state.positionsAreCurrent = false;
3655
3676
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
3656
3677
  const extraData = peek$(ctx, "extraData");
3657
3678
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
@@ -3766,6 +3787,10 @@ function updateItemPositions(ctx, dataChanged, {
3766
3787
  }
3767
3788
  if (!didBreakEarly) {
3768
3789
  updateTotalSize(ctx);
3790
+ state.positionsAreCurrent = !hasColumns;
3791
+ if (startIndex <= ((_f = state.positionRecalculationStartIndex) != null ? _f : -1)) {
3792
+ state.positionRecalculationStartIndex = void 0;
3793
+ }
3769
3794
  }
3770
3795
  if (snapToIndices) {
3771
3796
  updateSnapToOffsets(ctx);
@@ -4328,7 +4353,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4328
4353
  }
4329
4354
  function trackVisibleRange(range, i, top, size, scroll, scrollBottom) {
4330
4355
  let didPassVisibleEnd = false;
4331
- if (range.startNoBuffer === null && top + size > scroll) {
4356
+ if (range.startNoBuffer === null && top + size > scroll && top <= scrollBottom) {
4332
4357
  range.startNoBuffer = i;
4333
4358
  }
4334
4359
  if (range.firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
@@ -4394,7 +4419,7 @@ function findFirstVisibleIndexInCachedRange(ctx, scroll) {
4394
4419
  return null;
4395
4420
  }
4396
4421
  function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, scrollLength, scroll, scrollBottom) {
4397
- var _a3, _b;
4422
+ var _a3, _b, _c, _d;
4398
4423
  const state = ctx.state;
4399
4424
  const {
4400
4425
  endBuffered,
@@ -4428,14 +4453,14 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4428
4453
  startNoBuffer: visibleRange.startNoBuffer
4429
4454
  });
4430
4455
  maybeEmitFirstVisibleItemChanged(state, visibleRange.startNoBuffer);
4431
- if (visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4456
+ if (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4432
4457
  updateViewableItems(
4433
4458
  state,
4434
4459
  ctx,
4435
4460
  viewabilityConfigCallbackPairs,
4436
4461
  scrollLength,
4437
- visibleRange.startNoBuffer,
4438
- visibleRange.endNoBuffer,
4462
+ (_c = visibleRange.startNoBuffer) != null ? _c : 0,
4463
+ (_d = visibleRange.endNoBuffer) != null ? _d : -1,
4439
4464
  startBuffered,
4440
4465
  endBuffered
4441
4466
  );
@@ -4444,7 +4469,7 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4444
4469
  function calculateItemsInView(ctx, params = {}) {
4445
4470
  const state = ctx.state;
4446
4471
  batchedUpdates(() => {
4447
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
4472
+ 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;
4448
4473
  const {
4449
4474
  columns,
4450
4475
  containerItemKeys,
@@ -4496,7 +4521,7 @@ function calculateItemsInView(ctx, params = {}) {
4496
4521
  scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4497
4522
  scrollAdjustPad = scrollAdjustPending - topPad;
4498
4523
  scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4499
- if (scroll + scrollLength > totalSize) {
4524
+ if (!state.props.hasExternalScroll && scroll + scrollLength > totalSize) {
4500
4525
  scroll = Math.max(0, totalSize - scrollLength);
4501
4526
  }
4502
4527
  };
@@ -4523,7 +4548,11 @@ function calculateItemsInView(ctx, params = {}) {
4523
4548
  let stickyState = dataChanged ? void 0 : resolveStickyState();
4524
4549
  let scrollBufferTop = drawDistance;
4525
4550
  let scrollBufferBottom = drawDistance;
4526
- if (speed > 0 || speed === 0 && scroll < Math.max(50, drawDistance)) {
4551
+ if (speed !== 0) {
4552
+ state.scrollBufferDirection = speed > 0 ? 1 : -1;
4553
+ }
4554
+ const bufferForward = state.scrollBufferDirection === 1 || state.scrollBufferDirection === void 0 && scroll < Math.max(50, drawDistance);
4555
+ if (bufferForward) {
4527
4556
  scrollBufferTop = drawDistance * 0.5;
4528
4557
  scrollBufferBottom = drawDistance * 1.5;
4529
4558
  } else {
@@ -4533,8 +4562,8 @@ function calculateItemsInView(ctx, params = {}) {
4533
4562
  const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4534
4563
  const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4535
4564
  const updateScrollRange = () => {
4536
- const scrollStart = Math.max(0, scroll);
4537
- const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4565
+ const scrollStart = state.props.hasExternalScroll ? scroll : Math.max(0, scroll);
4566
+ const overscrollBeforeContent = state.props.hasExternalScroll ? 0 : Math.max(0, -nativeScrollState);
4538
4567
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4539
4568
  scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4540
4569
  scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
@@ -4663,7 +4692,7 @@ function calculateItemsInView(ctx, params = {}) {
4663
4692
  nextTop = top;
4664
4693
  }
4665
4694
  }
4666
- if (visibleRange.startNoBuffer !== null) {
4695
+ if (startBuffered !== null) {
4667
4696
  if (top <= scrollBottomBuffered) {
4668
4697
  endBuffered = i;
4669
4698
  if (scrollBottomBuffered > totalSize) {
@@ -4674,6 +4703,8 @@ function calculateItemsInView(ctx, params = {}) {
4674
4703
  } else {
4675
4704
  foundEnd = true;
4676
4705
  }
4706
+ } else if (top > scrollBottomBuffered) {
4707
+ foundEnd = true;
4677
4708
  }
4678
4709
  }
4679
4710
  }
@@ -4691,6 +4722,12 @@ function calculateItemsInView(ctx, params = {}) {
4691
4722
  bottom: nextBottom,
4692
4723
  top: nextTop
4693
4724
  };
4725
+ } else if (enableScrollForNextCalculateItemsInView && state.props.hasExternalScroll) {
4726
+ if (scrollBottomBuffered < 0) {
4727
+ state.scrollForNextCalculateItemsInView = { bottom: 0, top: null };
4728
+ } else if (scrollTopBuffered > totalSize) {
4729
+ state.scrollForNextCalculateItemsInView = { bottom: null, top: totalSize };
4730
+ }
4694
4731
  }
4695
4732
  let numContainers = prevNumContainers;
4696
4733
  const pendingRemoval = [];
@@ -4714,7 +4751,9 @@ function calculateItemsInView(ctx, params = {}) {
4714
4751
  }
4715
4752
  const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4716
4753
  const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4717
- if (startBuffered !== null && endBuffered !== null) {
4754
+ if (startBuffered !== null && endBuffered !== null || alwaysRenderIndicesArr.length || hasScrollTargetPinnedRange) {
4755
+ const allocationStart = startBuffered != null ? startBuffered : 0;
4756
+ const allocationEnd = endBuffered != null ? endBuffered : -1;
4718
4757
  const needNewContainers = [];
4719
4758
  const needNewContainersSet = /* @__PURE__ */ new Set();
4720
4759
  const addPinnedIndex = (index) => {
@@ -4730,7 +4769,7 @@ function calculateItemsInView(ctx, params = {}) {
4730
4769
  }
4731
4770
  }
4732
4771
  };
4733
- for (let i = startBuffered; i <= endBuffered; i++) {
4772
+ for (let i = allocationStart; i <= allocationEnd; i++) {
4734
4773
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4735
4774
  if (!containerItemKeys.has(id)) {
4736
4775
  needNewContainersSet.add(i);
@@ -4752,8 +4791,8 @@ function calculateItemsInView(ctx, params = {}) {
4752
4791
  (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4753
4792
  needNewContainers,
4754
4793
  needNewContainersSet,
4755
- startBuffered,
4756
- endBuffered
4794
+ allocationStart,
4795
+ allocationEnd
4757
4796
  );
4758
4797
  } else if (previousStickyIndex !== -1) {
4759
4798
  set$(ctx, "activeStickyIndex", -1);
@@ -4762,8 +4801,8 @@ function calculateItemsInView(ctx, params = {}) {
4762
4801
  const availableContainerAllocations = findAvailableContainers(
4763
4802
  ctx,
4764
4803
  needNewContainers,
4765
- startBuffered,
4766
- endBuffered,
4804
+ allocationStart,
4805
+ allocationEnd,
4767
4806
  pendingRemoval,
4768
4807
  protectedContainerKeys,
4769
4808
  // Initial end alignment has no scroll velocity yet. Keep its
@@ -4890,19 +4929,19 @@ function calculateItemsInView(ctx, params = {}) {
4890
4929
  handleInitialScrollLayoutReady(ctx);
4891
4930
  }
4892
4931
  }
4893
- if (viewabilityConfigCallbackPairs && visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4932
+ if (viewabilityConfigCallbackPairs && (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null)) {
4894
4933
  updateViewableItems(
4895
4934
  ctx.state,
4896
4935
  ctx,
4897
4936
  viewabilityConfigCallbackPairs,
4898
4937
  scrollLength,
4899
- visibleRange.startNoBuffer,
4900
- visibleRange.endNoBuffer,
4901
- startBuffered != null ? startBuffered : visibleRange.startNoBuffer,
4902
- endBuffered != null ? endBuffered : visibleRange.endNoBuffer
4938
+ (_u = visibleRange.startNoBuffer) != null ? _u : 0,
4939
+ (_v = visibleRange.endNoBuffer) != null ? _v : -1,
4940
+ (_w = startBuffered != null ? startBuffered : visibleRange.startNoBuffer) != null ? _w : 0,
4941
+ (_x = endBuffered != null ? endBuffered : visibleRange.endNoBuffer) != null ? _x : -1
4903
4942
  );
4904
4943
  }
4905
- (_u = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _u.call(stickyState);
4944
+ (_y = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _y.call(stickyState);
4906
4945
  });
4907
4946
  }
4908
4947
 
@@ -5181,6 +5220,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notif
5181
5220
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
5182
5221
  const prevSizeKnown = sizesKnown.get(itemKey);
5183
5222
  const size = Math.round(rawSize) ;
5223
+ if (prevSizeKnown !== size) state.positionsAreCurrent = false;
5184
5224
  sizesKnown.set(itemKey, size);
5185
5225
  if (fixedItemSize === void 0 && size > 0) {
5186
5226
  itemType != null ? itemType : itemType = getItemType ? (_b = getItemType(itemData, index)) != null ? _b : "" : "";
@@ -5601,10 +5641,19 @@ function useOnLayoutSync({
5601
5641
  measureInLayoutEffect = true,
5602
5642
  onLayoutProp,
5603
5643
  onLayoutChange,
5604
- webLayoutResync
5644
+ webLayoutResync,
5645
+ webExternalScroll = false
5605
5646
  }, deps) {
5647
+ const onExternalLayout = React3.useCallback(
5648
+ (event) => {
5649
+ onLayoutChange(event.nativeEvent.layout, false);
5650
+ onLayoutProp == null ? void 0 : onLayoutProp(event);
5651
+ },
5652
+ [onLayoutChange, onLayoutProp]
5653
+ );
5606
5654
  React3.useLayoutEffect(() => {
5607
5655
  var _a3, _b;
5656
+ if (webExternalScroll) return;
5608
5657
  const current = ref.current;
5609
5658
  const scrollableNode = (_b = (_a3 = current == null ? void 0 : current.getScrollableNode) == null ? void 0 : _a3.call(current)) != null ? _b : null;
5610
5659
  const element = scrollableNode || current;
@@ -5642,8 +5691,8 @@ function useOnLayoutSync({
5642
5691
  emit(toLayout(rectObserved), false);
5643
5692
  }
5644
5693
  });
5645
- }, [measureInLayoutEffect, ...deps || []]);
5646
- return {};
5694
+ }, [measureInLayoutEffect, webExternalScroll, ...deps || []]);
5695
+ return webExternalScroll ? { onLayout: onExternalLayout } : {};
5647
5696
  }
5648
5697
  function toLayout(rect) {
5649
5698
  if (!rect) {
@@ -6205,94 +6254,121 @@ function useRafCoalescer(callback) {
6205
6254
  return coalescer;
6206
6255
  }
6207
6256
 
6208
- // src/components/webConstants.ts
6209
- var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
6210
- var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
6211
- var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
6212
-
6213
6257
  // src/components/webScrollUtils.ts
6214
- function getDocumentScrollerNode() {
6215
- if (typeof document === "undefined") {
6216
- return null;
6217
- }
6218
- return document.scrollingElement || document.documentElement || document.body;
6258
+ function isWindowTarget(target) {
6259
+ return typeof window !== "undefined" && target === window;
6219
6260
  }
6220
- function getWindowScrollPosition() {
6221
- var _a3, _b, _c, _d;
6222
- if (typeof window === "undefined") {
6223
- return { x: 0, y: 0 };
6261
+ function getScrollPosition(target) {
6262
+ var _a3, _b, _c, _d, _e, _f;
6263
+ if (isWindowTarget(target)) {
6264
+ 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 };
6224
6265
  }
6225
- return {
6226
- x: (_b = (_a3 = window.scrollX) != null ? _a3 : window.pageXOffset) != null ? _b : 0,
6227
- y: (_d = (_c = window.scrollY) != null ? _c : window.pageYOffset) != null ? _d : 0
6228
- };
6266
+ return { x: (_e = target == null ? void 0 : target.scrollLeft) != null ? _e : 0, y: (_f = target == null ? void 0 : target.scrollTop) != null ? _f : 0 };
6229
6267
  }
6230
- function getElementDocumentPosition(element, scroll) {
6231
- var _a3, _b;
6268
+ function getElementScrollPosition(element, target) {
6269
+ var _a3, _b, _c, _d, _e, _f;
6232
6270
  const rect = element == null ? void 0 : element.getBoundingClientRect();
6271
+ const scroll = getScrollPosition(target);
6272
+ const parent = target && !isWindowTarget(target) ? target : null;
6273
+ const parentRect = parent == null ? void 0 : parent.getBoundingClientRect();
6233
6274
  return {
6234
- left: ((_a3 = rect == null ? void 0 : rect.left) != null ? _a3 : 0) + scroll.x,
6235
- top: ((_b = rect == null ? void 0 : rect.top) != null ? _b : 0) + scroll.y
6275
+ 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,
6276
+ 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
6236
6277
  };
6237
6278
  }
6238
6279
  function getContentSize2(content) {
6239
6280
  var _a3, _b;
6240
- return {
6241
- height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0,
6242
- width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0
6243
- };
6281
+ return { height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0, width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0 };
6244
6282
  }
6245
- function getScrollContentSize(scrollElement, contentElement, isWindowScroll) {
6246
- return getContentSize2(isWindowScroll ? contentElement : scrollElement);
6283
+ function getScrollContentSize(scrollElement, contentElement, externalTarget) {
6284
+ return getContentSize2(externalTarget !== void 0 ? contentElement : scrollElement);
6247
6285
  }
6248
- function getLayoutMeasurement(scrollElement, isWindowScroll, horizontal) {
6249
- var _a3, _b, _c, _d, _e, _f;
6250
- if (isWindowScroll && typeof window !== "undefined") {
6286
+ function getLayoutMeasurement(scrollElement, externalTarget, horizontal) {
6287
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
6288
+ if (externalTarget !== void 0) {
6251
6289
  const rect = scrollElement == null ? void 0 : scrollElement.getBoundingClientRect();
6290
+ 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 };
6252
6291
  return {
6253
- // In window-scroll mode, use viewport size on the scroll axis.
6254
- height: horizontal ? (_b = (_a3 = rect == null ? void 0 : rect.height) != null ? _a3 : scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _b : window.innerHeight : window.innerHeight,
6255
- // Keep the cross-axis size list-relative to avoid inflating container measurements.
6256
- width: horizontal ? window.innerWidth : (_d = (_c = rect == null ? void 0 : rect.width) != null ? _c : scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _d : window.innerWidth
6292
+ height: horizontal ? (_d = (_c = rect == null ? void 0 : rect.height) != null ? _c : scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _d : 0 : viewport.height,
6293
+ width: horizontal ? viewport.width : (_f = (_e = rect == null ? void 0 : rect.width) != null ? _e : scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _f : 0
6257
6294
  };
6258
6295
  }
6259
- return {
6260
- height: (_e = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _e : 0,
6261
- width: (_f = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _f : 0
6262
- };
6296
+ return { height: (_g = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _g : 0, width: (_h = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _h : 0 };
6263
6297
  }
6264
6298
  function clampOffset(offset, maxOffset) {
6265
6299
  return Math.max(0, Math.min(offset, maxOffset));
6266
6300
  }
6267
- function getAxisSize(size, horizontal) {
6268
- return horizontal ? size.width : size.height;
6269
- }
6270
6301
  function getMaxOffset(contentSize, layoutMeasurement, horizontal) {
6271
- return Math.max(0, getAxisSize(contentSize, horizontal) - getAxisSize(layoutMeasurement, horizontal));
6302
+ const axis = horizontal ? "width" : "height";
6303
+ return Math.max(0, contentSize[axis] - layoutMeasurement[axis]);
6272
6304
  }
6273
- function resolveScrollableNode(scrollElement, isWindowScroll) {
6274
- return isWindowScroll ? getDocumentScrollerNode() || scrollElement : scrollElement;
6305
+ function resolveScrollableNode(scrollElement, externalTarget) {
6306
+ if (isWindowTarget(externalTarget)) {
6307
+ return document.scrollingElement || document.documentElement || document.body;
6308
+ }
6309
+ return externalTarget === void 0 ? scrollElement : externalTarget;
6275
6310
  }
6276
- function resolveScrollEventTarget(scrollElement, isWindowScroll) {
6277
- return isWindowScroll && typeof window !== "undefined" ? window : scrollElement;
6311
+ function resolveScrollEventTarget(scrollElement, externalTarget) {
6312
+ return externalTarget === void 0 ? scrollElement : externalTarget;
6278
6313
  }
6279
- function getLayoutRectangle(element, isWindowScroll, horizontal) {
6280
- const rect = element.getBoundingClientRect();
6281
- const scroll = getWindowScrollPosition();
6282
- return {
6283
- height: isWindowScroll && typeof window !== "undefined" && !horizontal ? window.innerHeight : rect.height,
6284
- width: isWindowScroll && typeof window !== "undefined" && horizontal ? window.innerWidth : rect.width,
6285
- x: isWindowScroll ? rect.left + scroll.x : rect.left,
6286
- y: isWindowScroll ? rect.top + scroll.y : rect.top
6287
- };
6314
+ function getLayoutRectangle(element, externalTarget, horizontal) {
6315
+ if (externalTarget === void 0) {
6316
+ const rect = element.getBoundingClientRect();
6317
+ return { height: rect.height, width: rect.width, x: rect.left, y: rect.top };
6318
+ }
6319
+ const { left, top } = getElementScrollPosition(element, externalTarget);
6320
+ const { height, width } = getLayoutMeasurement(element, externalTarget, horizontal);
6321
+ return { height, width, x: left, y: top };
6288
6322
  }
6289
- function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll }) {
6323
+ function resolveExternalScrollOffset({
6324
+ clampedOffset,
6325
+ horizontal,
6326
+ listPos,
6327
+ scroll
6328
+ }) {
6290
6329
  return {
6291
6330
  left: horizontal ? listPos.left + clampedOffset : scroll.x,
6292
6331
  top: horizontal ? scroll.y : listPos.top + clampedOffset
6293
6332
  };
6294
6333
  }
6295
6334
 
6335
+ // src/components/observeScrollLayout.ts
6336
+ function observeScrollLayout(element, owner, onLayout) {
6337
+ if (owner === null) return;
6338
+ const resizeObserver = new ResizeObserver(onLayout);
6339
+ const mutationObserver = owner === void 0 ? void 0 : new MutationObserver(observe);
6340
+ function observe() {
6341
+ resizeObserver.disconnect();
6342
+ mutationObserver == null ? void 0 : mutationObserver.disconnect();
6343
+ resizeObserver.observe(element);
6344
+ if (owner !== void 0) {
6345
+ for (let node = element; node && node !== owner; node = node.parentElement) {
6346
+ const parent = node.parentElement;
6347
+ if (parent) {
6348
+ resizeObserver.observe(parent);
6349
+ mutationObserver == null ? void 0 : mutationObserver.observe(parent, { childList: true });
6350
+ }
6351
+ for (let sibling = node.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
6352
+ resizeObserver.observe(sibling);
6353
+ }
6354
+ }
6355
+ }
6356
+ onLayout();
6357
+ }
6358
+ observe();
6359
+ if (isWindowTarget(owner)) owner.addEventListener("resize", onLayout);
6360
+ return () => {
6361
+ resizeObserver.disconnect();
6362
+ mutationObserver == null ? void 0 : mutationObserver.disconnect();
6363
+ if (isWindowTarget(owner)) owner.removeEventListener("resize", onLayout);
6364
+ };
6365
+ }
6366
+
6367
+ // src/components/webConstants.ts
6368
+ var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
6369
+ var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
6370
+ var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
6371
+
6296
6372
  // src/components/ListComponentScrollView.tsx
6297
6373
  var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
6298
6374
  var SCROLL_END_FALLBACK_MS = 200;
@@ -6351,6 +6427,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6351
6427
  showsVerticalScrollIndicator = true,
6352
6428
  refreshControl,
6353
6429
  useWindowScroll = false,
6430
+ scrollElement: externalScrollElement,
6354
6431
  onLayout,
6355
6432
  ...props
6356
6433
  }, ref) {
@@ -6359,31 +6436,38 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6359
6436
  const [anchoredEndSpaceSize] = useArr$(["anchoredEndSpaceSize"]);
6360
6437
  const scrollRef = React3.useRef(null);
6361
6438
  const contentRef = React3.useRef(null);
6362
- const isWindowScroll = useWindowScroll;
6439
+ const externalTarget = externalScrollElement === void 0 && useWindowScroll ? typeof window === "undefined" ? null : window : externalScrollElement;
6440
+ const isWindowScroll = isWindowTarget(externalTarget);
6441
+ const isExternalScroll = externalTarget !== void 0;
6363
6442
  const getScrollTarget = React3.useCallback(
6364
- () => resolveScrollEventTarget(scrollRef.current, isWindowScroll),
6365
- [isWindowScroll]
6443
+ () => resolveScrollEventTarget(scrollRef.current, externalTarget),
6444
+ [externalTarget]
6366
6445
  );
6367
6446
  const getMaxScrollOffset = React3.useCallback(() => {
6368
6447
  const scrollElement = scrollRef.current;
6369
- const contentSize = getScrollContentSize(scrollElement, contentRef.current, isWindowScroll);
6370
- const layoutMeasurement = getLayoutMeasurement(scrollElement, isWindowScroll, horizontal);
6448
+ const contentSize = getScrollContentSize(scrollElement, contentRef.current, externalTarget);
6449
+ const layoutMeasurement = getLayoutMeasurement(scrollElement, externalTarget, horizontal);
6371
6450
  return getMaxOffset(contentSize, layoutMeasurement, horizontal);
6372
- }, [horizontal, isWindowScroll]);
6451
+ }, [externalTarget, horizontal]);
6452
+ const getRawScrollOffset = React3.useCallback(() => {
6453
+ var _a4;
6454
+ const element = scrollRef.current;
6455
+ if (isExternalScroll) {
6456
+ const scroll = getScrollPosition(externalTarget);
6457
+ const listPos = getElementScrollPosition(element, externalTarget);
6458
+ return horizontal ? scroll.x - listPos.left : scroll.y - listPos.top;
6459
+ }
6460
+ return (_a4 = horizontal ? element == null ? void 0 : element.scrollLeft : element == null ? void 0 : element.scrollTop) != null ? _a4 : 0;
6461
+ }, [externalTarget, horizontal, isExternalScroll]);
6462
+ const isScrollInRange = React3.useCallback(() => {
6463
+ if (!isExternalScroll) return true;
6464
+ const offset = getRawScrollOffset();
6465
+ return offset >= -1 && offset <= getMaxScrollOffset() + 1;
6466
+ }, [getMaxScrollOffset, getRawScrollOffset, isExternalScroll]);
6373
6467
  const getCurrentScrollOffset = React3.useCallback(() => {
6374
- const scrollElement = scrollRef.current;
6375
- if (isWindowScroll) {
6376
- const maxOffset = getMaxScrollOffset();
6377
- const scroll = getWindowScrollPosition();
6378
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6379
- const rawOffset = horizontal ? scroll.x - listPos.left : scroll.y - listPos.top;
6380
- return clampOffset(rawOffset, maxOffset);
6381
- }
6382
- if (!scrollElement) {
6383
- return 0;
6384
- }
6385
- return horizontal ? scrollElement.scrollLeft : scrollElement.scrollTop;
6386
- }, [getMaxScrollOffset, horizontal, isWindowScroll]);
6468
+ const offset = getRawScrollOffset();
6469
+ return isExternalScroll ? clampOffset(offset, getMaxScrollOffset()) : offset;
6470
+ }, [getMaxScrollOffset, getRawScrollOffset, isExternalScroll]);
6387
6471
  const scrollToLocalOffset = React3.useCallback(
6388
6472
  (offset, animated) => {
6389
6473
  const scrollElement = scrollRef.current;
@@ -6395,10 +6479,10 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6395
6479
  const clampedOffset = clampOffset(offset, maxOffset);
6396
6480
  const behavior = animated ? "smooth" : "auto";
6397
6481
  const options = { behavior };
6398
- if (isWindowScroll) {
6399
- const scroll = getWindowScrollPosition();
6400
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6401
- const { left, top } = resolveWindowScrollTarget({
6482
+ if (isExternalScroll) {
6483
+ const scroll = getScrollPosition(externalTarget);
6484
+ const listPos = getElementScrollPosition(scrollElement, externalTarget);
6485
+ const { left, top } = resolveExternalScrollOffset({
6402
6486
  clampedOffset,
6403
6487
  horizontal,
6404
6488
  listPos,
@@ -6413,7 +6497,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6413
6497
  }
6414
6498
  target.scrollTo(options);
6415
6499
  },
6416
- [getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll]
6500
+ [externalTarget, getMaxScrollOffset, getScrollTarget, horizontal, isExternalScroll]
6417
6501
  );
6418
6502
  React3.useImperativeHandle(ref, () => {
6419
6503
  const api = {
@@ -6421,11 +6505,14 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6421
6505
  var _a4;
6422
6506
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6423
6507
  },
6508
+ getContentNode: () => contentRef.current,
6424
6509
  getCurrentScrollOffset,
6425
6510
  getMaxScrollOffset,
6426
- getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6427
- getScrollEventTarget: () => getScrollTarget(),
6428
- getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6511
+ getRawScrollOffset,
6512
+ getScrollableNode: () => resolveScrollableNode(scrollRef.current, externalTarget),
6513
+ getScrollEventTarget: getScrollTarget,
6514
+ getScrollResponder: () => resolveScrollableNode(scrollRef.current, externalTarget),
6515
+ isScrollInRange,
6429
6516
  isWindowScroll: () => isWindowScroll,
6430
6517
  scrollBy: (x, y) => {
6431
6518
  const target = getScrollTarget();
@@ -6449,14 +6536,24 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6449
6536
  }
6450
6537
  };
6451
6538
  return api;
6452
- }, [getCurrentScrollOffset, getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll, scrollToLocalOffset]);
6539
+ }, [
6540
+ externalTarget,
6541
+ getCurrentScrollOffset,
6542
+ getRawScrollOffset,
6543
+ getMaxScrollOffset,
6544
+ isScrollInRange,
6545
+ getScrollTarget,
6546
+ horizontal,
6547
+ isWindowScroll,
6548
+ scrollToLocalOffset
6549
+ ]);
6453
6550
  const emitScroll = React3.useCallback(() => {
6454
6551
  if (!onScroll2 || !scrollRef.current) {
6455
6552
  return;
6456
6553
  }
6457
6554
  const contentSize = getContentSize2(contentRef.current);
6458
- const layoutMeasurement = getLayoutMeasurement(scrollRef.current, isWindowScroll, horizontal);
6459
- const offset = getCurrentScrollOffset();
6555
+ const layoutMeasurement = getLayoutMeasurement(scrollRef.current, externalTarget, horizontal);
6556
+ const offset = getRawScrollOffset();
6460
6557
  const scrollEvent = {
6461
6558
  nativeEvent: {
6462
6559
  contentOffset: {
@@ -6474,7 +6571,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6474
6571
  }
6475
6572
  };
6476
6573
  onScroll2(scrollEvent);
6477
- }, [getCurrentScrollOffset, horizontal, isWindowScroll, onScroll2]);
6574
+ }, [externalTarget, getRawScrollOffset, horizontal, onScroll2]);
6478
6575
  const scrollEventCoalescer = useRafCoalescer(emitScroll);
6479
6576
  const scrollEndFallbackRef = React3.useRef(void 0);
6480
6577
  const emitScrollEnd = React3.useCallback(() => {
@@ -6536,14 +6633,14 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6536
6633
  const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6537
6634
  if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6538
6635
  const offset = getCurrentScrollOffset();
6539
- if (interruptMaintainScrollAtEnd(ctx)) {
6636
+ if (interruptMaintainScrollAtEnd(ctx) && isScrollInRange()) {
6540
6637
  scrollToLocalOffset(offset, false);
6541
6638
  }
6542
6639
  }
6543
6640
  };
6544
6641
  target.addEventListener("wheel", onWheel, { passive: true });
6545
6642
  return () => target.removeEventListener("wheel", onWheel);
6546
- }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6643
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, isScrollInRange, scrollToLocalOffset]);
6547
6644
  React3.useEffect(() => {
6548
6645
  const doScroll = () => {
6549
6646
  if (contentOffset) {
@@ -6551,44 +6648,28 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6551
6648
  }
6552
6649
  };
6553
6650
  doScroll();
6554
- requestAnimationFrame(doScroll);
6651
+ const frame = requestAnimationFrame(doScroll);
6652
+ return () => cancelAnimationFrame(frame);
6555
6653
  }, [contentOffset == null ? void 0 : contentOffset.x, contentOffset == null ? void 0 : contentOffset.y, horizontal, scrollToLocalOffset]);
6556
6654
  React3.useLayoutEffect(() => {
6557
- if (!onLayout || !scrollRef.current) return;
6558
6655
  const element = scrollRef.current;
6559
- const fireLayout = () => {
6560
- onLayout({
6561
- nativeEvent: {
6562
- layout: getLayoutRectangle(element, isWindowScroll, horizontal)
6563
- }
6564
- });
6565
- };
6566
- fireLayout();
6567
- const resizeObserver = new ResizeObserver(() => {
6568
- fireLayout();
6569
- });
6570
- resizeObserver.observe(element);
6571
- const onWindowResize = () => {
6572
- fireLayout();
6573
- };
6574
- if (isWindowScroll && typeof window !== "undefined" && typeof window.addEventListener === "function") {
6575
- window.addEventListener("resize", onWindowResize);
6576
- }
6577
- return () => {
6578
- resizeObserver.disconnect();
6579
- if (isWindowScroll && typeof window !== "undefined" && typeof window.removeEventListener === "function") {
6580
- window.removeEventListener("resize", onWindowResize);
6656
+ if (!onLayout || !element) return;
6657
+ return observeScrollLayout(element, externalTarget, () => {
6658
+ if (isExternalScroll && !ctx.state.lastLayout && !ctx.state.initialScroll) {
6659
+ ctx.state.scroll = ctx.state.scrollPending = getRawScrollOffset();
6581
6660
  }
6582
- };
6583
- }, [isWindowScroll, onLayout]);
6584
- const hiddenScrollIndicatorClassName = !isWindowScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
6661
+ onLayout({ nativeEvent: { layout: getLayoutRectangle(element, externalTarget, horizontal) } });
6662
+ if (isExternalScroll) emitScroll();
6663
+ });
6664
+ }, [ctx, emitScroll, externalTarget, getRawScrollOffset, horizontal, isExternalScroll, onLayout]);
6665
+ const hiddenScrollIndicatorClassName = !isExternalScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
6585
6666
  React3.useLayoutEffect(() => {
6586
6667
  if (hiddenScrollIndicatorClassName) {
6587
6668
  ensureScrollbarHiddenStyle();
6588
6669
  }
6589
6670
  }, [hiddenScrollIndicatorClassName]);
6590
6671
  const scrollViewStyle = {
6591
- ...isWindowScroll ? {} : {
6672
+ ...isExternalScroll ? {} : {
6592
6673
  overflow: "auto",
6593
6674
  overflowX: horizontal ? "auto" : showsHorizontalScrollIndicator ? "auto" : "hidden",
6594
6675
  overflowY: horizontal ? showsVerticalScrollIndicator ? "auto" : "hidden" : "auto",
@@ -6623,11 +6704,10 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6623
6704
  scrollEventThrottle: _scrollEventThrottle,
6624
6705
  ScrollComponent: _ScrollComponent,
6625
6706
  snapToOffsets,
6626
- useWindowScroll: _useWindowScroll,
6627
6707
  className: scrollViewClassNameProp,
6628
6708
  ...webProps
6629
6709
  } = props;
6630
- const snapOffsets = !isWindowScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6710
+ const snapOffsets = !isExternalScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6631
6711
  if (snapOffsets.length > 0) {
6632
6712
  scrollViewStyle.scrollSnapType = horizontal ? "x mandatory" : "y mandatory";
6633
6713
  contentStyle.position = (_c = contentStyle.position) != null ? _c : "relative";
@@ -6702,12 +6782,12 @@ function getScrollAdjustAxis(horizontal) {
6702
6782
  };
6703
6783
  }
6704
6784
  function getScrollAdjustTarget(ctx, contentNode) {
6705
- var _a3, _b, _c;
6785
+ var _a3, _b, _c, _d, _e;
6706
6786
  const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
6707
6787
  const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
6708
6788
  let resolvedContentNode = null;
6709
6789
  if (scrollElement) {
6710
- resolvedContentNode = (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
6790
+ 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}`);
6711
6791
  }
6712
6792
  return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
6713
6793
  }
@@ -6722,6 +6802,7 @@ function ScrollAdjust() {
6722
6802
  const temporaryPaddingRef = React3__namespace.useRef(void 0);
6723
6803
  const contentNodeRef = React3__namespace.useRef(null);
6724
6804
  const callback = React3__namespace.useCallback(() => {
6805
+ var _a3, _b, _c;
6725
6806
  const scrollAdjust = peek$(ctx, "scrollAdjust");
6726
6807
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
6727
6808
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
@@ -6732,17 +6813,21 @@ function ScrollAdjust() {
6732
6813
  const horizontal = !!ctx.state.props.horizontal;
6733
6814
  const axis = getScrollAdjustAxis(horizontal);
6734
6815
  const { contentNode, scrollElement: el } = target;
6735
- const currentScroll = horizontal ? el.scrollLeft : el.scrollTop;
6816
+ const scroller = ctx.state.refScroller.current;
6817
+ const parentScroll = horizontal ? el.scrollLeft : el.scrollTop;
6818
+ const isExternal = ctx.state.props.hasExternalScroll;
6819
+ const currentScroll = (_b = isExternal ? (_a3 = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _a3.call(scroller) : void 0) != null ? _b : parentScroll;
6820
+ const isOutsideList = ((_c = scroller == null ? void 0 : scroller.isScrollInRange) == null ? void 0 : _c.call(scroller)) === false;
6736
6821
  const userOffsetDelta = (scrollAdjustUserOffset || 0) - lastScrollAdjustUserOffsetRef.current;
6737
6822
  const intendedScroll = userOffsetDelta !== 0 ? currentScroll + userOffsetDelta : ctx.state.scroll;
6738
6823
  const scrollDelta = intendedScroll - currentScroll;
6739
- const shouldScroll = Math.abs(scrollDelta) > 0.01;
6824
+ const shouldScroll = !isOutsideList && Math.abs(scrollDelta) > 0.01;
6740
6825
  const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
6741
6826
  contentNodeRef.current = contentNode;
6742
6827
  if (shouldScroll && contentNode) {
6743
- const totalSize = contentNode[axis.contentSizeKey];
6828
+ const totalSize = isExternal ? el[axis.contentSizeKey] : contentNode[axis.contentSizeKey];
6744
6829
  const viewportSize = el[axis.viewportSizeKey];
6745
- const nextScroll = currentScroll + scrollDelta;
6830
+ const nextScroll = parentScroll + scrollDelta;
6746
6831
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
6747
6832
  if (needsTemporaryPadding) {
6748
6833
  const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp];
@@ -6907,6 +6992,7 @@ var ListComponent = typedMemo(function ListComponent2({
6907
6992
  stickyHeaderConfig,
6908
6993
  stickyHeaderIndices,
6909
6994
  useWindowScroll = false,
6995
+ scrollElement,
6910
6996
  ...rest
6911
6997
  }) {
6912
6998
  const ctx = useStateContext();
@@ -6972,7 +7058,7 @@ var ListComponent = typedMemo(function ListComponent2({
6972
7058
  SnapOrScroll,
6973
7059
  {
6974
7060
  ...rest,
6975
- ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} ,
7061
+ ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, scrollElement, useWindowScroll } : {} ,
6976
7062
  contentContainerStyle: [
6977
7063
  horizontal ? { height: "100%" } : {},
6978
7064
  contentContainerStyle,
@@ -7012,7 +7098,15 @@ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
7012
7098
  var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
7013
7099
  function useDevChecksImpl(props) {
7014
7100
  const ctx = useStateContext();
7015
- const { anchoredEndSpace, childrenMode, keyExtractor, numColumns, renderScrollComponent, useWindowScroll } = props;
7101
+ const {
7102
+ anchoredEndSpace,
7103
+ childrenMode,
7104
+ keyExtractor,
7105
+ numColumns,
7106
+ renderScrollComponent,
7107
+ scrollElement,
7108
+ useWindowScroll
7109
+ } = props;
7016
7110
  const hasAnchoredEndSpace = !!anchoredEndSpace;
7017
7111
  React3.useEffect(() => {
7018
7112
  if (hasAnchoredEndSpace && (numColumns != null ? numColumns : 1) > 1) {
@@ -7023,13 +7117,13 @@ function useDevChecksImpl(props) {
7023
7117
  }
7024
7118
  }, [hasAnchoredEndSpace, numColumns]);
7025
7119
  React3.useEffect(() => {
7026
- if (useWindowScroll && renderScrollComponent) {
7120
+ if ((useWindowScroll || scrollElement !== void 0) && renderScrollComponent) {
7027
7121
  warnDevOnce(
7028
7122
  "useWindowScrollRenderScrollComponent",
7029
- "useWindowScroll is not supported when renderScrollComponent is provided."
7123
+ "useWindowScroll and scrollElement are not supported when renderScrollComponent is provided."
7030
7124
  );
7031
7125
  }
7032
- }, [renderScrollComponent, useWindowScroll]);
7126
+ }, [renderScrollComponent, scrollElement, useWindowScroll]);
7033
7127
  React3.useEffect(() => {
7034
7128
  if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
7035
7129
  warnDevOnce(
@@ -7041,8 +7135,8 @@ function useDevChecksImpl(props) {
7041
7135
  React3.useEffect(() => {
7042
7136
  const state = ctx.state;
7043
7137
  const dataLength = state.props.data.length;
7044
- const useWindowScrollResolved = state.props.useWindowScroll;
7045
- if (useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
7138
+ const usesExternalScroll = state.props.hasExternalScroll;
7139
+ if (usesExternalScroll || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
7046
7140
  return;
7047
7141
  }
7048
7142
  const warnIfUnboundedOuterSize = () => {
@@ -7206,7 +7300,8 @@ function doInitialAllocateContainers(ctx) {
7206
7300
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7207
7301
  });
7208
7302
  } else {
7209
- calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7303
+ const reuseInitialPositions = !state.didLoad && state.positionsAreCurrent && !state.didDataChange && !state.didColumnsChange;
7304
+ calculateItemsInView(ctx, { dataChanged: !reuseInitialPositions, doMVCP: true });
7210
7305
  }
7211
7306
  }
7212
7307
  return true;
@@ -7249,10 +7344,8 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7249
7344
  updateContentMetricsState(ctx);
7250
7345
  state.lastBatchingAction = Date.now();
7251
7346
  state.scrollForNextCalculateItemsInView = void 0;
7252
- if (scrollLength > 0) {
7253
- doInitialAllocateContainers(ctx);
7254
- }
7255
- if (needsCalculate) {
7347
+ const didAllocate = scrollLength > 0 && doInitialAllocateContainers(ctx);
7348
+ if (needsCalculate && (!didAllocate || state.initialScroll)) {
7256
7349
  calculateItemsInView(ctx, { doMVCP: true });
7257
7350
  }
7258
7351
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
@@ -7330,7 +7423,7 @@ function onScroll(ctx, event) {
7330
7423
  state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
7331
7424
  state.lastNativeScroll = newScroll;
7332
7425
  state.lastNativeScrollTime = Date.now();
7333
- if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7426
+ if (!state.props.hasExternalScroll && state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7334
7427
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
7335
7428
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
7336
7429
  newScroll = maxOffset;
@@ -8097,6 +8190,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8097
8190
  stickyHeaderIndices: stickyHeaderIndicesProp,
8098
8191
  style: styleProp,
8099
8192
  useWindowScroll = false,
8193
+ scrollElement,
8100
8194
  viewabilityConfig,
8101
8195
  viewabilityConfigCallbackPairs,
8102
8196
  ...rest
@@ -8191,7 +8285,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8191
8285
  dataVersion,
8192
8286
  keyExtractor
8193
8287
  ]);
8194
- const useWindowScrollResolved = !!useWindowScroll && !renderScrollComponent;
8288
+ const scrollElementResolved = !renderScrollComponent ? scrollElement : void 0;
8289
+ const useWindowScrollResolved = !!useWindowScroll && scrollElementResolved === void 0 && !renderScrollComponent;
8290
+ const hasExternalScroll = useWindowScrollResolved || scrollElementResolved !== void 0;
8195
8291
  const refState = React3.useRef(void 0);
8196
8292
  const hasOverrideItemLayout = !!overrideItemLayout;
8197
8293
  const prevHasOverrideItemLayout = React3.useRef(hasOverrideItemLayout);
@@ -8329,6 +8425,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8329
8425
  estimatedItemSize,
8330
8426
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8331
8427
  getItemType: useWrapIfItem(getItemType),
8428
+ hasExternalScroll,
8332
8429
  hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8333
8430
  horizontal: !!horizontal,
8334
8431
  itemsAreEqual,
@@ -8520,8 +8617,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8520
8617
  const { onLayout } = useOnLayoutSync({
8521
8618
  onLayoutChange,
8522
8619
  onLayoutProp,
8523
- ref: refScroller
8620
+ ref: refScroller,
8524
8621
  // the type of ScrollView doesn't include measure?
8622
+ webExternalScroll: hasExternalScroll
8525
8623
  });
8526
8624
  React3.useLayoutEffect(() => {
8527
8625
  if (snapToIndices) {
@@ -8687,6 +8785,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8687
8785
  refScrollView: combinedRef,
8688
8786
  renderScrollComponent,
8689
8787
  scrollAdjustHandler: (_s = refState.current) == null ? void 0 : _s.scrollAdjustHandler,
8788
+ scrollElement: scrollElementResolved,
8690
8789
  scrollEventThrottle: 0,
8691
8790
  snapToIndices,
8692
8791
  stickyHeaderIndices,