@legendapp/list 3.3.11 → 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
@@ -409,6 +409,14 @@ var EDGE_POSITION_EPSILON = 1;
409
409
  var ENABLE_DEVMODE = IS_DEV && false;
410
410
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
411
411
 
412
+ // src/core/endOfContentTarget.ts
413
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
414
+ return -stylePaddingEnd - footerSize;
415
+ }
416
+ function isEndOfContentTarget(target, dataLength) {
417
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
418
+ }
419
+
412
420
  // src/core/cancelImperativeScroll.ts
413
421
  function cancelScrollCompletionChecks({ scheduledWork }) {
414
422
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -593,26 +601,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
593
601
  scrollPosition: context.scrollPosition
594
602
  });
595
603
  };
604
+ let reached = !!wasReached;
596
605
  if (!wasReached) {
597
- if (!within) {
598
- return false;
599
- }
600
- onReached(distance);
601
- updateSnapshot();
602
- return true;
603
- }
604
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
605
- if (reset) {
606
- setSnapshot(void 0);
607
- return false;
608
- }
609
- if (within) {
610
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
611
- if (changed) {
606
+ if (within && onReached(distance)) {
612
607
  updateSnapshot();
608
+ reached = true;
609
+ }
610
+ } else {
611
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
612
+ if (reset) {
613
+ setSnapshot(void 0);
614
+ reached = false;
615
+ } else if (within) {
616
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
617
+ if (changed) {
618
+ updateSnapshot();
619
+ }
613
620
  }
614
621
  }
615
- return true;
622
+ return reached;
616
623
  };
617
624
 
618
625
  // src/utils/edgeReachedGate.ts
@@ -659,7 +666,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
659
666
  return void 0;
660
667
  }
661
668
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
662
- state.edgeReachedGate = "closed";
663
669
  resetEdgeLatch(ctx, allowedEdge);
664
670
  return allowedEdge;
665
671
  }
@@ -700,7 +706,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
700
706
  set$(
701
707
  ctx,
702
708
  "isWithinMaintainScrollAtEndThreshold",
703
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
709
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
704
710
  );
705
711
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
706
712
  if (!shouldSkipThresholdChecks) {
@@ -716,11 +722,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
716
722
  scrollPosition: scroll
717
723
  },
718
724
  (distance) => {
719
- var _a4, _b;
720
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
725
+ let dispatched = false;
726
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
721
727
  markReachedEdge(ctx);
722
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
728
+ state.props.onEndReached({ distanceFromEnd: distance });
729
+ dispatched = true;
723
730
  }
731
+ return dispatched;
724
732
  },
725
733
  (snapshot) => {
726
734
  state.endReachedSnapshot = snapshot;
@@ -753,7 +761,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
753
761
  }
754
762
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
755
763
  set$(ctx, "isNearStart", scroll <= threshold);
756
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
764
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
757
765
  if (!shouldSkipThresholdChecks) {
758
766
  state.isStartReached = checkThreshold(
759
767
  scroll,
@@ -767,11 +775,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
767
775
  scrollPosition: scroll
768
776
  },
769
777
  (distance) => {
770
- var _a3, _b;
771
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
778
+ let dispatched = false;
779
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
772
780
  markReachedEdge(ctx);
773
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
781
+ state.props.onStartReached({ distanceFromStart: distance });
782
+ dispatched = true;
774
783
  }
784
+ return dispatched;
775
785
  },
776
786
  (snapshot) => {
777
787
  state.startReachedSnapshot = snapshot;
@@ -870,8 +880,14 @@ function scheduleFullDrawDistancePrewarm(ctx) {
870
880
  return;
871
881
  }
872
882
  state.scheduledWork.frame(() => {
873
- var _a3;
874
- 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);
875
891
  }, "fullDrawDistancePrewarm");
876
892
  }
877
893
 
@@ -884,6 +900,14 @@ function resetInitialRenderState(ctx, {
884
900
  if (resetLayout) {
885
901
  state.didContainersLayout = false;
886
902
  state.queuedInitialLayout = false;
903
+ state.scrollBufferDirection = void 0;
904
+ state.scrollHistory.length = 0;
905
+ state.scheduledWork.cancel("renderRangeProjection");
906
+ state.edgeReachedGate = void 0;
907
+ state.isStartReached = null;
908
+ state.isEndReached = null;
909
+ state.startReachedSnapshot = void 0;
910
+ state.endReachedSnapshot = void 0;
887
911
  }
888
912
  if (resetInitialScroll) {
889
913
  state.didFinishInitialScroll = false;
@@ -898,7 +922,7 @@ function setInitialRenderState(ctx, {
898
922
  const { state } = ctx;
899
923
  const {
900
924
  loadStartTime,
901
- props: { onLoad }
925
+ props: { onLoad, onReady }
902
926
  } = state;
903
927
  if (didLayout) {
904
928
  state.didContainersLayout = true;
@@ -919,6 +943,7 @@ function setInitialRenderState(ctx, {
919
943
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
920
944
  }
921
945
  }
946
+ onReady == null ? void 0 : onReady();
922
947
  }
923
948
  }
924
949
 
@@ -1104,6 +1129,7 @@ function setSize(ctx, itemKey, size, notifyTotalSize = true) {
1104
1129
  const previousSize = sizes.get(itemKey);
1105
1130
  const diff = previousSize !== void 0 ? size - previousSize : size;
1106
1131
  if (diff !== 0) {
1132
+ state.positionsAreCurrent = false;
1107
1133
  addTotalSize(ctx, itemKey, diff, notifyTotalSize);
1108
1134
  }
1109
1135
  sizes.set(itemKey, size);
@@ -1164,6 +1190,7 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
1164
1190
  const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
1165
1191
  if (fixedSize !== void 0) {
1166
1192
  size = fixedSize + ctx.scrollAxisGap;
1193
+ state.positionsAreCurrent = false;
1167
1194
  state.sizesKnown.set(key, size);
1168
1195
  }
1169
1196
  }
@@ -1262,10 +1289,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1262
1289
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1263
1290
  const trailingInset = getContentInsetEnd(ctx);
1264
1291
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1265
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1266
- const footerSize = peek$(ctx, "footerSize") || 0;
1267
- offset += footerSize;
1268
- }
1269
1292
  }
1270
1293
  return offset;
1271
1294
  }
@@ -1749,6 +1772,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1749
1772
  state.scrollTime = currentTime;
1750
1773
  const scrollDelta = Math.abs(newScroll - prevScroll);
1751
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
+ }
1752
1778
  const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1753
1779
  if (didCancelMaintainScrollAtEnd) {
1754
1780
  finishMaintainScrollAtEnd(ctx);
@@ -1827,89 +1853,104 @@ function finishMaintainScrollAtEnd(ctx) {
1827
1853
  requestAdjust(ctx, nextScroll - state.scroll);
1828
1854
  }
1829
1855
  }
1830
- function doMaintainScrollAtEnd(ctx) {
1831
- var _a3, _b, _c, _d, _e;
1856
+ function doMaintainScrollAtEnd(ctx, options) {
1857
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
1832
1858
  const state = ctx.state;
1833
- const {
1834
- didContainersLayout,
1835
- didFinishInitialScroll,
1836
- pendingNativeMVCPAdjust,
1837
- props: { maintainScrollAtEnd }
1838
- } = state;
1839
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1840
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1841
- 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);
1842
- if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1843
- const scroller = state.refScroller.current;
1844
- const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1845
- const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1846
- if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1859
+ let didMaintain = false;
1860
+ if (state.isDragging || ((_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.isScrollInRange) == null ? void 0 : _b.call(_a3)) === false) {
1861
+ finishMaintainScrollAtEnd(ctx);
1862
+ } else {
1863
+ const {
1864
+ didContainersLayout,
1865
+ didFinishInitialScroll,
1866
+ pendingNativeMVCPAdjust,
1867
+ props: { maintainScrollAtEnd }
1868
+ } = state;
1869
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1870
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1871
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
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);
1873
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1874
+ const scroller = state.refScroller.current;
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);
1877
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1878
+ state.pendingMaintainScrollAtEnd = true;
1879
+ state.scrollPending = observed;
1880
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1881
+ }
1882
+ }
1883
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1847
1884
  state.pendingMaintainScrollAtEnd = true;
