@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.
@@ -430,6 +430,14 @@ var EDGE_POSITION_EPSILON = 1;
430
430
  var ENABLE_DEVMODE = IS_DEV && false;
431
431
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
432
432
 
433
+ // src/core/endOfContentTarget.ts
434
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
435
+ return -stylePaddingEnd - footerSize;
436
+ }
437
+ function isEndOfContentTarget(target, dataLength) {
438
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
439
+ }
440
+
433
441
  // src/core/cancelImperativeScroll.ts
434
442
  function cancelScrollCompletionChecks({ scheduledWork }) {
435
443
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -614,26 +622,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
614
622
  scrollPosition: context.scrollPosition
615
623
  });
616
624
  };
625
+ let reached = !!wasReached;
617
626
  if (!wasReached) {
618
- if (!within) {
619
- return false;
620
- }
621
- onReached(distance);
622
- updateSnapshot();
623
- return true;
624
- }
625
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
626
- if (reset) {
627
- setSnapshot(void 0);
628
- return false;
629
- }
630
- if (within) {
631
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
632
- if (changed) {
627
+ if (within && onReached(distance)) {
633
628
  updateSnapshot();
629
+ reached = true;
630
+ }
631
+ } else {
632
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
633
+ if (reset) {
634
+ setSnapshot(void 0);
635
+ reached = false;
636
+ } else if (within) {
637
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
638
+ if (changed) {
639
+ updateSnapshot();
640
+ }
634
641
  }
635
642
  }
636
- return true;
643
+ return reached;
637
644
  };
638
645
 
639
646
  // src/utils/edgeReachedGate.ts
@@ -680,7 +687,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
680
687
  return void 0;
681
688
  }
682
689
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
683
- state.edgeReachedGate = "closed";
684
690
  resetEdgeLatch(ctx, allowedEdge);
685
691
  return allowedEdge;
686
692
  }
@@ -721,7 +727,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
721
727
  set$(
722
728
  ctx,
723
729
  "isWithinMaintainScrollAtEndThreshold",
724
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
730
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
725
731
  );
726
732
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
727
733
  if (!shouldSkipThresholdChecks) {
@@ -737,11 +743,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
737
743
  scrollPosition: scroll
738
744
  },
739
745
  (distance) => {
740
- var _a4, _b;
741
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
746
+ let dispatched = false;
747
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
742
748
  markReachedEdge(ctx);
743
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
749
+ state.props.onEndReached({ distanceFromEnd: distance });
750
+ dispatched = true;
744
751
  }
752
+ return dispatched;
745
753
  },
746
754
  (snapshot) => {
747
755
  state.endReachedSnapshot = snapshot;
@@ -774,7 +782,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
774
782
  }
775
783
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
776
784
  set$(ctx, "isNearStart", scroll <= threshold);
777
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
785
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
778
786
  if (!shouldSkipThresholdChecks) {
779
787
  state.isStartReached = checkThreshold(
780
788
  scroll,
@@ -788,11 +796,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
788
796
  scrollPosition: scroll
789
797
  },
790
798
  (distance) => {
791
- var _a3, _b;
792
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
799
+ let dispatched = false;
800
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
793
801
  markReachedEdge(ctx);
794
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
802
+ state.props.onStartReached({ distanceFromStart: distance });
803
+ dispatched = true;
795
804
  }
805
+ return dispatched;
796
806
  },
797
807
  (snapshot) => {
798
808
  state.startReachedSnapshot = snapshot;
@@ -891,8 +901,14 @@ function scheduleFullDrawDistancePrewarm(ctx) {
891
901
  return;
892
902
  }
893
903
  state.scheduledWork.frame(() => {
894
- var _a3;
895
- 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);
896
912
  }, "fullDrawDistancePrewarm");
897
913
  }
898
914
 
@@ -905,6 +921,14 @@ function resetInitialRenderState(ctx, {
905
921
  if (resetLayout) {
906
922
  state.didContainersLayout = false;
907
923
  state.queuedInitialLayout = false;
924
+ state.scrollBufferDirection = void 0;
925
+ state.scrollHistory.length = 0;
926
+ state.scheduledWork.cancel("renderRangeProjection");
927
+ state.edgeReachedGate = void 0;
928
+ state.isStartReached = null;
929
+ state.isEndReached = null;
930
+ state.startReachedSnapshot = void 0;
931
+ state.endReachedSnapshot = void 0;
908
932
  }
909
933
  if (resetInitialScroll) {
910
934
  state.didFinishInitialScroll = false;
@@ -919,7 +943,7 @@ function setInitialRenderState(ctx, {
919
943
  const { state } = ctx;
920
944
  const {
921
945
  loadStartTime,
922
- props: { onLoad }
946
+ props: { onLoad, onReady }
923
947
  } = state;
924
948
  if (didLayout) {
925
949
  state.didContainersLayout = true;
@@ -940,6 +964,7 @@ function setInitialRenderState(ctx, {
940
964
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
941
965
  }
942
966
  }
967
+ onReady == null ? void 0 : onReady();
943
968
  }
944
969
  }
945
970
 
@@ -1125,6 +1150,7 @@ function setSize(ctx, itemKey, size, notifyTotalSize = true) {
1125
1150
  const previousSize = sizes.get(itemKey);
1126
1151
  const diff = previousSize !== void 0 ? size - previousSize : size;
1127
1152
  if (diff !== 0) {
1153
+ state.positionsAreCurrent = false;
1128
1154
  addTotalSize(ctx, itemKey, diff, notifyTotalSize);
1129
1155
  }
1130
1156
  sizes.set(itemKey, size);
@@ -1185,6 +1211,7 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
1185
1211
  const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
1186
1212
  if (fixedSize !== void 0) {
1187
1213
  size = fixedSize + ctx.scrollAxisGap;
1214
+ state.positionsAreCurrent = false;
1188
1215
  state.sizesKnown.set(key, size);
1189
1216
  }
1190
1217
  }
@@ -1283,10 +1310,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1283
1310
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1284
1311
  const trailingInset = getContentInsetEnd(ctx);
1285
1312
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1286
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1287
- const footerSize = peek$(ctx, "footerSize") || 0;
1288
- offset += footerSize;
1289
- }
1290
1313
  }
1291
1314
  return offset;
1292
1315
  }
@@ -1770,6 +1793,9 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1770
1793
  state.scrollTime = currentTime;
1771
1794
  const scrollDelta = Math.abs(newScroll - prevScroll);
1772
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
+ }
1773
1799
  const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1774
1800
  if (didCancelMaintainScrollAtEnd) {
1775
1801
  finishMaintainScrollAtEnd(ctx);
@@ -1848,89 +1874,104 @@ function finishMaintainScrollAtEnd(ctx) {
1848
1874
  requestAdjust(ctx, nextScroll - state.scroll);
1849
1875
  }
1850
1876
  }
1851
- function doMaintainScrollAtEnd(ctx) {
1852
- var _a3, _b, _c, _d, _e;
1877
+ function doMaintainScrollAtEnd(ctx, options) {
1878
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
1853
1879
  const state = ctx.state;
1854
- const {
1855
- didContainersLayout,
1856
- didFinishInitialScroll,
1857
- pendingNativeMVCPAdjust,
1858
- props: { maintainScrollAtEnd }
1859
- } = state;
1860
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1861
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1862
- 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);
1863
- if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1864
- const scroller = state.refScroller.current;
1865
- const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1866
- const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1867
- if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1880
+ let didMaintain = false;
1881
+ if (state.isDragging || ((_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.isScrollInRange) == null ? void 0 : _b.call(_a3)) === false) {
1882
+ finishMaintainScrollAtEnd(ctx);
1883
+ } else {
1884
+ const {
1885
+ didContainersLayout,
1886
+ didFinishInitialScroll,
1887
+ pendingNativeMVCPAdjust,
1888
+ props: { maintainScrollAtEnd }
1889
+ } = state;
1890
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1891
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1892
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
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);
1894
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1895
+ const scroller = state.refScroller.current;
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);
1898
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1899
+ state.pendingMaintainScrollAtEnd = true;
1900
+ state.scrollPending = observed;
1901
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1902
+ }
1903
+ }
1904
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1868
1905
  state.pendingMaintainScrollAtEnd = true;
