@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-native.js CHANGED
@@ -458,6 +458,14 @@ var EDGE_POSITION_EPSILON = 1;
458
458
  var ENABLE_DEVMODE = IS_DEV && false;
459
459
  var ENABLE_DEBUG_VIEW = IS_DEV && false;
460
460
 
461
+ // src/core/endOfContentTarget.ts
462
+ function getEndAlignedViewOffset(stylePaddingEnd, footerSize = 0) {
463
+ return -stylePaddingEnd - footerSize;
464
+ }
465
+ function isEndOfContentTarget(target, dataLength) {
466
+ return !!target && dataLength > 0 && target.viewPosition === 1 && !!target.preserveForBottomPadding && target.index !== void 0 && target.index >= dataLength - 1;
467
+ }
468
+
461
469
  // src/core/cancelImperativeScroll.ts
462
470
  function cancelScrollCompletionChecks({ scheduledWork }) {
463
471
  scheduledWork.cancel("checkFinishedScrollFrame");
@@ -642,26 +650,25 @@ var checkThreshold = (distance, atThreshold, threshold, wasReached, snapshot, co
642
650
  scrollPosition: context.scrollPosition
643
651
  });
644
652
  };
653
+ let reached = !!wasReached;
645
654
  if (!wasReached) {
646
- if (!within) {
647
- return false;
648
- }
649
- onReached(distance);
650
- updateSnapshot();
651
- return true;
652
- }
653
- const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
654
- if (reset) {
655
- setSnapshot(void 0);
656
- return false;
657
- }
658
- if (within) {
659
- const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
660
- if (changed) {
655
+ if (within && onReached(distance)) {
661
656
  updateSnapshot();
657
+ reached = true;
658
+ }
659
+ } else {
660
+ const reset = isOutsideThresholdHysteresis(distance, atThreshold, threshold);
661
+ if (reset) {
662
+ setSnapshot(void 0);
663
+ reached = false;
664
+ } else if (within) {
665
+ const changed = !snapshot || snapshot.atThreshold !== atThreshold || snapshot.contentSize !== context.contentSize || snapshot.dataLength !== context.dataLength;
666
+ if (changed) {
667
+ updateSnapshot();
668
+ }
662
669
  }
663
670
  }
664
- return true;
671
+ return reached;
665
672
  };
666
673
 
667
674
  // src/utils/edgeReachedGate.ts
@@ -708,7 +715,6 @@ function beginReachedEdgeUserScroll(ctx, scrollDelta) {
708
715
  return void 0;
709
716
  }
710
717
  const allowedEdge = scrollDelta < 0 ? "start" : "end";
711
- state.edgeReachedGate = "closed";
712
718
  resetEdgeLatch(ctx, allowedEdge);
713
719
  return allowedEdge;
714
720
  }
@@ -749,7 +755,7 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
749
755
  set$(
750
756
  ctx,
751
757
  "isWithinMaintainScrollAtEndThreshold",
752
- isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant"
758
+ isContentLess || distanceFromEnd <= maintainScrollAtEndThreshold * scrollLength || state.pendingMaintainScrollAtEnd || !!maintainingScrollAtEnd
753
759
  );
754
760
  const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || maintainingScrollAtEnd;