1848
- state.scrollPending = observed;
1849
- updateScroll(ctx, observed, true, { markHasScrolled: false });
1850
- }
1851
- }
1852
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1853
- state.pendingMaintainScrollAtEnd = true;
1854
- return false;
1855
- }
1856
- if (pendingNativeMVCPAdjust) {
1857
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1858
- return false;
1859
- }
1860
- if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1861
- state.pendingMaintainScrollAtEnd = true;
1862
- (_e = state.maintainingScrollAtEnd) != null ? _e : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1863
- return false;
1864
- }
1865
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1866
- state.pendingMaintainScrollAtEnd = false;
1867
- const contentSize = getContentSize(ctx);
1868
- if (contentSize < state.scrollLength) {
1869
- state.scroll = 0;
1870
- }
1871
- if (!state.maintainingScrollAtEnd) {
1872
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1873
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1874
- const scrollAtRequest = state.scroll;
1875
- state.maintainingScrollAtEnd = pendingState;
1876
- requestAnimationFrame(() => {
1877
- if (state.maintainingScrollAtEnd !== pendingState) {
1878
- return;
1879
- }
1880
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1881
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1882
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1883
- if (state.scrollingTo || state.pendingScrollResolve) {
1884
- state.pendingMaintainScrollAtEnd = true;
1885
- return;
1886
- }
1887
- state.maintainingScrollAtEnd = activeState;
1888
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1889
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1890
- );
1891
- void scrollPromise.then(() => {
1892
- if (state.maintainingScrollAtEnd !== activeState) {
1893
- return;
1894
- }
1895
- if (state.pendingMaintainScrollAtEnd) {
1896
- state.maintainingScrollAtEnd = void 0;
1897
- doMaintainScrollAtEnd(ctx);
1898
- } else {
1885
+ } else if (pendingNativeMVCPAdjust) {
1886
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1887
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1888
+ if (immediate && ((_g = state.scrollingTo) == null ? void 0 : _g.isScrollToEnd) && !state.scrollingTo.animated) {
1889
+ state.pendingMaintainScrollAtEnd = false;
1890
+ ctx.scrollToEnd({ animated: false });
1891
+ didMaintain = true;
1892
+ } else {
1893
+ state.pendingMaintainScrollAtEnd = true;
1894
+ (_h = state.maintainingScrollAtEnd) != null ? _h : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1895
+ }
1896
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1897
+ state.pendingMaintainScrollAtEnd = false;
1898
+ const contentSize = getContentSize(ctx);
1899
+ if (contentSize < state.scrollLength) {
1900
+ state.scroll = 0;
1901
+ }
1902
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1903
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1904
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1905
+ const scrollAtRequest = state.scroll;
1906
+ state.maintainingScrollAtEnd = pendingState;
1907
+ const run = () => {
1908
+ var _a4, _b2;
1909
+ if (state.maintainingScrollAtEnd === pendingState) {
1910
+ if (state.isDragging || ((_b2 = (_a4 = state.refScroller.current) == null ? void 0 : _a4.isScrollInRange) == null ? void 0 : _b2.call(_a4)) === false) {
1899
1911
  finishMaintainScrollAtEnd(ctx);
1912
+ } else {
1913
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1914
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1915
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1916
+ if (state.scrollingTo || state.pendingScrollResolve) {
1917
+ state.pendingMaintainScrollAtEnd = true;
1918
+ } else {
1919
+ state.maintainingScrollAtEnd = activeState;
1920
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1921
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1922
+ );
1923
+ void scrollPromise.then(() => {
1924
+ if (state.maintainingScrollAtEnd === activeState) {
1925
+ if (state.pendingMaintainScrollAtEnd) {
1926
+ state.maintainingScrollAtEnd = void 0;
1927
+ doMaintainScrollAtEnd(ctx);
1928
+ } else {
1929
+ finishMaintainScrollAtEnd(ctx);
1930
+ }
1931
+ }
1932
+ });
1933
+ }
1934
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1935
+ finishMaintainScrollAtEnd(ctx);
1936
+ }
1900
1937
  }
1901
- });
1902
- } else if (state.maintainingScrollAtEnd === pendingState) {
1903
- finishMaintainScrollAtEnd(ctx);
1938
+ }
1939
+ };
1940
+ if (immediate) {
1941
+ run();
1942
+ } else {
1943
+ requestAnimationFrame(run);
1904
1944
  }
1905
- });
1945
+ } else {
1946
+ state.pendingMaintainScrollAtEnd = true;
1947
+ }
1948
+ didMaintain = true;
1906
1949
  } else {
1907
- state.pendingMaintainScrollAtEnd = true;
1950
+ finishMaintainScrollAtEnd(ctx);
1908
1951
  }
1909
- return true;
1910
1952
  }
1911
- finishMaintainScrollAtEnd(ctx);
1912
- return false;
1953
+ return didMaintain;
1913
1954
  }
1914
1955
 
1915
1956
  // src/core/finishScrollTo.ts
@@ -2213,7 +2254,9 @@ function scrollTo(ctx, params) {
2213
2254
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2214
2255
  }
2215
2256
  }
2216
- state.scrollPending = targetOffset;
2257
+ if (!animated || isInitialScroll) {
2258
+ state.scrollPending = targetOffset;
2259
+ }
2217
2260
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2218
2261
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2219
2262
  if (animated) {
@@ -2590,13 +2633,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2590
2633
  queueBootstrapInitialScrollReevaluation(ctx.state);
2591
2634
  }
2592
2635
  function createInitialScrollAtEndTarget(options) {
2593
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2636
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2594
2637
  return {
2595
2638
  contentOffset: void 0,
2596
- index: Math.max(0, dataLength - 1),
2639
+ index: index != null ? index : Math.max(0, dataLength - 1),
2597
2640
  preserveForBottomPadding: true,
2598
2641
  preserveForFooterLayout,
2599
- viewOffset: -stylePaddingEnd - footerSize,
2642
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2600
2643
  viewPosition: 1
2601
2644
  };
2602
2645
  }
@@ -2612,13 +2655,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2612
2655
  function createRetargetedBottomAlignedInitialScroll(options) {
2613
2656
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2614
2657
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2658
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2615
2659
  return {
2616
2660
  ...target,
2617
2661
  contentOffset: void 0,
2618
2662
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2619
2663
  preserveForBottomPadding: true,
2620
2664
  preserveForFooterLayout,
2621
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2665
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2622
2666
  viewPosition: 1
2623
2667
  };
2624
2668
  }
@@ -2632,7 +2676,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2632
2676
  }
2633
2677
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2634
2678
  dataLength,
2635
- footerSize: 0,
2679
+ footerSize: peek$(ctx, "footerSize") || 0,
2636
2680
  preserveForFooterLayout: void 0,
2637
2681
  stylePaddingEnd
2638
2682
  });
@@ -2655,13 +2699,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2655
2699
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2656
2700
  }
2657
2701
  function getPreservedEndAnchorOffsetDiff(ctx) {
2658
- var _a3;
2659
2702
  const state = ctx.state;
2660
2703
  const initialScroll = state.initialScroll;
2661
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2704
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2662
2705
  return;
2663
2706
  }
2664
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2707
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
2665
2708
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2666
2709
  }
2667
2710
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2689,6 +2732,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2689
2732
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2690
2733
  if (hasObservedNativeScrollAfterRequest) {
2691
2734
  activeCorrection.lastRequestTime = Date.now();
2735
+ {
2736
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2737
+ }
2692
2738
  requestAdjust(ctx, offsetDiff);
2693
2739
  }
2694
2740
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2811,10 +2857,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2811
2857
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2812
2858
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2813
2859
  const state = ctx.state;
2814
- if (!initialScrollAtEnd) {
2860
+ const initialScroll = state.initialScroll;
2861
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2815
2862
  return;
2816
2863
  }
2817
- const initialScroll = state.initialScroll;
2818
2864
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2819
2865
  return;
2820
2866
  }
@@ -2832,6 +2878,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2832
2878
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2833
2879
  dataLength,
2834
2880
  footerSize,
