@legendapp/list 3.0.5 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/react.js CHANGED
@@ -162,8 +162,10 @@ function StateProvider({ children }) {
162
162
  mapViewabilityConfigStates: /* @__PURE__ */ new Map(),
163
163
  mapViewabilityValues: /* @__PURE__ */ new Map(),
164
164
  positionListeners: /* @__PURE__ */ new Map(),
165
+ scrollAxisGap: 0,
165
166
  state: void 0,
166
167
  values: /* @__PURE__ */ new Map([
168
+ ["alignItemsAtEndPadding", 0],
167
169
  ["stylePaddingTop", 0],
168
170
  ["headerSize", 0],
169
171
  ["numContainers", 0],
@@ -173,6 +175,7 @@ function StateProvider({ children }) {
173
175
  ["isNearEnd", false],
174
176
  ["isNearStart", false],
175
177
  ["isWithinMaintainScrollAtEndThreshold", false],
178
+ ["adaptiveRender", "light"],
176
179
  ["totalSize", 0],
177
180
  ["scrollAdjustPending", 0]
178
181
  ]),
@@ -306,15 +309,16 @@ function getContentInsetEnd(ctx, contentInsetEndAdjustmentOverride) {
306
309
 
307
310
  // src/state/getContentSize.ts
308
311
  function getContentSize(ctx) {
309
- var _a3;
312
+ var _a3, _b;
310
313
  const { values, state } = ctx;
311
314
  const stylePaddingTop = values.get("stylePaddingTop") || 0;
312
315
  const stylePaddingBottom = state.props.stylePaddingBottom || 0;
316
+ const alignItemsAtEndPadding = values.get("alignItemsAtEndPadding") || 0;
313
317
  const headerSize = values.get("headerSize") || 0;
314
318
  const footerSize = values.get("footerSize") || 0;
315
319
  const contentInsetBottom = getContentInsetEnd(ctx);
316
- const totalSize = (_a3 = state.pendingTotalSize) != null ? _a3 : values.get("totalSize");
317
- return headerSize + footerSize + totalSize + stylePaddingTop + stylePaddingBottom + (contentInsetBottom || 0);
320
+ const totalSize = (_b = (_a3 = state.pendingTotalSize) != null ? _a3 : state.totalSize) != null ? _b : values.get("totalSize");
321
+ return headerSize + footerSize + totalSize + stylePaddingTop + alignItemsAtEndPadding + stylePaddingBottom + (contentInsetBottom || 0);
318
322
  }
319
323
 
320
324
  // src/components/DebugView.tsx
@@ -552,6 +556,24 @@ var ContextContainer = React3.createContext(null);
552
556
  function useContextContainer() {
553
557
  return React3.useContext(ContextContainer);
554
558
  }
559
+ function useAdaptiveRender() {
560
+ const [mode] = useArr$(["adaptiveRender"]);
561
+ return mode;
562
+ }
563
+ function useAdaptiveRenderChange(callback) {
564
+ const ctx = useStateContext();
565
+ const callbackRef = React3.useRef(callback);
566
+ callbackRef.current = callback;
567
+ React3.useLayoutEffect(() => {
568
+ let mode = peek$(ctx, "adaptiveRender");
569
+ return listen$(ctx, "adaptiveRender", (nextMode) => {
570
+ if (mode !== nextMode) {
571
+ mode = nextMode;
572
+ callbackRef.current(nextMode);
573
+ }
574
+ });
575
+ }, [ctx]);
576
+ }
555
577
  function useViewability(callback, configId) {
556
578
  const ctx = useStateContext();
557
579
  const containerContext = useContextContainer();
@@ -821,6 +843,7 @@ function isInMVCPActiveMode(state) {
821
843
  // src/components/Container.tsx
822
844
  function getContainerPositionStyle({
823
845
  columnWrapperStyle,
846
+ contentContainerAlignItems,
824
847
  horizontal,
825
848
  hasItemSeparator,
826
849
  isHorizontalRTLList,
@@ -846,13 +869,14 @@ function getContainerPositionStyle({
846
869
  }
847
870
  }
848
871
  return horizontal ? {
872
+ bottom: contentContainerAlignItems === "flex-end" && numColumns === 1 ? 0 : void 0,
849
873
  boxSizing: paddingStyles ? "border-box" : void 0,
850
874
  direction: isHorizontalRTLList && Platform.OS === "web" ? "ltr" : void 0,
851
875
  flexDirection: hasItemSeparator ? "row" : void 0,
852
876
  height: otherAxisSize,
853
877
  left: 0,
854
878
  position: "absolute",
855
- top: otherAxisPos,
879
+ top: contentContainerAlignItems === "flex-end" && numColumns === 1 ? void 0 : otherAxisPos,
856
880
  ...paddingStyles || {}
857
881
  } : {
858
882
  boxSizing: paddingStyles ? "border-box" : void 0,
@@ -906,6 +930,7 @@ var Container = typedMemo(function Container2({
906
930
  const style = React3.useMemo(
907
931
  () => getContainerPositionStyle({
908
932
  columnWrapperStyle,
933
+ contentContainerAlignItems: ctx.state.props.contentContainerAlignItems,
909
934
  hasItemSeparator: !!ItemSeparatorComponent,
910
935
  horizontal,
911
936
  isHorizontalRTLList,
@@ -919,6 +944,7 @@ var Container = typedMemo(function Container2({
919
944
  otherAxisPos,
920
945
  otherAxisSize,
921
946
  columnWrapperStyle,
947
+ ctx.state.props.contentContainerAlignItems,
922
948
  numColumns,
923
949
  ItemSeparatorComponent
924
950
  ]
@@ -1634,7 +1660,12 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1634
1660
  WebkitOverflowScrolling: "touch"
1635
1661
  // iOS momentum scrolling
1636
1662
  },
1637
- ...StyleSheet.flatten(style)
1663
+ ...StyleSheet.flatten(style),
1664
+ ...maintainVisibleContentPosition ? {
1665
+ // Chrome's native scroll anchoring can apply after LegendList's MVCP adjustment,
1666
+ // causing the same header/item-size delta to be compensated twice.
1667
+ overflowAnchor: "none"
1668
+ } : {}
1638
1669
  };
1639
1670
  const contentInsetEndAdjustment = getContentInsetEndAdjustmentEnd2(ctx);
1640
1671
  const anchoredEndInset = ((_c = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.props) == null ? void 0 : _b.anchoredEndSpace) == null ? void 0 : _c.includeInEndInset) && anchoredEndSpaceSize ? anchoredEndSpaceSize : 0;
@@ -1645,7 +1676,10 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1645
1676
  flexDirection: horizontal ? "row" : void 0,
1646
1677
  minHeight: horizontal ? void 0 : "100%",
1647
1678
  minWidth: horizontal ? "100%" : void 0,
1648
- ...StyleSheet.flatten(contentContainerStyle)
1679
+ ...StyleSheet.flatten(contentContainerStyle),
1680
+ ...maintainVisibleContentPosition ? {
1681
+ overflowAnchor: "none"
1682
+ } : {}
1649
1683
  };
1650
1684
  const className = contentContainerClassName ? `${LEGEND_LIST_CONTENT_CONTAINER_CLASS} ${contentContainerClassName}` : LEGEND_LIST_CONTENT_CONTAINER_CLASS;
1651
1685
  const {
@@ -1748,6 +1782,7 @@ function scrollAdjustBy(el, left, top) {
1748
1782
  function ScrollAdjust() {
1749
1783
  const ctx = useStateContext();
1750
1784
  const lastScrollOffsetRef = React3__namespace.useRef(0);
1785
+ const lastScrollAdjustUserOffsetRef = React3__namespace.useRef(0);
1751
1786
  const resetPaddingRafRef = React3__namespace.useRef(void 0);
1752
1787
  const resetPaddingBaselineRef = React3__namespace.useRef(void 0);
1753
1788
  const contentNodeRef = React3__namespace.useRef(null);
@@ -1756,20 +1791,24 @@ function ScrollAdjust() {
1756
1791
  const scrollAdjust = peek$(ctx, "scrollAdjust");
1757
1792
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
1758
1793
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
1759
- const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1760
- if (scrollDelta !== 0) {
1794
+ const signalScrollDelta = scrollOffset - lastScrollOffsetRef.current;
1795
+ if (signalScrollDelta !== 0) {
1761
1796
  const target = getScrollAdjustTarget(ctx, contentNodeRef.current);
1762
1797
  if (target) {
1763
1798
  const horizontal = !!ctx.state.props.horizontal;
1764
1799
  const axis = getScrollAdjustAxis(horizontal);
1765
1800
  const { contentNode, scrollElement: el } = target;
1801
+ const currentScroll = horizontal ? el.scrollLeft : el.scrollTop;
1802
+ const userOffsetDelta = (scrollAdjustUserOffset || 0) - lastScrollAdjustUserOffsetRef.current;
1803
+ const intendedScroll = userOffsetDelta !== 0 ? currentScroll + userOffsetDelta : ctx.state.scroll;
1804
+ const scrollDelta = intendedScroll - currentScroll;
1805
+ const shouldScroll = Math.abs(scrollDelta) > 0.01;
1766
1806
  const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
1767
1807
  contentNodeRef.current = contentNode;
1768
- if (contentNode) {
1769
- const prevScroll = horizontal ? el.scrollLeft : el.scrollTop;
1808
+ if (shouldScroll && contentNode) {
1770
1809
  const totalSize = contentNode[axis.contentSizeKey];
1771
1810
  const viewportSize = el[axis.viewportSizeKey];
1772
- const nextScroll = prevScroll + scrollDelta;
1811
+ const nextScroll = currentScroll + scrollDelta;
1773
1812
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
1774
1813
  if (needsTemporaryPadding) {
1775
1814
  const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
@@ -1789,11 +1828,12 @@ function ScrollAdjust() {
1789
1828
  } else {
1790
1829
  scrollBy();
1791
1830
  }
1792
- } else {
1831
+ } else if (shouldScroll) {
1793
1832
  scrollBy();
1794
1833
  }
1795
1834
  }
1796
1835
  lastScrollOffsetRef.current = scrollOffset;
1836
+ lastScrollAdjustUserOffsetRef.current = scrollAdjustUserOffset || 0;
1797
1837
  }
1798
1838
  }, [ctx]);
1799
1839
  useValueListener$("scrollAdjust", callback);
@@ -1814,193 +1854,53 @@ function WebAnchoredEndSpace({ horizontal }) {
1814
1854
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1815
1855
  return /* @__PURE__ */ React3__namespace.createElement("div", { style }, null);
1816
1856
  }
1817
- function useStableRenderComponent(renderComponent, mapProps) {
1818
- const renderComponentRef = useLatestRef(renderComponent);
1819
- const mapPropsRef = useLatestRef(mapProps);
1820
- return React3__namespace.useMemo(
1821
- () => React3__namespace.forwardRef(
1822
- (props, ref) => {
1823
- var _a3, _b;
1824
- return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1825
- }
1826
- ),
1827
- [mapPropsRef, renderComponentRef]
1828
- );
1857
+
1858
+ // src/core/updateContentMetricsState.ts
1859
+ function getRawContentLength(ctx) {
1860
+ var _a3, _b, _c;
1861
+ const { state, values } = ctx;
1862
+ return (values.get("headerSize") || 0) + (values.get("footerSize") || 0) + ((_c = (_b = (_a3 = state.pendingTotalSize) != null ? _a3 : state.totalSize) != null ? _b : values.get("totalSize")) != null ? _c : 0) + (state.props.stylePaddingTop || 0) + (state.props.stylePaddingBottom || 0);
1863
+ }
1864
+ function getAlignItemsAtEndPadding(ctx) {
1865
+ const { state } = ctx;
1866
+ const shouldPad = !!state.props.alignItemsAtEndPaddingEnabled && !state.props.horizontal && state.props.data.length > 0 && state.scrollLength > 0;
1867
+ return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1868
+ }
1869
+ function updateContentMetricsState(ctx) {
1870
+ const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1871
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1872
+ if (previousPadding !== nextPadding) {
1873
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1874
+ }
1829
1875
  }
1830
- var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1831
- const localRef = React3.useRef(null);
1832
- const ref = refView != null ? refView : localRef;
1833
- useOnLayoutSync({ onLayoutChange, ref });
1834
- return /* @__PURE__ */ React3__namespace.createElement("div", { ...rest, ref }, children);
1835
- };
1836
1876
 
1837
- // src/components/ListComponent.tsx
1838
- var ListComponent = typedMemo(function ListComponent2({
1839
- canRender,
1840
- style,
1841
- contentContainerStyle,
1842
- horizontal,
1843
- initialContentOffset,
1844
- recycleItems,
1845
- ItemSeparatorComponent,
1846
- alignItemsAtEnd: _alignItemsAtEnd,
1847
- onScroll: onScroll2,
1848
- onLayout,
1849
- ListHeaderComponent,
1850
- ListHeaderComponentStyle,
1851
- ListFooterComponent,
1852
- ListFooterComponentStyle,
1853
- ListEmptyComponent,
1854
- getRenderedItem: getRenderedItem2,
1855
- updateItemSize: updateItemSize2,
1856
- refScrollView,
1857
- renderScrollComponent,
1858
- onLayoutFooter,
1859
- scrollAdjustHandler,
1860
- snapToIndices,
1861
- stickyHeaderConfig,
1862
- stickyHeaderIndices,
1863
- useWindowScroll = false,
1864
- ...rest
1865
- }) {
1866
- const ctx = useStateContext();
1867
- const maintainVisibleContentPosition = ctx.state.props.maintainVisibleContentPosition;
1868
- const [otherAxisSize = 0] = useArr$(["otherAxisSize"]);
1869
- const autoOtherAxisStyle = getAutoOtherAxisStyle({
1870
- horizontal,
1871
- needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1872
- otherAxisSize
1873
- });
1874
- const CustomScrollComponent = useStableRenderComponent(
1875
- renderScrollComponent,
1876
- (props, ref) => ({ ...props, ref })
1877
- );
1878
- const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1879
- const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1880
- React3.useLayoutEffect(() => {
1881
- if (!ListHeaderComponent) {
1882
- set$(ctx, "headerSize", 0);
1883
- }
1884
- if (!ListFooterComponent) {
1885
- set$(ctx, "footerSize", 0);
1877
+ // src/core/addTotalSize.ts
1878
+ function addTotalSize(ctx, key, add, notifyTotalSize = true) {
1879
+ const state = ctx.state;
1880
+ const prevTotalSize = state.totalSize;
1881
+ let totalSize = state.totalSize;
1882
+ if (key === null) {
1883
+ totalSize = add;
1884
+ if (state.timeoutSetPaddingTop) {
1885
+ clearTimeout(state.timeoutSetPaddingTop);
1886
+ state.timeoutSetPaddingTop = void 0;
1886
1887
  }
1887
- }, [ListHeaderComponent, ListFooterComponent, ctx]);
1888
- const onLayoutHeader = React3.useCallback(
1889
- (rect) => {
1890
- const size = rect[horizontal ? "width" : "height"];
1891
- set$(ctx, "headerSize", size);
1892
- },
1893
- [ctx, horizontal]
1894
- );
1895
- const onLayoutFooterInternal = React3.useCallback(
1896
- (rect, fromLayoutEffect) => {
1897
- const size = rect[horizontal ? "width" : "height"];
1898
- set$(ctx, "footerSize", size);
1899
- onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
1900
- },
1901
- [ctx, horizontal, onLayoutFooter]
1902
- );
1903
- return /* @__PURE__ */ React3__namespace.createElement(
1904
- SnapOrScroll,
1888
+ } else {
1889
+ totalSize += add;
1890
+ }
1891
+ if (prevTotalSize !== totalSize) {
1905
1892
  {
1906
- ...rest,
1907
- ...ScrollComponent === ListComponentScrollView ? { useWindowScroll } : {},
1908
- contentContainerStyle: [
1909
- horizontal ? {
1910
- height: "100%"
1911
- } : {},
1912
- contentContainerStyle
1913
- ],
1914
- contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
1915
- horizontal,
1916
- maintainVisibleContentPosition: maintainVisibleContentPosition.size || maintainVisibleContentPosition.data ? { minIndexForVisible: 0 } : void 0,
1917
- onLayout,
1918
- onScroll: onScroll2,
1919
- ref: refScrollView,
1920
- ScrollComponent: snapToIndices ? ScrollComponent : void 0,
1921
- style: autoOtherAxisStyle ? [autoOtherAxisStyle, style] : style
1922
- },
1923
- /* @__PURE__ */ React3__namespace.createElement(ScrollAdjust, null),
1924
- ListHeaderComponent && /* @__PURE__ */ React3__namespace.createElement(LayoutView, { onLayoutChange: onLayoutHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
1925
- ListEmptyComponent && getComponent(ListEmptyComponent),
1926
- canRender && !ListEmptyComponent && /* @__PURE__ */ React3__namespace.createElement(
1927
- Containers,
1928
- {
1929
- getRenderedItem: getRenderedItem2,
1930
- horizontal,
1931
- ItemSeparatorComponent,
1932
- recycleItems,
1933
- stickyHeaderConfig,
1934
- updateItemSize: updateItemSize2
1893
+ state.pendingTotalSize = void 0;
1894
+ state.totalSize = totalSize;
1895
+ if (notifyTotalSize) {
1896
+ set$(ctx, "totalSize", totalSize);
1935
1897
  }
1936
- ),
1937
- ListFooterComponent && /* @__PURE__ */ React3__namespace.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
1938
- /* @__PURE__ */ React3__namespace.createElement(WebAnchoredEndSpace, { horizontal }),
1939
- IS_DEV && ENABLE_DEVMODE
1940
- );
1941
- });
1942
- var WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH = 100;
1943
- var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
1944
- var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
1945
- function useDevChecksImpl(props) {
1946
- const ctx = useStateContext();
1947
- const { childrenMode, keyExtractor, renderScrollComponent, useWindowScroll } = props;
1948
- React3.useEffect(() => {
1949
- if (useWindowScroll && renderScrollComponent) {
1950
- warnDevOnce(
1951
- "useWindowScrollRenderScrollComponent",
1952
- "useWindowScroll is not supported when renderScrollComponent is provided."
1953
- );
1954
- }
1955
- }, [renderScrollComponent, useWindowScroll]);
1956
- React3.useEffect(() => {
1957
- if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
1958
- warnDevOnce(
1959
- "keyExtractor",
1960
- "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
1961
- );
1962
- }
1963
- }, [childrenMode, ctx, keyExtractor]);
1964
- React3.useEffect(() => {
1965
- const state = ctx.state;
1966
- const dataLength = state.props.data.length;
1967
- const useWindowScrollResolved = state.props.useWindowScroll;
1968
- if (useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
1969
- return;
1898
+ updateContentMetricsState(ctx);
1970
1899
  }
1971
- const warnIfUnboundedOuterSize = () => {
1972
- const readyToRender = peek$(ctx, "readyToRender");
1973
- const numContainers = peek$(ctx, "numContainers") || 0;
1974
- const totalSize = peek$(ctx, "totalSize") || 0;
1975
- const scrollLength = ctx.state.scrollLength || 0;
1976
- if (!readyToRender || totalSize <= 0 || scrollLength <= 0) {
1977
- return;
1978
- }
1979
- const rendersAlmostEverything = numContainers >= Math.ceil(dataLength * WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO);
1980
- const viewportMatchesContent = scrollLength >= totalSize * WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO;
1981
- if (rendersAlmostEverything && viewportMatchesContent) {
1982
- warnDevOnce(
1983
- "webUnboundedOuterSize",
1984
- "LegendList appears to have an unbounded outer height on web, so virtualization is effectively disabled. Set a bounded height or flex: 1 on the list container, or use useWindowScroll."
1985
- );
1986
- }
1987
- };
1988
- warnIfUnboundedOuterSize();
1989
- const unsubscribe = [
1990
- listen$(ctx, "numContainers", warnIfUnboundedOuterSize),
1991
- listen$(ctx, "readyToRender", warnIfUnboundedOuterSize),
1992
- listen$(ctx, "totalSize", warnIfUnboundedOuterSize)
1993
- ];
1994
- return () => {
1995
- for (const unsub of unsubscribe) {
1996
- unsub();
1997
- }
1998
- };
1999
- }, [ctx]);
2000
- }
2001
- function useDevChecksNoop(_props) {
1900
+ } else if (notifyTotalSize && ctx.values.get("totalSize") !== totalSize) {
1901
+ set$(ctx, "totalSize", totalSize);
1902
+ }
2002
1903
  }
2003
- var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
2004
1904
 
2005
1905
  // src/core/deferredPublicOnScroll.ts
2006
1906
  function withResolvedContentOffset(state, event, resolvedOffset) {
@@ -2313,6 +2213,66 @@ function recalculateSettledScroll(ctx) {
2313
2213
  checkThresholds(ctx);
2314
2214
  }
2315
2215
 
2216
+ // src/core/adaptiveRender.ts
2217
+ var DEFAULT_ENTER_VELOCITY = 4;
2218
+ var DEFAULT_EXIT_VELOCITY = 1;
2219
+ var DEFAULT_EXIT_DELAY = 1e3;
2220
+ function scheduleAdaptiveRenderExit(ctx, exitDelay) {
2221
+ const state = ctx.state;
2222
+ const previousTimeout = state.timeoutAdaptiveRender;
2223
+ if (previousTimeout !== void 0) {
2224
+ clearTimeout(previousTimeout);
2225
+ state.timeouts.delete(previousTimeout);
2226
+ state.timeoutAdaptiveRender = void 0;
2227
+ }
2228
+ if (exitDelay <= 0) {
2229
+ setAdaptiveRender(ctx, "normal");
2230
+ } else {
2231
+ const timeout = setTimeout(() => {
2232
+ state.timeouts.delete(timeout);
2233
+ state.timeoutAdaptiveRender = void 0;
2234
+ setAdaptiveRender(ctx, "normal");
2235
+ }, exitDelay);
2236
+ state.timeoutAdaptiveRender = timeout;
2237
+ state.timeouts.add(timeout);
2238
+ }
2239
+ }
2240
+ function setAdaptiveRender(ctx, mode) {
2241
+ var _a3, _b;
2242
+ const previousMode = peek$(ctx, "adaptiveRender");
2243
+ if (previousMode !== mode) {
2244
+ set$(ctx, "adaptiveRender", mode);
2245
+ (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.onChange) == null ? void 0 : _b.call(_a3, mode);
2246
+ }
2247
+ }
2248
+ function updateAdaptiveRender(ctx, scrollVelocity) {
2249
+ var _a3, _b, _c;
2250
+ const state = ctx.state;
2251
+ const adaptiveRender = state.props.adaptiveRender;
2252
+ const enterVelocity = (_a3 = adaptiveRender == null ? void 0 : adaptiveRender.enterVelocity) != null ? _a3 : DEFAULT_ENTER_VELOCITY;
2253
+ const exitVelocity = (_b = adaptiveRender == null ? void 0 : adaptiveRender.exitVelocity) != null ? _b : DEFAULT_EXIT_VELOCITY;
2254
+ const exitDelay = (_c = adaptiveRender == null ? void 0 : adaptiveRender.exitDelay) != null ? _c : DEFAULT_EXIT_DELAY;
2255
+ const currentMode = peek$(ctx, "adaptiveRender");
2256
+ const threshold = currentMode === "light" ? exitVelocity : enterVelocity;
2257
+ const nextMode = Math.abs(scrollVelocity) > threshold ? "light" : "normal";
2258
+ const previousMode = state.timeoutAdaptiveRender !== void 0 ? "normal" : currentMode;
2259
+ if (nextMode !== previousMode) {
2260
+ if (nextMode === "light") {
2261
+ setAdaptiveRender(ctx, "light");
2262
+ scheduleAdaptiveRenderExit(ctx, exitDelay);
2263
+ } else if (currentMode === "light") {
2264
+ scheduleAdaptiveRenderExit(ctx, exitDelay);
2265
+ }
2266
+ }
2267
+ }
2268
+
2269
+ // src/utils/getEffectiveDrawDistance.ts
2270
+ var INITIAL_DRAW_DISTANCE = 100;
2271
+ function getEffectiveDrawDistance(ctx) {
2272
+ const drawDistance = ctx.state.props.drawDistance;
2273
+ return peek$(ctx, "readyToRender") ? drawDistance : Math.min(drawDistance, INITIAL_DRAW_DISTANCE);
2274
+ }
2275
+
2316
2276
  // src/utils/setInitialRenderState.ts
2317
2277
  function setInitialRenderState(ctx, {
2318
2278
  didLayout,
@@ -2332,6 +2292,13 @@ function setInitialRenderState(ctx, {
2332
2292
  const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll);
2333
2293
  if (isReadyToRender && !peek$(ctx, "readyToRender")) {
2334
2294
  set$(ctx, "readyToRender", true);
2295
+ setAdaptiveRender(ctx, "normal");
2296
+ if (state.props.drawDistance > INITIAL_DRAW_DISTANCE) {
2297
+ requestAnimationFrame(() => {
2298
+ var _a3;
2299
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
2300
+ });
2301
+ }
2335
2302
  if (onLoad) {
2336
2303
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
2337
2304
  }
@@ -2406,54 +2373,423 @@ function finishInitialScroll(ctx, options) {
2406
2373
  complete();
2407
2374
  }
2408
2375
 
2409
- // src/core/calculateOffsetForIndex.ts
2410
- function calculateOffsetForIndex(ctx, index) {
2376
+ // src/core/finishScrollTo.ts
2377
+ function finishScrollTo(ctx) {
2378
+ var _a3, _b;
2411
2379
  const state = ctx.state;
2412
- return index !== void 0 ? state.positions[index] || 0 : 0;
2413
- }
2414
-
2415
- // src/core/getTopOffsetAdjustment.ts
2416
- function getTopOffsetAdjustment(ctx) {
2417
- return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
2418
- }
2419
-
2420
- // src/utils/getId.ts
2421
- function getId(state, index) {
2422
- const { data, keyExtractor } = state.props;
2423
- if (!data) {
2424
- return "";
2425
- }
2426
- const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
2427
- const id = ret;
2428
- state.idCache[index] = id;
2429
- return id;
2380
+ if (state == null ? void 0 : state.scrollingTo) {
2381
+ const resolvePendingScroll = state.pendingScrollResolve;
2382
+ state.pendingScrollResolve = void 0;
2383
+ const scrollingTo = state.scrollingTo;
2384
+ state.scrollHistory.length = 0;
2385
+ state.scrollingTo = void 0;
2386
+ if (state.pendingTotalSize !== void 0) {
2387
+ addTotalSize(ctx, null, state.pendingTotalSize);
2388
+ }
2389
+ {
2390
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
2391
+ }
2392
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
2393
+ const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2394
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
2395
+ finishInitialScroll(ctx, {
2396
+ onFinished: () => {
2397
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2398
+ },
2399
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
2400
+ recalculateItems: true,
2401
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
2402
+ syncObservedOffset: isOffsetSession,
2403
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
2404
+ });
2405
+ return;
2406
+ }
2407
+ recalculateSettledScroll(ctx);
2408
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2409
+ }
2430
2410
  }
2431
2411
 
2432
- // src/core/addTotalSize.ts
2433
- function addTotalSize(ctx, key, add, notifyTotalSize = true) {
2412
+ // src/core/doScrollTo.ts
2413
+ var SCROLL_END_IDLE_MS = 80;
2414
+ var SCROLL_END_MAX_MS = 1500;
2415
+ var SMOOTH_SCROLL_DURATION_MS = 320;
2416
+ var SCROLL_END_TARGET_EPSILON = 1;
2417
+ function doScrollTo(ctx, params) {
2418
+ var _a3, _b;
2434
2419
  const state = ctx.state;
2435
- const prevTotalSize = state.totalSize;
2436
- let totalSize = state.totalSize;
2437
- if (key === null) {
2438
- totalSize = add;
2439
- if (state.timeoutSetPaddingTop) {
2440
- clearTimeout(state.timeoutSetPaddingTop);
2441
- state.timeoutSetPaddingTop = void 0;
2420
+ const { animated, horizontal, offset } = params;
2421
+ const scroller = state.refScroller.current;
2422
+ const node = scroller == null ? void 0 : scroller.getScrollableNode();
2423
+ if (!scroller || !node) {
2424
+ return;
2425
+ }
2426
+ const isAnimated = !!animated;
2427
+ const isHorizontal = !!horizontal;
2428
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2429
+ const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
2430
+ const top = isHorizontal ? 0 : offset;
2431
+ scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2432
+ if (isAnimated) {
2433
+ const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
2434
+ listenForScrollEnd(ctx, {
2435
+ readOffset: () => scroller.getCurrentScrollOffset(),
2436
+ target,
2437
+ targetOffset: offset
2438
+ });
2439
+ } else {
2440
+ state.scroll = offset;
2441
+ setTimeout(() => {
2442
+ finishScrollTo(ctx);
2443
+ }, 100);
2444
+ }
2445
+ }
2446
+ function listenForScrollEnd(ctx, params) {
2447
+ const { readOffset, target, targetOffset } = params;
2448
+ if (!target) {
2449
+ finishScrollTo(ctx);
2450
+ return;
2451
+ }
2452
+ const supportsScrollEnd = "onscrollend" in target;
2453
+ let idleTimeout;
2454
+ let settled = false;
2455
+ const targetToken = ctx.state.scrollingTo;
2456
+ const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2457
+ const cleanup = () => {
2458
+ target.removeEventListener("scroll", onScroll2);
2459
+ if (supportsScrollEnd) {
2460
+ target.removeEventListener("scrollend", onScrollEnd);
2461
+ }
2462
+ if (idleTimeout) {
2463
+ clearTimeout(idleTimeout);
2464
+ }
2465
+ clearTimeout(maxTimeout);
2466
+ };
2467
+ const finish = (reason) => {
2468
+ if (settled) return;
2469
+ if (targetToken !== ctx.state.scrollingTo) {
2470
+ settled = true;
2471
+ cleanup();
2472
+ return;
2473
+ }
2474
+ const currentOffset = readOffset();
2475
+ const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2476
+ if (reason === "scrollend" && !isNearTarget) {
2477
+ return;
2478
+ }
2479
+ settled = true;
2480
+ cleanup();
2481
+ finishScrollTo(ctx);
2482
+ };
2483
+ const onScroll2 = () => {
2484
+ if (idleTimeout) {
2485
+ clearTimeout(idleTimeout);
2442
2486
  }
2487
+ idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2488
+ };
2489
+ const onScrollEnd = () => finish("scrollend");
2490
+ target.addEventListener("scroll", onScroll2);
2491
+ if (supportsScrollEnd) {
2492
+ target.addEventListener("scrollend", onScrollEnd);
2443
2493
  } else {
2444
- totalSize += add;
2494
+ idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2445
2495
  }
2446
- if (prevTotalSize !== totalSize) {
2496
+ }
2497
+
2498
+ // src/utils/requestAdjust.ts
2499
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2500
+ const state = ctx.state;
2501
+ if (Math.abs(positionDiff) > 0.1) {
2502
+ const doit = () => {
2503
+ {
2504
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2505
+ if (state.adjustingFromInitialMount) {
2506
+ state.adjustingFromInitialMount--;
2507
+ }
2508
+ }
2509
+ };
2510
+ state.scroll += positionDiff;
2511
+ state.scrollForNextCalculateItemsInView = void 0;
2512
+ const readyToRender = peek$(ctx, "readyToRender");
2513
+ if (readyToRender) {
2514
+ doit();
2515
+ } else {
2516
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2517
+ requestAnimationFrame(doit);
2518
+ }
2519
+ }
2520
+ }
2521
+
2522
+ // src/core/updateContentMetrics.ts
2523
+ var SCROLL_ADJUST_EPSILON = 0.1;
2524
+ function setContentLengthSignal(ctx, signalName, size) {
2525
+ if (peek$(ctx, signalName) !== size) {
2526
+ set$(ctx, signalName, size);
2527
+ updateContentMetricsState(ctx);
2528
+ }
2529
+ }
2530
+ function shouldAdjustForHeaderSizeChange(ctx, previousHeaderSize, nextHeaderSize) {
2531
+ const { didContainersLayout, didFinishInitialScroll, props, scroll, scrollingTo } = ctx.state;
2532
+ const sizeDiff = nextHeaderSize - previousHeaderSize;
2533
+ const leadingPadding = props.horizontal ? props.stylePaddingLeft : props.stylePaddingTop;
2534
+ const previousHeaderEnd = (leadingPadding || 0) + previousHeaderSize;
2535
+ return props.maintainVisibleContentPosition.size && didContainersLayout && didFinishInitialScroll && !scrollingTo && scroll >= previousHeaderEnd - SCROLL_ADJUST_EPSILON && Math.abs(sizeDiff) > SCROLL_ADJUST_EPSILON;
2536
+ }
2537
+ function setHeaderSize(ctx, size) {
2538
+ const { state } = ctx;
2539
+ const previousHeaderSize = peek$(ctx, "headerSize") || 0;
2540
+ const didChange = previousHeaderSize !== size;
2541
+ const hasMeasuredOrEstimatedHeaderBaseline = state.didMeasureHeader || previousHeaderSize > SCROLL_ADJUST_EPSILON;
2542
+ if (didChange) {
2543
+ set$(ctx, "headerSize", size);
2544
+ updateContentMetricsState(ctx);
2545
+ if (hasMeasuredOrEstimatedHeaderBaseline && shouldAdjustForHeaderSizeChange(ctx, previousHeaderSize, size)) {
2546
+ requestAdjust(ctx, size - previousHeaderSize);
2547
+ }
2548
+ }
2549
+ state.didMeasureHeader = true;
2550
+ }
2551
+ function setFooterSize(ctx, size) {
2552
+ setContentLengthSignal(ctx, "footerSize", size);
2553
+ }
2554
+ function areInsetsEqual(left, right) {
2555
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
2556
+ return ((_a3 = left == null ? void 0 : left.top) != null ? _a3 : 0) === ((_b = right == null ? void 0 : right.top) != null ? _b : 0) && ((_c = left == null ? void 0 : left.bottom) != null ? _c : 0) === ((_d = right == null ? void 0 : right.bottom) != null ? _d : 0) && ((_e = left == null ? void 0 : left.left) != null ? _e : 0) === ((_f = right == null ? void 0 : right.left) != null ? _f : 0) && ((_g = left == null ? void 0 : left.right) != null ? _g : 0) === ((_h = right == null ? void 0 : right.right) != null ? _h : 0);
2557
+ }
2558
+ function setContentInsetOverride(ctx, inset) {
2559
+ const { state } = ctx;
2560
+ const previousInset = state.contentInsetOverride;
2561
+ const nextInset = inset != null ? inset : void 0;
2562
+ const didChange = !areInsetsEqual(previousInset, nextInset);
2563
+ state.contentInsetOverride = nextInset;
2564
+ if (didChange) {
2565
+ updateContentMetricsState(ctx);
2566
+ }
2567
+ return didChange;
2568
+ }
2569
+ function useStableRenderComponent(renderComponent, mapProps) {
2570
+ const renderComponentRef = useLatestRef(renderComponent);
2571
+ const mapPropsRef = useLatestRef(mapProps);
2572
+ return React3__namespace.useMemo(
2573
+ () => React3__namespace.forwardRef(
2574
+ (props, ref) => {
2575
+ var _a3, _b;
2576
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
2577
+ }
2578
+ ),
2579
+ [mapPropsRef, renderComponentRef]
2580
+ );
2581
+ }
2582
+ var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
2583
+ const localRef = React3.useRef(null);
2584
+ const ref = refView != null ? refView : localRef;
2585
+ useOnLayoutSync({ onLayoutChange, ref });
2586
+ return /* @__PURE__ */ React3__namespace.createElement("div", { ...rest, ref }, children);
2587
+ };
2588
+
2589
+ // src/components/ListComponent.tsx
2590
+ var AlignItemsAtEndSpacer = typedMemo(function AlignItemsAtEndSpacer2({ horizontal }) {
2591
+ const [alignItemsAtEndPadding = 0] = useArr$(["alignItemsAtEndPadding"]);
2592
+ if (alignItemsAtEndPadding <= 0) {
2593
+ return null;
2594
+ }
2595
+ return /* @__PURE__ */ React3__namespace.createElement(
2596
+ View,
2447
2597
  {
2448
- state.pendingTotalSize = void 0;
2449
- state.totalSize = totalSize;
2450
- if (notifyTotalSize) {
2451
- set$(ctx, "totalSize", totalSize);
2598
+ style: horizontal ? { flexShrink: 0, width: alignItemsAtEndPadding } : { flexShrink: 0, height: alignItemsAtEndPadding }
2599
+ },
2600
+ null
2601
+ );
2602
+ });
2603
+ var ListComponent = typedMemo(function ListComponent2({
2604
+ canRender,
2605
+ style,
2606
+ contentContainerStyle,
2607
+ horizontal,
2608
+ initialContentOffset,
2609
+ recycleItems,
2610
+ ItemSeparatorComponent,
2611
+ alignItemsAtEnd: _alignItemsAtEnd,
2612
+ onScroll: onScroll2,
2613
+ onLayout,
2614
+ ListHeaderComponent,
2615
+ ListHeaderComponentStyle,
2616
+ ListFooterComponent,
2617
+ ListFooterComponentStyle,
2618
+ ListEmptyComponent,
2619
+ getRenderedItem: getRenderedItem2,
2620
+ updateItemSize: updateItemSize2,
2621
+ refScrollView,
2622
+ renderScrollComponent,
2623
+ onLayoutFooter,
2624
+ scrollAdjustHandler,
2625
+ snapToIndices,
2626
+ stickyHeaderConfig,
2627
+ stickyHeaderIndices,
2628
+ useWindowScroll = false,
2629
+ ...rest
2630
+ }) {
2631
+ const ctx = useStateContext();
2632
+ const maintainVisibleContentPosition = ctx.state.props.maintainVisibleContentPosition;
2633
+ const [otherAxisSize = 0] = useArr$(["otherAxisSize"]);
2634
+ const shouldRenderAlignItemsAtEndSpacer = ctx.state.props.alignItemsAtEndPaddingEnabled;
2635
+ const autoOtherAxisStyle = getAutoOtherAxisStyle({
2636
+ horizontal,
2637
+ needsOtherAxisSize: ctx.state.needsOtherAxisSize,
2638
+ otherAxisSize
2639
+ });
2640
+ const CustomScrollComponent = useStableRenderComponent(
2641
+ renderScrollComponent,
2642
+ (props, ref) => ({ ...props, ref })
2643
+ );
2644
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
2645
+ const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
2646
+ React3.useLayoutEffect(() => {
2647
+ if (!ListHeaderComponent) {
2648
+ setHeaderSize(ctx, 0);
2649
+ }
2650
+ if (!ListFooterComponent) {
2651
+ setFooterSize(ctx, 0);
2652
+ }
2653
+ }, [ListHeaderComponent, ListFooterComponent, ctx]);
2654
+ const onLayoutHeader = React3.useCallback(
2655
+ (rect) => {
2656
+ const size = rect[horizontal ? "width" : "height"];
2657
+ setHeaderSize(ctx, size);
2658
+ },
2659
+ [ctx, horizontal]
2660
+ );
2661
+ const onLayoutFooterInternal = React3.useCallback(
2662
+ (rect, fromLayoutEffect) => {
2663
+ const size = rect[horizontal ? "width" : "height"];
2664
+ setFooterSize(ctx, size);
2665
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
2666
+ },
2667
+ [ctx, horizontal, onLayoutFooter]
2668
+ );
2669
+ return /* @__PURE__ */ React3__namespace.createElement(
2670
+ SnapOrScroll,
2671
+ {
2672
+ ...rest,
2673
+ ...ScrollComponent === ListComponentScrollView ? { useWindowScroll } : {},
2674
+ contentContainerStyle: [
2675
+ horizontal ? {
2676
+ height: "100%"
2677
+ } : {},
2678
+ contentContainerStyle
2679
+ ],
2680
+ contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
2681
+ horizontal,
2682
+ maintainVisibleContentPosition: maintainVisibleContentPosition.size || maintainVisibleContentPosition.data ? { minIndexForVisible: 0 } : void 0,
2683
+ onLayout,
2684
+ onScroll: onScroll2,
2685
+ ref: refScrollView,
2686
+ ScrollComponent: snapToIndices ? ScrollComponent : void 0,
2687
+ style: autoOtherAxisStyle ? [autoOtherAxisStyle, style] : style
2688
+ },
2689
+ /* @__PURE__ */ React3__namespace.createElement(ScrollAdjust, null),
2690
+ ListHeaderComponent && /* @__PURE__ */ React3__namespace.createElement(LayoutView, { onLayoutChange: onLayoutHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
2691
+ ListEmptyComponent && getComponent(ListEmptyComponent),
2692
+ shouldRenderAlignItemsAtEndSpacer && /* @__PURE__ */ React3__namespace.createElement(AlignItemsAtEndSpacer, { horizontal }),
2693
+ canRender && !ListEmptyComponent && /* @__PURE__ */ React3__namespace.createElement(
2694
+ Containers,
2695
+ {
2696
+ getRenderedItem: getRenderedItem2,
2697
+ horizontal,
2698
+ ItemSeparatorComponent,
2699
+ recycleItems,
2700
+ stickyHeaderConfig,
2701
+ updateItemSize: updateItemSize2
2452
2702
  }
2703
+ ),
2704
+ ListFooterComponent && /* @__PURE__ */ React3__namespace.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
2705
+ /* @__PURE__ */ React3__namespace.createElement(WebAnchoredEndSpace, { horizontal }),
2706
+ IS_DEV && ENABLE_DEVMODE
2707
+ );
2708
+ });
2709
+ var WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH = 100;
2710
+ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
2711
+ var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
2712
+ function useDevChecksImpl(props) {
2713
+ const ctx = useStateContext();
2714
+ const { childrenMode, keyExtractor, renderScrollComponent, useWindowScroll } = props;
2715
+ React3.useEffect(() => {
2716
+ if (useWindowScroll && renderScrollComponent) {
2717
+ warnDevOnce(
2718
+ "useWindowScrollRenderScrollComponent",
2719
+ "useWindowScroll is not supported when renderScrollComponent is provided."
2720
+ );
2453
2721
  }
2454
- } else if (notifyTotalSize && ctx.values.get("totalSize") !== totalSize) {
2455
- set$(ctx, "totalSize", totalSize);
2722
+ }, [renderScrollComponent, useWindowScroll]);
2723
+ React3.useEffect(() => {
2724
+ if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
2725
+ warnDevOnce(
2726
+ "keyExtractor",
2727
+ "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
2728
+ );
2729
+ }
2730
+ }, [childrenMode, ctx, keyExtractor]);
2731
+ React3.useEffect(() => {
2732
+ const state = ctx.state;
2733
+ const dataLength = state.props.data.length;
2734
+ const useWindowScrollResolved = state.props.useWindowScroll;
2735
+ if (useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
2736
+ return;
2737
+ }
2738
+ const warnIfUnboundedOuterSize = () => {
2739
+ const readyToRender = peek$(ctx, "readyToRender");
2740
+ const numContainers = peek$(ctx, "numContainers") || 0;
2741
+ const totalSize = peek$(ctx, "totalSize") || 0;
2742
+ const scrollLength = ctx.state.scrollLength || 0;
2743
+ if (!readyToRender || totalSize <= 0 || scrollLength <= 0) {
2744
+ return;
2745
+ }
2746
+ const rendersAlmostEverything = numContainers >= Math.ceil(dataLength * WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO);
2747
+ const viewportMatchesContent = scrollLength >= totalSize * WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO;
2748
+ if (rendersAlmostEverything && viewportMatchesContent) {
2749
+ warnDevOnce(
2750
+ "webUnboundedOuterSize",
2751
+ "LegendList appears to have an unbounded outer height on web, so virtualization is effectively disabled. Set a bounded height or flex: 1 on the list container, or use useWindowScroll."
2752
+ );
2753
+ }
2754
+ };
2755
+ warnIfUnboundedOuterSize();
2756
+ const unsubscribe = [
2757
+ listen$(ctx, "numContainers", warnIfUnboundedOuterSize),
2758
+ listen$(ctx, "readyToRender", warnIfUnboundedOuterSize),
2759
+ listen$(ctx, "totalSize", warnIfUnboundedOuterSize)
2760
+ ];
2761
+ return () => {
2762
+ for (const unsub of unsubscribe) {
2763
+ unsub();
2764
+ }
2765
+ };
2766
+ }, [ctx]);
2767
+ }
2768
+ function useDevChecksNoop(_props) {
2769
+ }
2770
+ var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
2771
+
2772
+ // src/core/calculateOffsetForIndex.ts
2773
+ function calculateOffsetForIndex(ctx, index) {
2774
+ const state = ctx.state;
2775
+ return index !== void 0 ? state.positions[index] || 0 : 0;
2776
+ }
2777
+
2778
+ // src/core/getTopOffsetAdjustment.ts
2779
+ function getTopOffsetAdjustment(ctx) {
2780
+ return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "alignItemsAtEndPadding") || 0) + (peek$(ctx, "headerSize") || 0);
2781
+ }
2782
+
2783
+ // src/utils/getId.ts
2784
+ function getId(state, index) {
2785
+ const { data, keyExtractor } = state.props;
2786
+ if (!data) {
2787
+ return "";
2456
2788
  }
2789
+ const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
2790
+ const id = ret;
2791
+ state.idCache[index] = id;
2792
+ return id;
2457
2793
  }
2458
2794
 
2459
2795
  // src/core/setSize.ts
@@ -2476,8 +2812,9 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
2476
2812
  let size = key ? state.sizesKnown.get(key) : void 0;
2477
2813
  if (size === void 0 && key && getFixedItemSize) {
2478
2814
  const itemType = (_b = resolved == null ? void 0 : resolved.itemType) != null ? _b : getItemType ? (_a3 = getItemType(data, index)) != null ? _a3 : "" : "";
2479
- size = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
2480
- if (size !== void 0) {
2815
+ const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
2816
+ if (fixedSize !== void 0) {
2817
+ size = fixedSize + ctx.scrollAxisGap;
2481
2818
  state.sizesKnown.set(key, size);
2482
2819
  }
2483
2820
  }
@@ -2527,198 +2864,76 @@ function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize, no
2527
2864
  size = roundSize(averageSizeForType);
2528
2865
  }
2529
2866
  }
2530
- if (size === void 0 && renderedSize !== void 0) {
2531
- return renderedSize;
2532
- }
2533
- if (size === void 0 && useAverageSize && scrollingTo) {
2534
- const averageSizeForType = (_d = scrollingTo.averageSizeSnapshot) == null ? void 0 : _d[itemType];
2535
- if (averageSizeForType !== void 0) {
2536
- size = roundSize(averageSizeForType);
2537
- }
2538
- }
2539
- if (size === void 0) {
2540
- size = estimatedItemSize;
2541
- }
2542
- setSize(ctx, key, size, notifyTotalSize);
2543
- return size;
2544
- }
2545
- function getItemSizeAtIndex(ctx, index) {
2546
- if (index === void 0 || index < 0) {
2547
- return void 0;
2548
- }
2549
- const targetId = getId(ctx.state, index);
2550
- return getItemSize(ctx, targetId, index, ctx.state.props.data[index]);
2551
- }
2552
-
2553
- // src/core/calculateOffsetWithOffsetPosition.ts
2554
- function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
2555
- var _a3;
2556
- const state = ctx.state;
2557
- const { index, viewOffset, viewPosition } = params;
2558
- let offset = offsetParam;
2559
- if (viewOffset) {
2560
- offset -= viewOffset;
2561
- }
2562
- if (index !== void 0) {
2563
- const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
2564
- if (topOffsetAdjustment) {
2565
- offset += topOffsetAdjustment;
2566
- }
2567
- }
2568
- if (viewPosition !== void 0 && index !== void 0) {
2569
- const dataLength = state.props.data.length;
2570
- if (dataLength === 0) {
2571
- return offset;
2572
- }
2573
- const isOutOfBounds = index < 0 || index >= dataLength;
2574
- const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
2575
- const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
2576
- const trailingInset = getContentInsetEnd(ctx);
2577
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
2578
- if (!isOutOfBounds && index === state.props.data.length - 1) {
2579
- const footerSize = peek$(ctx, "footerSize") || 0;
2580
- offset += footerSize;
2581
- }
2582
- }
2583
- return offset;
2584
- }
2585
-
2586
- // src/core/clampScrollOffset.ts
2587
- function clampScrollOffset(ctx, offset, scrollTarget) {
2588
- const state = ctx.state;
2589
- const contentSize = getContentSize(ctx);
2590
- let clampedOffset = offset;
2591
- if (Number.isFinite(contentSize) && Number.isFinite(state.scrollLength) && (Platform.OS !== "android")) {
2592
- const baseMaxOffset = Math.max(0, contentSize - state.scrollLength);
2593
- const viewOffset = scrollTarget == null ? void 0 : scrollTarget.viewOffset;
2594
- const extraEndOffset = typeof viewOffset === "number" && viewOffset < 0 ? -viewOffset : 0;
2595
- const maxOffset = baseMaxOffset + extraEndOffset;
2596
- clampedOffset = Math.min(offset, maxOffset);
2597
- }
2598
- clampedOffset = Math.max(0, clampedOffset);
2599
- return clampedOffset;
2600
- }
2601
-
2602
- // src/core/finishScrollTo.ts
2603
- function finishScrollTo(ctx) {
2604
- var _a3, _b;
2605
- const state = ctx.state;
2606
- if (state == null ? void 0 : state.scrollingTo) {
2607
- const resolvePendingScroll = state.pendingScrollResolve;
2608
- state.pendingScrollResolve = void 0;
2609
- const scrollingTo = state.scrollingTo;
2610
- state.scrollHistory.length = 0;
2611
- state.scrollingTo = void 0;
2612
- if (state.pendingTotalSize !== void 0) {
2613
- addTotalSize(ctx, null, state.pendingTotalSize);
2614
- }
2615
- {
2616
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
2617
- }
2618
- if (scrollingTo.isInitialScroll || state.initialScroll) {
2619
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2620
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
2621
- finishInitialScroll(ctx, {
2622
- onFinished: () => {
2623
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2624
- },
2625
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
2626
- recalculateItems: true,
2627
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
2628
- syncObservedOffset: isOffsetSession,
2629
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
2630
- });
2631
- return;
2632
- }
2633
- recalculateSettledScroll(ctx);
2634
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2635
- }
2636
- }
2637
-
2638
- // src/core/doScrollTo.ts
2639
- var SCROLL_END_IDLE_MS = 80;
2640
- var SCROLL_END_MAX_MS = 1500;
2641
- var SMOOTH_SCROLL_DURATION_MS = 320;
2642
- var SCROLL_END_TARGET_EPSILON = 1;
2643
- function doScrollTo(ctx, params) {
2644
- var _a3, _b;
2645
- const state = ctx.state;
2646
- const { animated, horizontal, offset } = params;
2647
- const scroller = state.refScroller.current;
2648
- const node = scroller == null ? void 0 : scroller.getScrollableNode();
2649
- if (!scroller || !node) {
2650
- return;
2651
- }
2652
- const isAnimated = !!animated;
2653
- const isHorizontal = !!horizontal;
2654
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2655
- const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
2656
- const top = isHorizontal ? 0 : offset;
2657
- scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2658
- if (isAnimated) {
2659
- const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
2660
- listenForScrollEnd(ctx, {
2661
- readOffset: () => scroller.getCurrentScrollOffset(),
2662
- target,
2663
- targetOffset: offset
2664
- });
2665
- } else {
2666
- state.scroll = offset;
2667
- setTimeout(() => {
2668
- finishScrollTo(ctx);
2669
- }, 100);
2867
+ if (size === void 0 && renderedSize !== void 0) {
2868
+ return renderedSize;
2869
+ }
2870
+ if (size === void 0 && useAverageSize && scrollingTo) {
2871
+ const averageSizeForType = (_d = scrollingTo.averageSizeSnapshot) == null ? void 0 : _d[itemType];
2872
+ if (averageSizeForType !== void 0) {
2873
+ size = roundSize(averageSizeForType);
2874
+ }
2670
2875
  }
2876
+ if (size === void 0) {
2877
+ size = estimatedItemSize + ctx.scrollAxisGap;
2878
+ }
2879
+ setSize(ctx, key, size, notifyTotalSize);
2880
+ return size;
2671
2881
  }
2672
- function listenForScrollEnd(ctx, params) {
2673
- const { readOffset, target, targetOffset } = params;
2674
- if (!target) {
2675
- finishScrollTo(ctx);
2676
- return;
2882
+ function getItemSizeAtIndex(ctx, index) {
2883
+ if (index === void 0 || index < 0) {
2884
+ return void 0;
2677
2885
  }
2678
- const supportsScrollEnd = "onscrollend" in target;
2679
- let idleTimeout;
2680
- let settled = false;
2681
- const targetToken = ctx.state.scrollingTo;
2682
- const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2683
- const cleanup = () => {
2684
- target.removeEventListener("scroll", onScroll2);
2685
- if (supportsScrollEnd) {
2686
- target.removeEventListener("scrollend", onScrollEnd);
2687
- }
2688
- if (idleTimeout) {
2689
- clearTimeout(idleTimeout);
2690
- }
2691
- clearTimeout(maxTimeout);
2692
- };
2693
- const finish = (reason) => {
2694
- if (settled) return;
2695
- if (targetToken !== ctx.state.scrollingTo) {
2696
- settled = true;
2697
- cleanup();
2698
- return;
2886
+ const targetId = getId(ctx.state, index);
2887
+ return getItemSize(ctx, targetId, index, ctx.state.props.data[index]);
2888
+ }
2889
+
2890
+ // src/core/calculateOffsetWithOffsetPosition.ts
2891
+ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
2892
+ var _a3;
2893
+ const state = ctx.state;
2894
+ const { index, viewOffset, viewPosition } = params;
2895
+ let offset = offsetParam;
2896
+ if (viewOffset) {
2897
+ offset -= viewOffset;
2898
+ }
2899
+ if (index !== void 0) {
2900
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
2901
+ if (topOffsetAdjustment) {
2902
+ offset += topOffsetAdjustment;
2699
2903
  }
2700
- const currentOffset = readOffset();
2701
- const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2702
- if (reason === "scrollend" && !isNearTarget) {
2703
- return;
2904
+ }
2905
+ if (viewPosition !== void 0 && index !== void 0) {
2906
+ const dataLength = state.props.data.length;
2907
+ if (dataLength === 0) {
2908
+ return offset;
2704
2909
  }
2705
- settled = true;
2706
- cleanup();
2707
- finishScrollTo(ctx);
2708
- };
2709
- const onScroll2 = () => {
2710
- if (idleTimeout) {
2711
- clearTimeout(idleTimeout);
2910
+ const isOutOfBounds = index < 0 || index >= dataLength;
2911
+ const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
2912
+ const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
2913
+ const trailingInset = getContentInsetEnd(ctx);
2914
+ offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
2915
+ if (!isOutOfBounds && index === state.props.data.length - 1) {
2916
+ const footerSize = peek$(ctx, "footerSize") || 0;
2917
+ offset += footerSize;
2712
2918
  }
2713
- idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2714
- };
2715
- const onScrollEnd = () => finish("scrollend");
2716
- target.addEventListener("scroll", onScroll2);
2717
- if (supportsScrollEnd) {
2718
- target.addEventListener("scrollend", onScrollEnd);
2719
- } else {
2720
- idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2721
2919
  }
2920
+ return offset;
2921
+ }
2922
+
2923
+ // src/core/clampScrollOffset.ts
2924
+ function clampScrollOffset(ctx, offset, scrollTarget) {
2925
+ const state = ctx.state;
2926
+ const contentSize = getContentSize(ctx);
2927
+ let clampedOffset = offset;
2928
+ if (Number.isFinite(contentSize) && Number.isFinite(state.scrollLength) && (Platform.OS !== "android")) {
2929
+ const baseMaxOffset = Math.max(0, contentSize - state.scrollLength);
2930
+ const viewOffset = scrollTarget == null ? void 0 : scrollTarget.viewOffset;
2931
+ const extraEndOffset = typeof viewOffset === "number" && viewOffset < 0 ? -viewOffset : 0;
2932
+ const maxOffset = baseMaxOffset + extraEndOffset;
2933
+ clampedOffset = Math.min(offset, maxOffset);
2934
+ }
2935
+ clampedOffset = Math.max(0, clampedOffset);
2936
+ return clampedOffset;
2722
2937
  }
2723
2938
 
2724
2939
  // src/core/doMaintainScrollAtEnd.ts
@@ -2743,9 +2958,12 @@ function doMaintainScrollAtEnd(ctx) {
2743
2958
  state.scroll = 0;
2744
2959
  }
2745
2960
  if (!state.maintainingScrollAtEnd) {
2746
- state.maintainingScrollAtEnd = true;
2961
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
2962
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
2963
+ state.maintainingScrollAtEnd = pendingState;
2747
2964
  requestAnimationFrame(() => {
2748
2965
  if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
2966
+ state.maintainingScrollAtEnd = activeState;
2749
2967
  const scroller = refScroller.current;
2750
2968
  if (state.props.horizontal && isHorizontalRTL(state)) {
2751
2969
  const currentContentSize = getContentSize(ctx);
@@ -2763,12 +2981,14 @@ function doMaintainScrollAtEnd(ctx) {
2763
2981
  }
2764
2982
  setTimeout(
2765
2983
  () => {
2766
- state.maintainingScrollAtEnd = false;
2984
+ if (state.maintainingScrollAtEnd === activeState) {
2985
+ state.maintainingScrollAtEnd = void 0;
2986
+ }
2767
2987
  },
2768
2988
  maintainScrollAtEnd.animated ? 500 : 0
2769
2989
  );
2770
- } else {
2771
- state.maintainingScrollAtEnd = false;
2990
+ } else if (state.maintainingScrollAtEnd === pendingState) {
2991
+ state.maintainingScrollAtEnd = void 0;
2772
2992
  }
2773
2993
  });
2774
2994
  }
@@ -2777,30 +2997,6 @@ function doMaintainScrollAtEnd(ctx) {
2777
2997
  return false;
2778
2998
  }
2779
2999
 
2780
- // src/utils/requestAdjust.ts
2781
- function requestAdjust(ctx, positionDiff, dataChanged) {
2782
- const state = ctx.state;
2783
- if (Math.abs(positionDiff) > 0.1) {
2784
- const doit = () => {
2785
- {
2786
- state.scrollAdjustHandler.requestAdjust(positionDiff);
2787
- if (state.adjustingFromInitialMount) {
2788
- state.adjustingFromInitialMount--;
2789
- }
2790
- }
2791
- };
2792
- state.scroll += positionDiff;
2793
- state.scrollForNextCalculateItemsInView = void 0;
2794
- const readyToRender = peek$(ctx, "readyToRender");
2795
- if (readyToRender) {
2796
- doit();
2797
- } else {
2798
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2799
- requestAnimationFrame(doit);
2800
- }
2801
- }
2802
- }
2803
-
2804
3000
  // src/core/mvcp.ts
2805
3001
  var MVCP_POSITION_EPSILON = 0.1;
2806
3002
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2946,6 +3142,10 @@ function prepareMVCP(ctx, dataChanged) {
2946
3142
  const now = Date.now();
2947
3143
  const enableMVCPAnchorLock = (!!dataChanged || !!state.mvcpAnchorLock);
2948
3144
  const scrollingTo = state.scrollingTo;
3145
+ if (dataChanged && state.pendingScrollToEnd && scrollingTo === void 0) {
3146
+ state.mvcpAnchorLock = void 0;
3147
+ return void 0;
3148
+ }
2949
3149
  const anchorLock = resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) ;
2950
3150
  let prevPosition;
2951
3151
  let targetId;
@@ -3074,7 +3274,7 @@ function prepareMVCP(ctx, dataChanged) {
3074
3274
  return;
3075
3275
  }
3076
3276
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3077
- const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3277
+ const shouldSkipAdjustForMaintainedEnd = (state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated") && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3078
3278
  if (!shouldSkipAdjustForMaintainedEnd) {
3079
3279
  requestAdjust(ctx, positionDiff);
3080
3280
  }
@@ -3083,6 +3283,45 @@ function prepareMVCP(ctx, dataChanged) {
3083
3283
  }
3084
3284
  }
3085
3285
 
3286
+ // src/utils/getScrollVelocity.ts
3287
+ var getScrollVelocity = (state) => {
3288
+ const { scrollHistory } = state;
3289
+ const newestIndex = scrollHistory.length - 1;
3290
+ if (newestIndex < 1) {
3291
+ return 0;
3292
+ }
3293
+ const newest = scrollHistory[newestIndex];
3294
+ const now = Date.now();
3295
+ let direction = 0;
3296
+ for (let i = newestIndex; i > 0; i--) {
3297
+ const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
3298
+ if (delta !== 0) {
3299
+ direction = Math.sign(delta);
3300
+ break;
3301
+ }
3302
+ }
3303
+ if (direction === 0) {
3304
+ return 0;
3305
+ }
3306
+ let oldest = newest;
3307
+ for (let i = newestIndex - 1; i >= 0; i--) {
3308
+ const current = scrollHistory[i];
3309
+ const next = scrollHistory[i + 1];
3310
+ const delta = next.scroll - current.scroll;
3311
+ const deltaSign = Math.sign(delta);
3312
+ if (deltaSign !== 0 && deltaSign !== direction) {
3313
+ break;
3314
+ }
3315
+ if (now - current.time > 1e3) {
3316
+ break;
3317
+ }
3318
+ oldest = current;
3319
+ }
3320
+ const scrollDiff = newest.scroll - oldest.scroll;
3321
+ const timeDiff = newest.time - oldest.time;
3322
+ return timeDiff > 0 ? scrollDiff / timeDiff : 0;
3323
+ };
3324
+
3086
3325
  // src/core/updateScroll.ts
3087
3326
  function updateScroll(ctx, newScroll, forceUpdate, options) {
3088
3327
  var _a3;
@@ -3108,6 +3347,8 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
3108
3347
  if (scrollHistory.length > 5) {
3109
3348
  scrollHistory.shift();
3110
3349
  }
3350
+ const scrollVelocity = getScrollVelocity(state);
3351
+ updateAdaptiveRender(ctx, scrollVelocity);
3111
3352
  if (ignoreScrollFromMVCP && !scrollingTo) {
3112
3353
  const { lt, gt } = ignoreScrollFromMVCP;
3113
3354
  if (lt && newScroll < lt || gt && newScroll > gt) {
@@ -3131,7 +3372,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
3131
3372
  state.lastScrollDelta = scrollDelta;
3132
3373
  const runCalculateItems = () => {
3133
3374
  var _a4;
3134
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
3375
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0, scrollVelocity });
3135
3376
  checkThresholds(ctx);
3136
3377
  };
3137
3378
  if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
@@ -4211,9 +4452,32 @@ function initializeInitialScrollOnMount(ctx, options) {
4211
4452
  }
4212
4453
  function handleInitialScrollDataChange(ctx, options) {
4213
4454
  var _a3, _b, _c;
4214
- const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
4455
+ const {
4456
+ dataLength,
4457
+ didDataChange,
4458
+ initialScrollAtEnd,
4459
+ latestInitialScroll,
4460
+ latestInitialScrollSessionKind,
4461
+ stylePaddingBottom,
4462
+ useBootstrapInitialScroll
4463
+ } = options;
4215
4464
  const state = ctx.state;
4216
4465
  const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
4466
+ const isFirstNonEmptyData = !state.hasHadNonEmptyData && dataLength > 0;
4467
+ if (dataLength > 0) {
4468
+ state.hasHadNonEmptyData = true;
4469
+ }
4470
+ if (isFirstNonEmptyData) {
4471
+ if (latestInitialScroll) {
4472
+ setInitialScrollTarget(state, latestInitialScroll);
4473
+ setInitialScrollSession(state, {
4474
+ kind: latestInitialScrollSessionKind,
4475
+ previousDataLength
4476
+ });
4477
+ } else {
4478
+ clearPreservedInitialScrollTarget(state);
4479
+ }
4480
+ }
4217
4481
  if (state.initialScrollSession) {
4218
4482
  state.initialScrollSession.previousDataLength = dataLength;
4219
4483
  }
@@ -4437,45 +4701,6 @@ function updateTotalSize(ctx) {
4437
4701
  }
4438
4702
  }
4439
4703
 
4440
- // src/utils/getScrollVelocity.ts
4441
- var getScrollVelocity = (state) => {
4442
- const { scrollHistory } = state;
4443
- const newestIndex = scrollHistory.length - 1;
4444
- if (newestIndex < 1) {
4445
- return 0;
4446
- }
4447
- const newest = scrollHistory[newestIndex];
4448
- const now = Date.now();
4449
- let direction = 0;
4450
- for (let i = newestIndex; i > 0; i--) {
4451
- const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
4452
- if (delta !== 0) {
4453
- direction = Math.sign(delta);
4454
- break;
4455
- }
4456
- }
4457
- if (direction === 0) {
4458
- return 0;
4459
- }
4460
- let oldest = newest;
4461
- for (let i = newestIndex - 1; i >= 0; i--) {
4462
- const current = scrollHistory[i];
4463
- const next = scrollHistory[i + 1];
4464
- const delta = next.scroll - current.scroll;
4465
- const deltaSign = Math.sign(delta);
4466
- if (deltaSign !== 0 && deltaSign !== direction) {
4467
- break;
4468
- }
4469
- if (now - current.time > 1e3) {
4470
- break;
4471
- }
4472
- oldest = current;
4473
- }
4474
- const scrollDiff = newest.scroll - oldest.scroll;
4475
- const timeDiff = newest.time - oldest.time;
4476
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
4477
- };
4478
-
4479
4704
  // src/utils/updateSnapToOffsets.ts
4480
4705
  function updateSnapToOffsets(ctx) {
4481
4706
  const state = ctx.state;
@@ -4796,7 +5021,7 @@ function areViewabilityAmountTokensEqual(prev, next) {
4796
5021
  }
4797
5022
  function computeViewability(state, ctx, viewabilityConfig, containerId, key, scrollSize, item, index) {
4798
5023
  const { sizes, scroll: scrollState } = state;
4799
- const topPad = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
5024
+ const topPad = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "alignItemsAtEndPadding") || 0) + (peek$(ctx, "headerSize") || 0);
4800
5025
  const { itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold } = viewabilityConfig;
4801
5026
  const viewAreaMode = viewAreaCoveragePercentThreshold != null;
4802
5027
  const viewablePercentThreshold = viewAreaMode ? viewAreaCoveragePercentThreshold : itemVisiblePercentThreshold;
@@ -5211,7 +5436,7 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
5211
5436
  function calculateItemsInView(ctx, params = {}) {
5212
5437
  const state = ctx.state;
5213
5438
  batchedUpdates(() => {
5214
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
5439
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
5215
5440
  const {
5216
5441
  columns,
5217
5442
  containerItemKeys,
@@ -5220,14 +5445,7 @@ function calculateItemsInView(ctx, params = {}) {
5220
5445
  indexByKey,
5221
5446
  minIndexSizeChanged,
5222
5447
  positions,
5223
- props: {
5224
- alwaysRenderIndicesArr,
5225
- alwaysRenderIndicesSet,
5226
- drawDistance,
5227
- getItemType,
5228
- keyExtractor,
5229
- onStickyHeaderChange
5230
- },
5448
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
5231
5449
  scrollForNextCalculateItemsInView,
5232
5450
  scrollLength,
5233
5451
  sizes,
@@ -5239,6 +5457,7 @@ function calculateItemsInView(ctx, params = {}) {
5239
5457
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
5240
5458
  const alwaysRenderArr = alwaysRenderIndicesArr || [];
5241
5459
  const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
5460
+ const drawDistance = getEffectiveDrawDistance(ctx);
5242
5461
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
5243
5462
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
5244
5463
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
@@ -5247,12 +5466,12 @@ function calculateItemsInView(ctx, params = {}) {
5247
5466
  return;
5248
5467
  }
5249
5468
  let totalSize = getContentSize(ctx);
5250
- const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
5469
+ const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "alignItemsAtEndPadding") + peek$(ctx, "headerSize");
5251
5470
  const numColumns = peek$(ctx, "numColumns");
5252
- const speed = getScrollVelocity(state);
5471
+ const speed = (_b = params.scrollVelocity) != null ? _b : getScrollVelocity(state);
5253
5472
  const scrollExtra = 0;
5254
5473
  const { initialScroll, queuedInitialLayout } = state;
5255
- const scrollState = suppressInitialScrollSideEffects ? (_b = bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.scroll) != null ? _b : state.scroll : !queuedInitialLayout && hasActiveInitialScroll(state) && initialScroll ? (
5474
+ const scrollState = suppressInitialScrollSideEffects ? (_c = bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.scroll) != null ? _c : state.scroll : !queuedInitialLayout && hasActiveInitialScroll(state) && initialScroll ? (
5256
5475
  // Before the initial layout settles, keep viewport math anchored to the
5257
5476
  // current initial-scroll target instead of transient native adjustments.
5258
5477
  resolveInitialScrollOffset(ctx, initialScroll)
@@ -5276,19 +5495,25 @@ function calculateItemsInView(ctx, params = {}) {
5276
5495
  };
5277
5496
  updateScroll2(scrollState);
5278
5497
  const previousStickyIndex = peek$(ctx, "activeStickyIndex");
5279
- const currentStickyIdx = stickyHeaderIndicesArr.length > 0 ? findCurrentStickyIndex(stickyHeaderIndicesArr, scroll, state) : -1;
5280
- const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyHeaderIndicesArr[currentStickyIdx] : -1;
5281
- const stickyIndexDidChange = previousStickyIndex !== nextActiveStickyIndex;
5282
- if (currentStickyIdx >= 0 || previousStickyIndex >= 0) {
5283
- set$(ctx, "activeStickyIndex", nextActiveStickyIndex);
5284
- }
5285
- const shouldNotifyStickyHeaderChange = !!onStickyHeaderChange && stickyHeaderIndicesArr.length > 0 && stickyIndexDidChange;
5286
- const finishCalculateItemsInView = shouldNotifyStickyHeaderChange ? () => {
5287
- const item = data[nextActiveStickyIndex];
5288
- if (item !== void 0) {
5289
- onStickyHeaderChange == null ? void 0 : onStickyHeaderChange({ index: nextActiveStickyIndex, item });
5498
+ const resolveStickyState = () => {
5499
+ const currentStickyIdx = stickyHeaderIndicesArr.length > 0 ? findCurrentStickyIndex(stickyHeaderIndicesArr, scroll, state) : -1;
5500
+ const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyHeaderIndicesArr[currentStickyIdx] : -1;
5501
+ const stickyIndexDidChange = previousStickyIndex !== nextActiveStickyIndex;
5502
+ if (currentStickyIdx >= 0 || previousStickyIndex >= 0) {
5503
+ set$(ctx, "activeStickyIndex", nextActiveStickyIndex);
5290
5504
  }
5291
- } : void 0;
5505
+ const shouldNotifyStickyHeaderChange = !!onStickyHeaderChange && stickyHeaderIndicesArr.length > 0 && stickyIndexDidChange;
5506
+ return {
5507
+ currentStickyIdx,
5508
+ finishCalculateItemsInView: shouldNotifyStickyHeaderChange ? () => {
5509
+ const item = data[nextActiveStickyIndex];
5510
+ if (item !== void 0) {
5511
+ onStickyHeaderChange == null ? void 0 : onStickyHeaderChange({ index: nextActiveStickyIndex, item });
5512
+ }
5513
+ } : void 0
5514
+ };
5515
+ };
5516
+ let stickyState = dataChanged ? void 0 : resolveStickyState();
5292
5517
  let scrollBufferTop = drawDistance;
5293
5518
  let scrollBufferBottom = drawDistance;
5294
5519
  if (speed > 0 || speed === 0 && scroll < Math.max(50, drawDistance)) {
@@ -5321,7 +5546,7 @@ function calculateItemsInView(ctx, params = {}) {
5321
5546
  scrollBottom
5322
5547
  );
5323
5548
  }
5324
- finishCalculateItemsInView == null ? void 0 : finishCalculateItemsInView();
5549
+ (_d = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _d.call(stickyState);
5325
5550
  return;
5326
5551
  }
5327
5552
  }
@@ -5330,7 +5555,7 @@ function calculateItemsInView(ctx, params = {}) {
5330
5555
  if (dataChanged) {
5331
5556
  resetLayoutCachesForDataChange(state);
5332
5557
  }
5333
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
5558
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
5334
5559
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
5335
5560
  updateItemPositions(ctx, dataChanged, {
5336
5561
  doMVCP,
@@ -5356,21 +5581,24 @@ function calculateItemsInView(ctx, params = {}) {
5356
5581
  }
5357
5582
  }
5358
5583
  const scrollBeforeMVCP = state.scroll;
5359
- const scrollAdjustPendingBeforeMVCP = (_d = peek$(ctx, "scrollAdjustPending")) != null ? _d : 0;
5584
+ const scrollAdjustPendingBeforeMVCP = (_f = peek$(ctx, "scrollAdjustPending")) != null ? _f : 0;
5360
5585
  checkMVCP == null ? void 0 : checkMVCP();
5361
- const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0) !== scrollAdjustPendingBeforeMVCP);
5586
+ const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_g = peek$(ctx, "scrollAdjustPending")) != null ? _g : 0) !== scrollAdjustPendingBeforeMVCP);
5362
5587
  if (didMVCPAdjustScroll && initialScroll) {
5363
5588
  updateScroll2(state.scroll);
5364
5589
  updateScrollRange();
5365
5590
  }
5591
+ if (dataChanged) {
5592
+ stickyState = resolveStickyState();
5593
+ }
5366
5594
  let startBuffered = null;
5367
5595
  let startBufferedId = null;
5368
5596
  let endBuffered = null;
5369
- let loopStart = (_f = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _f : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
5597
+ let loopStart = (_h = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _h : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
5370
5598
  for (let i = loopStart; i >= 0; i--) {
5371
- const id = (_g = idCache[i]) != null ? _g : getId(state, i);
5599
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
5372
5600
  const top = positions[i];
5373
- const size = (_h = sizes.get(id)) != null ? _h : getItemSize(ctx, id, i, data[i]);
5601
+ const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
5374
5602
  const bottom = top + size;
5375
5603
  if (bottom > scrollTopBuffered) {
5376
5604
  loopStart = i;
@@ -5405,8 +5633,8 @@ function calculateItemsInView(ctx, params = {}) {
5405
5633
  };
5406
5634
  const dataLength = data.length;
5407
5635
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
5408
- const id = (_i = idCache[i]) != null ? _i : getId(state, i);
5409
- const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
5636
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
5637
+ const size = (_l = sizes.get(id)) != null ? _l : getItemSize(ctx, id, i, data[i]);
5410
5638
  const top = positions[i];
5411
5639
  if (!foundEnd) {
5412
5640
  trackVisibleRange(visibleRange, i, top, size, scroll, scrollBottom);
@@ -5462,7 +5690,7 @@ function calculateItemsInView(ctx, params = {}) {
5462
5690
  const needNewContainers = [];
5463
5691
  const needNewContainersSet = /* @__PURE__ */ new Set();
5464
5692
  for (let i = startBuffered; i <= endBuffered; i++) {
5465
- const id = (_k = idCache[i]) != null ? _k : getId(state, i);
5693
+ const id = (_m = idCache[i]) != null ? _m : getId(state, i);
5466
5694
  if (!containerItemKeys.has(id)) {
5467
5695
  needNewContainersSet.add(i);
5468
5696
  needNewContainers.push(i);
@@ -5471,7 +5699,7 @@ function calculateItemsInView(ctx, params = {}) {
5471
5699
  if (alwaysRenderArr.length > 0) {
5472
5700
  for (const index of alwaysRenderArr) {
5473
5701
  if (index < 0 || index >= dataLength) continue;
5474
- const id = (_l = idCache[index]) != null ? _l : getId(state, index);
5702
+ const id = (_n = idCache[index]) != null ? _n : getId(state, index);
5475
5703
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
5476
5704
  needNewContainersSet.add(index);
5477
5705
  needNewContainers.push(index);
@@ -5482,7 +5710,7 @@ function calculateItemsInView(ctx, params = {}) {
5482
5710
  handleStickyActivation(
5483
5711
  ctx,
5484
5712
  stickyHeaderIndicesArr,
5485
- currentStickyIdx,
5713
+ (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
5486
5714
  needNewContainers,
5487
5715
  needNewContainersSet,
5488
5716
  startBuffered,
@@ -5508,7 +5736,7 @@ function calculateItemsInView(ctx, params = {}) {
5508
5736
  for (const allocation of availableContainerAllocations) {
5509
5737
  const i = allocation.itemIndex;
5510
5738
  const containerIndex = allocation.containerIndex;
5511
- const id = (_m = idCache[i]) != null ? _m : getId(state, i);
5739
+ const id = (_p = idCache[i]) != null ? _p : getId(state, i);
5512
5740
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
5513
5741
  if (oldKey && oldKey !== id) {
5514
5742
  containerItemKeys.delete(oldKey);
@@ -5519,7 +5747,7 @@ function calculateItemsInView(ctx, params = {}) {
5519
5747
  state.containerItemTypes.set(containerIndex, allocation.itemType);
5520
5748
  }
5521
5749
  containerItemKeys.set(id, containerIndex);
5522
- (_n = state.userScrollAnchorReset) == null ? void 0 : _n.keys.add(id);
5750
+ (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
5523
5751
  const containerSticky = `containerSticky${containerIndex}`;
5524
5752
  const isSticky = stickyHeaderIndicesSet.has(i);
5525
5753
  const isAlwaysRender = alwaysRenderSet.has(i);
@@ -5550,14 +5778,12 @@ function calculateItemsInView(ctx, params = {}) {
5550
5778
  if (state.userScrollAnchorReset) {
5551
5779
  if (state.userScrollAnchorReset.keys.size === 0) {
5552
5780
  state.userScrollAnchorReset = void 0;
5553
- } else {
5554
- state.userScrollAnchorReset.batchSize = state.userScrollAnchorReset.keys.size;
5555
5781
  }
5556
5782
  }
5557
5783
  if (alwaysRenderArr.length > 0) {
5558
5784
  for (const index of alwaysRenderArr) {
5559
5785
  if (index < 0 || index >= dataLength) continue;
5560
- const id = (_o = idCache[index]) != null ? _o : getId(state, index);
5786
+ const id = (_r = idCache[index]) != null ? _r : getId(state, index);
5561
5787
  const containerIndex = containerItemKeys.get(id);
5562
5788
  if (containerIndex !== void 0) {
5563
5789
  state.stickyContainerPool.add(containerIndex);
@@ -5571,7 +5797,7 @@ function calculateItemsInView(ctx, params = {}) {
5571
5797
  stickyHeaderIndicesArr,
5572
5798
  scroll,
5573
5799
  drawDistance,
5574
- currentStickyIdx,
5800
+ (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
5575
5801
  pendingRemoval,
5576
5802
  alwaysRenderSet
5577
5803
  );
@@ -5632,7 +5858,7 @@ function calculateItemsInView(ctx, params = {}) {
5632
5858
  );
5633
5859
  }
5634
5860
  }
5635
- finishCalculateItemsInView == null ? void 0 : finishCalculateItemsInView();
5861
+ (_t = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _t.call(stickyState);
5636
5862
  });
5637
5863
  }
5638
5864
 
@@ -5715,8 +5941,9 @@ function doInitialAllocateContainers(ctx) {
5715
5941
  const state = ctx.state;
5716
5942
  const {
5717
5943
  scrollLength,
5718
- props: { data, drawDistance, getFixedItemSize, getItemType, numColumns, estimatedItemSize }
5944
+ props: { data, getFixedItemSize, getItemType, numColumns, estimatedItemSize }
5719
5945
  } = state;
5946
+ const drawDistance = getEffectiveDrawDistance(ctx);
5720
5947
  const hasContainers = peek$(ctx, "numContainers");
5721
5948
  if (scrollLength > 0 && data.length > 0 && !hasContainers) {
5722
5949
  let averageItemSize;
@@ -5727,12 +5954,12 @@ function doInitialAllocateContainers(ctx) {
5727
5954
  const item = data[i];
5728
5955
  if (item !== void 0) {
5729
5956
  const itemType = (_a3 = getItemType == null ? void 0 : getItemType(item, i)) != null ? _a3 : "";
5730
- totalSize += (_b = getFixedItemSize(item, i, itemType)) != null ? _b : estimatedItemSize;
5957
+ totalSize += ((_b = getFixedItemSize(item, i, itemType)) != null ? _b : estimatedItemSize) + ctx.scrollAxisGap;
5731
5958
  }
5732
5959
  }
5733
5960
  averageItemSize = totalSize / num;
5734
5961
  } else {
5735
- averageItemSize = estimatedItemSize;
5962
+ averageItemSize = estimatedItemSize + ctx.scrollAxisGap;
5736
5963
  }
5737
5964
  const numContainers = Math.max(
5738
5965
  1,
@@ -5791,6 +6018,7 @@ function handleLayout(ctx, layoutParam, setCanRender) {
5791
6018
  if (didChange) {
5792
6019
  state.scrollLength = scrollLength;
5793
6020
  state.otherAxisSize = otherAxisSize;
6021
+ updateContentMetricsState(ctx);
5794
6022
  state.lastBatchingAction = Date.now();
5795
6023
  state.scrollForNextCalculateItemsInView = void 0;
5796
6024
  if (scrollLength > 0) {
@@ -6027,28 +6255,12 @@ function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment)
6027
6255
 
6028
6256
  // src/core/updateItemSize.ts
6029
6257
  function runOrScheduleMVCPRecalculate(ctx) {
6030
- var _a3, _b;
6258
+ var _a3;
6031
6259
  const state = ctx.state;
6032
6260
  if (state.userScrollAnchorReset !== void 0) {
6033
- const replacementBatchSize = (_a3 = state.userScrollAnchorReset.batchSize) != null ? _a3 : state.userScrollAnchorReset.keys.size;
6034
- const replacementMeasurementBatchThreshold = 3;
6035
- const shouldBatchReplacementMeasurements = replacementBatchSize > replacementMeasurementBatchThreshold;
6036
- if (shouldBatchReplacementMeasurements) {
6037
- if (state.queuedMVCPRecalculate === void 0) {
6038
- state.queuedMVCPRecalculate = requestAnimationFrame(() => {
6039
- var _a4;
6040
- state.queuedMVCPRecalculate = void 0;
6041
- calculateItemsInView(ctx);
6042
- if (((_a4 = state.userScrollAnchorReset) == null ? void 0 : _a4.keys.size) === 0) {
6043
- state.userScrollAnchorReset = void 0;
6044
- }
6045
- });
6046
- }
6047
- } else {
6048
- calculateItemsInView(ctx);
6049
- if (((_b = state.userScrollAnchorReset) == null ? void 0 : _b.keys.size) === 0) {
6050
- state.userScrollAnchorReset = void 0;
6051
- }
6261
+ calculateItemsInView(ctx);
6262
+ if (((_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys.size) === 0) {
6263
+ state.userScrollAnchorReset = void 0;
6052
6264
  }
6053
6265
  } else {
6054
6266
  if (!state.mvcpAnchorLock) {
@@ -6456,10 +6668,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6456
6668
  startBuffered: state.startBuffered
6457
6669
  }),
6458
6670
  reportContentInset: (inset) => {
6459
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
6460
- const previousInset = state.contentInsetOverride;
6461
- state.contentInsetOverride = inset != null ? inset : void 0;
6462
- const didChange = ((_a3 = previousInset == null ? void 0 : previousInset.top) != null ? _a3 : 0) !== ((_c = (_b = state.contentInsetOverride) == null ? void 0 : _b.top) != null ? _c : 0) || ((_d = previousInset == null ? void 0 : previousInset.bottom) != null ? _d : 0) !== ((_f = (_e = state.contentInsetOverride) == null ? void 0 : _e.bottom) != null ? _f : 0) || ((_g = previousInset == null ? void 0 : previousInset.left) != null ? _g : 0) !== ((_i = (_h = state.contentInsetOverride) == null ? void 0 : _h.left) != null ? _i : 0) || ((_j = previousInset == null ? void 0 : previousInset.right) != null ? _j : 0) !== ((_l = (_k = state.contentInsetOverride) == null ? void 0 : _k.right) != null ? _l : 0);
6671
+ const didChange = setContentInsetOverride(ctx, inset);
6463
6672
  updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
6464
6673
  if (didChange) {
6465
6674
  retargetActiveInitialScrollAtEnd(ctx);
@@ -6511,6 +6720,9 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6511
6720
  scrollTo(ctx, params);
6512
6721
  return true;
6513
6722
  }),
6723
+ setItemSize: (itemKey, size) => {
6724
+ updateItemSize(ctx, itemKey, size);
6725
+ },
6514
6726
  setScrollProcessingEnabled: (enabled) => {
6515
6727
  state.scrollProcessingEnabled = enabled;
6516
6728
  },
@@ -6735,7 +6947,7 @@ var LegendList = typedMemo(
6735
6947
  })
6736
6948
  );
6737
6949
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
6738
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i;
6950
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
6739
6951
  const noopOnScroll = React3.useCallback((_event) => {
6740
6952
  }, []);
6741
6953
  if (props.recycleItems === void 0) {
@@ -6766,6 +6978,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6766
6978
  initialScrollAtEnd = false,
6767
6979
  initialScrollIndex: initialScrollIndexProp,
6768
6980
  initialScrollOffset: initialScrollOffsetProp,
6981
+ experimental_adaptiveRender,
6769
6982
  itemsAreEqual,
6770
6983
  keyExtractor: keyExtractorProp,
6771
6984
  ListEmptyComponent,
@@ -6815,10 +7028,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6815
7028
  ...restProps
6816
7029
  } = rest;
6817
7030
  const contentContainerStyleBase = StyleSheet.flatten(contentContainerStyleProp);
6818
- const shouldFlexGrow = alignItemsAtEnd && (horizontal ? (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minWidth) == null : (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minHeight) == null);
7031
+ const useAlignItemsAtEndPadding = alignItemsAtEnd && !horizontal && (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minHeight) == null && dataProp.length > 0;
7032
+ const shouldFlexGrow = alignItemsAtEnd && !useAlignItemsAtEndPadding && (horizontal ? (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minWidth) == null : (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minHeight) == null);
6819
7033
  const contentContainerStyle = {
6820
7034
  ...contentContainerStyleBase,
6821
- ...alignItemsAtEnd ? {
7035
+ ...alignItemsAtEnd && !useAlignItemsAtEndPadding ? {
6822
7036
  display: "flex",
6823
7037
  flexDirection: horizontal ? "row" : "column",
6824
7038
  ...shouldFlexGrow ? { flexGrow: 1 } : {},
@@ -6861,6 +7075,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6861
7075
  const [, scheduleImperativeScrollCommit] = React3__namespace.useReducer((value) => value + 1, 0);
6862
7076
  const ctx = useStateContext();
6863
7077
  ctx.columnWrapperStyle = columnWrapperStyle || (contentContainerStyle ? createColumnWrapperStyle(contentContainerStyle) : void 0);
7078
+ const scrollAxisGap = horizontal ? (_f = (_d = ctx.columnWrapperStyle) == null ? void 0 : _d.columnGap) != null ? _f : (_e = ctx.columnWrapperStyle) == null ? void 0 : _e.gap : (_i = (_g = ctx.columnWrapperStyle) == null ? void 0 : _g.rowGap) != null ? _i : (_h = ctx.columnWrapperStyle) == null ? void 0 : _h.gap;
7079
+ const nextScrollAxisGap = typeof scrollAxisGap === "number" && Number.isFinite(scrollAxisGap) ? scrollAxisGap : 0;
6864
7080
  const refScroller = React3.useRef(null);
6865
7081
  const combinedRef = useCombinedRef(refScroller, refScrollView);
6866
7082
  const keyExtractor = keyExtractorProp != null ? keyExtractorProp : ((_item, index) => index.toString());
@@ -6874,8 +7090,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6874
7090
  anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex,
6875
7091
  alwaysRender == null ? void 0 : alwaysRender.top,
6876
7092
  alwaysRender == null ? void 0 : alwaysRender.bottom,
6877
- (_d = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _d.join(","),
6878
- (_e = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _e.join(","),
7093
+ (_j = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _j.join(","),
7094
+ (_k = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _k.join(","),
6879
7095
  dataProp,
6880
7096
  dataVersion,
6881
7097
  keyExtractor
@@ -6903,6 +7119,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6903
7119
  endNoBuffer: -1,
6904
7120
  endReachedSnapshot: void 0,
6905
7121
  firstFullyOnScreenIndex: -1,
7122
+ hasHadNonEmptyData: dataProp.length > 0,
6906
7123
  idCache: [],
6907
7124
  idsInView: [],
6908
7125
  indexByKey: /* @__PURE__ */ new Map(),
@@ -6945,6 +7162,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6945
7162
  startReachedSnapshotDataChangeEpoch: void 0,
6946
7163
  stickyContainerPool: /* @__PURE__ */ new Set(),
6947
7164
  stickyContainers: /* @__PURE__ */ new Map(),
7165
+ timeoutAdaptiveRender: void 0,
6948
7166
  timeouts: /* @__PURE__ */ new Set(),
6949
7167
  totalSize: 0,
6950
7168
  viewabilityConfigCallbackPairs: void 0
@@ -6963,11 +7181,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6963
7181
  const state = refState.current;
6964
7182
  const isFirstLocal = state.isFirst;
6965
7183
  const previousNumColumnsProp = state.props.numColumns;
6966
- state.didColumnsChange = numColumnsProp !== previousNumColumnsProp;
7184
+ const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7185
+ ctx.scrollAxisGap = nextScrollAxisGap;
7186
+ state.didColumnsChange = numColumnsProp !== previousNumColumnsProp || didScrollAxisGapChange;
6967
7187
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6968
7188
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6969
7189
  const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6970
- if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
7190
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_l = state.initialScroll) == null ? void 0 : _l.viewPosition) === 1 && state.props.data.length > 0) {
6971
7191
  clearPreservedInitialScrollTarget(state);
6972
7192
  }
6973
7193
  if (didDataChangeLocal) {
@@ -6979,14 +7199,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6979
7199
  const throttledOnScroll = useThrottledOnScroll(onScrollProp != null ? onScrollProp : noopOnScroll, scrollEventThrottle != null ? scrollEventThrottle : 0);
6980
7200
  const throttleScrollFn = scrollEventThrottle && onScrollProp ? throttledOnScroll : onScrollProp;
6981
7201
  const anchoredEndSpaceResolved = anchoredEndSpace ? { ...anchoredEndSpace, includeInEndInset: true } : anchoredEndSpace;
6982
- const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_g = state.props.anchoredEndSpace) == null ? void 0 : _g.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
7202
+ const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_m = state.props.anchoredEndSpace) == null ? void 0 : _m.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
6983
7203
  state.props = {
7204
+ adaptiveRender: experimental_adaptiveRender,
6984
7205
  alignItemsAtEnd,
7206
+ alignItemsAtEndPaddingEnabled: useAlignItemsAtEndPadding,
6985
7207
  alwaysRender,
6986
7208
  alwaysRenderIndicesArr: alwaysRenderIndices.arr,
6987
7209
  alwaysRenderIndicesSet: alwaysRenderIndices.set,
6988
7210
  anchoredEndSpace: anchoredEndSpaceResolved,
6989
7211
  animatedProps: animatedPropsInternal,
7212
+ contentContainerAlignItems: contentContainerStyle.alignItems,
6990
7213
  contentInset,
6991
7214
  contentInsetEndAdjustment: contentInsetEndAdjustmentResolved,
6992
7215
  data: dataProp,
@@ -7039,6 +7262,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7039
7262
  const prevPaddingTop = peek$(ctx, "stylePaddingTop");
7040
7263
  setPaddingTop(ctx, { stylePaddingTop: stylePaddingTopState });
7041
7264
  refState.current.props.stylePaddingBottom = stylePaddingBottomState;
7265
+ updateContentMetricsState(ctx);
7042
7266
  let paddingDiff = stylePaddingTopState - prevPaddingTop;
7043
7267
  if (shouldAdjustPadding && maintainVisibleContentPositionConfig.size && paddingDiff && prevPaddingTop !== void 0 && Platform.OS === "ios") ;
7044
7268
  };
@@ -7070,7 +7294,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7070
7294
  useBootstrapInitialScroll: usesBootstrapInitialScroll
7071
7295
  });
7072
7296
  }, []);
7073
- if (isFirstLocal || didDataChangeLocal || numColumnsProp !== peek$(ctx, "numColumns")) {
7297
+ if (isFirstLocal || didDataChangeLocal || state.didColumnsChange) {
7074
7298
  refState.current.lastBatchingAction = Date.now();
7075
7299
  if (!keyExtractorProp && !isFirstLocal && didDataChangeLocal) {
7076
7300
  refState.current.sizes.clear();
@@ -7087,6 +7311,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7087
7311
  dataLength: dataProp.length,
7088
7312
  didDataChange: didDataChangeLocal,
7089
7313
  initialScrollAtEnd,
7314
+ latestInitialScroll: initialScrollProp,
7315
+ latestInitialScrollSessionKind: initialScrollUsesOffsetOnly ? "offset" : "bootstrap",
7090
7316
  stylePaddingBottom: stylePaddingBottomState,
7091
7317
  useBootstrapInitialScroll: usesBootstrapInitialScroll
7092
7318
  });
@@ -7157,7 +7383,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7157
7383
  }, [snapToIndices]);
7158
7384
  React3.useLayoutEffect(
7159
7385
  () => initializeStateVars(true),
7160
- [dataVersion, memoizedLastItemKeys.join(","), numColumnsProp, stylePaddingBottomState, stylePaddingTopState]
7386
+ [
7387
+ dataVersion,
7388
+ memoizedLastItemKeys.join(","),
7389
+ numColumnsProp,
7390
+ nextScrollAxisGap,
7391
+ stylePaddingBottomState,
7392
+ stylePaddingTopState,
7393
+ useAlignItemsAtEndPadding
7394
+ ]
7161
7395
  );
7162
7396
  React3.useLayoutEffect(() => {
7163
7397
  const {
@@ -7176,7 +7410,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7176
7410
  state.didColumnsChange = false;
7177
7411
  state.didDataChange = false;
7178
7412
  state.isFirst = false;
7179
- }, [dataProp, dataVersion, numColumnsProp]);
7413
+ }, [dataProp, dataVersion, numColumnsProp, nextScrollAxisGap]);
7180
7414
  React3.useLayoutEffect(() => {
7181
7415
  var _a4;
7182
7416
  set$(ctx, "extraData", extraData);
@@ -7224,6 +7458,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7224
7458
  useInit(() => {
7225
7459
  });
7226
7460
  React3.useImperativeHandle(forwardedRef, () => createImperativeHandle(ctx, scheduleImperativeScrollCommit), []);
7461
+ React3.useEffect(() => {
7462
+ return () => {
7463
+ for (const timeout of state.timeouts) {
7464
+ clearTimeout(timeout);
7465
+ }
7466
+ state.timeouts.clear();
7467
+ };
7468
+ }, [state]);
7227
7469
  React3.useLayoutEffect(() => {
7228
7470
  var _a4;
7229
7471
  (_a4 = state.runPendingScrollToEnd) == null ? void 0 : _a4.call(state);
@@ -7271,7 +7513,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7271
7513
  onScroll: onScrollHandler,
7272
7514
  recycleItems,
7273
7515
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React3__namespace.cloneElement(refreshControlElement, {
7274
- progressViewOffset: ((_h = refreshControlElement.props.progressViewOffset) != null ? _h : 0) + stylePaddingTopState
7516
+ progressViewOffset: ((_n = refreshControlElement.props.progressViewOffset) != null ? _n : 0) + stylePaddingTopState
7275
7517
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React3__namespace.createElement(
7276
7518
  RefreshControl,
7277
7519
  {
@@ -7282,7 +7524,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7282
7524
  ),
7283
7525
  refScrollView: combinedRef,
7284
7526
  renderScrollComponent,
7285
- scrollAdjustHandler: (_i = refState.current) == null ? void 0 : _i.scrollAdjustHandler,
7527
+ scrollAdjustHandler: (_o = refState.current) == null ? void 0 : _o.scrollAdjustHandler,
7286
7528
  scrollEventThrottle: 0,
7287
7529
  snapToIndices,
7288
7530
  stickyHeaderIndices,
@@ -7340,6 +7582,8 @@ var internal2 = internal;
7340
7582
 
7341
7583
  exports.LegendList = LegendList3;
7342
7584
  exports.internal = internal2;
7585
+ exports.useAdaptiveRender = useAdaptiveRender;
7586
+ exports.useAdaptiveRenderChange = useAdaptiveRenderChange;
7343
7587
  exports.useIsLastItem = useIsLastItem;
7344
7588
  exports.useListScrollSize = useListScrollSize;
7345
7589
  exports.useRecyclingEffect = useRecyclingEffect;