755
761
  if (!shouldSkipThresholdChecks) {
@@ -765,11 +771,13 @@ function checkAtBottom(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
765
771
  scrollPosition: scroll
766
772
  },
767
773
  (distance) => {
768
- var _a4, _b;
769
- if (canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
774
+ let dispatched = false;
775
+ if (state.props.onEndReached && canDispatchReachedEdge(ctx, "end", allowedEdge, allowGateCreatedInCurrentCheck)) {
770
776
  markReachedEdge(ctx);
771
- (_b = (_a4 = state.props).onEndReached) == null ? void 0 : _b.call(_a4, { distanceFromEnd: distance });
777
+ state.props.onEndReached({ distanceFromEnd: distance });
778
+ dispatched = true;
772
779
  }
780
+ return dispatched;
773
781
  },
774
782
  (snapshot) => {
775
783
  state.endReachedSnapshot = snapshot;
@@ -802,7 +810,7 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
802
810
  }
803
811
  set$(ctx, "isAtStart", scroll <= EDGE_POSITION_EPSILON);
804
812
  set$(ctx, "isNearStart", scroll <= threshold);
805
- const shouldSkipThresholdChecks = hasActiveInitialScroll(state) || !!state.scrollingTo;
813
+ const shouldSkipThresholdChecks = !state.queuedInitialLayout || hasActiveInitialScroll(state) || !!state.scrollingTo;
806
814
  if (!shouldSkipThresholdChecks) {
807
815
  state.isStartReached = checkThreshold(
808
816
  scroll,
@@ -816,11 +824,13 @@ function checkAtTop(ctx, allowedEdge, allowGateCreatedInCurrentCheck) {
816
824
  scrollPosition: scroll
817
825
  },
818
826
  (distance) => {
819
- var _a3, _b;
820
- if (canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
827
+ let dispatched = false;
828
+ if (state.props.onStartReached && canDispatchReachedEdge(ctx, "start", allowedEdge, allowGateCreatedInCurrentCheck)) {
821
829
  markReachedEdge(ctx);
822
- (_b = (_a3 = state.props).onStartReached) == null ? void 0 : _b.call(_a3, { distanceFromStart: distance });
830
+ state.props.onStartReached({ distanceFromStart: distance });
831
+ dispatched = true;
823
832
  }
833
+ return dispatched;
824
834
  },
825
835
  (snapshot) => {
826
836
  state.startReachedSnapshot = snapshot;
@@ -925,8 +935,14 @@ function scheduleFullDrawDistancePrewarm(ctx) {
925
935
  return;
926
936
  }
927
937
  state.scheduledWork.frame(() => {
928
- var _a3;
929
- return (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
938
+ var _a3, _b, _c;
939
+ if (state.props.hasExternalScroll && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust) {
940
+ const offset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getRawScrollOffset) == null ? void 0 : _b.call(_a3);
941
+ if (offset !== void 0) {
942
+ state.scroll = state.scrollPending = state.props.horizontal ? toLogicalHorizontalOffset(state, offset, state.totalSize) : offset;
943
+ }
944
+ }
945
+ (_c = state.triggerCalculateItemsInView) == null ? void 0 : _c.call(state);
930
946
  }, "fullDrawDistancePrewarm");
931
947
  }
932
948
 
@@ -939,6 +955,14 @@ function resetInitialRenderState(ctx, {
939
955
  if (resetLayout) {
940
956
  state.didContainersLayout = false;
941
957
  state.queuedInitialLayout = false;
958
+ state.scrollBufferDirection = void 0;
959
+ state.scrollHistory.length = 0;
960
+ state.scheduledWork.cancel("renderRangeProjection");
961
+ state.edgeReachedGate = void 0;
962
+ state.isStartReached = null;
963
+ state.isEndReached = null;
964
+ state.startReachedSnapshot = void 0;
965
+ state.endReachedSnapshot = void 0;
942
966
  }
943
967
  if (resetInitialScroll) {
944
968
  state.didFinishInitialScroll = false;
@@ -953,7 +977,7 @@ function setInitialRenderState(ctx, {
953
977
  const { state } = ctx;
954
978
  const {
955
979
  loadStartTime,
956
- props: { onLoad }
980
+ props: { onLoad, onReady }
957
981
  } = state;
958
982
  if (didLayout) {
959
983
  state.didContainersLayout = true;
@@ -974,6 +998,7 @@ function setInitialRenderState(ctx, {
974
998
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
975
999
  }
976
1000
  }
1001
+ onReady == null ? void 0 : onReady();
977
1002
  }
978
1003
  }
979
1004
 
@@ -1161,6 +1186,7 @@ function setSize(ctx, itemKey, size, notifyTotalSize = true) {
1161
1186
  const previousSize = sizes.get(itemKey);
1162
1187
  const diff = previousSize !== void 0 ? size - previousSize : size;
1163
1188
  if (diff !== 0) {
1189
+ state.positionsAreCurrent = false;
1164
1190
  addTotalSize(ctx, itemKey, diff, notifyTotalSize);
1165
1191
  }
1166
1192
  sizes.set(itemKey, size);
@@ -1221,6 +1247,7 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
1221
1247
  const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
1222
1248
  if (fixedSize !== void 0) {
1223
1249
  size = fixedSize + ctx.scrollAxisGap;
1250
+ state.positionsAreCurrent = false;
1224
1251
  state.sizesKnown.set(key, size);
1225
1252
  }
1226
1253
  }
@@ -1319,10 +1346,6 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1319
1346
  const itemSize = Math.max(0, measuredItemSize - (isOutOfBounds ? 0 : ctx.scrollAxisGap));
1320
1347
  const trailingInset = getContentInsetEnd(ctx);
1321
1348
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1322
- if (!isOutOfBounds && index === state.props.data.length - 1) {
1323
- const footerSize = peek$(ctx, "footerSize") || 0;
1324
- offset += footerSize;
1325
- }
1326
1349
  }
1327
1350
  return offset;
1328
1351
  }
@@ -1838,7 +1861,10 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
1838
1861
  state.scrollTime = currentTime;
1839
1862
  const scrollDelta = Math.abs(newScroll - prevScroll);
1840
1863
  const isUserScrollEvent = !!(options == null ? void 0 : options.fromNativeScrollEvent) && scrollDelta > 0.1 && !adjustChanged && scrollingTo === void 0 && !state.pendingNativeMVCPAdjust;
1841
- const didCancelMaintainScrollAtEnd = isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1864
+ if (isUserScrollEvent) {
1865
+ state.scrollBufferDirection = newScroll > prevScroll ? 1 : -1;
1866
+ }
1867
+ const didCancelMaintainScrollAtEnd = Platform.OS === "web" && isUserScrollEvent && newScroll < prevScroll && !!(state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd);
1842
1868
  if (didCancelMaintainScrollAtEnd) {
1843
1869
  finishMaintainScrollAtEnd(ctx);
1844
1870
  }
@@ -1916,89 +1942,104 @@ function finishMaintainScrollAtEnd(ctx) {
1916
1942
  requestAdjust(ctx, nextScroll - state.scroll);
1917
1943
  }
1918
1944
  }
1919
- function doMaintainScrollAtEnd(ctx) {
1920
- var _a3, _b, _c, _d, _e;
1945
+ function doMaintainScrollAtEnd(ctx, options) {
1946
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
1921
1947
  const state = ctx.state;
1922
- const {
1923
- didContainersLayout,
1924
- didFinishInitialScroll,
1925
- pendingNativeMVCPAdjust,
1926
- props: { maintainScrollAtEnd }
1927
- } = state;
1928
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1929
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1930
- 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);
1931
- if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1932
- const scroller = state.refScroller.current;
1933
- const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1934
- const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1935
- if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1936
- state.pendingMaintainScrollAtEnd = true;
1937
- state.scrollPending = observed;
1938
- updateScroll(ctx, observed, true, { markHasScrolled: false });
1948
+ let didMaintain = false;
1949
+ if (state.isDragging || ((_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.isScrollInRange) == null ? void 0 : _b.call(_a3)) === false) {
1950
+ finishMaintainScrollAtEnd(ctx);
1951
+ } else {
1952
+ const {
1953
+ didContainersLayout,
1954
+ didFinishInitialScroll,
1955
+ pendingNativeMVCPAdjust,
1956
+ props: { maintainScrollAtEnd }
1957
+ } = state;
1958
+ const immediate = (options == null ? void 0 : options.immediate) && Platform.OS === "web" && !(maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.animated);
1959
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1960
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1961
+ 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);
1962
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1963
+ const scroller = state.refScroller.current;
1964
+ const nativeMax = (_e = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _e.call(scroller);
1965
+ const observed = (_f = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _f.call(scroller);
1966
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1967
+ state.pendingMaintainScrollAtEnd = true;
1968
+ state.scrollPending = observed;
1969
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1970
+ }
1939
1971
  }
1940
- }
1941
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1942
- state.pendingMaintainScrollAtEnd = true;
1943
- return false;
1944
- }
1945
- if (pendingNativeMVCPAdjust) {
1946
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1947
- return false;
1948
- }
1949
- if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1950
- state.pendingMaintainScrollAtEnd = true;
1951
- (_e = state.maintainingScrollAtEnd) != null ? _e : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1952
- return false;
1953
- }
1954
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1955
- state.pendingMaintainScrollAtEnd = false;
1956
- const contentSize = getContentSize(ctx);
1957
- if (contentSize < state.scrollLength) {
1958
- state.scroll = 0;
1959
- }
1960
- if (!state.maintainingScrollAtEnd) {
1961
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1962
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1963
- const scrollAtRequest = state.scroll;
1964
- state.maintainingScrollAtEnd = pendingState;
1965
- requestAnimationFrame(() => {
1966
- if (state.maintainingScrollAtEnd !== pendingState) {
1967
- return;
1968
- }
1969
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1970
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1971
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1972
- if (state.scrollingTo || state.pendingScrollResolve) {
1973
- state.pendingMaintainScrollAtEnd = true;
1974
- return;
1975
- }
1976
- state.maintainingScrollAtEnd = activeState;
1977
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1978
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1979
- );
1980
- void scrollPromise.then(() => {
1981
- if (state.maintainingScrollAtEnd !== activeState) {
1982
- return;
1983
- }
1984
- if (state.pendingMaintainScrollAtEnd) {
1985
- state.maintainingScrollAtEnd = void 0;
1986
- doMaintainScrollAtEnd(ctx);
1987
- } else {
1972
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1973
+ state.pendingMaintainScrollAtEnd = true;
1974
+ } else if (pendingNativeMVCPAdjust) {
1975
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1976
+ } else if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1977
+ if (immediate && ((_g = state.scrollingTo) == null ? void 0 : _g.isScrollToEnd) && !state.scrollingTo.animated) {
1978
+ state.pendingMaintainScrollAtEnd = false;
1979
+ ctx.scrollToEnd({ animated: false });
1980
+ didMaintain = true;
1981
+ } else {
1982
+ state.pendingMaintainScrollAtEnd = true;
1983
+ (_h = state.maintainingScrollAtEnd) != null ? _h : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1984
+ }
1985
+ } else if (shouldMaintainScrollAtEnd && didContainersLayout) {
1986
+ state.pendingMaintainScrollAtEnd = false;
1987
+ const contentSize = getContentSize(ctx);
1988
+ if (contentSize < state.scrollLength) {
1989
+ state.scroll = 0;
1990
+ }
1991
+ if (!state.maintainingScrollAtEnd || immediate && state.maintainingScrollAtEnd === "pending-instant") {
1992
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1993
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1994
+ const scrollAtRequest = state.scroll;
1995
+ state.maintainingScrollAtEnd = pendingState;
1996
+ const run = () => {
1997
+ var _a4, _b2;
1998
+ if (state.maintainingScrollAtEnd === pendingState) {
1999
+ if (state.isDragging || ((_b2 = (_a4 = state.refScroller.current) == null ? void 0 : _a4.isScrollInRange) == null ? void 0 : _b2.call(_a4)) === false) {
1988
2000
  finishMaintainScrollAtEnd(ctx);
2001
+ } else {
2002
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
2003
+ const didScrollSinceRequest = Platform.OS === "web" && state.scroll !== scrollAtRequest;
2004
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
2005
+ if (state.scrollingTo || state.pendingScrollResolve) {
2006
+ state.pendingMaintainScrollAtEnd = true;
2007
+ } else {
2008
+ state.maintainingScrollAtEnd = activeState;
2009
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
2010
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
2011
+ );
2012
+ void scrollPromise.then(() => {
2013
+ if (state.maintainingScrollAtEnd === activeState) {
2014
+ if (state.pendingMaintainScrollAtEnd) {
2015
+ state.maintainingScrollAtEnd = void 0;
2016
+ doMaintainScrollAtEnd(ctx);
2017
+ } else {
2018
+ finishMaintainScrollAtEnd(ctx);
2019
+ }
2020
+ }
2021
+ });
2022
+ }
2023
+ } else if (state.maintainingScrollAtEnd === pendingState) {
2024
+ finishMaintainScrollAtEnd(ctx);
2025
+ }
1989
2026
  }
1990
- });
1991
- } else if (state.maintainingScrollAtEnd === pendingState) {
1992
- finishMaintainScrollAtEnd(ctx);
2027
+ }
2028
+ };
2029
+ if (immediate) {
2030
+ run();
2031
+ } else {
2032
+ requestAnimationFrame(run);
1993
2033
  }
1994
- });
2034
+ } else {
2035
+ state.pendingMaintainScrollAtEnd = true;
2036
+ }
2037
+ didMaintain = true;
1995
2038
  } else {
1996
- state.pendingMaintainScrollAtEnd = true;
2039
+ finishMaintainScrollAtEnd(ctx);
1997
2040
  }
1998
- return true;
1999
2041
  }