1869
- state.scrollPending = observed;
1870
- updateScroll(ctx, observed, true, { markHasScrolled: false });
1871
- }
1872
- }
1873
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1874
- state.pendingMaintainScrollAtEnd = true;
1875
- return false;
1876
- }
1877
- if (pendingNativeMVCPAdjust) {
1878
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1879
- return false;
1880
- }
1881
- if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1882
- state.pendingMaintainScrollAtEnd = true;
1883
- (_e = state.maintainingScrollAtEnd) != null ? _e : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1884
- return false;
1885
- }
1886
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1887
- state.pendingMaintainScrollAtEnd = false;
1888
- const contentSize = getContentSize(ctx);
1889
- if (contentSize < state.scrollLength) {
1890
- state.scroll = 0;
1891
- }
1892
- if (!state.maintainingScrollAtEnd) {
1893
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1894
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1895
- const scrollAtRequest = state.scroll;
1896
- state.maintainingScrollAtEnd = pendingState;
1897
- requestAnimationFrame(() => {
1898
- if (state.maintainingScrollAtEnd !== pendingState) {
1899
- return;
1900
- }
1901
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1902
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1903
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1904
- if (state.scrollingTo || state.pendingScrollResolve) {
1905
- state.pendingMaintainScrollAtEnd = true;
1906
- return;
1907
- }
1908
- state.maintainingScrollAtEnd = activeState;
1909
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1910
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1911
- );
1912
- void scrollPromise.then(() => {
1913
- if (state.maintainingScrollAtEnd !== activeState) {
1914
- return;
1915
- }
1916
- if (state.pendingMaintainScrollAtEnd) {
1917
- state.maintainingScrollAtEnd = void 0;
1918
- doMaintainScrollAtEnd(ctx);
1919
- } else {
1906
+ } else if (pendingNativeMVCPAdjust) {
1907
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1908
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1909
+ if (immediate && ((_g = state.scrollingTo) == null ? void 0 : _g.isScrollToEnd) && !state.scrollingTo.animated) {
1910
+ state.pendingMaintainScrollAtEnd = false;
1911
+ ctx.scrollToEnd({ animated: false });
1912
+ didMaintain = true;
1913
+ } else {
1914
+ state.pendingMaintainScrollAtEnd = true;
1915
+ (_h = state.maintainingScrollAtEnd) != null ? _h : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1916
+ }
1917
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1918
+ state.pendingMaintainScrollAtEnd = false;
1919
+ const contentSize = getContentSize(ctx);
1920
+ if (contentSize < state.scrollLength) {
1921
+ state.scroll = 0;
1922
+ }
1923
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1924
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1925
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1926
+ const scrollAtRequest = state.scroll;
1927
+ state.maintainingScrollAtEnd = pendingState;
1928
+ const run = () => {
1929
+ var _a4, _b2;
1930
+ if (state.maintainingScrollAtEnd === pendingState) {
1931
+ if (state.isDragging || ((_b2 = (_a4 = state.refScroller.current) == null ? void 0 : _a4.isScrollInRange) == null ? void 0 : _b2.call(_a4)) === false) {
1920
1932
  finishMaintainScrollAtEnd(ctx);
1933
+ } else {
1934
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1935
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1936
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1937
+ if (state.scrollingTo || state.pendingScrollResolve) {
1938
+ state.pendingMaintainScrollAtEnd = true;
1939
+ } else {
1940
+ state.maintainingScrollAtEnd = activeState;
1941
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1942
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1943
+ );
1944
+ void scrollPromise.then(() => {
1945
+ if (state.maintainingScrollAtEnd === activeState) {
1946
+ if (state.pendingMaintainScrollAtEnd) {
1947
+ state.maintainingScrollAtEnd = void 0;
1948
+ doMaintainScrollAtEnd(ctx);
1949
+ } else {
1950
+ finishMaintainScrollAtEnd(ctx);
1951
+ }
1952
+ }
1953
+ });
1954
+ }
1955
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1956
+ finishMaintainScrollAtEnd(ctx);
1957
+ }
1921
1958
  }
1922
- });
1923
- } else if (state.maintainingScrollAtEnd === pendingState) {
1924
- finishMaintainScrollAtEnd(ctx);
1959
+ }
1960
+ };
1961
+ if (immediate) {
1962
+ run();
1963
+ } else {
1964
+ requestAnimationFrame(run);
1925
1965
  }
1926
- });
1966
+ } else {
1967
+ state.pendingMaintainScrollAtEnd = true;
1968
+ }
1969
+ didMaintain = true;
1927
1970
  } else {
1928
- state.pendingMaintainScrollAtEnd = true;
1971
+ finishMaintainScrollAtEnd(ctx);
1929
1972
  }
1930
- return true;
1931
1973
  }
1932
- finishMaintainScrollAtEnd(ctx);
1933
- return false;
1974
+ return didMaintain;
1934
1975
  }
1935
1976
 
1936
1977
  // src/core/finishScrollTo.ts
@@ -2234,7 +2275,9 @@ function scrollTo(ctx, params) {
2234
2275
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2235
2276
  }
2236
2277
  }
2237
- state.scrollPending = targetOffset;
2278
+ if (!animated || isInitialScroll) {
2279
+ state.scrollPending = targetOffset;
2280
+ }
2238
2281
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2239
2282
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2240
2283
  if (animated) {
@@ -2611,13 +2654,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2611
2654
  queueBootstrapInitialScrollReevaluation(ctx.state);
2612
2655
  }
2613
2656
  function createInitialScrollAtEndTarget(options) {
2614
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2657
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2615
2658
  return {
2616
2659
  contentOffset: void 0,
2617
- index: Math.max(0, dataLength - 1),
2660
+ index: index != null ? index : Math.max(0, dataLength - 1),
2618
2661
  preserveForBottomPadding: true,
2619
2662
  preserveForFooterLayout,
2620
- viewOffset: -stylePaddingEnd - footerSize,
2663
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2621
2664
  viewPosition: 1
2622
2665
  };
2623
2666
  }
@@ -2633,13 +2676,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2633
2676
  function createRetargetedBottomAlignedInitialScroll(options) {
2634
2677
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2635
2678
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2679
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2636
2680
  return {
2637
2681
  ...target,
2638
2682
  contentOffset: void 0,
2639
2683
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2640
2684
  preserveForBottomPadding: true,
2641
2685
  preserveForFooterLayout,
2642
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2686
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2643
2687
  viewPosition: 1
2644
2688
  };
2645
2689
  }
@@ -2653,7 +2697,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2653
2697
  }
2654
2698
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2655
2699
  dataLength,
2656
- footerSize: 0,
2700
+ footerSize: peek$(ctx, "footerSize") || 0,
2657
2701
  preserveForFooterLayout: void 0,
2658
2702
  stylePaddingEnd
2659
2703
  });
@@ -2676,13 +2720,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2676
2720
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2677
2721
  }
2678
2722
  function getPreservedEndAnchorOffsetDiff(ctx) {
2679
- var _a3;
2680
2723
  const state = ctx.state;
2681
2724
  const initialScroll = state.initialScroll;
2682
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2725
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2683
2726
  return;
2684
2727
  }
2685
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2728
+ const currentOffset = getObservedBootstrapInitialScrollOffset(state);
2686
2729
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2687
2730
  }
2688
2731
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2710,6 +2753,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2710
2753
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2711
2754
  if (hasObservedNativeScrollAfterRequest) {
2712
2755
  activeCorrection.lastRequestTime = Date.now();
2756
+ {
2757
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2758
+ }
2713
2759
  requestAdjust(ctx, offsetDiff);
2714
2760
  }
2715
2761
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2832,10 +2878,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2832
2878
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2833
2879
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2834
2880
  const state = ctx.state;
2835
- if (!initialScrollAtEnd) {
2881
+ const initialScroll = state.initialScroll;
2882
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2836
2883
  return;
2837
2884
  }
2838
- const initialScroll = state.initialScroll;
2839
2885
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2840
2886
  return;
2841
2887
  }
@@ -2853,6 +2899,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2853
2899
  const updatedInitialScroll = createInitialScrollAtEndTarget({
2854
2900
  dataLength,
2855
2901
  footerSize,
2902
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
2856
2903
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
2857
2904
  stylePaddingEnd
2858
2905
  });