2881
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
2835
2882
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2836
2883
  stylePaddingEnd
2837
2884
  });
@@ -3303,6 +3350,7 @@ function handleInitialScrollDataChange(ctx, options) {
3303
3350
 
3304
3351
  // src/core/resetLayoutCachesForDataChange.ts
3305
3352
  function resetLayoutCachesForDataChange(state) {
3353
+ state.positionsAreCurrent = false;
3306
3354
  state.indexByKey.clear();
3307
3355
  state.idCache.length = 0;
3308
3356
  state.positions.length = 0;
@@ -3583,7 +3631,7 @@ function updateItemPositions(ctx, dataChanged, {
3583
3631
  scrollBottomBuffered: -1,
3584
3632
  startIndex: 0
3585
3633
  }) {
3586
- var _a3, _b, _c, _d, _e;
3634
+ var _a3, _b, _c, _d, _e, _f;
3587
3635
  const state = ctx.state;
3588
3636
  const hasPositionListeners = ctx.positionListeners.size > 0;
3589
3637
  const {
@@ -3599,6 +3647,11 @@ function updateItemPositions(ctx, dataChanged, {
3599
3647
  const dataLength = data.length;
3600
3648
  const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
3601
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;
3602
3655
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
3603
3656
  const extraData = peek$(ctx, "extraData");
3604
3657
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
@@ -3713,6 +3766,10 @@ function updateItemPositions(ctx, dataChanged, {
3713
3766
  }
3714
3767
  if (!didBreakEarly) {
3715
3768
  updateTotalSize(ctx);
3769
+ state.positionsAreCurrent = !hasColumns;
3770
+ if (startIndex <= ((_f = state.positionRecalculationStartIndex) != null ? _f : -1)) {
3771
+ state.positionRecalculationStartIndex = void 0;
3772
+ }
3716
3773
  }
3717
3774
  if (snapToIndices) {
3718
3775
  updateSnapToOffsets(ctx);
@@ -3981,7 +4038,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
3981
4038
  }
3982
4039
 
3983
4040
  // src/utils/findAvailableContainers.ts
3984
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
4041
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
3985
4042
  var _a3;
3986
4043
  const numNeeded = needNewContainers.length;
3987
4044
  if (numNeeded === 0) {
@@ -3990,16 +4047,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3990
4047
  const numContainers = peek$(ctx, "numContainers");
3991
4048
  const state = ctx.state;
3992
4049
  const { containerItemMetadata, stickyContainerPool } = state;
4050
+ const { data, getItemType } = state.props;
4051
+ const hasItemTypes = !!getItemType;
3993
4052
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
3994
4053
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
3995
- const requests = needNewContainers.map((itemIndex, order) => ({
3996
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
3997
- itemIndex,
3998
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
3999
- order
4000
- }));
4001
- const normalRequests = requests.filter((request) => !request.isSticky);
4002
- const stickyRequests = requests.filter((request) => request.isSticky);
4054
+ const requests = new Array(numNeeded);
4055
+ const normalRequests = [];
4056
+ const stickyRequests = [];
4057
+ for (let order = 0; order < numNeeded; order++) {
4058
+ const itemIndex = needNewContainers[order];
4059
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
4060
+ const request = {
4061
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4062
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4063
+ itemIndex,
4064
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4065
+ order,
4066
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4067
+ };
4068
+ requests[order] = request;
4069
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4070
+ }
4003
4071
  const normalCandidates = [];
4004
4072
  const stickyCandidates = [];
4005
4073
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -4055,7 +4123,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4055
4123
  }
4056
4124
  };
4057
4125
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
4058
- if (getRequiredItemType) {
4126
+ if (hasItemTypes) {
4059
4127
  assignMatching(
4060
4128
  pendingRequests,
4061
4129
  candidates,
@@ -4079,6 +4147,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4079
4147
  }
4080
4148
  assign(request, containerIndex);
4081
4149
  }
4150
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4151
+ const containerIndices = [];
4152
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4153
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4154
+ }
4082
4155
  if (pendingRemovalChanged) {
4083
4156
  pendingRemoval.length = 0;
4084
4157
  if (pendingRemovalSet) {
@@ -4108,12 +4181,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4108
4181
  function comparatorByDistance(a, b) {
4109
4182
  return b.distance - a.distance;
4110
4183
  }
4184
+ function comparatorByRenderPriority(a, b) {
4185
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4186
+ }
4187
+ function comparatorByIndex(a, b) {
4188
+ return a - b;
4189
+ }
4190
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4191
+ if (requests.length >= 2) {
4192
+ if (groupByType) {
4193
+ const groups = /* @__PURE__ */ new Map();
4194
+ for (const request of requests) {
4195
+ const group = groups.get(request.itemType);
4196
+ if (group) {
4197
+ group.push(request);
4198
+ } else {
4199
+ groups.set(request.itemType, [request]);
4200
+ }
4201
+ }
4202
+ for (const group of groups.values()) {
4203
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4204
+ }
4205
+ } else {
4206
+ containerIndices.length = 0;
4207
+ let indicesSorted = true;
4208
+ for (let i = 0; i < requests.length; i++) {
4209
+ const containerIndex = allocations[requests[i].order].containerIndex;
4210
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4211
+ indicesSorted = false;
4212
+ }
4213
+ containerIndices.push(containerIndex);
4214
+ }
4215
+ if (!indicesSorted) {
4216
+ containerIndices.sort(comparatorByIndex);
4217
+ }
4218
+ requests.sort(comparatorByRenderPriority);
4219
+ for (let i = 0; i < requests.length; i++) {
4220
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4221
+ }
4222
+ }
4223
+ }
4224
+ }
4111
4225
 
4112
4226
  // src/utils/setDidLayout.ts
4113
4227
  function setDidLayout(ctx) {
4114
4228
  const state = ctx.state;
4115
4229
  state.queuedInitialLayout = true;
4116
- checkAtBottom(ctx);
4230
+ checkThresholds(ctx);
4117
4231
  setInitialRenderState(ctx, { didLayout: true });
4118
4232
  if (state.pendingMaintainScrollAtEnd) {
4119
4233
  doMaintainScrollAtEnd(ctx);
@@ -4218,7 +4332,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4218
4332
  }
4219
4333
  function trackVisibleRange(range, i, top, size, scroll, scrollBottom) {
4220
4334
  let didPassVisibleEnd = false;
4221
- if (range.startNoBuffer === null && top + size > scroll) {
4335
+ if (range.startNoBuffer === null && top + size > scroll && top <= scrollBottom) {
4222
4336
  range.startNoBuffer = i;
4223
4337
  }
4224
4338
  if (range.firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
@@ -4284,7 +4398,7 @@ function findFirstVisibleIndexInCachedRange(ctx, scroll) {
4284
4398
  return null;
4285
4399
  }
4286
4400
  function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, scrollLength, scroll, scrollBottom) {
4287
- var _a3, _b;
4401
+ var _a3, _b, _c, _d;
4288
4402
  const state = ctx.state;
4289
4403
  const {
4290
4404
  endBuffered,
@@ -4318,14 +4432,14 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4318
4432
  startNoBuffer: visibleRange.startNoBuffer
4319
4433
  });
4320
4434
  maybeEmitFirstVisibleItemChanged(state, visibleRange.startNoBuffer);
4321
- if (visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4435
+ if (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4322
4436
  updateViewableItems(
4323
4437
  state,
4324
4438
  ctx,
4325
4439
  viewabilityConfigCallbackPairs,
4326
4440
  scrollLength,
4327
- visibleRange.startNoBuffer,
4328
- visibleRange.endNoBuffer,
4441
+ (_c = visibleRange.startNoBuffer) != null ? _c : 0,
4442
+ (_d = visibleRange.endNoBuffer) != null ? _d : -1,
4329
4443
  startBuffered,
4330
4444
  endBuffered
4331
4445
  );
@@ -4334,16 +4448,16 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4334
4448
  function calculateItemsInView(ctx, params = {}) {
4335
4449
  const state = ctx.state;
4336
4450
  batchedUpdates(() => {
4337
- 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;
4338
4452
  const {
4339
4453
  columns,
4340
4454
  containerItemKeys,
4341
4455
  enableScrollForNextCalculateItemsInView,
4342
4456
  idCache,
4343
4457
  indexByKey,
4344
- minIndexSizeChanged,
4458
+ positionRecalculationStartIndex,
4345
4459
  positions,
4346
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4460
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4347
4461
  scrollForNextCalculateItemsInView,
4348
4462
  scrollLength,
4349
4463
  sizes,
@@ -4386,7 +4500,7 @@ function calculateItemsInView(ctx, params = {}) {
4386
4500
  scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4387
4501
  scrollAdjustPad = scrollAdjustPending - topPad;
4388
4502
  scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4389
- if (scroll + scrollLength > totalSize) {
4503
+ if (!state.props.hasExternalScroll && scroll + scrollLength > totalSize) {
4390
4504
  scroll = Math.max(0, totalSize - scrollLength);
4391
4505
  }
4392
4506
  };
@@ -4413,7 +4527,11 @@ function calculateItemsInView(ctx, params = {}) {
4413
4527
  let stickyState = dataChanged ? void 0 : resolveStickyState();
4414
4528
  let scrollBufferTop = drawDistance;
4415
4529
  let scrollBufferBottom = drawDistance;
4416
- 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) {
4417
4535
  scrollBufferTop = drawDistance * 0.5;
4418
4536
  scrollBufferBottom = drawDistance * 1.5;
4419
4537
  } else {
@@ -4423,8 +4541,8 @@ function calculateItemsInView(ctx, params = {}) {
4423
4541
  const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4424
4542
  const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4425
4543
  const updateScrollRange = () => {
4426
- const scrollStart = Math.max(0, scroll);
4427
- 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);
4428
4546
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4429
4547
  scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4430
4548
  scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
@@ -4459,21 +4577,21 @@ function calculateItemsInView(ctx, params = {}) {
4459
4577
  if (dataChanged) {
4460
4578
  resetLayoutCachesForDataChange(state);
4461
4579
  }
4462
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4463
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4580
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4581
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4464
4582
  updateItemPositions(ctx, dataChanged, {
4465
4583
  doMVCP,
4466
4584
  // A changed size shifts every following position. The scrolling early-exit
4467
4585
  // would leave the untouched suffix in the old coordinate space.
4468
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4586
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4469
4587
  optimizeForVisibleWindow,
4470
4588
  scrollBottomBuffered,
4471
4589
  scrollVelocity: speed,
4472
4590
  startIndex
4473
4591
  });
4474
4592
  totalSize = getContentSize(ctx);
4475
- if (minIndexSizeChanged !== void 0) {
4476
- state.minIndexSizeChanged = void 0;
4593
+ if (positionRecalculationStartIndex !== void 0) {
4594
+ state.positionRecalculationStartIndex = void 0;
4477
4595
  }
4478
4596
  let protectedContainerKeys;
4479
4597
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4553,7 +4671,7 @@ function calculateItemsInView(ctx, params = {}) {
4553
4671
  nextTop = top;
4554
4672
  }
4555
4673
  }
4556
- if (visibleRange.startNoBuffer !== null) {
4674
+ if (startBuffered !== null) {
4557
4675
  if (top <= scrollBottomBuffered) {
4558
4676
  endBuffered = i;
4559
4677
  if (scrollBottomBuffered > totalSize) {
@@ -4564,6 +4682,8 @@ function calculateItemsInView(ctx, params = {}) {
4564
4682
  } else {
4565
4683
  foundEnd = true;
4566
4684
  }
4685
+ } else if (top > scrollBottomBuffered) {
4686
+ foundEnd = true;
4567
4687
  }
4568
4688
  }
4569
4689
  }
@@ -4581,6 +4701,12 @@ function calculateItemsInView(ctx, params = {}) {
4581
4701
  bottom: nextBottom,
4582
4702
  top: nextTop
4583
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
+ }
4584
4710
  }
4585
4711
  let numContainers = prevNumContainers;
4586
4712
  const pendingRemoval = [];
@@ -4604,7 +4730,9 @@ function calculateItemsInView(ctx, params = {}) {
4604
4730
  }
4605
4731
  const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4606
4732
  const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4607
- 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;
4608
4736
  const needNewContainers = [];
4609
4737
  const needNewContainersSet = /* @__PURE__ */ new Set();
4610
4738
  const addPinnedIndex = (index) => {
@@ -4620,7 +4748,7 @@ function calculateItemsInView(ctx, params = {}) {
4620
4748
  }
4621
4749
  }
4622
4750
  };
4623
- for (let i = startBuffered; i <= endBuffered; i++) {
4751
+ for (let i = allocationStart; i <= allocationEnd; i++) {
4624
4752
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4625
4753
  if (!containerItemKeys.has(id)) {
4626
4754
  needNewContainersSet.add(i);
@@ -4642,25 +4770,25 @@ function calculateItemsInView(ctx, params = {}) {
4642
4770
  (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4643
4771
  needNewContainers,
4644
4772
  needNewContainersSet,
4645
- startBuffered,
4646
- endBuffered
4773
+ allocationStart,
4774
+ allocationEnd
4647
4775
  );
4648
4776
  } else if (previousStickyIndex !== -1) {
4649
4777
  set$(ctx, "activeStickyIndex", -1);
4650
4778
  }
4651
4779
  if (needNewContainers.length > 0) {
4652
- const getRequiredItemType = getItemType ? (i) => {
4653
- const itemType = getItemType(data[i], i);
4654
- return itemType !== void 0 ? String(itemType) : "";
4655
- } : void 0;
4656
4780
  const availableContainerAllocations = findAvailableContainers(
4657
4781
  ctx,
4658
4782
  needNewContainers,
4659
- startBuffered,
4660
- endBuffered,
4783
+ allocationStart,
4784
+ allocationEnd,
4661
4785
  pendingRemoval,
4662
- getRequiredItemType,
4663
- protectedContainerKeys
4786
+ protectedContainerKeys,
4787
+ // Initial end alignment has no scroll velocity yet. Keep its
4788
+ // target-side rows first until the initial scroll settles.
4789
+ // After a pause velocity can be zero on the first movement;
4790
+ // use its displacement without treating initial insets as motion.
4791
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4664
4792
  );
4665
4793
  for (const allocation of availableContainerAllocations) {
4666
4794
  const i = allocation.itemIndex;
@@ -4780,19 +4908,19 @@ function calculateItemsInView(ctx, params = {}) {
4780
4908
  handleInitialScrollLayoutReady(ctx);
4781
4909
  }
4782
4910
  }
4783
- if (viewabilityConfigCallbackPairs && visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4911
+ if (viewabilityConfigCallbackPairs && (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null)) {
4784
4912
  updateViewableItems(
4785
4913
  ctx.state,
4786
4914
  ctx,
4787
4915
  viewabilityConfigCallbackPairs,
4788
4916
  scrollLength,
4789
- visibleRange.startNoBuffer,
4790
- visibleRange.endNoBuffer,
4791
- startBuffered != null ? startBuffered : visibleRange.startNoBuffer,
4792
- 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
4793
4921
  );
4794
4922
  }
4795
- (_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);
4796
4924
  });
4797
4925
  }
4798
4926
 
@@ -4943,6 +5071,7 @@ function updateItemSizesBatch(ctx, measurements) {
4943
5071
  var _a3;
4944
5072
  const state = ctx.state;
4945
5073
  const result = {};
5074
+ const previousTotalSize = state.totalSize;
4946
5075
  for (const measurement of measurements) {
4947
5076
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4948
5077
  if (ownsMeasuredItem) {
@@ -4953,6 +5082,9 @@ function updateItemSizesBatch(ctx, measurements) {
4953
5082
  mergeItemSizeUpdateResult(result, nextResult);
4954
5083
  }
4955
5084
  }
5085
+ if (state.totalSize !== previousTotalSize) {
5086
+ set$(ctx, "totalSize", state.totalSize);
5087
+ }
4956
5088
  flushItemSizeUpdates(ctx, result);
4957
5089
  }
4958
5090
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -4991,12 +5123,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4991
5123
  }
4992
5124
  let needsRecalculate = !didContainersLayout;
4993
5125
  let shouldMaintainScrollAtEnd = false;
4994
- let minIndexSizeChanged;
5126
+ let positionRecalculationStartIndex;
4995
5127
  const prevSizeKnown = state.sizesKnown.get(itemKey);
4996
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5128
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
4997
5129
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
4998
5130
  if (diff !== 0) {
4999
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5131
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
5000
5132
  const { startBuffered, endBuffered } = state;
5001
5133
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
5002
5134
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -5013,8 +5145,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5013
5145
  size
5014
5146
  });
5015
5147
  }
5016
- if (minIndexSizeChanged !== void 0) {
5017
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5148
+ if (positionRecalculationStartIndex !== void 0) {
5149
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
5018
5150
  }
5019
5151
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
5020
5152
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -5031,7 +5163,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5031
5163
  didMeasureUserScrollAnchorResetItem
5032
5164
  };
5033
5165
  }
5034
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5166
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
5035
5167
  var _a3, _b;
5036
5168
  const state = ctx.state;
5037
5169
  const {
@@ -5054,10 +5186,20 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5054
5186
  fixedItemSize,
5055
5187
  itemType
5056
5188
  } : void 0;
5057
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5189
+ const prevSize = getItemSize(
5190
+ ctx,
5191
+ itemKey,
5192
+ index,
5193
+ itemData,
5194
+ void 0,
5195
+ void 0,
5196
+ notifyTotalSize,
5197
+ resolvedItemSize
5198
+ );
5058
5199
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
5059
5200
  const prevSizeKnown = sizesKnown.get(itemKey);
5060
5201
  const size = Math.round(rawSize) ;
5202
+ if (prevSizeKnown !== size) state.positionsAreCurrent = false;
5061
5203
  sizesKnown.set(itemKey, size);
5062
5204
  if (fixedItemSize === void 0 && size > 0) {
5063
5205
  itemType != null ? itemType : itemType = getItemType ? (_b = getItemType(itemData, index)) != null ? _b : "" : "";
@@ -5076,7 +5218,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5076
5218
  }
5077
5219
  }
5078
5220
  if (!prevSize || Math.abs(prevSize - size) > 0.1) {
5079
- setSize(ctx, itemKey, size);
5221
+ setSize(ctx, itemKey, size, notifyTotalSize);
5080
5222
  return size - prevSize;
5081
5223
  }
5082
5224
  return 0;
@@ -5478,10 +5620,19 @@ function useOnLayoutSync({
5478
5620
  measureInLayoutEffect = true,
5479
5621
  onLayoutProp,
5480
5622
  onLayoutChange,
5481
- webLayoutResync
5623
+ webLayoutResync,
5624
+ webExternalScroll = false
5482
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
+ );
5483
5633
  useLayoutEffect(() => {
5484
5634
  var _a3, _b;
5635
+ if (webExternalScroll) return;
5485
5636
  const current = ref.current;
5486
5637
  const scrollableNode = (_b = (_a3 = current == null ? void 0 : current.getScrollableNode) == null ? void 0 : _a3.call(current)) != null ? _b : null;
5487
5638
  const element = scrollableNode || current;
@@ -5519,8 +5670,8 @@ function useOnLayoutSync({
5519
5670
  emit(toLayout(rectObserved), false);
5520
5671
  }
5521
5672
  });
5522
- }, [measureInLayoutEffect, ...deps || []]);
5523
- return {};
5673
+ }, [measureInLayoutEffect, webExternalScroll, ...deps || []]);
5674
+ return webExternalScroll ? { onLayout: onExternalLayout } : {};
5524
5675
  }
5525
5676
  function toLayout(rect) {
5526
5677
  if (!rect) {
@@ -6082,94 +6233,121 @@ function useRafCoalescer(callback) {
6082
6233
  return coalescer;
6083
6234
  }
6084
6235
 
6085
- // src/components/webConstants.ts
6086
- var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
6087
- var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
6088
- var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
6089
-
6090
6236
  // src/components/webScrollUtils.ts
6091
- function getDocumentScrollerNode() {
6092
- if (typeof document === "undefined") {
6093
- return null;
6094
- }
6095
- return document.scrollingElement || document.documentElement || document.body;
6237
+ function isWindowTarget(target) {
6238
+ return typeof window !== "undefined" && target === window;
6096
6239
  }
6097
- function getWindowScrollPosition() {
6098
- var _a3, _b, _c, _d;
6099
- if (typeof window === "undefined") {
6100
- 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 };
6101
6244
  }
6102
- return {
6103
- x: (_b = (_a3 = window.scrollX) != null ? _a3 : window.pageXOffset) != null ? _b : 0,
6104
- y: (_d = (_c = window.scrollY) != null ? _c : window.pageYOffset) != null ? _d : 0
6105
- };
6245
+ return { x: (_e = target == null ? void 0 : target.scrollLeft) != null ? _e : 0, y: (_f = target == null ? void 0 : target.scrollTop) != null ? _f : 0 };
6106
6246
  }
6107
- function getElementDocumentPosition(element, scroll) {
6108
- var _a3, _b;
6247
+ function getElementScrollPosition(element, target) {
6248
+ var _a3, _b, _c, _d, _e, _f;
6109
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();
6110
6253
  return {
6111
- left: ((_a3 = rect == null ? void 0 : rect.left) != null ? _a3 : 0) + scroll.x,
6112
- 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
6113
6256
  };
6114
6257
  }
6115
6258
  function getContentSize2(content) {
6116
6259
  var _a3, _b;
6117
- return {
6118
- height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0,
6119
- width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0
6120
- };
6260
+ return { height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0, width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0 };
6121
6261
  }
6122
- function getScrollContentSize(scrollElement, contentElement, isWindowScroll) {
6123
- return getContentSize2(isWindowScroll ? contentElement : scrollElement);
6262
+ function getScrollContentSize(scrollElement, contentElement, externalTarget) {
6263
+ return getContentSize2(externalTarget !== void 0 ? contentElement : scrollElement);
6124
6264
  }
6125
- function getLayoutMeasurement(scrollElement, isWindowScroll, horizontal) {
6126
- var _a3, _b, _c, _d, _e, _f;
6127
- 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) {
6128
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 };
6129
6270
  return {
6130
- // In window-scroll mode, use viewport size on the scroll axis.
6131
- height: horizontal ? (_b = (_a3 = rect == null ? void 0 : rect.height) != null ? _a3 : scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _b : window.innerHeight : window.innerHeight,
6132
- // Keep the cross-axis size list-relative to avoid inflating container measurements.
6133
- 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
6134
6273
  };
6135
6274
  }
6136
- return {
6137
- height: (_e = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _e : 0,
6138
- width: (_f = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _f : 0
6139
- };
6275
+ return { height: (_g = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _g : 0, width: (_h = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _h : 0 };
6140
6276
  }
6141
6277
  function clampOffset(offset, maxOffset) {
6142
6278
  return Math.max(0, Math.min(offset, maxOffset));
6143
6279
  }
6144
- function getAxisSize(size, horizontal) {
6145
- return horizontal ? size.width : size.height;
6146
- }
6147
6280
  function getMaxOffset(contentSize, layoutMeasurement, horizontal) {
6148
- 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]);
6149
6283
  }
6150
- function resolveScrollableNode(scrollElement, isWindowScroll) {
6151
- 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;
6152
6289
  }
6153
- function resolveScrollEventTarget(scrollElement, isWindowScroll) {
6154
- return isWindowScroll && typeof window !== "undefined" ? window : scrollElement;
6290
+ function resolveScrollEventTarget(scrollElement, externalTarget) {
6291
+ return externalTarget === void 0 ? scrollElement : externalTarget;
6155
6292
  }
6156
- function getLayoutRectangle(element, isWindowScroll, horizontal) {
6157
- const rect = element.getBoundingClientRect();
6158
- const scroll = getWindowScrollPosition();
6159
- return {
6160
- height: isWindowScroll && typeof window !== "undefined" && !horizontal ? window.innerHeight : rect.height,
6161
- width: isWindowScroll && typeof window !== "undefined" && horizontal ? window.innerWidth : rect.width,
6162
- x: isWindowScroll ? rect.left + scroll.x : rect.left,
6163
- y: isWindowScroll ? rect.top + scroll.y : rect.top
6164
- };
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 };
6165
6301
  }
6166
- function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll }) {
6302
+ function resolveExternalScrollOffset({
6303
+ clampedOffset,
6304
+ horizontal,
6305
+ listPos,
6306
+ scroll
6307
+ }) {
6167
6308
  return {
6168
6309
  left: horizontal ? listPos.left + clampedOffset : scroll.x,
6169
6310
  top: horizontal ? scroll.y : listPos.top + clampedOffset
6170
6311
  };
6171
6312
  }
6172
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
+
6173
6351
  // src/components/ListComponentScrollView.tsx
6174
6352
  var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
6175
6353
  var SCROLL_END_FALLBACK_MS = 200;
@@ -6228,6 +6406,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6228
6406
  showsVerticalScrollIndicator = true,
6229
6407
  refreshControl,
6230
6408
  useWindowScroll = false,
6409
+ scrollElement: externalScrollElement,
6231
6410
  onLayout,
6232
6411
  ...props
6233
6412
  }, ref) {
@@ -6236,31 +6415,38 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6236
6415
  const [anchoredEndSpaceSize] = useArr$(["anchoredEndSpaceSize"]);
6237
6416
  const scrollRef = useRef(null);
6238
6417
  const contentRef = useRef(null);
6239
- 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;
6240
6421
  const getScrollTarget = useCallback(
6241
- () => resolveScrollEventTarget(scrollRef.current, isWindowScroll),
6242
- [isWindowScroll]
6422
+ () => resolveScrollEventTarget(scrollRef.current, externalTarget),
6423
+ [externalTarget]
6243
6424
  );
6244
6425
  const getMaxScrollOffset = useCallback(() => {
6245
6426
  const scrollElement = scrollRef.current;
6246
- const contentSize = getScrollContentSize(scrollElement, contentRef.current, isWindowScroll);
6247
- const layoutMeasurement = getLayoutMeasurement(scrollElement, isWindowScroll, horizontal);
6427
+ const contentSize = getScrollContentSize(scrollElement, contentRef.current, externalTarget);
6428
+ const layoutMeasurement = getLayoutMeasurement(scrollElement, externalTarget, horizontal);
6248
6429
  return getMaxOffset(contentSize, layoutMeasurement, horizontal);
6249
- }, [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]);
6250
6446
  const getCurrentScrollOffset = useCallback(() => {
6251
- const scrollElement = scrollRef.current;
6252
- if (isWindowScroll) {
6253
- const maxOffset = getMaxScrollOffset();
6254
- const scroll = getWindowScrollPosition();
6255
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6256
- const rawOffset = horizontal ? scroll.x - listPos.left : scroll.y - listPos.top;
6257
- return clampOffset(rawOffset, maxOffset);
6258
- }
6259
- if (!scrollElement) {
6260
- return 0;
6261
- }
6262
- return horizontal ? scrollElement.scrollLeft : scrollElement.scrollTop;
6263
- }, [getMaxScrollOffset, horizontal, isWindowScroll]);
6447
+ const offset = getRawScrollOffset();
6448
+ return isExternalScroll ? clampOffset(offset, getMaxScrollOffset()) : offset;
6449
+ }, [getMaxScrollOffset, getRawScrollOffset, isExternalScroll]);
6264
6450
  const scrollToLocalOffset = useCallback(
6265
6451
  (offset, animated) => {
6266
6452
  const scrollElement = scrollRef.current;
@@ -6272,10 +6458,10 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6272
6458
  const clampedOffset = clampOffset(offset, maxOffset);
6273
6459
  const behavior = animated ? "smooth" : "auto";
6274
6460
  const options = { behavior };
6275
- if (isWindowScroll) {
6276
- const scroll = getWindowScrollPosition();
6277
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6278
- const { left, top } = resolveWindowScrollTarget({
6461
+ if (isExternalScroll) {
6462
+ const scroll = getScrollPosition(externalTarget);
6463
+ const listPos = getElementScrollPosition(scrollElement, externalTarget);
6464
+ const { left, top } = resolveExternalScrollOffset({
6279
6465
  clampedOffset,
6280
6466
  horizontal,
6281
6467
  listPos,
@@ -6290,7 +6476,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6290
6476
  }
6291
6477
  target.scrollTo(options);
6292
6478
  },
6293
- [getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll]
6479
+ [externalTarget, getMaxScrollOffset, getScrollTarget, horizontal, isExternalScroll]
6294
6480
  );
6295
6481
  useImperativeHandle(ref, () => {
6296
6482
  const api = {
@@ -6298,11 +6484,14 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6298
6484
  var _a4;
6299
6485
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6300
6486
  },
6487
+ getContentNode: () => contentRef.current,
6301
6488
  getCurrentScrollOffset,
6302
6489
  getMaxScrollOffset,
6303
- getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6304
- getScrollEventTarget: () => getScrollTarget(),
6305
- getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6490
+ getRawScrollOffset,
6491
+ getScrollableNode: () => resolveScrollableNode(scrollRef.current, externalTarget),
6492
+ getScrollEventTarget: getScrollTarget,
6493
+ getScrollResponder: () => resolveScrollableNode(scrollRef.current, externalTarget),
6494
+ isScrollInRange,
6306
6495
  isWindowScroll: () => isWindowScroll,
6307
6496
  scrollBy: (x, y) => {
6308
6497
  const target = getScrollTarget();
@@ -6326,14 +6515,24 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6326
6515
  }
6327
6516
  };
6328
6517
  return api;
6329
- }, [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
+ ]);
6330
6529
  const emitScroll = useCallback(() => {
6331
6530
  if (!onScroll2 || !scrollRef.current) {
6332
6531
  return;
6333
6532
  }
6334
6533
  const contentSize = getContentSize2(contentRef.current);
6335
- const layoutMeasurement = getLayoutMeasurement(scrollRef.current, isWindowScroll, horizontal);
6336
- const offset = getCurrentScrollOffset();
6534
+ const layoutMeasurement = getLayoutMeasurement(scrollRef.current, externalTarget, horizontal);
6535
+ const offset = getRawScrollOffset();
6337
6536
  const scrollEvent = {
6338
6537
  nativeEvent: {
6339
6538
  contentOffset: {
@@ -6351,7 +6550,7 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6351
6550
  }
6352
6551
  };
6353
6552
  onScroll2(scrollEvent);
6354
- }, [getCurrentScrollOffset, horizontal, isWindowScroll, onScroll2]);
6553
+ }, [externalTarget, getRawScrollOffset, horizontal, onScroll2]);
6355
6554
  const scrollEventCoalescer = useRafCoalescer(emitScroll);
6356
6555
  const scrollEndFallbackRef = useRef(void 0);
6357
6556
  const emitScrollEnd = useCallback(() => {
@@ -6413,14 +6612,14 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6413
6612
  const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6414
6613
  if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6415
6614
  const offset = getCurrentScrollOffset();
6416
- if (interruptMaintainScrollAtEnd(ctx)) {
6615
+ if (interruptMaintainScrollAtEnd(ctx) && isScrollInRange()) {
6417
6616
  scrollToLocalOffset(offset, false);
6418
6617
  }
6419
6618
  }
6420
6619
  };
6421
6620
  target.addEventListener("wheel", onWheel, { passive: true });
6422
6621
  return () => target.removeEventListener("wheel", onWheel);
6423
- }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6622
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, isScrollInRange, scrollToLocalOffset]);
6424
6623
  useEffect(() => {
6425
6624
  const doScroll = () => {
6426
6625
  if (contentOffset) {
@@ -6428,44 +6627,28 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6428
6627
  }
6429
6628
  };
6430
6629
  doScroll();
6431
- requestAnimationFrame(doScroll);
6630
+ const frame = requestAnimationFrame(doScroll);
6631
+ return () => cancelAnimationFrame(frame);
6432
6632
  }, [contentOffset == null ? void 0 : contentOffset.x, contentOffset == null ? void 0 : contentOffset.y, horizontal, scrollToLocalOffset]);
6433
6633
  useLayoutEffect(() => {
6434
- if (!onLayout || !scrollRef.current) return;
6435
6634
  const element = scrollRef.current;
6436
- const fireLayout = () => {
6437
- onLayout({
6438
- nativeEvent: {
6439
- layout: getLayoutRectangle(element, isWindowScroll, horizontal)
6440
- }
6441
- });
6442
- };
6443
- fireLayout();
6444
- const resizeObserver = new ResizeObserver(() => {
6445
- fireLayout();
6446
- });
6447
- resizeObserver.observe(element);
6448
- const onWindowResize = () => {
6449
- fireLayout();
6450
- };
6451
- if (isWindowScroll && typeof window !== "undefined" && typeof window.addEventListener === "function") {
6452
- window.addEventListener("resize", onWindowResize);
6453
- }
6454
- return () => {
6455
- resizeObserver.disconnect();
6456
- if (isWindowScroll && typeof window !== "undefined" && typeof window.removeEventListener === "function") {
6457
- 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();
6458
6639
  }
6459
- };
6460
- }, [isWindowScroll, onLayout]);
6461
- 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);
6462
6645
  useLayoutEffect(() => {
6463
6646
  if (hiddenScrollIndicatorClassName) {
6464
6647
  ensureScrollbarHiddenStyle();
6465
6648
  }
6466
6649
  }, [hiddenScrollIndicatorClassName]);
6467
6650
  const scrollViewStyle = {
6468
- ...isWindowScroll ? {} : {
6651
+ ...isExternalScroll ? {} : {
6469
6652
  overflow: "auto",
6470
6653
  overflowX: horizontal ? "auto" : showsHorizontalScrollIndicator ? "auto" : "hidden",
6471
6654
  overflowY: horizontal ? showsVerticalScrollIndicator ? "auto" : "hidden" : "auto",
@@ -6500,11 +6683,10 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
6500
6683
  scrollEventThrottle: _scrollEventThrottle,
6501
6684
  ScrollComponent: _ScrollComponent,
6502
6685
  snapToOffsets,
6503
- useWindowScroll: _useWindowScroll,
6504
6686
  className: scrollViewClassNameProp,
6505
6687
  ...webProps
6506
6688
  } = props;
6507
- const snapOffsets = !isWindowScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6689
+ const snapOffsets = !isExternalScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6508
6690
  if (snapOffsets.length > 0) {
6509
6691
  scrollViewStyle.scrollSnapType = horizontal ? "x mandatory" : "y mandatory";
6510
6692
  contentStyle.position = (_c = contentStyle.position) != null ? _c : "relative";
@@ -6579,12 +6761,12 @@ function getScrollAdjustAxis(horizontal) {
6579
6761
  };
6580
6762
  }
6581
6763
  function getScrollAdjustTarget(ctx, contentNode) {
6582
- var _a3, _b, _c;
6764
+ var _a3, _b, _c, _d, _e;
6583
6765
  const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
6584
6766
  const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
6585
6767
  let resolvedContentNode = null;
6586
6768
  if (scrollElement) {
6587
- 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}`);
6588
6770
  }
6589
6771
  return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
6590
6772
  }
@@ -6599,6 +6781,7 @@ function ScrollAdjust() {
6599
6781
  const temporaryPaddingRef = React3.useRef(void 0);
6600
6782
  const contentNodeRef = React3.useRef(null);
6601
6783
  const callback = React3.useCallback(() => {
6784
+ var _a3, _b, _c;
6602
6785
  const scrollAdjust = peek$(ctx, "scrollAdjust");
6603
6786
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
6604
6787
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
@@ -6609,17 +6792,21 @@ function ScrollAdjust() {
6609
6792
  const horizontal = !!ctx.state.props.horizontal;
6610
6793
  const axis = getScrollAdjustAxis(horizontal);
6611
6794
  const { contentNode, scrollElement: el } = target;
6612
- 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;
6613
6800
  const userOffsetDelta = (scrollAdjustUserOffset || 0) - lastScrollAdjustUserOffsetRef.current;
6614
6801
  const intendedScroll = userOffsetDelta !== 0 ? currentScroll + userOffsetDelta : ctx.state.scroll;
6615
6802
  const scrollDelta = intendedScroll - currentScroll;
6616
- const shouldScroll = Math.abs(scrollDelta) > 0.01;
6803
+ const shouldScroll = !isOutsideList && Math.abs(scrollDelta) > 0.01;
6617
6804
  const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
6618
6805
  contentNodeRef.current = contentNode;
6619
6806
  if (shouldScroll && contentNode) {
6620
- const totalSize = contentNode[axis.contentSizeKey];
6807
+ const totalSize = isExternal ? el[axis.contentSizeKey] : contentNode[axis.contentSizeKey];
6621
6808
  const viewportSize = el[axis.viewportSizeKey];
6622
- const nextScroll = currentScroll + scrollDelta;
6809
+ const nextScroll = parentScroll + scrollDelta;
6623
6810
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
6624
6811
  if (needsTemporaryPadding) {
6625
6812
  const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp];
@@ -6777,12 +6964,14 @@ var ListComponent = typedMemo(function ListComponent2({
6777
6964
  renderScrollComponent,
6778
6965
  onLayoutFooter,
6779
6966
  onInternalScrollBeginDrag,
6967
+ onInternalScrollEndDrag,
6780
6968
  onInternalScrollEnd,
6781
6969
  scrollAdjustHandler,
6782
6970
  snapToIndices,
6783
6971
  stickyHeaderConfig,
6784
6972
  stickyHeaderIndices,
6785
6973
  useWindowScroll = false,
6974
+ scrollElement,
6786
6975
  ...rest
6787
6976
  }) {
6788
6977
  const ctx = useStateContext();
@@ -6848,7 +7037,7 @@ var ListComponent = typedMemo(function ListComponent2({
6848
7037
  SnapOrScroll,
6849
7038
  {
6850
7039
  ...rest,
6851
- ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} ,
7040
+ ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, scrollElement, useWindowScroll } : {} ,
6852
7041
  contentContainerStyle: [
6853
7042
  horizontal ? { height: "100%" } : {},
6854
7043
  contentContainerStyle,
@@ -6888,7 +7077,15 @@ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
6888
7077
  var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
6889
7078
  function useDevChecksImpl(props) {
6890
7079
  const ctx = useStateContext();
6891
- 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;
6892
7089
  const hasAnchoredEndSpace = !!anchoredEndSpace;
6893
7090
  useEffect(() => {
6894
7091
  if (hasAnchoredEndSpace && (numColumns != null ? numColumns : 1) > 1) {
@@ -6899,13 +7096,13 @@ function useDevChecksImpl(props) {
6899
7096
  }
6900
7097
  }, [hasAnchoredEndSpace, numColumns]);
6901
7098
  useEffect(() => {
6902
- if (useWindowScroll && renderScrollComponent) {
7099
+ if ((useWindowScroll || scrollElement !== void 0) && renderScrollComponent) {
6903
7100
  warnDevOnce(
6904
7101
  "useWindowScrollRenderScrollComponent",
6905
- "useWindowScroll is not supported when renderScrollComponent is provided."
7102
+ "useWindowScroll and scrollElement are not supported when renderScrollComponent is provided."
6906
7103
  );
6907
7104
  }
6908
- }, [renderScrollComponent, useWindowScroll]);
7105
+ }, [renderScrollComponent, scrollElement, useWindowScroll]);
6909
7106
  useEffect(() => {
6910
7107
  if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
6911
7108
  warnDevOnce(
@@ -6917,8 +7114,8 @@ function useDevChecksImpl(props) {
6917
7114
  useEffect(() => {
6918
7115
  const state = ctx.state;
6919
7116
  const dataLength = state.props.data.length;
6920
- const useWindowScrollResolved = state.props.useWindowScroll;
6921
- 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) {
6922
7119
  return;
6923
7120
  }
6924
7121
  const warnIfUnboundedOuterSize = () => {
@@ -6967,7 +7164,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6967
7164
  for (const key in state.averageSizes) {
6968
7165
  delete state.averageSizes[key];
6969
7166
  }
6970
- state.minIndexSizeChanged = 0;
7167
+ state.positionRecalculationStartIndex = 0;
6971
7168
  state.scrollForNextCalculateItemsInView = void 0;
6972
7169
  }
6973
7170
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -7082,7 +7279,8 @@ function doInitialAllocateContainers(ctx) {
7082
7279
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7083
7280
  });
7084
7281
  } else {
7085
- calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7282
+ const reuseInitialPositions = !state.didLoad && state.positionsAreCurrent && !state.didDataChange && !state.didColumnsChange;
7283
+ calculateItemsInView(ctx, { dataChanged: !reuseInitialPositions, doMVCP: true });
7086
7284
  }
7087
7285
  }
7088
7286
  return true;
@@ -7125,17 +7323,15 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7125
7323
  updateContentMetricsState(ctx);
7126
7324
  state.lastBatchingAction = Date.now();
7127
7325
  state.scrollForNextCalculateItemsInView = void 0;
7128
- if (scrollLength > 0) {
7129
- doInitialAllocateContainers(ctx);
7130
- }
7131
- if (needsCalculate) {
7326
+ const didAllocate = scrollLength > 0 && doInitialAllocateContainers(ctx);
7327
+ if (needsCalculate && (!didAllocate || state.initialScroll)) {
7132
7328
  calculateItemsInView(ctx, { doMVCP: true });
7133
7329
  }
7134
7330
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
7135
7331
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7136
7332
  }
7137
7333
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
7138
- doMaintainScrollAtEnd(ctx);
7334
+ doMaintainScrollAtEnd(ctx, { immediate: true });
7139
7335
  }
7140
7336
  checkThresholds(ctx);
7141
7337
  if (state) {
@@ -7206,7 +7402,7 @@ function onScroll(ctx, event) {
7206
7402
  state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
7207
7403
  state.lastNativeScroll = newScroll;
7208
7404
  state.lastNativeScrollTime = Date.now();
7209
- if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7405
+ if (!state.props.hasExternalScroll && state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7210
7406
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
7211
7407
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
7212
7408
  newScroll = maxOffset;
@@ -7364,12 +7560,12 @@ function scrollToEnd(ctx, options) {
7364
7560
  if (index === -1) {
7365
7561
  return false;
7366
7562
  }
7367
- const paddingBottom = state.props.stylePaddingBottom || 0;
7563
+ const paddingBottom = getStylePaddingEnd(state.props);
7368
7564
  const footerSize = peek$(ctx, "footerSize") || 0;
7369
7565
  scrollToIndex(ctx, {
7370
7566
  ...options,
7371
7567
  index,
7372
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7568
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
7373
7569
  viewPosition: 1
7374
7570
  });
7375
7571
  if (state.scrollingTo) {
@@ -7556,7 +7752,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7556
7752
  for (const key in state.averageSizes) {
7557
7753
  delete state.averageSizes[key];
7558
7754
  }
7559
- state.minIndexSizeChanged = 0;
7755
+ state.positionRecalculationStartIndex = 0;
7560
7756
  state.scrollForNextCalculateItemsInView = void 0;
7561
7757
  state.pendingTotalSize = void 0;
7562
7758
  state.totalSize = 0;
@@ -7950,10 +8146,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7950
8146
  onMetricsChange,
7951
8147
  onLayout: onLayoutProp,
7952
8148
  onLoad,
8149
+ onReady,
7953
8150
  onMomentumScrollEnd,
7954
8151
  onRefresh,
7955
8152
  onScroll: onScrollProp,
7956
8153
  onScrollBeginDrag,
8154
+ onScrollEndDrag,
7957
8155
  onStartReached,
7958
8156
  onStartReachedThreshold = 0.5,
7959
8157
  onStickyHeaderChange,
@@ -7971,6 +8169,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7971
8169
  stickyHeaderIndices: stickyHeaderIndicesProp,
7972
8170
  style: styleProp,
7973
8171
  useWindowScroll = false,
8172
+ scrollElement,
7974
8173
  viewabilityConfig,
7975
8174
  viewabilityConfigCallbackPairs,
7976
8175
  ...rest
@@ -8022,17 +8221,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8022
8221
  const initialScrollProp = initialScrollAtEnd ? {
8023
8222
  index: Math.max(0, dataProp.length - 1),
8024
8223
  preserveForBottomPadding: true,
8025
- viewOffset: -stylePaddingEndState,
8224
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
8026
8225
  viewPosition: 1
8027
8226
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
8028
8227
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
8029
8228
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
8030
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
8229
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8031
8230
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
8032
8231
  } : {
8033
8232
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
8034
8233
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
8035
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
8234
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8036
8235
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
8037
8236
  } : initialScrollUsesOffsetOnly ? {
8038
8237
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -8065,7 +8264,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8065
8264
  dataVersion,
8066
8265
  keyExtractor
8067
8266
  ]);
8068
- 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;
8069
8270
  const refState = useRef(void 0);
8070
8271
  const hasOverrideItemLayout = !!overrideItemLayout;
8071
8272
  const prevHasOverrideItemLayout = useRef(hasOverrideItemLayout);
@@ -8110,11 +8311,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8110
8311
  lastLayout: void 0,
8111
8312
  lastScrollDelta: 0,
8112
8313
  loadStartTime: Date.now(),
8113
- minIndexSizeChanged: 0,
8114
8314
  nativeContentInset: void 0,
8115
8315
  nativeMarginTop: 0,
8116
8316
  pendingDataComparison: void 0,
8117
8317
  pendingNativeMVCPAdjust: void 0,
8318
+ positionRecalculationStartIndex: 0,
8118
8319
  positions: [],
8119
8320
  props: {},
8120
8321
  queuedCalculateItemsInView: 0,
@@ -8203,6 +8404,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8203
8404
  estimatedItemSize,
8204
8405
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8205
8406
  getItemType: useWrapIfItem(getItemType),
8407
+ hasExternalScroll,
8206
8408
  hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8207
8409
  horizontal: !!horizontal,
8208
8410
  itemsAreEqual,
@@ -8217,8 +8419,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8217
8419
  onItemSizeChanged,
8218
8420
  onLoad,
8219
8421
  onMomentumScrollEnd,
8422
+ onReady,
8220
8423
  onScroll: throttleScrollFn,
8221
8424
  onScrollBeginDrag,
8425
+ onScrollEndDrag,
8222
8426
  onStartReached,
8223
8427
  onStartReachedThreshold,
8224
8428
  onStickyHeaderChange,
@@ -8392,8 +8596,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8392
8596
  const { onLayout } = useOnLayoutSync({
8393
8597
  onLayoutChange,
8394
8598
  onLayoutProp,
8395
- ref: refScroller
8599
+ ref: refScroller,
8396
8600
  // the type of ScrollView doesn't include measure?
8601
+ webExternalScroll: hasExternalScroll
8397
8602
  });
8398
8603
  useLayoutEffect(() => {
8399
8604
  if (snapToIndices) {
@@ -8506,11 +8711,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8506
8711
  onScroll: (event) => onScroll(ctx, event),
8507
8712
  onScrollBeginDrag: (event) => {
8508
8713
  var _a4, _b2;
8714
+ state.isDragging = true;
8509
8715
  interruptMaintainScrollAtEnd(ctx);
8510
8716
  prepareReachedEdgeForNextUserScroll(ctx);
8511
8717
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8512
8718
  },
8513
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
8719
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
8720
+ onScrollEndDrag: (event) => {
8721
+ var _a4, _b2;
8722
+ state.isDragging = false;
8723
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8724
+ }
8514
8725
  }),
8515
8726
  []
8516
8727
  );
@@ -8534,6 +8745,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8534
8745
  ListHeaderComponent,
8535
8746
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
8536
8747
  onInternalScrollEnd: fns.onScrollEnd,
8748
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
8537
8749
  onLayout,
8538
8750
  onLayoutFooter,
8539
8751
  onMomentumScrollEnd: fns.onMomentumScrollEnd,
@@ -8552,6 +8764,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8552
8764
  refScrollView: combinedRef,
8553
8765
  renderScrollComponent,
8554
8766
  scrollAdjustHandler: (_s = refState.current) == null ? void 0 : _s.scrollAdjustHandler,
8767
+ scrollElement: scrollElementResolved,
8555
8768
  scrollEventThrottle: 0,
8556
8769
  snapToIndices,
8557
8770
  stickyHeaderIndices,