2000
- finishMaintainScrollAtEnd(ctx);
2001
- return false;
2042
+ return didMaintain;
2002
2043
  }
2003
2044
 
2004
2045
  // src/core/finishScrollTo.ts
@@ -2387,7 +2428,9 @@ function scrollTo(ctx, params) {
2387
2428
  pinScrollTargetRenderRange(ctx, targetOffset, scrollTarget.index);
2388
2429
  }
2389
2430
  }
2390
- state.scrollPending = targetOffset;
2431
+ if (!animated || isInitialScroll) {
2432
+ state.scrollPending = targetOffset;
2433
+ }
2391
2434
  syncInitialScrollNativeWatchdog(state, { isInitialScroll, requestedOffset: offset, targetOffset });
2392
2435
  if (!isInitialScroll && !noScrollingTo && Math.abs(state.scroll - targetOffset) > 1) {
2393
2436
  if (animated) {
@@ -2764,13 +2807,13 @@ function rearmBootstrapInitialScroll(ctx, options) {
2764
2807
  queueBootstrapInitialScrollReevaluation(ctx.state);
2765
2808
  }
2766
2809
  function createInitialScrollAtEndTarget(options) {
2767
- const { dataLength, footerSize, preserveForFooterLayout, stylePaddingEnd } = options;
2810
+ const { dataLength, footerSize, index, preserveForFooterLayout, stylePaddingEnd } = options;
2768
2811
  return {
2769
2812
  contentOffset: void 0,
2770
- index: Math.max(0, dataLength - 1),
2813
+ index: index != null ? index : Math.max(0, dataLength - 1),
2771
2814
  preserveForBottomPadding: true,
2772
2815
  preserveForFooterLayout,
2773
- viewOffset: -stylePaddingEnd - footerSize,
2816
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, footerSize),
2774
2817
  viewPosition: 1
2775
2818
  };
2776
2819
  }
@@ -2786,13 +2829,14 @@ function isRetargetableBottomAlignedInitialScrollTarget(target) {
2786
2829
  function createRetargetedBottomAlignedInitialScroll(options) {
2787
2830
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd, target } = options;
2788
2831
  const preserveForFooterLayout = shouldPreserveInitialScrollForFooterLayout(target);
2832
+ const alignsToContentEnd = initialScrollAtEnd || isEndOfContentTarget(target, dataLength);
2789
2833
  return {
2790
2834
  ...target,
2791
2835
  contentOffset: void 0,
2792
2836
  index: initialScrollAtEnd ? Math.max(0, dataLength - 1) : target.index,
2793
2837
  preserveForBottomPadding: true,
2794
2838
  preserveForFooterLayout,
2795
- viewOffset: -stylePaddingEnd - (preserveForFooterLayout ? footerSize : 0),
2839
+ viewOffset: getEndAlignedViewOffset(stylePaddingEnd, alignsToContentEnd ? footerSize : 0),
2796
2840
  viewPosition: 1
2797
2841
  };
2798
2842
  }
@@ -2806,7 +2850,7 @@ function clearPendingInitialScrollFooterLayout(ctx, options) {
2806
2850
  }
2807
2851
  const clearedFooterTarget = createInitialScrollAtEndTarget({
2808
2852
  dataLength,
2809
- footerSize: 0,
2853
+ footerSize: peek$(ctx, "footerSize") || 0,
2810
2854
  preserveForFooterLayout: void 0,
2811
2855
  stylePaddingEnd
2812
2856
  });
@@ -2829,13 +2873,12 @@ function getObservedBootstrapInitialScrollOffset(state) {
2829
2873
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2830
2874
  }
2831
2875
  function getPreservedEndAnchorOffsetDiff(ctx) {
2832
- var _a3;
2833
2876
  const state = ctx.state;
2834
2877
  const initialScroll = state.initialScroll;
2835
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2878
+ if (!state.didFinishInitialScroll || state.scrollingTo || state.pendingScrollResolve || state.maintainingScrollAtEnd || state.isDragging || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2836
2879
  return;
2837
2880
  }
2838
- const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2881
+ const currentOffset = Platform.OS !== "web" && typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2839
2882
  return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2840
2883
  }
2841
2884
  function schedulePreservedEndAnchorCorrection(ctx) {
@@ -2863,6 +2906,9 @@ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2863
2906
  const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2864
2907
  if (hasObservedNativeScrollAfterRequest) {
2865
2908
  activeCorrection.lastRequestTime = Date.now();
2909
+ if (Platform.OS === "web") {
2910
+ state.scroll = getObservedBootstrapInitialScrollOffset(state);
2911
+ }
2866
2912
  requestAdjust(ctx, offsetDiff);
2867
2913
  }
2868
2914
  schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
@@ -2985,10 +3031,10 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2985
3031
  function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2986
3032
  const { dataLength, footerSize, initialScrollAtEnd, stylePaddingEnd } = options;
2987
3033
  const state = ctx.state;
2988
- if (!initialScrollAtEnd) {
3034
+ const initialScroll = state.initialScroll;
3035
+ if (!initialScrollAtEnd && !isEndOfContentTarget(initialScroll, dataLength)) {
2989
3036
  return;
2990
3037
  }
2991
- const initialScroll = state.initialScroll;
2992
3038
  if (isOffsetInitialScrollSession(state) || dataLength === 0 || !initialScroll) {
2993
3039
  return;
2994
3040
  }
@@ -3006,6 +3052,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3006
3052
  const updatedInitialScroll = createInitialScrollAtEndTarget({
3007
3053
  dataLength,
3008
3054
  footerSize,
3055
+ index: initialScrollAtEnd ? void 0 : initialScroll.index,
3009
3056
  preserveForFooterLayout: shouldPreserveInitialScrollForFooterLayout(initialScroll),
3010
3057
  stylePaddingEnd
3011
3058
  });
@@ -3287,6 +3334,7 @@ function handleInitialScrollDataChange(ctx, options) {
3287
3334
 
3288
3335
  // src/core/resetLayoutCachesForDataChange.ts
3289
3336
  function resetLayoutCachesForDataChange(state) {
3337
+ state.positionsAreCurrent = false;
3290
3338
  state.indexByKey.clear();
3291
3339
  state.idCache.length = 0;
3292
3340
  state.positions.length = 0;
@@ -3567,7 +3615,7 @@ function updateItemPositions(ctx, dataChanged, {
3567
3615
  scrollBottomBuffered: -1,
3568
3616
  startIndex: 0
3569
3617
  }) {
3570
- var _a3, _b, _c, _d, _e;
3618
+ var _a3, _b, _c, _d, _e, _f;
3571
3619
  const state = ctx.state;
3572
3620
  const hasPositionListeners = ctx.positionListeners.size > 0;
3573
3621
  const {
@@ -3583,6 +3631,11 @@ function updateItemPositions(ctx, dataChanged, {
3583
3631
  const dataLength = data.length;
3584
3632
  const numColumns = (_a3 = peek$(ctx, "numColumns")) != null ? _a3 : 1;
3585
3633
  const hasColumns = numColumns > 1;
3634
+ const canReuseEstimates = !doMVCP || !state.didContainersLayout && Object.keys(state.averageSizes).length === 0;
3635
+ if (state.positionsAreCurrent && !dataChanged && !forceFullUpdate && state.positionRecalculationStartIndex === void 0 && !scrollingTo && !hasColumns && !snapToIndices && positions.length === dataLength && canReuseEstimates) {
3636
+ return;
3637
+ }
3638
+ state.positionsAreCurrent = false;
3586
3639
  const indexByKeyForChecking = IS_DEV ? /* @__PURE__ */ new Map() : void 0;
3587
3640
  const extraData = peek$(ctx, "extraData");
3588
3641
  const layoutConfig = overrideItemLayout ? { span: 1 } : void 0;
@@ -3697,6 +3750,10 @@ function updateItemPositions(ctx, dataChanged, {
3697
3750
  }
3698
3751
  if (!didBreakEarly) {
3699
3752
  updateTotalSize(ctx);
3753
+ state.positionsAreCurrent = !hasColumns;
3754
+ if (startIndex <= ((_f = state.positionRecalculationStartIndex) != null ? _f : -1)) {
3755
+ state.positionRecalculationStartIndex = void 0;
3756
+ }
3700
3757
  }
3701
3758
  if (snapToIndices) {
3702
3759
  updateSnapToOffsets(ctx);
@@ -3965,7 +4022,7 @@ function getExpandedContainerPoolSize(dataLength, numContainers) {
3965
4022
  }
3966
4023
 
3967
4024
  // src/utils/findAvailableContainers.ts
3968
- function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, getRequiredItemType, protectedKeys) {
4025
+ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffered, pendingRemoval, protectedKeys, reverseItemOrder = false) {
3969
4026
  var _a3;
3970
4027
  const numNeeded = needNewContainers.length;
3971
4028
  if (numNeeded === 0) {
@@ -3974,16 +4031,27 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
3974
4031
  const numContainers = peek$(ctx, "numContainers");
3975
4032
  const state = ctx.state;
3976
4033
  const { containerItemMetadata, stickyContainerPool } = state;
4034
+ const { data, getItemType } = state.props;
4035
+ const hasItemTypes = !!getItemType;
3977
4036
  const shouldAvoidAssignedContainerReuse = state.props.recycleItems && !!state.props.positionComponentInternal;
3978
4037
  const pendingRemovalSet = pendingRemoval.length > 0 ? new Set(pendingRemoval) : void 0;
3979
- const requests = needNewContainers.map((itemIndex, order) => ({
3980
- isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
3981
- itemIndex,
3982
- itemType: getRequiredItemType == null ? void 0 : getRequiredItemType(itemIndex),
3983
- order
3984
- }));
3985
- const normalRequests = requests.filter((request) => !request.isSticky);
3986
- const stickyRequests = requests.filter((request) => request.isSticky);
4038
+ const requests = new Array(numNeeded);
4039
+ const normalRequests = [];
4040
+ const stickyRequests = [];
4041
+ for (let order = 0; order < numNeeded; order++) {
4042
+ const itemIndex = needNewContainers[order];
4043
+ const itemType = getItemType == null ? void 0 : getItemType(data[itemIndex], itemIndex);
4044
+ const request = {
4045
+ isBuffered: itemIndex >= startBuffered && itemIndex <= endBuffered,
4046
+ isSticky: state.props.stickyHeaderIndicesSet.has(itemIndex),
4047
+ itemIndex,
4048
+ itemType: hasItemTypes ? itemType !== void 0 ? String(itemType) : "" : void 0,
4049
+ order,
4050
+ renderPriority: reverseItemOrder ? -itemIndex : itemIndex
4051
+ };
4052
+ requests[order] = request;
4053
+ (request.isSticky ? stickyRequests : normalRequests).push(request);
4054
+ }
3987
4055
  const normalCandidates = [];
3988
4056
  const stickyCandidates = [];
3989
4057
  for (let containerIndex = 0; containerIndex < numContainers; containerIndex++) {
@@ -4039,7 +4107,7 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4039
4107
  }
4040
4108
  };
4041
4109
  const assignFromPool = (pendingRequests, candidates, allowCrossType) => {
4042
- if (getRequiredItemType) {
4110
+ if (hasItemTypes) {
4043
4111
  assignMatching(
4044
4112
  pendingRequests,
4045
4113
  candidates,
@@ -4063,6 +4131,11 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4063
4131
  }
4064
4132
  assign(request, containerIndex);
4065
4133
  }
4134
+ if (normalRequests.length > 1 || stickyRequests.length > 1) {
4135
+ const containerIndices = [];
4136
+ reorderPoolAssignments(normalRequests, allocations, containerIndices, hasItemTypes);
4137
+ reorderPoolAssignments(stickyRequests, allocations, containerIndices, hasItemTypes);
4138
+ }
4066
4139
  if (pendingRemovalChanged) {
4067
4140
  pendingRemoval.length = 0;
4068
4141
  if (pendingRemovalSet) {
@@ -4092,12 +4165,53 @@ function findAvailableContainers(ctx, needNewContainers, startBuffered, endBuffe
4092
4165
  function comparatorByDistance(a, b) {
4093
4166
  return b.distance - a.distance;
4094
4167
  }
4168
+ function comparatorByRenderPriority(a, b) {
4169
+ return Number(b.isBuffered) - Number(a.isBuffered) || a.renderPriority - b.renderPriority;
4170
+ }
4171
+ function comparatorByIndex(a, b) {
4172
+ return a - b;
4173
+ }
4174
+ function reorderPoolAssignments(requests, allocations, containerIndices, groupByType) {
4175
+ if (requests.length >= 2) {
4176
+ if (groupByType) {
4177
+ const groups = /* @__PURE__ */ new Map();
4178
+ for (const request of requests) {
4179
+ const group = groups.get(request.itemType);
4180
+ if (group) {
4181
+ group.push(request);
4182
+ } else {
4183
+ groups.set(request.itemType, [request]);
4184
+ }
4185
+ }
4186
+ for (const group of groups.values()) {
4187
+ reorderPoolAssignments(group, allocations, containerIndices, false);
4188
+ }
4189
+ } else {
4190
+ containerIndices.length = 0;
4191
+ let indicesSorted = true;
4192
+ for (let i = 0; i < requests.length; i++) {
4193
+ const containerIndex = allocations[requests[i].order].containerIndex;
4194
+ if (i > 0 && containerIndex < containerIndices[i - 1]) {
4195
+ indicesSorted = false;
4196
+ }
4197
+ containerIndices.push(containerIndex);
4198
+ }
4199
+ if (!indicesSorted) {
4200
+ containerIndices.sort(comparatorByIndex);
4201
+ }
4202
+ requests.sort(comparatorByRenderPriority);
4203
+ for (let i = 0; i < requests.length; i++) {
4204
+ allocations[requests[i].order].containerIndex = containerIndices[i];
4205
+ }
4206
+ }
4207
+ }
4208
+ }
4095
4209
 
4096
4210
  // src/utils/setDidLayout.ts
4097
4211
  function setDidLayout(ctx) {
4098
4212
  const state = ctx.state;
4099
4213
  state.queuedInitialLayout = true;
4100
- checkAtBottom(ctx);
4214
+ checkThresholds(ctx);
4101
4215
  setInitialRenderState(ctx, { didLayout: true });
4102
4216
  if (state.pendingMaintainScrollAtEnd) {
4103
4217
  doMaintainScrollAtEnd(ctx);
@@ -4202,7 +4316,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4202
4316
  }
4203
4317
  function trackVisibleRange(range, i, top, size, scroll, scrollBottom) {
4204
4318
  let didPassVisibleEnd = false;
4205
- if (range.startNoBuffer === null && top + size > scroll) {
4319
+ if (range.startNoBuffer === null && top + size > scroll && top <= scrollBottom) {
4206
4320
  range.startNoBuffer = i;
4207
4321
  }
4208
4322
  if (range.firstFullyOnScreenIndex === void 0 && top >= scroll - 10 && top <= scrollBottom) {
@@ -4268,7 +4382,7 @@ function findFirstVisibleIndexInCachedRange(ctx, scroll) {
4268
4382
  return null;
4269
4383
  }
4270
4384
  function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, scrollLength, scroll, scrollBottom) {
4271
- var _a3, _b;
4385
+ var _a3, _b, _c, _d;
4272
4386
  const state = ctx.state;
4273
4387
  const {
4274
4388
  endBuffered,
@@ -4302,14 +4416,14 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4302
4416
  startNoBuffer: visibleRange.startNoBuffer
4303
4417
  });
4304
4418
  maybeEmitFirstVisibleItemChanged(state, visibleRange.startNoBuffer);
4305
- if (visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4419
+ if (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4306
4420
  updateViewableItems(
4307
4421
  state,
4308
4422
  ctx,
4309
4423
  viewabilityConfigCallbackPairs,
4310
4424
  scrollLength,
4311
- visibleRange.startNoBuffer,
4312
- visibleRange.endNoBuffer,
4425
+ (_c = visibleRange.startNoBuffer) != null ? _c : 0,
4426
+ (_d = visibleRange.endNoBuffer) != null ? _d : -1,
4313
4427
  startBuffered,
4314
4428
  endBuffered
4315
4429
  );
@@ -4318,16 +4432,16 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
4318
4432
  function calculateItemsInView(ctx, params = {}) {
4319
4433
  const state = ctx.state;
4320
4434
  batchedUpdates(() => {
4321
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
4435
+ 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;
4322
4436
  const {
4323
4437
  columns,
4324
4438
  containerItemKeys,
4325
4439
  enableScrollForNextCalculateItemsInView,
4326
4440
  idCache,
4327
4441
  indexByKey,
4328
- minIndexSizeChanged,
4442
+ positionRecalculationStartIndex,
4329
4443
  positions,
4330
- props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
4444
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, keyExtractor, onStickyHeaderChange },
4331
4445
  scrollForNextCalculateItemsInView,
4332
4446
  scrollLength,
4333
4447
  sizes,
@@ -4370,7 +4484,7 @@ function calculateItemsInView(ctx, params = {}) {
4370
4484
  scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4371
4485
  scrollAdjustPad = scrollAdjustPending - topPad;
4372
4486
  scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4373
- if (scroll + scrollLength > totalSize) {
4487
+ if (!state.props.hasExternalScroll && scroll + scrollLength > totalSize) {
4374
4488
  scroll = Math.max(0, totalSize - scrollLength);
4375
4489
  }
4376
4490
  };
@@ -4397,7 +4511,11 @@ function calculateItemsInView(ctx, params = {}) {
4397
4511
  let stickyState = dataChanged ? void 0 : resolveStickyState();
4398
4512
  let scrollBufferTop = drawDistance;
4399
4513
  let scrollBufferBottom = drawDistance;
4400
- if (speed > 0 || speed === 0 && scroll < Math.max(50, drawDistance)) {
4514
+ if (speed !== 0) {
4515
+ state.scrollBufferDirection = speed > 0 ? 1 : -1;
4516
+ }
4517
+ const bufferForward = state.scrollBufferDirection === 1 || state.scrollBufferDirection === void 0 && scroll < Math.max(50, drawDistance);
4518
+ if (bufferForward) {
4401
4519
  scrollBufferTop = drawDistance * 0.5;
4402
4520
  scrollBufferBottom = drawDistance * 1.5;
4403
4521
  } else {
@@ -4407,8 +4525,8 @@ function calculateItemsInView(ctx, params = {}) {
4407
4525
  const shouldProjectRenderRange = !dataChanged && !forceFullItemPositions && !suppressInitialScrollSideEffects && !hasActiveInitialScroll(state) && !state.scrollingTo && !state.pendingNativeMVCPAdjust && !!peek$(ctx, "readyToRender");
4408
4526
  const projectedBufferAdjustment = shouldProjectRenderRange ? getProjectedBufferAdjustment(speed, Math.min(scrollBufferTop, scrollBufferBottom)) : 0;
4409
4527
  const updateScrollRange = () => {
4410
- const scrollStart = Math.max(0, scroll);
4411
- const overscrollBeforeContent = Math.max(0, -nativeScrollState);
4528
+ const scrollStart = state.props.hasExternalScroll ? scroll : Math.max(0, scroll);
4529
+ const overscrollBeforeContent = state.props.hasExternalScroll ? 0 : Math.max(0, -nativeScrollState);
4412
4530
  scrollBottom = Math.max(scrollStart, scroll + scrollLength + overscrollBeforeContent);
4413
4531
  scrollTopBuffered = scrollStart - scrollBufferTop + projectedBufferAdjustment;
4414
4532
  scrollBottomBuffered = scrollBottom + scrollBufferBottom + projectedBufferAdjustment;
@@ -4443,21 +4561,21 @@ function calculateItemsInView(ctx, params = {}) {
4443
4561
  if (dataChanged) {
4444
4562
  resetLayoutCachesForDataChange(state);
4445
4563
  }
4446
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
4447
- const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4564
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = positionRecalculationStartIndex != null ? positionRecalculationStartIndex : state.startBuffered) != null ? _e : 0;
4565
+ const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && positionRecalculationStartIndex !== void 0;
4448
4566
  updateItemPositions(ctx, dataChanged, {
4449
4567
  doMVCP,
4450
4568
  // A changed size shifts every following position. The scrolling early-exit
4451
4569
  // would leave the untouched suffix in the old coordinate space.
4452
- forceFullUpdate: !!forceFullItemPositions || minIndexSizeChanged !== void 0,
4570
+ forceFullUpdate: !!forceFullItemPositions || positionRecalculationStartIndex !== void 0,
4453
4571
  optimizeForVisibleWindow,
4454
4572
  scrollBottomBuffered,
4455
4573
  scrollVelocity: speed,
4456
4574
  startIndex
4457
4575
  });
4458
4576
  totalSize = getContentSize(ctx);
4459
- if (minIndexSizeChanged !== void 0) {
4460
- state.minIndexSizeChanged = void 0;
4577
+ if (positionRecalculationStartIndex !== void 0) {
4578
+ state.positionRecalculationStartIndex = void 0;
4461
4579
  }
4462
4580
  let protectedContainerKeys;
4463
4581
  if (dataChanged && doMVCP && state.props.maintainVisibleContentPosition.data && state.didContainersLayout && state.idsInView.length > 0) {
@@ -4537,7 +4655,7 @@ function calculateItemsInView(ctx, params = {}) {
4537
4655
  nextTop = top;
4538
4656
  }
4539
4657
  }
4540
- if (visibleRange.startNoBuffer !== null) {
4658
+ if (startBuffered !== null) {
4541
4659
  if (top <= scrollBottomBuffered) {
4542
4660
  endBuffered = i;
4543
4661
  if (scrollBottomBuffered > totalSize) {
@@ -4548,6 +4666,8 @@ function calculateItemsInView(ctx, params = {}) {
4548
4666
  } else {
4549
4667
  foundEnd = true;
4550
4668
  }
4669
+ } else if (top > scrollBottomBuffered) {
4670
+ foundEnd = true;
4551
4671
  }
4552
4672
  }
4553
4673
  }
@@ -4565,6 +4685,12 @@ function calculateItemsInView(ctx, params = {}) {
4565
4685
  bottom: nextBottom,
4566
4686
  top: nextTop
4567
4687
  };
4688
+ } else if (enableScrollForNextCalculateItemsInView && state.props.hasExternalScroll) {
4689
+ if (scrollBottomBuffered < 0) {
4690
+ state.scrollForNextCalculateItemsInView = { bottom: 0, top: null };
4691
+ } else if (scrollTopBuffered > totalSize) {
4692
+ state.scrollForNextCalculateItemsInView = { bottom: null, top: totalSize };
4693
+ }
4568
4694
  }
4569
4695
  let numContainers = prevNumContainers;
4570
4696
  const pendingRemoval = [];
@@ -4588,7 +4714,9 @@ function calculateItemsInView(ctx, params = {}) {
4588
4714
  }
4589
4715
  const hasScrollTargetPinnedRange = scrollTargetPinnedStart <= scrollTargetPinnedEnd;
4590
4716
  const isPinnedRenderIndex = (index) => alwaysRenderIndicesSet.has(index) || hasScrollTargetPinnedRange && index >= scrollTargetPinnedStart && index <= scrollTargetPinnedEnd;
4591
- if (startBuffered !== null && endBuffered !== null) {
4717
+ if (startBuffered !== null && endBuffered !== null || alwaysRenderIndicesArr.length || hasScrollTargetPinnedRange) {
4718
+ const allocationStart = startBuffered != null ? startBuffered : 0;
4719
+ const allocationEnd = endBuffered != null ? endBuffered : -1;
4592
4720
  const needNewContainers = [];
4593
4721
  const needNewContainersSet = /* @__PURE__ */ new Set();
4594
4722
  const addPinnedIndex = (index) => {
@@ -4604,7 +4732,7 @@ function calculateItemsInView(ctx, params = {}) {
4604
4732
  }
4605
4733
  }
4606
4734
  };
4607
- for (let i = startBuffered; i <= endBuffered; i++) {
4735
+ for (let i = allocationStart; i <= allocationEnd; i++) {
4608
4736
  const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4609
4737
  if (!containerItemKeys.has(id)) {
4610
4738
  needNewContainersSet.add(i);
@@ -4626,25 +4754,25 @@ function calculateItemsInView(ctx, params = {}) {
4626
4754
  (_n = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _n : -1,
4627
4755
  needNewContainers,
4628
4756
  needNewContainersSet,
4629
- startBuffered,
4630
- endBuffered
4757
+ allocationStart,
4758
+ allocationEnd
4631
4759
  );
4632
4760
  } else if (previousStickyIndex !== -1) {
4633
4761
  set$(ctx, "activeStickyIndex", -1);
4634
4762
  }
4635
4763
  if (needNewContainers.length > 0) {
4636
- const getRequiredItemType = getItemType ? (i) => {
4637
- const itemType = getItemType(data[i], i);
4638
- return itemType !== void 0 ? String(itemType) : "";
4639
- } : void 0;
4640
4764
  const availableContainerAllocations = findAvailableContainers(
4641
4765
  ctx,
4642
4766
  needNewContainers,
4643
- startBuffered,
4644
- endBuffered,
4767
+ allocationStart,
4768
+ allocationEnd,
4645
4769
  pendingRemoval,
4646
- getRequiredItemType,
4647
- protectedContainerKeys
4770
+ protectedContainerKeys,
4771
+ // Initial end alignment has no scroll velocity yet. Keep its
4772
+ // target-side rows first until the initial scroll settles.
4773
+ // After a pause velocity can be zero on the first movement;
4774
+ // use its displacement without treating initial insets as motion.
4775
+ hasActiveInitialScroll(state) ? (initialScroll == null ? void 0 : initialScroll.viewPosition) === 1 : (speed || (state.hasScrolled ? state.scroll - state.scrollPrev : 0)) < 0
4648
4776
  );
4649
4777
  for (const allocation of availableContainerAllocations) {
4650
4778
  const i = allocation.itemIndex;
@@ -4764,19 +4892,19 @@ function calculateItemsInView(ctx, params = {}) {
4764
4892
  handleInitialScrollLayoutReady(ctx);
4765
4893
  }
4766
4894
  }
4767
- if (viewabilityConfigCallbackPairs && visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null) {
4895
+ if (viewabilityConfigCallbackPairs && (state.props.hasExternalScroll || visibleRange.startNoBuffer !== null && visibleRange.endNoBuffer !== null)) {
4768
4896
  updateViewableItems(
4769
4897
  ctx.state,
4770
4898
  ctx,
4771
4899
  viewabilityConfigCallbackPairs,
4772
4900
  scrollLength,
4773
- visibleRange.startNoBuffer,
4774
- visibleRange.endNoBuffer,
4775
- startBuffered != null ? startBuffered : visibleRange.startNoBuffer,
4776
- endBuffered != null ? endBuffered : visibleRange.endNoBuffer
4901
+ (_u = visibleRange.startNoBuffer) != null ? _u : 0,
4902
+ (_v = visibleRange.endNoBuffer) != null ? _v : -1,
4903
+ (_w = startBuffered != null ? startBuffered : visibleRange.startNoBuffer) != null ? _w : 0,
4904
+ (_x = endBuffered != null ? endBuffered : visibleRange.endNoBuffer) != null ? _x : -1
4777
4905
  );
4778
4906
  }
4779
- (_u = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _u.call(stickyState);
4907
+ (_y = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _y.call(stickyState);
4780
4908
  });
4781
4909
  }
4782
4910
 
@@ -4940,6 +5068,7 @@ function updateItemSizesBatch(ctx, measurements) {
4940
5068
  var _a3;
4941
5069
  const state = ctx.state;
4942
5070
  const result = {};
5071
+ const previousTotalSize = state.totalSize;
4943
5072
  for (const measurement of measurements) {
4944
5073
  const ownsMeasuredItem = measurement.containerId === void 0 || peek$(ctx, `containerItemKey${measurement.containerId}`) === measurement.itemKey;
4945
5074
  if (ownsMeasuredItem) {
@@ -4950,6 +5079,9 @@ function updateItemSizesBatch(ctx, measurements) {
4950
5079
  mergeItemSizeUpdateResult(result, nextResult);
4951
5080
  }
4952
5081
  }
5082
+ if (state.totalSize !== previousTotalSize) {
5083
+ set$(ctx, "totalSize", state.totalSize);
5084
+ }
4953
5085
  flushItemSizeUpdates(ctx, result);
4954
5086
  }
4955
5087
  function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
@@ -4988,12 +5120,12 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
4988
5120
  }
4989
5121
  let needsRecalculate = !didContainersLayout;
4990
5122
  let shouldMaintainScrollAtEnd = false;
4991
- let minIndexSizeChanged;
5123
+ let positionRecalculationStartIndex;
4992
5124
  const prevSizeKnown = state.sizesKnown.get(itemKey);
4993
- const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem);
5125
+ const diff = updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, false);
4994
5126
  const size = roundSize(horizontal ? sizeObj.width : sizeObj.height);
4995
5127
  if (diff !== 0) {
4996
- minIndexSizeChanged = minIndexSizeChanged !== void 0 ? Math.min(minIndexSizeChanged, index) : index;
5128
+ positionRecalculationStartIndex = positionRecalculationStartIndex !== void 0 ? Math.min(positionRecalculationStartIndex, index) : index;
4997
5129
  const { startBuffered, endBuffered } = state;
4998
5130
  needsRecalculate || (needsRecalculate = index >= startBuffered && index <= endBuffered);
4999
5131
  if (!needsRecalculate && state.containerItemKeys.has(itemKey)) {
@@ -5010,8 +5142,8 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5010
5142
  size
5011
5143
  });
5012
5144
  }
5013
- if (minIndexSizeChanged !== void 0) {
5014
- state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5145
+ if (positionRecalculationStartIndex !== void 0) {
5146
+ state.positionRecalculationStartIndex = state.positionRecalculationStartIndex !== void 0 ? Math.min(state.positionRecalculationStartIndex, positionRecalculationStartIndex) : positionRecalculationStartIndex;
5015
5147
  }
5016
5148
  updateOtherAxisSizeIfNeeded(ctx, sizeObj, horizontal);
5017
5149
  if (didContainersLayout || checkAllSizesKnown(state, state.startBuffered, state.endBuffered)) {
@@ -5028,7 +5160,7 @@ function applyItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5028
5160
  didMeasureUserScrollAnchorResetItem
5029
5161
  };
5030
5162
  }
5031
- function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5163
+ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem, notifyTotalSize = true) {
5032
5164
  var _a3, _b;
5033
5165
  const state = ctx.state;
5034
5166
  const {
@@ -5051,13 +5183,23 @@ function updateOneItemSize(ctx, itemKey, sizeObj, resolvedMeasurementItem) {
5051
5183
  fixedItemSize,
5052
5184
  itemType
5053
5185
  } : void 0;
5054
- const prevSize = getItemSize(ctx, itemKey, index, itemData, void 0, void 0, void 0, resolvedItemSize);
5186
+ const prevSize = getItemSize(
5187
+ ctx,
5188
+ itemKey,
5189
+ index,
5190
+ itemData,
5191
+ void 0,
5192
+ void 0,
5193
+ notifyTotalSize,
5194
+ resolvedItemSize
5195
+ );
5055
5196
  const rawSize = horizontal ? sizeObj.width : sizeObj.height;
5056
5197
  const prevSizeKnown = sizesKnown.get(itemKey);
5057
5198
  if (Platform.OS !== "web" && prevSizeKnown !== void 0 && isNativeLayoutNoise(rawSize - prevSizeKnown)) {
5058
5199
  return 0;
5059
5200
  }
5060
5201
  const size = Platform.OS === "web" ? Math.round(rawSize) : roundSize(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;
@@ -5227,10 +5369,16 @@ function useValue$(key, params) {
5227
5369
  var _a3;
5228
5370
  return (_a3 = getValue ? getValue(peek$(ctx, key)) : peek$(ctx, key)) != null ? _a3 : 0;
5229
5371
  };
5230
- const animValue = useAnimatedValue(getNewValue());
5372
+ const initialValue = getNewValue();
5373
+ const animValue = useAnimatedValue(initialValue);
5374
+ const lastValue = React2.useRef(initialValue);
5231
5375
  React2.useLayoutEffect(() => {
5232
5376
  const syncCurrentValue = () => {
5233
- animValue.setValue(getNewValue());
5377
+ const nextValue = getNewValue();
5378
+ if (lastValue.current !== nextValue) {
5379
+ lastValue.current = nextValue;
5380
+ animValue.setValue(nextValue);
5381
+ }
5234
5382
  };
5235
5383
  const unsubscribe = listen$(ctx, key, syncCurrentValue);
5236
5384
  syncCurrentValue();
@@ -6145,12 +6293,14 @@ var ListComponent = typedMemo(function ListComponent2({
6145
6293
  renderScrollComponent,
6146
6294
  onLayoutFooter,
6147
6295
  onInternalScrollBeginDrag,
6296
+ onInternalScrollEndDrag,
6148
6297
  onInternalScrollEnd,
6149
6298
  scrollAdjustHandler,
6150
6299
  snapToIndices,
6151
6300
  stickyHeaderConfig,
6152
6301
  stickyHeaderIndices,
6153
6302
  useWindowScroll = false,
6303
+ scrollElement,
6154
6304
  ...rest
6155
6305
  }) {
6156
6306
  const ctx = useStateContext();
@@ -6216,7 +6366,7 @@ var ListComponent = typedMemo(function ListComponent2({
6216
6366
  SnapOrScroll,
6217
6367
  {
6218
6368
  ...rest,
6219
- ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag },
6369
+ ...Platform.OS === "web" ? ScrollComponent === ListComponentScrollView ? { onInternalScrollEnd, scrollElement, useWindowScroll } : {} : { onScrollBeginDrag: onInternalScrollBeginDrag, onScrollEndDrag: onInternalScrollEndDrag },
6220
6370
  contentContainerStyle: [
6221
6371
  horizontal ? { height: "100%" } : {},
6222
6372
  contentContainerStyle,
@@ -6256,7 +6406,15 @@ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
6256
6406
  var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
6257
6407
  function useDevChecksImpl(props) {
6258
6408
  const ctx = useStateContext();
6259
- const { anchoredEndSpace, childrenMode, keyExtractor, numColumns, renderScrollComponent, useWindowScroll } = props;
6409
+ const {
6410
+ anchoredEndSpace,
6411
+ childrenMode,
6412
+ keyExtractor,
6413
+ numColumns,
6414
+ renderScrollComponent,
6415
+ scrollElement,
6416
+ useWindowScroll
6417
+ } = props;
6260
6418
  const hasAnchoredEndSpace = !!anchoredEndSpace;
6261
6419
  React2.useEffect(() => {
6262
6420
  if (hasAnchoredEndSpace && (numColumns != null ? numColumns : 1) > 1) {
@@ -6267,13 +6425,13 @@ function useDevChecksImpl(props) {
6267
6425
  }
6268
6426
  }, [hasAnchoredEndSpace, numColumns]);
6269
6427
  React2.useEffect(() => {
6270
- if (useWindowScroll && renderScrollComponent) {
6428
+ if ((useWindowScroll || scrollElement !== void 0) && renderScrollComponent) {
6271
6429
  warnDevOnce(
6272
6430
  "useWindowScrollRenderScrollComponent",
6273
- "useWindowScroll is not supported when renderScrollComponent is provided."
6431
+ "useWindowScroll and scrollElement are not supported when renderScrollComponent is provided."
6274
6432
  );
6275
6433
  }
6276
- }, [renderScrollComponent, useWindowScroll]);
6434
+ }, [renderScrollComponent, scrollElement, useWindowScroll]);
6277
6435
  React2.useEffect(() => {
6278
6436
  if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
6279
6437
  warnDevOnce(
@@ -6285,8 +6443,8 @@ function useDevChecksImpl(props) {
6285
6443
  React2.useEffect(() => {
6286
6444
  const state = ctx.state;
6287
6445
  const dataLength = state.props.data.length;
6288
- const useWindowScrollResolved = state.props.useWindowScroll;
6289
- if (Platform.OS !== "web" || useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
6446
+ const usesExternalScroll = state.props.hasExternalScroll;
6447
+ if (Platform.OS !== "web" || usesExternalScroll || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
6290
6448
  return;
6291
6449
  }
6292
6450
  const warnIfUnboundedOuterSize = () => {
@@ -6335,7 +6493,7 @@ function checkResetContainers(ctx, dataProp, { didColumnsChange = false } = {})
6335
6493
  for (const key in state.averageSizes) {
6336
6494
  delete state.averageSizes[key];
6337
6495
  }
6338
- state.minIndexSizeChanged = 0;
6496
+ state.positionRecalculationStartIndex = 0;
6339
6497
  state.scrollForNextCalculateItemsInView = void 0;
6340
6498
  }
6341
6499
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
@@ -6450,7 +6608,8 @@ function doInitialAllocateContainers(ctx) {
6450
6608
  calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
6451
6609
  });
6452
6610
  } else {
6453
- calculateItemsInView(ctx, { dataChanged: true, doMVCP: true });
6611
+ const reuseInitialPositions = !state.didLoad && state.positionsAreCurrent && !state.didDataChange && !state.didColumnsChange;
6612
+ calculateItemsInView(ctx, { dataChanged: !reuseInitialPositions, doMVCP: true });
6454
6613
  }
6455
6614
  }
6456
6615
  return true;
@@ -6489,17 +6648,15 @@ function handleLayout(ctx, layoutParam, setCanRender) {
6489
6648
  updateContentMetricsState(ctx);
6490
6649
  state.lastBatchingAction = Date.now();
6491
6650
  state.scrollForNextCalculateItemsInView = void 0;
6492
- if (scrollLength > 0) {
6493
- doInitialAllocateContainers(ctx);
6494
- }
6495
- if (needsCalculate) {
6651
+ const didAllocate = scrollLength > 0 && doInitialAllocateContainers(ctx);
6652
+ if (needsCalculate && (!didAllocate || state.initialScroll)) {
6496
6653
  calculateItemsInView(ctx, { doMVCP: true });
6497
6654
  }
6498
6655
  if (didChange || otherAxisSize !== prevOtherAxisSize) {
6499
6656
  set$(ctx, "scrollSize", { height: layout.height, width: layout.width });
6500
6657
  }
6501
6658
  if (maintainScrollAtEnd == null ? void 0 : maintainScrollAtEnd.onLayout) {
6502
- doMaintainScrollAtEnd(ctx);
6659
+ doMaintainScrollAtEnd(ctx, { immediate: true });
6503
6660
  }
6504
6661
  checkThresholds(ctx);
6505
6662
  if (state) {
@@ -6574,7 +6731,7 @@ function onScroll(ctx, event) {
6574
6731
  }
6575
6732
  state.lastNativeScroll = newScroll;
6576
6733
  state.lastNativeScrollTime = Date.now();
6577
- if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
6734
+ if (!state.props.hasExternalScroll && state.scrollingTo && state.scrollingTo.offset >= newScroll) {
6578
6735
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
6579
6736
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
6580
6737
  newScroll = maxOffset;
@@ -6732,12 +6889,12 @@ function scrollToEnd(ctx, options) {
6732
6889
  if (index === -1) {
6733
6890
  return false;
6734
6891
  }
6735
- const paddingBottom = state.props.stylePaddingBottom || 0;
6892
+ const paddingBottom = getStylePaddingEnd(state.props);
6736
6893
  const footerSize = peek$(ctx, "footerSize") || 0;
6737
6894
  scrollToIndex(ctx, {
6738
6895
  ...options,
6739
6896
  index,
6740
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
6897
+ viewOffset: getEndAlignedViewOffset(paddingBottom, footerSize) + ((options == null ? void 0 : options.viewOffset) || 0),
6741
6898
  viewPosition: 1
6742
6899
  });
6743
6900
  if (state.scrollingTo) {
@@ -6940,7 +7097,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6940
7097
  for (const key in state.averageSizes) {
6941
7098
  delete state.averageSizes[key];
6942
7099
  }
6943
- state.minIndexSizeChanged = 0;
7100
+ state.positionRecalculationStartIndex = 0;
6944
7101
  state.scrollForNextCalculateItemsInView = void 0;
6945
7102
  state.pendingTotalSize = void 0;
6946
7103
  state.totalSize = 0;
@@ -7334,10 +7491,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7334
7491
  onMetricsChange,
7335
7492
  onLayout: onLayoutProp,
7336
7493
  onLoad,
7494
+ onReady,
7337
7495
  onMomentumScrollEnd,
7338
7496
  onRefresh,
7339
7497
  onScroll: onScrollProp,
7340
7498
  onScrollBeginDrag,
7499
+ onScrollEndDrag,
7341
7500
  onStartReached,
7342
7501
  onStartReachedThreshold = 0.5,
7343
7502
  onStickyHeaderChange,
@@ -7355,6 +7514,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7355
7514
  stickyHeaderIndices: stickyHeaderIndicesProp,
7356
7515
  style: styleProp,
7357
7516
  useWindowScroll = false,
7517
+ scrollElement,
7358
7518
  viewabilityConfig,
7359
7519
  viewabilityConfigCallbackPairs,
7360
7520
  ...rest
@@ -7406,17 +7566,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7406
7566
  const initialScrollProp = initialScrollAtEnd ? {
7407
7567
  index: Math.max(0, dataProp.length - 1),
7408
7568
  preserveForBottomPadding: true,
7409
- viewOffset: -stylePaddingEndState,
7569
+ viewOffset: getEndAlignedViewOffset(stylePaddingEndState),
7410
7570
  viewPosition: 1
7411
7571
  } : hasInitialScrollIndex ? typeof initialScrollIndexProp === "object" ? {
7412
7572
  index: (_b = initialScrollIndexProp.index) != null ? _b : 0,
7413
7573
  preserveForBottomPadding: initialScrollIndexProp.viewOffset === void 0 && initialScrollIndexProp.viewPosition === 1 ? true : void 0,
7414
- viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? -stylePaddingEndState : 0,
7574
+ viewOffset: (_c = initialScrollIndexProp.viewOffset) != null ? _c : initialScrollIndexProp.viewPosition === 1 ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7415
7575
  viewPosition: (_d = initialScrollIndexProp.viewPosition) != null ? _d : 0
7416
7576
  } : {
7417
7577
  index: initialScrollIndexProp != null ? initialScrollIndexProp : 0,
7418
7578
  preserveForBottomPadding: shouldBottomAlignNumericInitialScrollIndex ? true : void 0,
7419
- viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? -stylePaddingEndState : 0,
7579
+ viewOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : shouldBottomAlignNumericInitialScrollIndex ? getEndAlignedViewOffset(stylePaddingEndState) : 0,
7420
7580
  viewPosition: shouldBottomAlignNumericInitialScrollIndex ? 1 : void 0
7421
7581
  } : initialScrollUsesOffsetOnly ? {
7422
7582
  contentOffset: initialScrollOffsetProp != null ? initialScrollOffsetProp : 0,
@@ -7449,7 +7609,9 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7449
7609
  dataVersion,
7450
7610
  keyExtractor
7451
7611
  ]);
7452
- const useWindowScrollResolved = Platform.OS === "web" && !!useWindowScroll && !renderScrollComponent;
7612
+ const scrollElementResolved = Platform.OS === "web" && !renderScrollComponent ? scrollElement : void 0;
7613
+ const useWindowScrollResolved = Platform.OS === "web" && !!useWindowScroll && scrollElementResolved === void 0 && !renderScrollComponent;
7614
+ const hasExternalScroll = useWindowScrollResolved || scrollElementResolved !== void 0;
7453
7615
  const refState = React2.useRef(void 0);
7454
7616
  const hasOverrideItemLayout = !!overrideItemLayout;
7455
7617
  const prevHasOverrideItemLayout = React2.useRef(hasOverrideItemLayout);
@@ -7494,11 +7656,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7494
7656
  lastLayout: void 0,
7495
7657
  lastScrollDelta: 0,
7496
7658
  loadStartTime: Date.now(),
7497
- minIndexSizeChanged: 0,
7498
7659
  nativeContentInset: void 0,
7499
7660
  nativeMarginTop: 0,
7500
7661
  pendingDataComparison: void 0,
7501
7662
  pendingNativeMVCPAdjust: void 0,
7663
+ positionRecalculationStartIndex: 0,
7502
7664
  positions: [],
7503
7665
  props: {},
7504
7666
  queuedCalculateItemsInView: 0,
@@ -7587,6 +7749,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7587
7749
  estimatedItemSize,
7588
7750
  getFixedItemSize: useWrapIfItem(getFixedItemSize),
7589
7751
  getItemType: useWrapIfItem(getItemType),
7752
+ hasExternalScroll,
7590
7753
  hideItemsUntilMeasured: experimental_hideItemsUntilMeasured,
7591
7754
  horizontal: !!horizontal,
7592
7755
  itemsAreEqual,
@@ -7601,8 +7764,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7601
7764
  onItemSizeChanged,
7602
7765
  onLoad,
7603
7766
  onMomentumScrollEnd,
7767
+ onReady,
7604
7768
  onScroll: throttleScrollFn,
7605
7769
  onScrollBeginDrag,
7770
+ onScrollEndDrag,
7606
7771
  onStartReached,
7607
7772
  onStartReachedThreshold,
7608
7773
  onStickyHeaderChange,
@@ -7781,9 +7946,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7781
7946
  const { onLayout } = useOnLayoutSync({
7782
7947
  onLayoutChange,
7783
7948
  onLayoutProp,
7784
- ref: refScroller
7785
- // the type of ScrollView doesn't include measure?
7786
- });
7949
+ ref: refScroller});
7787
7950
  React2.useLayoutEffect(() => {
7788
7951
  if (snapToIndices) {
7789
7952
  updateSnapToOffsets(ctx);
@@ -7898,11 +8061,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7898
8061
  onScroll: (event) => onScroll(ctx, event),
7899
8062
  onScrollBeginDrag: (event) => {
7900
8063
  var _a4, _b2;
8064
+ state.isDragging = true;
7901
8065
  interruptMaintainScrollAtEnd(ctx);
7902
8066
  prepareReachedEdgeForNextUserScroll(ctx);
7903
8067
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
7904
8068
  },
7905
- onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx)
8069
+ onScrollEnd: () => prepareReachedEdgeForNextUserScroll(ctx),
8070
+ onScrollEndDrag: (event) => {
8071
+ var _a4, _b2;
8072
+ state.isDragging = false;
8073
+ (_b2 = (_a4 = state.props).onScrollEndDrag) == null ? void 0 : _b2.call(_a4, event);
8074
+ }
7906
8075
  }),
7907
8076
  []
7908
8077
  );
@@ -7926,6 +8095,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7926
8095
  ListHeaderComponent,
7927
8096
  onInternalScrollBeginDrag: fns.onScrollBeginDrag,
7928
8097
  onInternalScrollEnd: fns.onScrollEnd,
8098
+ onInternalScrollEndDrag: fns.onScrollEndDrag,
7929
8099
  onLayout,
7930
8100
  onLayoutFooter,
7931
8101
  onMomentumScrollEnd: fns.onMomentumScrollEnd,
@@ -7944,6 +8114,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7944
8114
  refScrollView: combinedRef,
7945
8115
  renderScrollComponent,
7946
8116
  scrollAdjustHandler: (_s = refState.current) == null ? void 0 : _s.scrollAdjustHandler,
8117
+ scrollElement: scrollElementResolved,
7947
8118
  scrollEventThrottle: 0,
7948
8119
  snapToIndices,
7949
8120
  stickyHeaderIndices,