@@ -3324,6 +3371,7 @@ function handleInitialScrollDataChange(ctx, options) {
3324
3371
 
3325
3372
  // src/core/resetLayoutCachesForDataChange.ts
3326
3373
  function resetLayoutCachesForDataChange(state) {
3374
+ state.positionsAreCurrent = false;
3327
3375
  state.indexByKey.clear();
3328
3376
  state.idCache.length = 0;
3329
3377
  state.positions.length = 0;
@@ -3604,7 +3652,7 @@ function updateItemPositions(ctx, dataChanged, {
3604
3652
  scrollBottomBuffered: -1,
3605
3653
  startIndex: 0
3606
3654
  }) {
3607
- var _a3, _b, _c, _d, _e;
3655
+ var _a3, _b, _c, _d, _e, _f;
3608
3656
  const state = ctx.state;
3609
3657
  const hasPositionListeners = ctx.positionListeners.size > 0;
3610
3658
  const {
@@ -3620,6 +3668,11 @@ function updateItemPositions(ctx, dataChanged, {
3620
3668
  const dataLength = data.length;
3621
3669
  const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
3622
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;
3623
3676
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
3624
3677
  const extraData = peek$(ctx, "extraData");
3625
3678
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
@@ -3734,6 +3787,10 @@ function updateItemPositions(ctx, dataChanged, {
3734
3787
  }
3735
3788
  if (!didBreakEarly) {
3736
3789
  updateTotalSize(ctx);
3790
+ state.positionsAreCurrent = !hasColumns;
3791
+ if (startIndex <= ((_f = state.positionRecalculationStartIndex) != null ? _f : -1)) {
3792
+ state.positionRecalculationStartIndex = void 0;
3793
+ }
3737
3794
  }
3738
3795
  if (snapToIndices) {
3739
3796
  updateSnapToOffsets(ctx);
@@ -4002,7 +4059,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
4002
4059
  }
4003
4060
 
4004
4061
  // src/utils/findAvailableContainers.ts
4005
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
4062
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
4006
4063
  var _a3;
4007
4064
  const numNeeded = needNewContainers.length;
4008
4065
  if (numNeeded === 0) {
@@ -4011,16 +4068,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4011
4068
  const numContainers = peek$(ctx, "numContainers");
4012
4069
  const state = ctx.state;
4013
4070
  const { containerItemMetadata, stickyContainerPool } = state;
4071
+ const { data, getItemType } = state.props;
4072
+ const hasItemTypes = !!getItemType;
4014
4073
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
4015
4074
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
4016
- const requests = needNewContainers.map((itemIndex, order) => ({
4017
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4018
- itemIndex,
4019
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
4020
- order
4021
- }));
4022
- const normalRequests = requests.filter((request) => !request.isSticky);
4023
- const stickyRequests = requests.filter((request) => request.isSticky);
4075
+ const requests = new Array(numNeeded);
4076
+ const normalRequests = [];
4077
+ const stickyRequests = [];
4078
+ for (let order = 0; order < numNeeded; order++) {
4079
+ const itemIndex = needNewContainers[order];
4080
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
4081
+ const request = {
4082
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4083
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4084
+ itemIndex,
4085
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4086
+ order,
4087
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4088
+ };
4089
+ requests[order] = request;
4090
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4091
+ }
4024
4092
  const normalCandidates = [];
4025
4093
  const stickyCandidates = [];
4026
4094
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -4076,7 +4144,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4076
4144
  }
4077
4145
  };
4078
4146
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
4079
- if (getRequiredItemType) {
4147
+ if (hasItemTypes) {
4080
4148
  assignMatching(
4081
4149
  pendingRequests,
4082
4150
  candidates,
@@ -4100,6 +4168,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4100
4168
  }
4101
4169
  assign(request, containerIndex);
4102
4170
  }
4171
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4172
+ const containerIndices = [];
4173
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4174
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4175
+ }
4103
4176
  if (pendingRemovalChanged) {
4104
4177
  pendingRemoval.length = 0;
4105
4178
  if (pendingRemovalSet) {
@@ -4129,12 +4202,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4129
4202
  function comparatorByDistance(a, b) {
4130
4203
  return b.distance - a.distance;
4131
4204
  }
4205
+ function comparatorByRenderPriority(a, b) {
4206
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4207
+ }
4208
+ function comparatorByIndex(a, b) {
4209
+ return a - b;
4210
+ }
4211
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4212
+ if (requests.length >= 2) {
4213
+ if (groupByType) {
4214
+ const groups = /* @__PURE__ */ new Map();
4215
+ for (const request of requests) {
4216
+ const group = groups.get(request.itemType);
4217
+ if (group) {
4218
+ group.push(request);
4219
+ } else {
4220
+ groups.set(request.itemType, [request]);
4221
+ }
4222
+ }
4223
+ for (const group of groups.values()) {
4224
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4225
+ }
4226
+ } else {
4227
+ containerIndices.length = 0;
4228
+ let indicesSorted = true;
4229
+ for (let i = 0; i < requests.length; i++) {
4230
+ const containerIndex = allocations[requests[i].order].containerIndex;
4231
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4232
+ indicesSorted = false;
4233
+ }
4234
+ containerIndices.push(containerIndex);
4235
+ }
4236
+ if (!indicesSorted) {
4237
+ containerIndices.sort(comparatorByIndex);
4238
+ }
4239
+ requests.sort(comparatorByRenderPriority);
4240
+ for (let i = 0; i < requests.length; i++) {
4241
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4242
+ }
4243
+ }
4244
+ }
4245
+ }
4132
4246
 
4133
4247
  // src/utils/setDidLayout.ts
4134
4248
  function setDidLayout(ctx) {
4135
4249
  const state = ctx.state;
4136
4250
  state.queuedInitialLayout = true;
4137
- checkAtBottom(ctx);
4251
+ checkThresholds(ctx);
4138
4252
  setInitialRenderState(ctx, { didLayout: true });
4139
4253
  if (state.pendingMaintainScrollAtEnd) {
4140
4254
  doMaintainScrollAtEnd(ctx);
@@ -4239,7 +4353,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4239
4353
  }
4240
4354
  function trackVisibleRange(range, i, top, size, scroll, scrollBottom) {
4241
4355
  let didPassVisibleEnd = false;
4242
- if (range.startNoBuffer === null && top + size > scroll) {
4356
+ if (range.startNoBuffer === null && top + size > scroll && top <= scrollBottom) {
4243
4357
  range.startNoBuffer = i;
4244
4358
  }
4245
4359
  if (range.firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
@@ -4305,7 +4419,7 @@ function findFirstVisibleIndexInCachedRange(ctx, scroll) {
4305
4419
  return null;
4306
4420
  }
4307
4421
  function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, scrollLength, scroll, scrollBottom) {
4308
- var _a3, _b;
4422
+ var _a3, _b, _c, _d;
4309
4423
  const state = ctx.state;
4310
4424
  const {
4311
4425
  endBuffered,
@@ -4339,14 +4453,14 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4339
4453
  startNoBuffer: visibleRange.startNoBuffer
4340
4454
  });
4341
4455
  maybeEmitFirstVisibleItemChanged(state, visibleRange.startNoBuffer);
4342
- if (visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4456
+ if (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4343
4457
  updateViewableItems(
4344
4458
  state,
4345
4459
  ctx,
4346
4460
  viewabilityConfigCallbackPairs,
4347
4461
  scrollLength,
4348
- visibleRange.startNoBuffer,
4349
- visibleRange.endNoBuffer,
4462
+ (_c = visibleRange.startNoBuffer) != null ? _c : 0,
4463
+ (_d = visibleRange.endNoBuffer) != null ? _d : -1,
4350
4464
  startBuffered,
4351
4465
  endBuffered
4352
4466
  );
@@ -4355,16 +4469,16 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4355
4469
  function calculateItemsInView(ctx, params = {}) {
4356
4470
  const state = ctx.state;
4357
4471
  batchedUpdates(() => {
4358
- 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;
4359
4473
  const {
4360
4474
  columns,
4361
4475
  containerItemKeys,
4362
4476
  enableScrollForNextCalculateItemsInView,
4363
4477
  idCache,
4364
4478
  indexByKey,
4365
- minIndexSizeChanged,
4479
+ positionRecalculationStartIndex,
4366
4480
  positions,
4367
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4481
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4368
4482
  scrollForNextCalculateItemsInView,
4369
4483
  scrollLength,
4370
4484
  sizes,
@@ -4407,7 +4521,7 @@ function calculateItemsInView(ctx, params = {}) {
4407
4521
  scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4408
4522
  scrollAdjustPad = scrollAdjustPending - topPad;
4409
4523
  scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4410
- if (scroll + scrollLength > totalSize) {
4524
+ if (!state.props.hasExternalScroll && scroll + scrollLength > totalSize) {
4411
4525
  scroll = Math.max(0, totalSize - scrollLength);
4412
4526
  }
4413
4527
  };
@@ -4434,7 +4548,11 @@ function calculateItemsInView(ctx, params = {}) {
4434
4548
  let stickyState = dataChanged ? void 0 : resolveStickyState();
4435
4549
  let scrollBufferTop = drawDistance;
4436
4550
  let scrollBufferBottom = drawDistance;
4437
- 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) {
4438
4556
  scrollBufferTop = drawDistance * 0.5;
4439
4557
  scrollBufferBottom = drawDistance * 1.5;
4440
4558
  } else {
@@ -4444,8 +4562,8 @@ function calculateItemsInView(ctx, params = {}) {
4444
4562
  const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4445
4563
  const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4446
4564
  const updateScrollRange = () => {
4447
- const scrollStart = Math.max(0, scroll);
4448
- 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);
4449
4567
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4450
4568
  scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4451
4569
  scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
@@ -4480,21 +4598,21 @@ function calculateItemsInView(ctx, params = {}) {
4480
4598
  if (dataChanged) {
4481
4599
  resetLayoutCachesForDataChange(state);
4482
4600
  }
4483
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4484
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4601
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4602
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4485
4603
  updateItemPositions(ctx, dataChanged, {
4486
4604
  doMVCP,
4487
4605
  // A changed size shifts every following position. The scrolling early-exit
4488
4606
  // would leave the untouched suffix in the old coordinate space.
4489
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4607
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4490
4608
  optimizeForVisibleWindow,
4491
4609
  scrollBottomBuffered,
4492
4610
  scrollVelocity: speed,
4493
4611
  startIndex
4494
4612
  });
4495
4613
  totalSize = getContentSize(ctx);
4496
- if (minIndexSizeChanged !== void 0) {
4497
- state.minIndexSizeChanged = void 0;
4614
+ if (positionRecalculationStartIndex !== void 0) {
4615
+ state.positionRecalculationStartIndex = void 0;
4498
4616
  }
4499
4617
  let protectedContainerKeys;
4500
4618
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4574,7 +4692,7 @@ function calculateItemsInView(ctx, params = {}) {
4574
4692
  nextTop = top;
4575
4693
  }
4576
4694
  }
4577
- if (visibleRange.startNoBuffer !== null) {
4695
+ if (startBuffered !== null) {
4578
4696
  if (top <= scrollBottomBuffered) {
4579
4697
  endBuffered = i;
4580
4698
  if (scrollBottomBuffered > totalSize) {
@@ -4585,6 +4703,8 @@ function calculateItemsInView(ctx, params = {}) {
4585
4703
  } else {
4586
4704
  foundEnd = true;
4587
4705
  }
4706
+ } else if (top > scrollBottomBuffered) {
4707
+ foundEnd = true;
4588
4708
  }
4589
4709
  }
4590
4710
  }
@@ -4602,6 +4722,12 @@ function calculateItemsInView(ctx, params = {}) {
4602
4722
  bottom: nextBottom,
4603
4723
  top: nextTop
4604
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
+ }
4605
4731
  }
4606
4732
  let numContainers = prevNumContainers;
4607
4733
  const pendingRemoval = [];
@@ -4625,7 +4751,9 @@ function calculateItemsInView(ctx, params = {}) {
4625
4751
  }
4626
4752
  const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4627
4753
  const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4628
- 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;
4629
4757
  const needNewContainers = [];
4630
4758
  const needNewContainersSet = /* @__PURE__ */ new Set();
4631
4759
  const addPinnedIndex = (index) => {
@@ -4641,7 +4769,7 @@ function calculateItemsInView(ctx, params = {}) {
4641
4769
  }
4642
4770
  }
4643
4771
  };
4644
- for (let i = startBuffered; i <= endBuffered; i++) {
4772
+ for (let i = allocationStart; i <= allocationEnd; i++) {
4645
4773
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4646
4774
  if (!containerItemKeys.has(id)) {
4647
4775
  needNewContainersSet.add(i);
@@ -4663,25 +4791,25 @@ function calculateItemsInView(ctx, params = {}) {
4663
4791
  (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4664
4792
  needNewContainers,
4665
4793
  needNewContainersSet,
4666
- startBuffered,
4667
- endBuffered
4794
+ allocationStart,
4795
+ allocationEnd
4668
4796
  );
4669
4797
  } else if (previousStickyIndex !== -1) {
4670
4798
  set$(ctx, "activeStickyIndex", -1);
4671
4799
  }
4672
4800
  if (needNewContainers.length > 0) {
4673
- const getRequiredItemType = getItemType ? (i) => {
4674
- const itemType = getItemType(data[i], i);
4675
- return itemType !== void 0 ? String(itemType) : "";
4676
- } : void 0;
4677
4801
  const availableContainerAllocations = findAvailableContainers(
4678
4802
  ctx,
4679
4803
  needNewContainers,
4680
- startBuffered,
4681
- endBuffered,
4804
+ allocationStart,
4805
+ allocationEnd,
4682
4806
  pendingRemoval,
4683
- getRequiredItemType,
4684
- protectedContainerKeys
4807
+ protectedContainerKeys,
4808
+ // Initial end alignment has no scroll velocity yet. Keep its
4809
+ // target-side rows first until the initial scroll settles.
4810
+ // After a pause velocity can be zero on the first movement;
4811
+ // use its displacement without treating initial insets as motion.
4812
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4685
4813
  );
4686
4814
  for (const allocation of availableContainerAllocations) {
4687
4815
  const i = allocation.itemIndex;
@@ -4801,19 +4929,19 @@ function calculateItemsInView(ctx, params = {}) {
4801
4929
  handleInitialScrollLayoutReady(ctx);
4802
4930
  }
4803
4931
  }
4804
- if (viewabilityConfigCallbackPairs && visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4932
+ if (viewabilityConfigCallbackPairs && (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null)) {
4805
4933
  updateViewableItems(
4806
4934
  ctx.state,
4807
4935
  ctx,
4808
4936
  viewabilityConfigCallbackPairs,
4809
4937
  scrollLength,
4810
- visibleRange.startNoBuffer,
4811
- visibleRange.endNoBuffer,
4812
- startBuffered != null ? startBuffered : visibleRange.startNoBuffer,
4813
- 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
4814
4942
  );
4815
4943
  }
4816
- (_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);
4817
4945
  });
4818
4946
  }
4819
4947
 
@@ -4964,6 +5092,7 @@ function updateItemSizesBatch(ctx, measurements) {
4964
5092
  var _a3;
4965
5093
  const state = ctx.state;
4966
5094
  const result = {};
5095
+ const previousTotalSize = state.totalSize;
4967
5096
  for (const measurement of measurements) {
4968
5097
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4969
5098
  if (ownsMeasuredItem) {
@@ -4974,6 +5103,9 @@ function updateItemSizesBatch(ctx, measurements) {
4974
5103
  mergeItemSizeUpdateResult(result, nextResult);
4975
5104
  }
4976
5105
  }
5106
+ if (state.totalSize !== previousTotalSize) {
5107
+ set$(ctx, "totalSize", state.totalSize);
5108
+ }
4977
5109
  flushItemSizeUpdates(ctx, result);
4978
5110
  }
4979
5111
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -5012,12 +5144,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5012
5144
  }
5013
5145
  let needsRecalculate = !didContainersLayout;
5014
5146
  let shouldMaintainScrollAtEnd = false;
5015
- let minIndexSizeChanged;
5147
+ let positionRecalculationStartIndex;
5016
5148
  const prevSizeKnown = state.sizesKnown.get(itemKey);
5017
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5149
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
5018
5150
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
5019
5151
  if (diff !== 0) {
5020
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5152
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
5021
5153
  const { startBuffered, endBuffered } = state;
5022
5154
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
5023
5155
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -5034,8 +5166,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5034
5166
  size
5035
5167
  });
5036
5168
  }
5037
- if (minIndexSizeChanged !== void 0) {
5038
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5169
+ if (positionRecalculationStartIndex !== void 0) {
5170
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
5039
5171
  }
5040
5172
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
5041
5173
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -5052,7 +5184,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5052
5184
  didMeasureUserScrollAnchorResetItem
5053
5185
  };
5054
5186
  }
5055
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5187
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
5056
5188
  var _a3, _b;
5057
5189
  const state = ctx.state;
5058
5190
  const {
@@ -5075,10 +5207,20 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5075
5207
  fixedItemSize,
5076
5208
  itemType
5077
5209
  } : void 0;
5078
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5210
+ const prevSize = getItemSize(
5211
+ ctx,
5212
+ itemKey,
5213
+ index,
5214
+ itemData,
5215
+ void 0,
5216
+ void 0,
5217
+ notifyTotalSize,
5218
+ resolvedItemSize
5219
+ );
5079
5220
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
5080
5221
  const prevSizeKnown = sizesKnown.get(itemKey);
5081
5222
  const size = Math.round(rawSize) ;
5223
+ if (prevSizeKnown !== size) state.positionsAreCurrent = false;
5082
5224
  sizesKnown.set(itemKey, size);
5083
5225
  if (fixedItemSize === void 0 && size > 0) {
5084
5226
  itemType != null ? itemType : itemType = getItemType ? (_b = getItemType(itemData, index)) != null ? _b : "" : "";
@@ -5097,7 +5239,7 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5097
5239
  }
5098
5240
  }
5099
5241
  if (!prevSize || Math.abs(prevSize - size) > 0.1) {
5100
- setSize(ctx, itemKey, size);
5242
+ setSize(ctx, itemKey, size, notifyTotalSize);
5101
5243
  return size - prevSize;
5102
5244
  }
5103
5245
  return 0;
@@ -5499,10 +5641,19 @@ function useOnLayoutSync({
5499
5641
  measureInLayoutEffect = true,
5500
5642
  onLayoutProp,
5501
5643
  onLayoutChange,
5502
- webLayoutResync
5644
+ webLayoutResync,
5645
+ webExternalScroll = false
5503
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
+ );
5504
5654
  React3.useLayoutEffect(() => {
5505
5655
  var _a3, _b;
5656
+ if (webExternalScroll) return;
5506
5657
  const current = ref.current;
5507
5658
  const scrollableNode = (_b = (_a3 = current == null ? void 0 : current.getScrollableNode) == null ? void 0 : _a3.call(current)) != null ? _b : null;
5508
5659
  const element = scrollableNode || current;
@@ -5540,8 +5691,8 @@ function useOnLayoutSync({
5540
5691
  emit(toLayout(rectObserved), false);
5541
5692
  }
5542
5693
  });
5543
- }, [measureInLayoutEffect, ...deps || []]);
5544
- return {};
5694
+ }, [measureInLayoutEffect, webExternalScroll, ...deps || []]);
5695
+ return webExternalScroll ? { onLayout: onExternalLayout } : {};
5545
5696
  }
5546
5697
  function toLayout(rect) {
5547
5698
  if (!rect) {
@@ -6103,94 +6254,121 @@ function useRafCoalescer(callback) {
6103
6254
  return coalescer;
6104
6255
  }
6105
6256
 
6106
- // src/components/webConstants.ts
6107
- var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
6108
- var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
6109
- var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
6110
-
6111
6257
  // src/components/webScrollUtils.ts
6112
- function getDocumentScrollerNode() {
6113
- if (typeof document === "undefined") {
6114
- return null;
6115
- }
6116
- return document.scrollingElement || document.documentElement || document.body;
6258
+ function isWindowTarget(target) {
6259
+ return typeof window !== "undefined" && target === window;
6117
6260
  }
6118
- function getWindowScrollPosition() {
6119
- var _a3, _b, _c, _d;
6120
- if (typeof window === "undefined") {
6121
- 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 };
6122
6265
  }
6123
- return {
6124
- x: (_b = (_a3 = window.scrollX) != null ? _a3 : window.pageXOffset) != null ? _b : 0,
6125
- y: (_d = (_c = window.scrollY) != null ? _c : window.pageYOffset) != null ? _d : 0
6126
- };
6266
+ return { x: (_e = target == null ? void 0 : target.scrollLeft) != null ? _e : 0, y: (_f = target == null ? void 0 : target.scrollTop) != null ? _f : 0 };
6127
6267
  }
6128
- function getElementDocumentPosition(element, scroll) {
6129
- var _a3, _b;
6268
+ function getElementScrollPosition(element, target) {
6269
+ var _a3, _b, _c, _d, _e, _f;
6130
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();
6131
6274
  return {
6132
- left: ((_a3 = rect == null ? void 0 : rect.left) != null ? _a3 : 0) + scroll.x,
6133
- 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
6134
6277
  };
6135
6278
  }
6136
6279
  function getContentSize2(content) {
6137
6280
  var _a3, _b;
6138
- return {
6139
- height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0,
6140
- width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0
6141
- };
6281
+ return { height: (_a3 = content == null ? void 0 : content.scrollHeight) != null ? _a3 : 0, width: (_b = content == null ? void 0 : content.scrollWidth) != null ? _b : 0 };
6142
6282
  }
6143
- function getScrollContentSize(scrollElement, contentElement, isWindowScroll) {
6144
- return getContentSize2(isWindowScroll ? contentElement : scrollElement);
6283
+ function getScrollContentSize(scrollElement, contentElement, externalTarget) {
6284
+ return getContentSize2(externalTarget !== void 0 ? contentElement : scrollElement);
6145
6285
  }
6146
- function getLayoutMeasurement(scrollElement, isWindowScroll, horizontal) {
6147
- var _a3, _b, _c, _d, _e, _f;
6148
- 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) {
6149
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 };
6150
6291
  return {
6151
- // In window-scroll mode, use viewport size on the scroll axis.
6152
- height: horizontal ? (_b = (_a3 = rect == null ? void 0 : rect.height) != null ? _a3 : scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _b : window.innerHeight : window.innerHeight,
6153
- // Keep the cross-axis size list-relative to avoid inflating container measurements.
6154
- 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
6155
6294
  };
6156
6295
  }
6157
- return {
6158
- height: (_e = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _e : 0,
6159
- width: (_f = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _f : 0
6160
- };
6296
+ return { height: (_g = scrollElement == null ? void 0 : scrollElement.clientHeight) != null ? _g : 0, width: (_h = scrollElement == null ? void 0 : scrollElement.clientWidth) != null ? _h : 0 };
6161
6297
  }
6162
6298
  function clampOffset(offset, maxOffset) {
6163
6299
  return Math.max(0, Math.min(offset, maxOffset));
6164
6300
  }
6165
- function getAxisSize(size, horizontal) {
6166
- return horizontal ? size.width : size.height;
6167
- }
6168
6301
  function getMaxOffset(contentSize, layoutMeasurement, horizontal) {
6169
- 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]);
6170
6304
  }
6171
- function resolveScrollableNode(scrollElement, isWindowScroll) {
6172
- 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;
6173
6310
  }
6174
- function resolveScrollEventTarget(scrollElement, isWindowScroll) {
6175
- return isWindowScroll && typeof window !== "undefined" ? window : scrollElement;
6311
+ function resolveScrollEventTarget(scrollElement, externalTarget) {
6312
+ return externalTarget === void 0 ? scrollElement : externalTarget;
6176
6313
  }
6177
- function getLayoutRectangle(element, isWindowScroll, horizontal) {
6178
- const rect = element.getBoundingClientRect();
6179
- const scroll = getWindowScrollPosition();
6180
- return {
6181
- height: isWindowScroll && typeof window !== "undefined" && !horizontal ? window.innerHeight : rect.height,
6182
- width: isWindowScroll && typeof window !== "undefined" && horizontal ? window.innerWidth : rect.width,
6183
- x: isWindowScroll ? rect.left + scroll.x : rect.left,
6184
- y: isWindowScroll ? rect.top + scroll.y : rect.top
6185
- };
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 };
6186
6322
  }
6187
- function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll }) {
6323
+ function resolveExternalScrollOffset({
6324
+ clampedOffset,
6325
+ horizontal,
6326
+ listPos,
6327
+ scroll
6328
+ }) {
6188
6329
  return {
6189
6330
  left: horizontal ? listPos.left + clampedOffset : scroll.x,
6190
6331
  top: horizontal ? scroll.y : listPos.top + clampedOffset
6191
6332
  };
6192
6333
  }
6193
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
+
6194
6372
  // src/components/ListComponentScrollView.tsx
6195
6373
  var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
6196
6374
  var SCROLL_END_FALLBACK_MS = 200;
@@ -6249,6 +6427,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6249
6427
  showsVerticalScrollIndicator = true,
6250
6428
  refreshControl,
6251
6429
  useWindowScroll = false,
6430
+ scrollElement: externalScrollElement,
6252
6431
  onLayout,
6253
6432
  ...props
6254
6433
  }, ref) {
@@ -6257,31 +6436,38 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6257
6436
  const [anchoredEndSpaceSize] = useArr$(["anchoredEndSpaceSize"]);
6258
6437
  const scrollRef = React3.useRef(null);
6259
6438
  const contentRef = React3.useRef(null);
6260
- 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;
6261
6442
  const getScrollTarget = React3.useCallback(
6262
- () => resolveScrollEventTarget(scrollRef.current, isWindowScroll),
6263
- [isWindowScroll]
6443
+ () => resolveScrollEventTarget(scrollRef.current, externalTarget),
6444
+ [externalTarget]
6264
6445
  );
6265
6446
  const getMaxScrollOffset = React3.useCallback(() => {
6266
6447
  const scrollElement = scrollRef.current;
6267
- const contentSize = getScrollContentSize(scrollElement, contentRef.current, isWindowScroll);
6268
- const layoutMeasurement = getLayoutMeasurement(scrollElement, isWindowScroll, horizontal);
6448
+ const contentSize = getScrollContentSize(scrollElement, contentRef.current, externalTarget);
6449
+ const layoutMeasurement = getLayoutMeasurement(scrollElement, externalTarget, horizontal);
6269
6450
  return getMaxOffset(contentSize, layoutMeasurement, horizontal);
6270
- }, [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]);
6271
6467
  const getCurrentScrollOffset = React3.useCallback(() => {
6272
- const scrollElement = scrollRef.current;
6273
- if (isWindowScroll) {
6274
- const maxOffset = getMaxScrollOffset();
6275
- const scroll = getWindowScrollPosition();
6276
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6277
- const rawOffset = horizontal ? scroll.x - listPos.left : scroll.y - listPos.top;
6278
- return clampOffset(rawOffset, maxOffset);
6279
- }
6280
- if (!scrollElement) {
6281
- return 0;
6282
- }
6283
- return horizontal ? scrollElement.scrollLeft : scrollElement.scrollTop;
6284
- }, [getMaxScrollOffset, horizontal, isWindowScroll]);
6468
+ const offset = getRawScrollOffset();
6469
+ return isExternalScroll ? clampOffset(offset, getMaxScrollOffset()) : offset;
6470
+ }, [getMaxScrollOffset, getRawScrollOffset, isExternalScroll]);
6285
6471
  const scrollToLocalOffset = React3.useCallback(
6286
6472
  (offset, animated) => {
6287
6473
  const scrollElement = scrollRef.current;
@@ -6293,10 +6479,10 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6293
6479
  const clampedOffset = clampOffset(offset, maxOffset);
6294
6480
  const behavior = animated ? "smooth" : "auto";
6295
6481
  const options = { behavior };
6296
- if (isWindowScroll) {
6297
- const scroll = getWindowScrollPosition();
6298
- const listPos = getElementDocumentPosition(scrollElement, scroll);
6299
- const { left, top } = resolveWindowScrollTarget({
6482
+ if (isExternalScroll) {
6483
+ const scroll = getScrollPosition(externalTarget);
6484
+ const listPos = getElementScrollPosition(scrollElement, externalTarget);
6485
+ const { left, top } = resolveExternalScrollOffset({
6300
6486
  clampedOffset,
6301
6487
  horizontal,
6302
6488
  listPos,
@@ -6311,7 +6497,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6311
6497
  }
6312
6498
  target.scrollTo(options);
6313
6499
  },
6314
- [getMaxScrollOffset, getScrollTarget, horizontal, isWindowScroll]
6500
+ [externalTarget, getMaxScrollOffset, getScrollTarget, horizontal, isExternalScroll]
6315
6501
  );
6316
6502
  React3.useImperativeHandle(ref, () => {
6317
6503
  const api = {
@@ -6319,11 +6505,14 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6319
6505
  var _a4;
6320
6506
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6321
6507
  },
6508
+ getContentNode: () => contentRef.current,
6322
6509
  getCurrentScrollOffset,
6323
6510
  getMaxScrollOffset,
6324
- getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6325
- getScrollEventTarget: () => getScrollTarget(),
6326
- getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6511
+ getRawScrollOffset,
6512
+ getScrollableNode: () => resolveScrollableNode(scrollRef.current, externalTarget),
6513
+ getScrollEventTarget: getScrollTarget,
6514
+ getScrollResponder: () => resolveScrollableNode(scrollRef.current, externalTarget),
6515
+ isScrollInRange,
6327
6516
  isWindowScroll: () => isWindowScroll,
6328
6517
  scrollBy: (x, y) => {
6329
6518
  const target = getScrollTarget();
@@ -6347,14 +6536,24 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6347
6536
  }
6348
6537
  };
6349
6538
  return api;
6350
- }, [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
+ ]);
6351
6550
  const emitScroll = React3.useCallback(() => {
6352
6551
  if (!onScroll2 || !scrollRef.current) {
6353
6552
  return;
6354
6553
  }
6355
6554
  const contentSize = getContentSize2(contentRef.current);
6356
- const layoutMeasurement = getLayoutMeasurement(scrollRef.current, isWindowScroll, horizontal);
6357
- const offset = getCurrentScrollOffset();
6555
+ const layoutMeasurement = getLayoutMeasurement(scrollRef.current, externalTarget, horizontal);
6556
+ const offset = getRawScrollOffset();
6358
6557
  const scrollEvent = {
6359
6558
  nativeEvent: {
6360
6559
  contentOffset: {
@@ -6372,7 +6571,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6372
6571
  }
6373
6572
  };
6374
6573
  onScroll2(scrollEvent);
6375
- }, [getCurrentScrollOffset, horizontal, isWindowScroll, onScroll2]);
6574
+ }, [externalTarget, getRawScrollOffset, horizontal, onScroll2]);
6376
6575
  const scrollEventCoalescer = useRafCoalescer(emitScroll);
6377
6576
  const scrollEndFallbackRef = React3.useRef(void 0);
6378
6577
  const emitScrollEnd = React3.useCallback(() => {
@@ -6434,14 +6633,14 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6434
6633
  const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6435
6634
  if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6436
6635
  const offset = getCurrentScrollOffset();
6437
- if (interruptMaintainScrollAtEnd(ctx)) {
6636
+ if (interruptMaintainScrollAtEnd(ctx) && isScrollInRange()) {
6438
6637
  scrollToLocalOffset(offset, false);
6439
6638
  }
6440
6639
  }
6441
6640
  };
6442
6641
  target.addEventListener("wheel", onWheel, { passive: true });
6443
6642
  return () => target.removeEventListener("wheel", onWheel);
6444
- }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6643
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, isScrollInRange, scrollToLocalOffset]);
6445
6644
  React3.useEffect(() => {
6446
6645
  const doScroll = () => {
6447
6646
  if (contentOffset) {
@@ -6449,44 +6648,28 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6449
6648
  }
6450
6649
  };
6451
6650
  doScroll();
6452
- requestAnimationFrame(doScroll);
6651
+ const frame = requestAnimationFrame(doScroll);
6652
+ return () => cancelAnimationFrame(frame);
6453
6653
  }, [contentOffset == null ? void 0 : contentOffset.x, contentOffset == null ? void 0 : contentOffset.y, horizontal, scrollToLocalOffset]);
6454
6654
  React3.useLayoutEffect(() => {
6455
- if (!onLayout || !scrollRef.current) return;
6456
6655
  const element = scrollRef.current;
6457
- const fireLayout = () => {
6458
- onLayout({
6459
- nativeEvent: {
6460
- layout: getLayoutRectangle(element, isWindowScroll, horizontal)
6461
- }
6462
- });
6463
- };
6464
- fireLayout();
6465
- const resizeObserver = new ResizeObserver(() => {
6466
- fireLayout();
6467
- });
6468
- resizeObserver.observe(element);
6469
- const onWindowResize = () => {
6470
- fireLayout();
6471
- };
6472
- if (isWindowScroll && typeof window !== "undefined" && typeof window.addEventListener === "function") {
6473
- window.addEventListener("resize", onWindowResize);
6474
- }
6475
- return () => {
6476
- resizeObserver.disconnect();
6477
- if (isWindowScroll && typeof window !== "undefined" && typeof window.removeEventListener === "function") {
6478
- 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();
6479
6660
  }
6480
- };
6481
- }, [isWindowScroll, onLayout]);
6482
- 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);
6483
6666
  React3.useLayoutEffect(() => {
6484
6667
  if (hiddenScrollIndicatorClassName) {
6485
6668
  ensureScrollbarHiddenStyle();
6486
6669
  }
6487
6670
  }, [hiddenScrollIndicatorClassName]);
6488
6671
  const scrollViewStyle = {
6489
- ...isWindowScroll ? {} : {
6672
+ ...isExternalScroll ? {} : {
6490
6673
  overflow: "auto",
6491
6674
  overflowX: horizontal ? "auto" : showsHorizontalScrollIndicator ? "auto" : "hidden",
6492
6675
  overflowY: horizontal ? showsVerticalScrollIndicator ? "auto" : "hidden" : "auto",
@@ -6521,11 +6704,10 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6521
6704
  scrollEventThrottle: _scrollEventThrottle,
6522
6705
  ScrollComponent: _ScrollComponent,
6523
6706
  snapToOffsets,
6524
- useWindowScroll: _useWindowScroll,
6525
6707
  className: scrollViewClassNameProp,
6526
6708
  ...webProps
6527
6709
  } = props;
6528
- const snapOffsets = !isWindowScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6710
+ const snapOffsets = !isExternalScroll ? getFiniteSnapOffsets(snapToOffsets) : [];
6529
6711
  if (snapOffsets.length > 0) {
6530
6712
  scrollViewStyle.scrollSnapType = horizontal ? "x mandatory" : "y mandatory";
6531
6713
  contentStyle.position = (_c = contentStyle.position) != null ? _c : "relative";
@@ -6600,12 +6782,12 @@ function getScrollAdjustAxis(horizontal) {
6600
6782
  };
6601
6783
  }
6602
6784
  function getScrollAdjustTarget(ctx, contentNode) {
6603
- var _a3, _b, _c;
6785
+ var _a3, _b, _c, _d, _e;
6604
6786
  const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
6605
6787
  const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
6606
6788
  let resolvedContentNode = null;
6607
6789
  if (scrollElement) {
6608
- 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}`);
6609
6791
  }
6610
6792
  return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
6611
6793
  }
@@ -6620,6 +6802,7 @@ function ScrollAdjust() {
6620
6802
  const temporaryPaddingRef = React3__namespace.useRef(void 0);
6621
6803
  const contentNodeRef = React3__namespace.useRef(null);
6622
6804
  const callback = React3__namespace.useCallback(() => {
6805
+ var _a3, _b, _c;
6623
6806
  const scrollAdjust = peek$(ctx, "scrollAdjust");
6624
6807
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
6625
6808
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
@@ -6630,17 +6813,21 @@ function ScrollAdjust() {
6630
6813
  const horizontal = !!ctx.state.props.horizontal;
6631
6814
  const axis = getScrollAdjustAxis(horizontal);
6632
6815
  const { contentNode, scrollElement: el } = target;
6633
- 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;
6634
6821
  const userOffsetDelta = (scrollAdjustUserOffset || 0) - lastScrollAdjustUserOffsetRef.current;
6635
6822
  const intendedScroll = userOffsetDelta !== 0 ? currentScroll + userOffsetDelta : ctx.state.scroll;
6636
6823
  const scrollDelta = intendedScroll - currentScroll;
6637
- const shouldScroll = Math.abs(scrollDelta) > 0.01;
6824
+ const shouldScroll = !isOutsideList && Math.abs(scrollDelta) > 0.01;
6638
6825
  const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
6639
6826
  contentNodeRef.current = contentNode;
6640
6827
  if (shouldScroll && contentNode) {
6641
- const totalSize = contentNode[axis.contentSizeKey];
6828
+ const totalSize = isExternal ? el[axis.contentSizeKey] : contentNode[axis.contentSizeKey];
6642
6829
  const viewportSize = el[axis.viewportSizeKey];
6643
- const nextScroll = currentScroll + scrollDelta;
6830
+ const nextScroll = parentScroll + scrollDelta;
6644
6831
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
6645
6832
  if (needsTemporaryPadding) {
6646
6833
  const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp];
@@ -6798,12 +6985,14 @@ var ListComponent = typedMemo(function ListComponent2({
6798
6985
  renderScrollComponent,
6799
6986
  onLayoutFooter,
6800
6987
  onInternalScrollBeginDrag,
6988
+ onInternalScrollEndDrag,
6801
6989
  onInternalScrollEnd,
6802
6990
  scrollAdjustHandler,
6803
6991
  snapToIndices,
6804
6992
  stickyHeaderConfig,
6805
6993
  stickyHeaderIndices,
6806
6994
  useWindowScroll = false,
6995
+ scrollElement,
6807
6996
  ...rest
6808
6997
  }) {
6809
6998
  const ctx = useStateContext();
@@ -6869,7 +7058,7 @@ var ListComponent = typedMemo(function ListComponent2({
6869
7058
  SnapOrScroll,
6870
7059
  {
6871
7060
  ...rest,
6872
- ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} ,
7061
+ ...ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, scrollElement, useWindowScroll } : {} ,
6873
7062
  contentContainerStyle: [
6874
7063
  horizontal ? { height: "100%" } : {},
6875
7064
  contentContainerStyle,
@@ -6909,7 +7098,15 @@ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
6909
7098
  var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
6910
7099
  function useDevChecksImpl(props) {
6911
7100
  const ctx = useStateContext();
6912
- 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;
6913
7110
  const hasAnchoredEndSpace = !!anchoredEndSpace;
6914
7111
  React3.useEffect(() => {
6915
7112
  if (hasAnchoredEndSpace && (numColumns != null ? numColumns : 1) > 1) {
@@ -6920,13 +7117,13 @@ function useDevChecksImpl(props) {
6920
7117
  }
6921
7118
  }, [hasAnchoredEndSpace, numColumns]);
6922
7119
  React3.useEffect(() => {
6923
- if (useWindowScroll && renderScrollComponent) {
7120
+ if ((useWindowScroll || scrollElement !== void 0) && renderScrollComponent) {
6924
7121
  warnDevOnce(
6925
7122
  "useWindowScrollRenderScrollComponent",
6926
- "useWindowScroll is not supported when renderScrollComponent is provided."
7123
+ "useWindowScroll and scrollElement are not supported when renderScrollComponent is provided."
6927
7124
  );
6928
7125
  }
6929
- }, [renderScrollComponent, useWindowScroll]);
7126
+ }, [renderScrollComponent, scrollElement, useWindowScroll]);
6930
7127
  React3.useEffect(() => {
6931
7128
  if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
6932
7129
  warnDevOnce(
@@ -6938,8 +7135,8 @@ function useDevChecksImpl(props) {
6938
7135
  React3.useEffect(() => {
6939
7136
  const state = ctx.state;
6940
7137
  const dataLength = state.props.data.length;
6941
- const useWindowScrollResolved = state.props.useWindowScroll;
6942
- 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) {
6943
7140
  return;
6944
7141
  }
6945
7142
  const warnIfUnboundedOuterSize = () => {
@@ -6988,7 +7185,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6988
7185
  for (const key in state.averageSizes) {
6989
7186
  delete state.averageSizes[key];
6990
7187
  }
6991
- state.minIndexSizeChanged = 0;
7188
+ state.positionRecalculationStartIndex = 0;
6992
7189
  state.scrollForNextCalculateItemsInView = void 0;
6993
7190
  }
6994
7191
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -7103,7 +7300,8 @@ function doInitialAllocateContainers(ctx) {
7103
7300
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7104
7301
  });
7105
7302
  } else {
7106
- calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
7303
+ const reuseInitialPositions = !state.didLoad && state.positionsAreCurrent && !state.didDataChange && !state.didColumnsChange;
7304
+ calculateItemsInView(ctx, { dataChanged: !reuseInitialPositions, doMVCP: true });
7107
7305
  }
7108
7306
  }
7109
7307
  return true;
@@ -7146,17 +7344,15 @@ function handleLayout(ctx, layoutParam, setCanRender) {
7146
7344
  updateContentMetricsState(ctx);
7147
7345
  state.lastBatchingAction = Date.now();
7148
7346
  state.scrollForNextCalculateItemsInView = void 0;
7149
- if (scrollLength > 0) {
7150
- doInitialAllocateContainers(ctx);
7151
- }
7152
- if (needsCalculate) {
7347
+ const didAllocate = scrollLength > 0 && doInitialAllocateContainers(ctx);
7348
+ if (needsCalculate && (!didAllocate || state.initialScroll)) {
7153
7349
  calculateItemsInView(ctx, { doMVCP: true });
7154
7350
  }
7155
7351
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
7156
7352
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
7157
7353
  }
7158
7354
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
7159
- doMaintainScrollAtEnd(ctx);
7355
+ doMaintainScrollAtEnd(ctx, { immediate: true });
7160
7356
  }
7161
7357
  checkThresholds(ctx);
7162
7358
  if (state) {
@@ -7227,7 +7423,7 @@ function onScroll(ctx, event) {
7227
7423
  state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
7228
7424
  state.lastNativeScroll = newScroll;
7229
7425
  state.lastNativeScrollTime = Date.now();
7230
- if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7426
+ if (!state.props.hasExternalScroll && state.scrollingTo && state.scrollingTo.offset >= newScroll) {
7231
7427
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
7232
7428
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
7233
7429
  newScroll = maxOffset;
@@ -7385,12 +7581,12 @@ function scrollToEnd(ctx, options) {
7385
7581
  if (index === -1) {
7386
7582
  return false;
7387
7583
  }
7388
- const paddingBottom = state.props.stylePaddingBottom || 0;
7584
+ const paddingBottom = getStylePaddingEnd(state.props);
7389
7585
  const footerSize = peek$(ctx, "footerSize") || 0;
7390
7586
  scrollToIndex(ctx, {
7391
7587
  ...options,
7392
7588
  index,
7393
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7589
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
7394
7590
  viewPosition: 1
7395
7591
  });
7396
7592
  if (state.scrollingTo) {
@@ -7577,7 +7773,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
7577
7773
  for (const key in state.averageSizes) {
7578
7774
  delete state.averageSizes[key];
7579
7775
  }
7580
- state.minIndexSizeChanged = 0;
7776
+ state.positionRecalculationStartIndex = 0;
7581
7777
  state.scrollForNextCalculateItemsInView = void 0;
7582
7778
  state.pendingTotalSize = void 0;
7583
7779
  state.totalSize = 0;
@@ -7971,10 +8167,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7971
8167
  onMetricsChange,
7972
8168
  onLayout: onLayoutProp,
7973
8169
  onLoad,
8170
+ onReady,
7974
8171
  onMomentumScrollEnd,
7975
8172
  onRefresh,
7976
8173
  onScroll: onScrollProp,
7977
8174
  onScrollBeginDrag,
8175
+ onScrollEndDrag,
7978
8176
  onStartReached,
7979
8177
  onStartReachedThreshold = 0.5,
7980
8178
  onStickyHeaderChange,
@@ -7992,6 +8190,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7992
8190
  stickyHeaderIndices: stickyHeaderIndicesProp,
7993
8191
  style: styleProp,
7994
8192
  useWindowScroll = false,
8193
+ scrollElement,
7995
8194
  viewabilityConfig,
7996
8195
  viewabilityConfigCallbackPairs,
7997
8196
  ...rest
@@ -8043,17 +8242,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8043
8242
  const initialScrollProp = initialScrollAtEnd ? {
8044
8243
  index: Math.max(0, dataProp.length - 1),
8045
8244
  preserveForBottomPadding: true,
8046
- viewOffset: -stylePaddingEndState,
8245
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
8047
8246
  viewPosition: 1
8048
8247
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
8049
8248
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
8050
8249
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
8051
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
8250
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8052
8251
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
8053
8252
  } : {
8054
8253
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
8055
8254
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
8056
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
8255
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
8057
8256
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
8058
8257
  } : initialScrollUsesOffsetOnly ? {
8059
8258
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -8086,7 +8285,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8086
8285
  dataVersion,
8087
8286
  keyExtractor
8088
8287
  ]);
8089
- 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;
8090
8291
  const refState = React3.useRef(void 0);
8091
8292
  const hasOverrideItemLayout = !!overrideItemLayout;
8092
8293
  const prevHasOverrideItemLayout = React3.useRef(hasOverrideItemLayout);
@@ -8131,11 +8332,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8131
8332
  lastLayout: void 0,
8132
8333
  lastScrollDelta: 0,
8133
8334
  loadStartTime: Date.now(),
8134
- minIndexSizeChanged: 0,
8135
8335
  nativeContentInset: void 0,
8136
8336
  nativeMarginTop: 0,
8137
8337
  pendingDataComparison: void 0,
8138
8338
  pendingNativeMVCPAdjust: void 0,
8339
+ positionRecalculationStartIndex: 0,
8139
8340
  positions: [],
8140
8341
  props: {},
8141
8342
  queuedCalculateItemsInView: 0,
@@ -8224,6 +8425,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8224
8425
  estimatedItemSize,
8225
8426
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
8226
8427
  getItemType: useWrapIfItem(getItemType),
8428
+ hasExternalScroll,
8227
8429
  hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
8228
8430
  horizontal: !!horizontal,
8229
8431
  itemsAreEqual,
@@ -8238,8 +8440,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8238
8440
  onItemSizeChanged,
8239
8441
  onLoad,
8240
8442
  onMomentumScrollEnd,
8443
+ onReady,
8241
8444
  onScroll: throttleScrollFn,
8242
8445
  onScrollBeginDrag,
8446
+ onScrollEndDrag,
8243
8447
  onStartReached,
8244
8448
  onStartReachedThreshold,
8245
8449
  onStickyHeaderChange,
@@ -8413,8 +8617,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8413
8617
  const { onLayout } = useOnLayoutSync({
8414
8618
  onLayoutChange,
8415
8619
  onLayoutProp,
8416
- ref: refScroller
8620
+ ref: refScroller,
8417
8621
  // the type of ScrollView doesn't include measure?
8622
+ webExternalScroll: hasExternalScroll
8418
8623
  });
8419
8624
  React3.useLayoutEffect(() => {
8420
8625
  if (snapToIndices) {
@@ -8527,11 +8732,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8527
8732
  onScroll: (event) => onScroll(ctx, event),
8528
8733
  onScrollBeginDrag: (event) => {
8529
8734
  var _a4, _b2;
8735
+ state.isDragging = true;
8530
8736
  interruptMaintainScrollAtEnd(ctx);
8531
8737
  prepareReachedEdgeForNextUserScroll(ctx);
8532
8738
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8533
8739
  },
8534
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
8740
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
8741
+ onScrollEndDrag: (event) => {
8742
+ var _a4, _b2;
8743
+ state.isDragging = false;
8744
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8745
+ }
8535
8746
  }),
8536
8747
  []
8537
8748
  );
@@ -8555,6 +8766,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8555
8766
  ListHeaderComponent,
8556
8767
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
8557
8768
  onInternalScrollEnd: fns.onScrollEnd,
8769
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
8558
8770
  onLayout,
8559
8771
  onLayoutFooter,
8560
8772
  onMomentumScrollEnd: fns.onMomentumScrollEnd,
@@ -8573,6 +8785,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8573
8785
  refScrollView: combinedRef,
8574
8786
  renderScrollComponent,
8575
8787
  scrollAdjustHandler: (_s = refState.current) == null ? void 0 : _s.scrollAdjustHandler,
8788
+ scrollElement: scrollElementResolved,
8576
8789
  scrollEventThrottle: 0,
8577
8790
  snapToIndices,
8578
8791
  stickyHeaderIndices,