@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.mjs CHANGED
@@ -141,8 +141,10 @@ function StateProvider({ children }) {
141
141
  mapViewabilityConfigStates: /* @__PURE__ */ new Map(),
142
142
  mapViewabilityValues: /* @__PURE__ */ new Map(),
143
143
  positionListeners: /* @__PURE__ */ new Map(),
144
+ scrollAxisGap: 0,
144
145
  state: void 0,
145
146
  values: /* @__PURE__ */ new Map([
147
+ ["alignItemsAtEndPadding", 0],
146
148
  ["stylePaddingTop", 0],
147
149
  ["headerSize", 0],
148
150
  ["numContainers", 0],
@@ -152,6 +154,7 @@ function StateProvider({ children }) {
152
154
  ["isNearEnd", false],
153
155
  ["isNearStart", false],
154
156
  ["isWithinMaintainScrollAtEndThreshold", false],
157
+ ["adaptiveRender", "light"],
155
158
  ["totalSize", 0],
156
159
  ["scrollAdjustPending", 0]
157
160
  ]),
@@ -285,15 +288,16 @@ function getContentInsetEnd(ctx, contentInsetEndAdjustmentOverride) {
285
288
 
286
289
  // src/state/getContentSize.ts
287
290
  function getContentSize(ctx) {
288
- var _a3;
291
+ var _a3, _b;
289
292
  const { values, state } = ctx;
290
293
  const stylePaddingTop = values.get("stylePaddingTop") || 0;
291
294
  const stylePaddingBottom = state.props.stylePaddingBottom || 0;
295
+ const alignItemsAtEndPadding = values.get("alignItemsAtEndPadding") || 0;
292
296
  const headerSize = values.get("headerSize") || 0;
293
297
  const footerSize = values.get("footerSize") || 0;
294
298
  const contentInsetBottom = getContentInsetEnd(ctx);
295
- const totalSize = (_a3 = state.pendingTotalSize) != null ? _a3 : values.get("totalSize");
296
- return headerSize + footerSize + totalSize + stylePaddingTop + stylePaddingBottom + (contentInsetBottom || 0);
299
+ const totalSize = (_b = (_a3 = state.pendingTotalSize) != null ? _a3 : state.totalSize) != null ? _b : values.get("totalSize");
300
+ return headerSize + footerSize + totalSize + stylePaddingTop + alignItemsAtEndPadding + stylePaddingBottom + (contentInsetBottom || 0);
297
301
  }
298
302
 
299
303
  // src/components/DebugView.tsx
@@ -531,6 +535,24 @@ var ContextContainer = createContext(null);
531
535
  function useContextContainer() {
532
536
  return useContext(ContextContainer);
533
537
  }
538
+ function useAdaptiveRender() {
539
+ const [mode] = useArr$(["adaptiveRender"]);
540
+ return mode;
541
+ }
542
+ function useAdaptiveRenderChange(callback) {
543
+ const ctx = useStateContext();
544
+ const callbackRef = useRef(callback);
545
+ callbackRef.current = callback;
546
+ useLayoutEffect(() => {
547
+ let mode = peek$(ctx, "adaptiveRender");
548
+ return listen$(ctx, "adaptiveRender", (nextMode) => {
549
+ if (mode !== nextMode) {
550
+ mode = nextMode;
551
+ callbackRef.current(nextMode);
552
+ }
553
+ });
554
+ }, [ctx]);
555
+ }
534
556
  function useViewability(callback, configId) {
535
557
  const ctx = useStateContext();
536
558
  const containerContext = useContextContainer();
@@ -800,6 +822,7 @@ function isInMVCPActiveMode(state) {
800
822
  // src/components/Container.tsx
801
823
  function getContainerPositionStyle({
802
824
  columnWrapperStyle,
825
+ contentContainerAlignItems,
803
826
  horizontal,
804
827
  hasItemSeparator,
805
828
  isHorizontalRTLList,
@@ -825,13 +848,14 @@ function getContainerPositionStyle({
825
848
  }
826
849
  }
827
850
  return horizontal ? {
851
+ bottom: contentContainerAlignItems === "flex-end" && numColumns === 1 ? 0 : void 0,
828
852
  boxSizing: paddingStyles ? "border-box" : void 0,
829
853
  direction: isHorizontalRTLList && Platform.OS === "web" ? "ltr" : void 0,
830
854
  flexDirection: hasItemSeparator ? "row" : void 0,
831
855
  height: otherAxisSize,
832
856
  left: 0,
833
857
  position: "absolute",
834
- top: otherAxisPos,
858
+ top: contentContainerAlignItems === "flex-end" && numColumns === 1 ? void 0 : otherAxisPos,
835
859
  ...paddingStyles || {}
836
860
  } : {
837
861
  boxSizing: paddingStyles ? "border-box" : void 0,
@@ -885,6 +909,7 @@ var Container = typedMemo(function Container2({
885
909
  const style = useMemo(
886
910
  () => getContainerPositionStyle({
887
911
  columnWrapperStyle,
912
+ contentContainerAlignItems: ctx.state.props.contentContainerAlignItems,
888
913
  hasItemSeparator: !!ItemSeparatorComponent,
889
914
  horizontal,
890
915
  isHorizontalRTLList,
@@ -898,6 +923,7 @@ var Container = typedMemo(function Container2({
898
923
  otherAxisPos,
899
924
  otherAxisSize,
900
925
  columnWrapperStyle,
926
+ ctx.state.props.contentContainerAlignItems,
901
927
  numColumns,
902
928
  ItemSeparatorComponent
903
929
  ]
@@ -1613,7 +1639,12 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
1613
1639
  WebkitOverflowScrolling: "touch"
1614
1640
  // iOS momentum scrolling
1615
1641
  },
1616
- ...StyleSheet.flatten(style)
1642
+ ...StyleSheet.flatten(style),
1643
+ ...maintainVisibleContentPosition ? {
1644
+ // Chrome's native scroll anchoring can apply after LegendList's MVCP adjustment,
1645
+ // causing the same header/item-size delta to be compensated twice.
1646
+ overflowAnchor: "none"
1647
+ } : {}
1617
1648
  };
1618
1649
  const contentInsetEndAdjustment = getContentInsetEndAdjustmentEnd2(ctx);
1619
1650
  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;
@@ -1624,7 +1655,10 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
1624
1655
  flexDirection: horizontal ? "row" : void 0,
1625
1656
  minHeight: horizontal ? void 0 : "100%",
1626
1657
  minWidth: horizontal ? "100%" : void 0,
1627
- ...StyleSheet.flatten(contentContainerStyle)
1658
+ ...StyleSheet.flatten(contentContainerStyle),
1659
+ ...maintainVisibleContentPosition ? {
1660
+ overflowAnchor: "none"
1661
+ } : {}
1628
1662
  };
1629
1663
  const className = contentContainerClassName ? `${LEGEND_LIST_CONTENT_CONTAINER_CLASS} ${contentContainerClassName}` : LEGEND_LIST_CONTENT_CONTAINER_CLASS;
1630
1664
  const {
@@ -1727,6 +1761,7 @@ function scrollAdjustBy(el, left, top) {
1727
1761
  function ScrollAdjust() {
1728
1762
  const ctx = useStateContext();
1729
1763
  const lastScrollOffsetRef = React3.useRef(0);
1764
+ const lastScrollAdjustUserOffsetRef = React3.useRef(0);
1730
1765
  const resetPaddingRafRef = React3.useRef(void 0);
1731
1766
  const resetPaddingBaselineRef = React3.useRef(void 0);
1732
1767
  const contentNodeRef = React3.useRef(null);
@@ -1735,20 +1770,24 @@ function ScrollAdjust() {
1735
1770
  const scrollAdjust = peek$(ctx, "scrollAdjust");
1736
1771
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
1737
1772
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
1738
- const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1739
- if (scrollDelta !== 0) {
1773
+ const signalScrollDelta = scrollOffset - lastScrollOffsetRef.current;
1774
+ if (signalScrollDelta !== 0) {
1740
1775
  const target = getScrollAdjustTarget(ctx, contentNodeRef.current);
1741
1776
  if (target) {
1742
1777
  const horizontal = !!ctx.state.props.horizontal;
1743
1778
  const axis = getScrollAdjustAxis(horizontal);
1744
1779
  const { contentNode, scrollElement: el } = target;
1780
+ const currentScroll = horizontal ? el.scrollLeft : el.scrollTop;
1781
+ const userOffsetDelta = (scrollAdjustUserOffset || 0) - lastScrollAdjustUserOffsetRef.current;
1782
+ const intendedScroll = userOffsetDelta !== 0 ? currentScroll + userOffsetDelta : ctx.state.scroll;
1783
+ const scrollDelta = intendedScroll - currentScroll;
1784
+ const shouldScroll = Math.abs(scrollDelta) > 0.01;
1745
1785
  const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
1746
1786
  contentNodeRef.current = contentNode;
1747
- if (contentNode) {
1748
- const prevScroll = horizontal ? el.scrollLeft : el.scrollTop;
1787
+ if (shouldScroll && contentNode) {
1749
1788
  const totalSize = contentNode[axis.contentSizeKey];
1750
1789
  const viewportSize = el[axis.viewportSizeKey];
1751
- const nextScroll = prevScroll + scrollDelta;
1790
+ const nextScroll = currentScroll + scrollDelta;
1752
1791
  const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
1753
1792
  if (needsTemporaryPadding) {
1754
1793
  const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
@@ -1768,11 +1807,12 @@ function ScrollAdjust() {
1768
1807
  } else {
1769
1808
  scrollBy();
1770
1809
  }
1771
- } else {
1810
+ } else if (shouldScroll) {
1772
1811
  scrollBy();
1773
1812
  }
1774
1813
  }
1775
1814
  lastScrollOffsetRef.current = scrollOffset;
1815
+ lastScrollAdjustUserOffsetRef.current = scrollAdjustUserOffset || 0;
1776
1816
  }
1777
1817
  }, [ctx]);
1778
1818
  useValueListener$("scrollAdjust", callback);
@@ -1793,193 +1833,53 @@ function WebAnchoredEndSpace({ horizontal }) {
1793
1833
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1794
1834
  return /* @__PURE__ */ React3.createElement("div", { style }, null);
1795
1835
  }
1796
- function useStableRenderComponent(renderComponent, mapProps) {
1797
- const renderComponentRef = useLatestRef(renderComponent);
1798
- const mapPropsRef = useLatestRef(mapProps);
1799
- return React3.useMemo(
1800
- () => React3.forwardRef(
1801
- (props, ref) => {
1802
- var _a3, _b;
1803
- return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1804
- }
1805
- ),
1806
- [mapPropsRef, renderComponentRef]
1807
- );
1836
+
1837
+ // src/core/updateContentMetricsState.ts
1838
+ function getRawContentLength(ctx) {
1839
+ var _a3, _b, _c;
1840
+ const { state, values } = ctx;
1841
+ 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);
1842
+ }
1843
+ function getAlignItemsAtEndPadding(ctx) {
1844
+ const { state } = ctx;
1845
+ const shouldPad = !!state.props.alignItemsAtEndPaddingEnabled && !state.props.horizontal && state.props.data.length > 0 && state.scrollLength > 0;
1846
+ return shouldPad ? Math.max(0, state.scrollLength - getRawContentLength(ctx) - getContentInsetEnd(ctx)) : 0;
1847
+ }
1848
+ function updateContentMetricsState(ctx) {
1849
+ const previousPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1850
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1851
+ if (previousPadding !== nextPadding) {
1852
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1853
+ }
1808
1854
  }
1809
- var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1810
- const localRef = useRef(null);
1811
- const ref = refView != null ? refView : localRef;
1812
- useOnLayoutSync({ onLayoutChange, ref });
1813
- return /* @__PURE__ */ React3.createElement("div", { ...rest, ref }, children);
1814
- };
1815
1855
 
1816
- // src/components/ListComponent.tsx
1817
- var ListComponent = typedMemo(function ListComponent2({
1818
- canRender,
1819
- style,
1820
- contentContainerStyle,
1821
- horizontal,
1822
- initialContentOffset,
1823
- recycleItems,
1824
- ItemSeparatorComponent,
1825
- alignItemsAtEnd: _alignItemsAtEnd,
1826
- onScroll: onScroll2,
1827
- onLayout,
1828
- ListHeaderComponent,
1829
- ListHeaderComponentStyle,
1830
- ListFooterComponent,
1831
- ListFooterComponentStyle,
1832
- ListEmptyComponent,
1833
- getRenderedItem: getRenderedItem2,
1834
- updateItemSize: updateItemSize2,
1835
- refScrollView,
1836
- renderScrollComponent,
1837
- onLayoutFooter,
1838
- scrollAdjustHandler,
1839
- snapToIndices,
1840
- stickyHeaderConfig,
1841
- stickyHeaderIndices,
1842
- useWindowScroll = false,
1843
- ...rest
1844
- }) {
1845
- const ctx = useStateContext();
1846
- const maintainVisibleContentPosition = ctx.state.props.maintainVisibleContentPosition;
1847
- const [otherAxisSize = 0] = useArr$(["otherAxisSize"]);
1848
- const autoOtherAxisStyle = getAutoOtherAxisStyle({
1849
- horizontal,
1850
- needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1851
- otherAxisSize
1852
- });
1853
- const CustomScrollComponent = useStableRenderComponent(
1854
- renderScrollComponent,
1855
- (props, ref) => ({ ...props, ref })
1856
- );
1857
- const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1858
- const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1859
- useLayoutEffect(() => {
1860
- if (!ListHeaderComponent) {
1861
- set$(ctx, "headerSize", 0);
1862
- }
1863
- if (!ListFooterComponent) {
1864
- set$(ctx, "footerSize", 0);
1856
+ // src/core/addTotalSize.ts
1857
+ function addTotalSize(ctx, key, add, notifyTotalSize = true) {
1858
+ const state = ctx.state;
1859
+ const prevTotalSize = state.totalSize;
1860
+ let totalSize = state.totalSize;
1861
+ if (key === null) {
1862
+ totalSize = add;
1863
+ if (state.timeoutSetPaddingTop) {
1864
+ clearTimeout(state.timeoutSetPaddingTop);
1865
+ state.timeoutSetPaddingTop = void 0;
1865
1866
  }
1866
- }, [ListHeaderComponent, ListFooterComponent, ctx]);
1867
- const onLayoutHeader = useCallback(
1868
- (rect) => {
1869
- const size = rect[horizontal ? "width" : "height"];
1870
- set$(ctx, "headerSize", size);
1871
- },
1872
- [ctx, horizontal]
1873
- );
1874
- const onLayoutFooterInternal = useCallback(
1875
- (rect, fromLayoutEffect) => {
1876
- const size = rect[horizontal ? "width" : "height"];
1877
- set$(ctx, "footerSize", size);
1878
- onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
1879
- },
1880
- [ctx, horizontal, onLayoutFooter]
1881
- );
1882
- return /* @__PURE__ */ React3.createElement(
1883
- SnapOrScroll,
1867
+ } else {
1868
+ totalSize += add;
1869
+ }
1870
+ if (prevTotalSize !== totalSize) {
1884
1871
  {
1885
- ...rest,
1886
- ...ScrollComponent === ListComponentScrollView ? { useWindowScroll } : {},
1887
- contentContainerStyle: [
1888
- horizontal ? {
1889
- height: "100%"
1890
- } : {},
1891
- contentContainerStyle
1892
- ],
1893
- contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
1894
- horizontal,
1895
- maintainVisibleContentPosition: maintainVisibleContentPosition.size || maintainVisibleContentPosition.data ? { minIndexForVisible: 0 } : void 0,
1896
- onLayout,
1897
- onScroll: onScroll2,
1898
- ref: refScrollView,
1899
- ScrollComponent: snapToIndices ? ScrollComponent : void 0,
1900
- style: autoOtherAxisStyle ? [autoOtherAxisStyle, style] : style
1901
- },
1902
- /* @__PURE__ */ React3.createElement(ScrollAdjust, null),
1903
- ListHeaderComponent && /* @__PURE__ */ React3.createElement(LayoutView, { onLayoutChange: onLayoutHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
1904
- ListEmptyComponent && getComponent(ListEmptyComponent),
1905
- canRender && !ListEmptyComponent && /* @__PURE__ */ React3.createElement(
1906
- Containers,
1907
- {
1908
- getRenderedItem: getRenderedItem2,
1909
- horizontal,
1910
- ItemSeparatorComponent,
1911
- recycleItems,
1912
- stickyHeaderConfig,
1913
- updateItemSize: updateItemSize2
1872
+ state.pendingTotalSize = void 0;
1873
+ state.totalSize = totalSize;
1874
+ if (notifyTotalSize) {
1875
+ set$(ctx, "totalSize", totalSize);
1914
1876
  }
1915
- ),
1916
- ListFooterComponent && /* @__PURE__ */ React3.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
1917
- /* @__PURE__ */ React3.createElement(WebAnchoredEndSpace, { horizontal }),
1918
- IS_DEV && ENABLE_DEVMODE
1919
- );
1920
- });
1921
- var WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH = 100;
1922
- var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
1923
- var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
1924
- function useDevChecksImpl(props) {
1925
- const ctx = useStateContext();
1926
- const { childrenMode, keyExtractor, renderScrollComponent, useWindowScroll } = props;
1927
- useEffect(() => {
1928
- if (useWindowScroll && renderScrollComponent) {
1929
- warnDevOnce(
1930
- "useWindowScrollRenderScrollComponent",
1931
- "useWindowScroll is not supported when renderScrollComponent is provided."
1932
- );
1933
- }
1934
- }, [renderScrollComponent, useWindowScroll]);
1935
- useEffect(() => {
1936
- if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
1937
- warnDevOnce(
1938
- "keyExtractor",
1939
- "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."
1940
- );
1941
- }
1942
- }, [childrenMode, ctx, keyExtractor]);
1943
- useEffect(() => {
1944
- const state = ctx.state;
1945
- const dataLength = state.props.data.length;
1946
- const useWindowScrollResolved = state.props.useWindowScroll;
1947
- if (useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
1948
- return;
1877
+ updateContentMetricsState(ctx);
1949
1878
  }
1950
- const warnIfUnboundedOuterSize = () => {
1951
- const readyToRender = peek$(ctx, "readyToRender");
1952
- const numContainers = peek$(ctx, "numContainers") || 0;
1953
- const totalSize = peek$(ctx, "totalSize") || 0;
1954
- const scrollLength = ctx.state.scrollLength || 0;
1955
- if (!readyToRender || totalSize <= 0 || scrollLength <= 0) {
1956
- return;
1957
- }
1958
- const rendersAlmostEverything = numContainers >= Math.ceil(dataLength * WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO);
1959
- const viewportMatchesContent = scrollLength >= totalSize * WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO;
1960
- if (rendersAlmostEverything && viewportMatchesContent) {
1961
- warnDevOnce(
1962
- "webUnboundedOuterSize",
1963
- "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."
1964
- );
1965
- }
1966
- };
1967
- warnIfUnboundedOuterSize();
1968
- const unsubscribe = [
1969
- listen$(ctx, "numContainers", warnIfUnboundedOuterSize),
1970
- listen$(ctx, "readyToRender", warnIfUnboundedOuterSize),
1971
- listen$(ctx, "totalSize", warnIfUnboundedOuterSize)
1972
- ];
1973
- return () => {
1974
- for (const unsub of unsubscribe) {
1975
- unsub();
1976
- }
1977
- };
1978
- }, [ctx]);
1979
- }
1980
- function useDevChecksNoop(_props) {
1879
+ } else if (notifyTotalSize && ctx.values.get("totalSize") !== totalSize) {
1880
+ set$(ctx, "totalSize", totalSize);
1881
+ }
1981
1882
  }
1982
- var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
1983
1883
 
1984
1884
  // src/core/deferredPublicOnScroll.ts
1985
1885
  function withResolvedContentOffset(state, event, resolvedOffset) {
@@ -2292,6 +2192,66 @@ function recalculateSettledScroll(ctx) {
2292
2192
  checkThresholds(ctx);
2293
2193
  }
2294
2194
 
2195
+ // src/core/adaptiveRender.ts
2196
+ var DEFAULT_ENTER_VELOCITY = 4;
2197
+ var DEFAULT_EXIT_VELOCITY = 1;
2198
+ var DEFAULT_EXIT_DELAY = 1e3;
2199
+ function scheduleAdaptiveRenderExit(ctx, exitDelay) {
2200
+ const state = ctx.state;
2201
+ const previousTimeout = state.timeoutAdaptiveRender;
2202
+ if (previousTimeout !== void 0) {
2203
+ clearTimeout(previousTimeout);
2204
+ state.timeouts.delete(previousTimeout);
2205
+ state.timeoutAdaptiveRender = void 0;
2206
+ }
2207
+ if (exitDelay <= 0) {
2208
+ setAdaptiveRender(ctx, "normal");
2209
+ } else {
2210
+ const timeout = setTimeout(() => {
2211
+ state.timeouts.delete(timeout);
2212
+ state.timeoutAdaptiveRender = void 0;
2213
+ setAdaptiveRender(ctx, "normal");
2214
+ }, exitDelay);
2215
+ state.timeoutAdaptiveRender = timeout;
2216
+ state.timeouts.add(timeout);
2217
+ }
2218
+ }
2219
+ function setAdaptiveRender(ctx, mode) {
2220
+ var _a3, _b;
2221
+ const previousMode = peek$(ctx, "adaptiveRender");
2222
+ if (previousMode !== mode) {
2223
+ set$(ctx, "adaptiveRender", mode);
2224
+ (_b = (_a3 = ctx.state.props.adaptiveRender) == null ? void 0 : _a3.onChange) == null ? void 0 : _b.call(_a3, mode);
2225
+ }
2226
+ }
2227
+ function updateAdaptiveRender(ctx, scrollVelocity) {
2228
+ var _a3, _b, _c;
2229
+ const state = ctx.state;
2230
+ const adaptiveRender = state.props.adaptiveRender;
2231
+ const enterVelocity = (_a3 = adaptiveRender == null ? void 0 : adaptiveRender.enterVelocity) != null ? _a3 : DEFAULT_ENTER_VELOCITY;
2232
+ const exitVelocity = (_b = adaptiveRender == null ? void 0 : adaptiveRender.exitVelocity) != null ? _b : DEFAULT_EXIT_VELOCITY;
2233
+ const exitDelay = (_c = adaptiveRender == null ? void 0 : adaptiveRender.exitDelay) != null ? _c : DEFAULT_EXIT_DELAY;
2234
+ const currentMode = peek$(ctx, "adaptiveRender");
2235
+ const threshold = currentMode === "light" ? exitVelocity : enterVelocity;
2236
+ const nextMode = Math.abs(scrollVelocity) > threshold ? "light" : "normal";
2237
+ const previousMode = state.timeoutAdaptiveRender !== void 0 ? "normal" : currentMode;
2238
+ if (nextMode !== previousMode) {
2239
+ if (nextMode === "light") {
2240
+ setAdaptiveRender(ctx, "light");
2241
+ scheduleAdaptiveRenderExit(ctx, exitDelay);
2242
+ } else if (currentMode === "light") {
2243
+ scheduleAdaptiveRenderExit(ctx, exitDelay);
2244
+ }
2245
+ }
2246
+ }
2247
+
2248
+ // src/utils/getEffectiveDrawDistance.ts
2249
+ var INITIAL_DRAW_DISTANCE = 100;
2250
+ function getEffectiveDrawDistance(ctx) {
2251
+ const drawDistance = ctx.state.props.drawDistance;
2252
+ return peek$(ctx, "readyToRender") ? drawDistance : Math.min(drawDistance, INITIAL_DRAW_DISTANCE);
2253
+ }
2254
+
2295
2255
  // src/utils/setInitialRenderState.ts
2296
2256
  function setInitialRenderState(ctx, {
2297
2257
  didLayout,
@@ -2311,6 +2271,13 @@ function setInitialRenderState(ctx, {
2311
2271
  const isReadyToRender = Boolean(state.didContainersLayout && state.didFinishInitialScroll);
2312
2272
  if (isReadyToRender && !peek$(ctx, "readyToRender")) {
2313
2273
  set$(ctx, "readyToRender", true);
2274
+ setAdaptiveRender(ctx, "normal");
2275
+ if (state.props.drawDistance > INITIAL_DRAW_DISTANCE) {
2276
+ requestAnimationFrame(() => {
2277
+ var _a3;
2278
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state);
2279
+ });
2280
+ }
2314
2281
  if (onLoad) {
2315
2282
  onLoad({ elapsedTimeInMs: Date.now() - loadStartTime });
2316
2283
  }
@@ -2385,54 +2352,423 @@ function finishInitialScroll(ctx, options) {
2385
2352
  complete();
2386
2353
  }
2387
2354
 
2388
- // src/core/calculateOffsetForIndex.ts
2389
- function calculateOffsetForIndex(ctx, index) {
2355
+ // src/core/finishScrollTo.ts
2356
+ function finishScrollTo(ctx) {
2357
+ var _a3, _b;
2390
2358
  const state = ctx.state;
2391
- return index !== void 0 ? state.positions[index] || 0 : 0;
2392
- }
2393
-
2394
- // src/core/getTopOffsetAdjustment.ts
2395
- function getTopOffsetAdjustment(ctx) {
2396
- return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
2397
- }
2398
-
2399
- // src/utils/getId.ts
2400
- function getId(state, index) {
2401
- const { data, keyExtractor } = state.props;
2402
- if (!data) {
2403
- return "";
2404
- }
2405
- const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
2406
- const id = ret;
2407
- state.idCache[index] = id;
2408
- return id;
2359
+ if (state == null ? void 0 : state.scrollingTo) {
2360
+ const resolvePendingScroll = state.pendingScrollResolve;
2361
+ state.pendingScrollResolve = void 0;
2362
+ const scrollingTo = state.scrollingTo;
2363
+ state.scrollHistory.length = 0;
2364
+ state.scrollingTo = void 0;
2365
+ if (state.pendingTotalSize !== void 0) {
2366
+ addTotalSize(ctx, null, state.pendingTotalSize);
2367
+ }
2368
+ {
2369
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
2370
+ }
2371
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
2372
+ const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2373
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
2374
+ finishInitialScroll(ctx, {
2375
+ onFinished: () => {
2376
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2377
+ },
2378
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
2379
+ recalculateItems: true,
2380
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
2381
+ syncObservedOffset: isOffsetSession,
2382
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
2383
+ });
2384
+ return;
2385
+ }
2386
+ recalculateSettledScroll(ctx);
2387
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2388
+ }
2409
2389
  }
2410
2390
 
2411
- // src/core/addTotalSize.ts
2412
- function addTotalSize(ctx, key, add, notifyTotalSize = true) {
2391
+ // src/core/doScrollTo.ts
2392
+ var SCROLL_END_IDLE_MS = 80;
2393
+ var SCROLL_END_MAX_MS = 1500;
2394
+ var SMOOTH_SCROLL_DURATION_MS = 320;
2395
+ var SCROLL_END_TARGET_EPSILON = 1;
2396
+ function doScrollTo(ctx, params) {
2397
+ var _a3, _b;
2413
2398
  const state = ctx.state;
2414
- const prevTotalSize = state.totalSize;
2415
- let totalSize = state.totalSize;
2416
- if (key === null) {
2417
- totalSize = add;
2418
- if (state.timeoutSetPaddingTop) {
2419
- clearTimeout(state.timeoutSetPaddingTop);
2420
- state.timeoutSetPaddingTop = void 0;
2399
+ const { animated, horizontal, offset } = params;
2400
+ const scroller = state.refScroller.current;
2401
+ const node = scroller == null ? void 0 : scroller.getScrollableNode();
2402
+ if (!scroller || !node) {
2403
+ return;
2404
+ }
2405
+ const isAnimated = !!animated;
2406
+ const isHorizontal = !!horizontal;
2407
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2408
+ const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
2409
+ const top = isHorizontal ? 0 : offset;
2410
+ scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2411
+ if (isAnimated) {
2412
+ const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
2413
+ listenForScrollEnd(ctx, {
2414
+ readOffset: () => scroller.getCurrentScrollOffset(),
2415
+ target,
2416
+ targetOffset: offset
2417
+ });
2418
+ } else {
2419
+ state.scroll = offset;
2420
+ setTimeout(() => {
2421
+ finishScrollTo(ctx);
2422
+ }, 100);
2423
+ }
2424
+ }
2425
+ function listenForScrollEnd(ctx, params) {
2426
+ const { readOffset, target, targetOffset } = params;
2427
+ if (!target) {
2428
+ finishScrollTo(ctx);
2429
+ return;
2430
+ }
2431
+ const supportsScrollEnd = "onscrollend" in target;
2432
+ let idleTimeout;
2433
+ let settled = false;
2434
+ const targetToken = ctx.state.scrollingTo;
2435
+ const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2436
+ const cleanup = () => {
2437
+ target.removeEventListener("scroll", onScroll2);
2438
+ if (supportsScrollEnd) {
2439
+ target.removeEventListener("scrollend", onScrollEnd);
2440
+ }
2441
+ if (idleTimeout) {
2442
+ clearTimeout(idleTimeout);
2443
+ }
2444
+ clearTimeout(maxTimeout);
2445
+ };
2446
+ const finish = (reason) => {
2447
+ if (settled) return;
2448
+ if (targetToken !== ctx.state.scrollingTo) {
2449
+ settled = true;
2450
+ cleanup();
2451
+ return;
2452
+ }
2453
+ const currentOffset = readOffset();
2454
+ const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2455
+ if (reason === "scrollend" && !isNearTarget) {
2456
+ return;
2457
+ }
2458
+ settled = true;
2459
+ cleanup();
2460
+ finishScrollTo(ctx);
2461
+ };
2462
+ const onScroll2 = () => {
2463
+ if (idleTimeout) {
2464
+ clearTimeout(idleTimeout);
2421
2465
  }
2466
+ idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2467
+ };
2468
+ const onScrollEnd = () => finish("scrollend");
2469
+ target.addEventListener("scroll", onScroll2);
2470
+ if (supportsScrollEnd) {
2471
+ target.addEventListener("scrollend", onScrollEnd);
2422
2472
  } else {
2423
- totalSize += add;
2473
+ idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2424
2474
  }
2425
- if (prevTotalSize !== totalSize) {
2475
+ }
2476
+
2477
+ // src/utils/requestAdjust.ts
2478
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2479
+ const state = ctx.state;
2480
+ if (Math.abs(positionDiff) > 0.1) {
2481
+ const doit = () => {
2482
+ {
2483
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2484
+ if (state.adjustingFromInitialMount) {
2485
+ state.adjustingFromInitialMount--;
2486
+ }
2487
+ }
2488
+ };
2489
+ state.scroll += positionDiff;
2490
+ state.scrollForNextCalculateItemsInView = void 0;
2491
+ const readyToRender = peek$(ctx, "readyToRender");
2492
+ if (readyToRender) {
2493
+ doit();
2494
+ } else {
2495
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2496
+ requestAnimationFrame(doit);
2497
+ }
2498
+ }
2499
+ }
2500
+
2501
+ // src/core/updateContentMetrics.ts
2502
+ var SCROLL_ADJUST_EPSILON = 0.1;
2503
+ function setContentLengthSignal(ctx, signalName, size) {
2504
+ if (peek$(ctx, signalName) !== size) {
2505
+ set$(ctx, signalName, size);
2506
+ updateContentMetricsState(ctx);
2507
+ }
2508
+ }
2509
+ function shouldAdjustForHeaderSizeChange(ctx, previousHeaderSize, nextHeaderSize) {
2510
+ const { didContainersLayout, didFinishInitialScroll, props, scroll, scrollingTo } = ctx.state;
2511
+ const sizeDiff = nextHeaderSize - previousHeaderSize;
2512
+ const leadingPadding = props.horizontal ? props.stylePaddingLeft : props.stylePaddingTop;
2513
+ const previousHeaderEnd = (leadingPadding || 0) + previousHeaderSize;
2514
+ return props.maintainVisibleContentPosition.size && didContainersLayout && didFinishInitialScroll && !scrollingTo && scroll >= previousHeaderEnd - SCROLL_ADJUST_EPSILON && Math.abs(sizeDiff) > SCROLL_ADJUST_EPSILON;
2515
+ }
2516
+ function setHeaderSize(ctx, size) {
2517
+ const { state } = ctx;
2518
+ const previousHeaderSize = peek$(ctx, "headerSize") || 0;
2519
+ const didChange = previousHeaderSize !== size;
2520
+ const hasMeasuredOrEstimatedHeaderBaseline = state.didMeasureHeader || previousHeaderSize > SCROLL_ADJUST_EPSILON;
2521
+ if (didChange) {
2522
+ set$(ctx, "headerSize", size);
2523
+ updateContentMetricsState(ctx);
2524
+ if (hasMeasuredOrEstimatedHeaderBaseline && shouldAdjustForHeaderSizeChange(ctx, previousHeaderSize, size)) {
2525
+ requestAdjust(ctx, size - previousHeaderSize);
2526
+ }
2527
+ }
2528
+ state.didMeasureHeader = true;
2529
+ }
2530
+ function setFooterSize(ctx, size) {
2531
+ setContentLengthSignal(ctx, "footerSize", size);
2532
+ }
2533
+ function areInsetsEqual(left, right) {
2534
+ var _a3, _b, _c, _d, _e, _f, _g, _h;
2535
+ 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);
2536
+ }
2537
+ function setContentInsetOverride(ctx, inset) {
2538
+ const { state } = ctx;
2539
+ const previousInset = state.contentInsetOverride;
2540
+ const nextInset = inset != null ? inset : void 0;
2541
+ const didChange = !areInsetsEqual(previousInset, nextInset);
2542
+ state.contentInsetOverride = nextInset;
2543
+ if (didChange) {
2544
+ updateContentMetricsState(ctx);
2545
+ }
2546
+ return didChange;
2547
+ }
2548
+ function useStableRenderComponent(renderComponent, mapProps) {
2549
+ const renderComponentRef = useLatestRef(renderComponent);
2550
+ const mapPropsRef = useLatestRef(mapProps);
2551
+ return React3.useMemo(
2552
+ () => React3.forwardRef(
2553
+ (props, ref) => {
2554
+ var _a3, _b;
2555
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
2556
+ }
2557
+ ),
2558
+ [mapPropsRef, renderComponentRef]
2559
+ );
2560
+ }
2561
+ var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
2562
+ const localRef = useRef(null);
2563
+ const ref = refView != null ? refView : localRef;
2564
+ useOnLayoutSync({ onLayoutChange, ref });
2565
+ return /* @__PURE__ */ React3.createElement("div", { ...rest, ref }, children);
2566
+ };
2567
+
2568
+ // src/components/ListComponent.tsx
2569
+ var AlignItemsAtEndSpacer = typedMemo(function AlignItemsAtEndSpacer2({ horizontal }) {
2570
+ const [alignItemsAtEndPadding = 0] = useArr$(["alignItemsAtEndPadding"]);
2571
+ if (alignItemsAtEndPadding <= 0) {
2572
+ return null;
2573
+ }
2574
+ return /* @__PURE__ */ React3.createElement(
2575
+ View,
2426
2576
  {
2427
- state.pendingTotalSize = void 0;
2428
- state.totalSize = totalSize;
2429
- if (notifyTotalSize) {
2430
- set$(ctx, "totalSize", totalSize);
2577
+ style: horizontal ? { flexShrink: 0, width: alignItemsAtEndPadding } : { flexShrink: 0, height: alignItemsAtEndPadding }
2578
+ },
2579
+ null
2580
+ );
2581
+ });
2582
+ var ListComponent = typedMemo(function ListComponent2({
2583
+ canRender,
2584
+ style,
2585
+ contentContainerStyle,
2586
+ horizontal,
2587
+ initialContentOffset,
2588
+ recycleItems,
2589
+ ItemSeparatorComponent,
2590
+ alignItemsAtEnd: _alignItemsAtEnd,
2591
+ onScroll: onScroll2,
2592
+ onLayout,
2593
+ ListHeaderComponent,
2594
+ ListHeaderComponentStyle,
2595
+ ListFooterComponent,
2596
+ ListFooterComponentStyle,
2597
+ ListEmptyComponent,
2598
+ getRenderedItem: getRenderedItem2,
2599
+ updateItemSize: updateItemSize2,
2600
+ refScrollView,
2601
+ renderScrollComponent,
2602
+ onLayoutFooter,
2603
+ scrollAdjustHandler,
2604
+ snapToIndices,
2605
+ stickyHeaderConfig,
2606
+ stickyHeaderIndices,
2607
+ useWindowScroll = false,
2608
+ ...rest
2609
+ }) {
2610
+ const ctx = useStateContext();
2611
+ const maintainVisibleContentPosition = ctx.state.props.maintainVisibleContentPosition;
2612
+ const [otherAxisSize = 0] = useArr$(["otherAxisSize"]);
2613
+ const shouldRenderAlignItemsAtEndSpacer = ctx.state.props.alignItemsAtEndPaddingEnabled;
2614
+ const autoOtherAxisStyle = getAutoOtherAxisStyle({
2615
+ horizontal,
2616
+ needsOtherAxisSize: ctx.state.needsOtherAxisSize,
2617
+ otherAxisSize
2618
+ });
2619
+ const CustomScrollComponent = useStableRenderComponent(
2620
+ renderScrollComponent,
2621
+ (props, ref) => ({ ...props, ref })
2622
+ );
2623
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
2624
+ const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
2625
+ useLayoutEffect(() => {
2626
+ if (!ListHeaderComponent) {
2627
+ setHeaderSize(ctx, 0);
2628
+ }
2629
+ if (!ListFooterComponent) {
2630
+ setFooterSize(ctx, 0);
2631
+ }
2632
+ }, [ListHeaderComponent, ListFooterComponent, ctx]);
2633
+ const onLayoutHeader = useCallback(
2634
+ (rect) => {
2635
+ const size = rect[horizontal ? "width" : "height"];
2636
+ setHeaderSize(ctx, size);
2637
+ },
2638
+ [ctx, horizontal]
2639
+ );
2640
+ const onLayoutFooterInternal = useCallback(
2641
+ (rect, fromLayoutEffect) => {
2642
+ const size = rect[horizontal ? "width" : "height"];
2643
+ setFooterSize(ctx, size);
2644
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
2645
+ },
2646
+ [ctx, horizontal, onLayoutFooter]
2647
+ );
2648
+ return /* @__PURE__ */ React3.createElement(
2649
+ SnapOrScroll,
2650
+ {
2651
+ ...rest,
2652
+ ...ScrollComponent === ListComponentScrollView ? { useWindowScroll } : {},
2653
+ contentContainerStyle: [
2654
+ horizontal ? {
2655
+ height: "100%"
2656
+ } : {},
2657
+ contentContainerStyle
2658
+ ],
2659
+ contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
2660
+ horizontal,
2661
+ maintainVisibleContentPosition: maintainVisibleContentPosition.size || maintainVisibleContentPosition.data ? { minIndexForVisible: 0 } : void 0,
2662
+ onLayout,
2663
+ onScroll: onScroll2,
2664
+ ref: refScrollView,
2665
+ ScrollComponent: snapToIndices ? ScrollComponent : void 0,
2666
+ style: autoOtherAxisStyle ? [autoOtherAxisStyle, style] : style
2667
+ },
2668
+ /* @__PURE__ */ React3.createElement(ScrollAdjust, null),
2669
+ ListHeaderComponent && /* @__PURE__ */ React3.createElement(LayoutView, { onLayoutChange: onLayoutHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)),
2670
+ ListEmptyComponent && getComponent(ListEmptyComponent),
2671
+ shouldRenderAlignItemsAtEndSpacer && /* @__PURE__ */ React3.createElement(AlignItemsAtEndSpacer, { horizontal }),
2672
+ canRender && !ListEmptyComponent && /* @__PURE__ */ React3.createElement(
2673
+ Containers,
2674
+ {
2675
+ getRenderedItem: getRenderedItem2,
2676
+ horizontal,
2677
+ ItemSeparatorComponent,
2678
+ recycleItems,
2679
+ stickyHeaderConfig,
2680
+ updateItemSize: updateItemSize2
2431
2681
  }
2682
+ ),
2683
+ ListFooterComponent && /* @__PURE__ */ React3.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
2684
+ /* @__PURE__ */ React3.createElement(WebAnchoredEndSpace, { horizontal }),
2685
+ IS_DEV && ENABLE_DEVMODE
2686
+ );
2687
+ });
2688
+ var WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH = 100;
2689
+ var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9;
2690
+ var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9;
2691
+ function useDevChecksImpl(props) {
2692
+ const ctx = useStateContext();
2693
+ const { childrenMode, keyExtractor, renderScrollComponent, useWindowScroll } = props;
2694
+ useEffect(() => {
2695
+ if (useWindowScroll && renderScrollComponent) {
2696
+ warnDevOnce(
2697
+ "useWindowScrollRenderScrollComponent",
2698
+ "useWindowScroll is not supported when renderScrollComponent is provided."
2699
+ );
2432
2700
  }
2433
- } else if (notifyTotalSize && ctx.values.get("totalSize") !== totalSize) {
2434
- set$(ctx, "totalSize", totalSize);
2701
+ }, [renderScrollComponent, useWindowScroll]);
2702
+ useEffect(() => {
2703
+ if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) {
2704
+ warnDevOnce(
2705
+ "keyExtractor",
2706
+ "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."
2707
+ );
2708
+ }
2709
+ }, [childrenMode, ctx, keyExtractor]);
2710
+ useEffect(() => {
2711
+ const state = ctx.state;
2712
+ const dataLength = state.props.data.length;
2713
+ const useWindowScrollResolved = state.props.useWindowScroll;
2714
+ if (useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) {
2715
+ return;
2716
+ }
2717
+ const warnIfUnboundedOuterSize = () => {
2718
+ const readyToRender = peek$(ctx, "readyToRender");
2719
+ const numContainers = peek$(ctx, "numContainers") || 0;
2720
+ const totalSize = peek$(ctx, "totalSize") || 0;
2721
+ const scrollLength = ctx.state.scrollLength || 0;
2722
+ if (!readyToRender || totalSize <= 0 || scrollLength <= 0) {
2723
+ return;
2724
+ }
2725
+ const rendersAlmostEverything = numContainers >= Math.ceil(dataLength * WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO);
2726
+ const viewportMatchesContent = scrollLength >= totalSize * WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO;
2727
+ if (rendersAlmostEverything && viewportMatchesContent) {
2728
+ warnDevOnce(
2729
+ "webUnboundedOuterSize",
2730
+ "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."
2731
+ );
2732
+ }
2733
+ };
2734
+ warnIfUnboundedOuterSize();
2735
+ const unsubscribe = [
2736
+ listen$(ctx, "numContainers", warnIfUnboundedOuterSize),
2737
+ listen$(ctx, "readyToRender", warnIfUnboundedOuterSize),
2738
+ listen$(ctx, "totalSize", warnIfUnboundedOuterSize)
2739
+ ];
2740
+ return () => {
2741
+ for (const unsub of unsubscribe) {
2742
+ unsub();
2743
+ }
2744
+ };
2745
+ }, [ctx]);
2746
+ }
2747
+ function useDevChecksNoop(_props) {
2748
+ }
2749
+ var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop;
2750
+
2751
+ // src/core/calculateOffsetForIndex.ts
2752
+ function calculateOffsetForIndex(ctx, index) {
2753
+ const state = ctx.state;
2754
+ return index !== void 0 ? state.positions[index] || 0 : 0;
2755
+ }
2756
+
2757
+ // src/core/getTopOffsetAdjustment.ts
2758
+ function getTopOffsetAdjustment(ctx) {
2759
+ return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "alignItemsAtEndPadding") || 0) + (peek$(ctx, "headerSize") || 0);
2760
+ }
2761
+
2762
+ // src/utils/getId.ts
2763
+ function getId(state, index) {
2764
+ const { data, keyExtractor } = state.props;
2765
+ if (!data) {
2766
+ return "";
2435
2767
  }
2768
+ const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
2769
+ const id = ret;
2770
+ state.idCache[index] = id;
2771
+ return id;
2436
2772
  }
2437
2773
 
2438
2774
  // src/core/setSize.ts
@@ -2455,8 +2791,9 @@ function getKnownOrFixedSize(ctx, key, index, data, resolved) {
2455
2791
  let size = key ? state.sizesKnown.get(key) : void 0;
2456
2792
  if (size === void 0 && key && getFixedItemSize) {
2457
2793
  const itemType = (_b = resolved == null ? void 0 : resolved.itemType) != null ? _b : getItemType ? (_a3 = getItemType(data, index)) != null ? _a3 : "" : "";
2458
- size = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
2459
- if (size !== void 0) {
2794
+ const fixedSize = (resolved == null ? void 0 : resolved.didResolveFixedItemSize) ? resolved.fixedItemSize : getFixedItemSize(data, index, itemType);
2795
+ if (fixedSize !== void 0) {
2796
+ size = fixedSize + ctx.scrollAxisGap;
2460
2797
  state.sizesKnown.set(key, size);
2461
2798
  }
2462
2799
  }
@@ -2506,198 +2843,76 @@ function getItemSize(ctx, key, index, data, useAverageSize, preferCachedSize, no
2506
2843
  size = roundSize(averageSizeForType);
2507
2844
  }
2508
2845
  }
2509
- if (size === void 0 && renderedSize !== void 0) {
2510
- return renderedSize;
2511
- }
2512
- if (size === void 0 && useAverageSize && scrollingTo) {
2513
- const averageSizeForType = (_d = scrollingTo.averageSizeSnapshot) == null ? void 0 : _d[itemType];
2514
- if (averageSizeForType !== void 0) {
2515
- size = roundSize(averageSizeForType);
2516
- }
2517
- }
2518
- if (size === void 0) {
2519
- size = estimatedItemSize;
2520
- }
2521
- setSize(ctx, key, size, notifyTotalSize);
2522
- return size;
2523
- }
2524
- function getItemSizeAtIndex(ctx, index) {
2525
- if (index === void 0 || index < 0) {
2526
- return void 0;
2527
- }
2528
- const targetId = getId(ctx.state, index);
2529
- return getItemSize(ctx, targetId, index, ctx.state.props.data[index]);
2530
- }
2531
-
2532
- // src/core/calculateOffsetWithOffsetPosition.ts
2533
- function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
2534
- var _a3;
2535
- const state = ctx.state;
2536
- const { index, viewOffset, viewPosition } = params;
2537
- let offset = offsetParam;
2538
- if (viewOffset) {
2539
- offset -= viewOffset;
2540
- }
2541
- if (index !== void 0) {
2542
- const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
2543
- if (topOffsetAdjustment) {
2544
- offset += topOffsetAdjustment;
2545
- }
2546
- }
2547
- if (viewPosition !== void 0 && index !== void 0) {
2548
- const dataLength = state.props.data.length;
2549
- if (dataLength === 0) {
2550
- return offset;
2551
- }
2552
- const isOutOfBounds = index < 0 || index >= dataLength;
2553
- const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
2554
- const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
2555
- const trailingInset = getContentInsetEnd(ctx);
2556
- offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
2557
- if (!isOutOfBounds && index === state.props.data.length - 1) {
2558
- const footerSize = peek$(ctx, "footerSize") || 0;
2559
- offset += footerSize;
2560
- }
2561
- }
2562
- return offset;
2563
- }
2564
-
2565
- // src/core/clampScrollOffset.ts
2566
- function clampScrollOffset(ctx, offset, scrollTarget) {
2567
- const state = ctx.state;
2568
- const contentSize = getContentSize(ctx);
2569
- let clampedOffset = offset;
2570
- if (Number.isFinite(contentSize) && Number.isFinite(state.scrollLength) && (Platform.OS !== "android")) {
2571
- const baseMaxOffset = Math.max(0, contentSize - state.scrollLength);
2572
- const viewOffset = scrollTarget == null ? void 0 : scrollTarget.viewOffset;
2573
- const extraEndOffset = typeof viewOffset === "number" && viewOffset < 0 ? -viewOffset : 0;
2574
- const maxOffset = baseMaxOffset + extraEndOffset;
2575
- clampedOffset = Math.min(offset, maxOffset);
2576
- }
2577
- clampedOffset = Math.max(0, clampedOffset);
2578
- return clampedOffset;
2579
- }
2580
-
2581
- // src/core/finishScrollTo.ts
2582
- function finishScrollTo(ctx) {
2583
- var _a3, _b;
2584
- const state = ctx.state;
2585
- if (state == null ? void 0 : state.scrollingTo) {
2586
- const resolvePendingScroll = state.pendingScrollResolve;
2587
- state.pendingScrollResolve = void 0;
2588
- const scrollingTo = state.scrollingTo;
2589
- state.scrollHistory.length = 0;
2590
- state.scrollingTo = void 0;
2591
- if (state.pendingTotalSize !== void 0) {
2592
- addTotalSize(ctx, null, state.pendingTotalSize);
2593
- }
2594
- {
2595
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
2596
- }
2597
- if (scrollingTo.isInitialScroll || state.initialScroll) {
2598
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
2599
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
2600
- finishInitialScroll(ctx, {
2601
- onFinished: () => {
2602
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2603
- },
2604
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
2605
- recalculateItems: true,
2606
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
2607
- syncObservedOffset: isOffsetSession,
2608
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
2609
- });
2610
- return;
2611
- }
2612
- recalculateSettledScroll(ctx);
2613
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
2614
- }
2615
- }
2616
-
2617
- // src/core/doScrollTo.ts
2618
- var SCROLL_END_IDLE_MS = 80;
2619
- var SCROLL_END_MAX_MS = 1500;
2620
- var SMOOTH_SCROLL_DURATION_MS = 320;
2621
- var SCROLL_END_TARGET_EPSILON = 1;
2622
- function doScrollTo(ctx, params) {
2623
- var _a3, _b;
2624
- const state = ctx.state;
2625
- const { animated, horizontal, offset } = params;
2626
- const scroller = state.refScroller.current;
2627
- const node = scroller == null ? void 0 : scroller.getScrollableNode();
2628
- if (!scroller || !node) {
2629
- return;
2630
- }
2631
- const isAnimated = !!animated;
2632
- const isHorizontal = !!horizontal;
2633
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
2634
- const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
2635
- const top = isHorizontal ? 0 : offset;
2636
- scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2637
- if (isAnimated) {
2638
- const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
2639
- listenForScrollEnd(ctx, {
2640
- readOffset: () => scroller.getCurrentScrollOffset(),
2641
- target,
2642
- targetOffset: offset
2643
- });
2644
- } else {
2645
- state.scroll = offset;
2646
- setTimeout(() => {
2647
- finishScrollTo(ctx);
2648
- }, 100);
2846
+ if (size === void 0 && renderedSize !== void 0) {
2847
+ return renderedSize;
2848
+ }
2849
+ if (size === void 0 && useAverageSize && scrollingTo) {
2850
+ const averageSizeForType = (_d = scrollingTo.averageSizeSnapshot) == null ? void 0 : _d[itemType];
2851
+ if (averageSizeForType !== void 0) {
2852
+ size = roundSize(averageSizeForType);
2853
+ }
2649
2854
  }
2855
+ if (size === void 0) {
2856
+ size = estimatedItemSize + ctx.scrollAxisGap;
2857
+ }
2858
+ setSize(ctx, key, size, notifyTotalSize);
2859
+ return size;
2650
2860
  }
2651
- function listenForScrollEnd(ctx, params) {
2652
- const { readOffset, target, targetOffset } = params;
2653
- if (!target) {
2654
- finishScrollTo(ctx);
2655
- return;
2861
+ function getItemSizeAtIndex(ctx, index) {
2862
+ if (index === void 0 || index < 0) {
2863
+ return void 0;
2656
2864
  }
2657
- const supportsScrollEnd = "onscrollend" in target;
2658
- let idleTimeout;
2659
- let settled = false;
2660
- const targetToken = ctx.state.scrollingTo;
2661
- const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2662
- const cleanup = () => {
2663
- target.removeEventListener("scroll", onScroll2);
2664
- if (supportsScrollEnd) {
2665
- target.removeEventListener("scrollend", onScrollEnd);
2666
- }
2667
- if (idleTimeout) {
2668
- clearTimeout(idleTimeout);
2669
- }
2670
- clearTimeout(maxTimeout);
2671
- };
2672
- const finish = (reason) => {
2673
- if (settled) return;
2674
- if (targetToken !== ctx.state.scrollingTo) {
2675
- settled = true;
2676
- cleanup();
2677
- return;
2865
+ const targetId = getId(ctx.state, index);
2866
+ return getItemSize(ctx, targetId, index, ctx.state.props.data[index]);
2867
+ }
2868
+
2869
+ // src/core/calculateOffsetWithOffsetPosition.ts
2870
+ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
2871
+ var _a3;
2872
+ const state = ctx.state;
2873
+ const { index, viewOffset, viewPosition } = params;
2874
+ let offset = offsetParam;
2875
+ if (viewOffset) {
2876
+ offset -= viewOffset;
2877
+ }
2878
+ if (index !== void 0) {
2879
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
2880
+ if (topOffsetAdjustment) {
2881
+ offset += topOffsetAdjustment;
2678
2882
  }
2679
- const currentOffset = readOffset();
2680
- const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2681
- if (reason === "scrollend" && !isNearTarget) {
2682
- return;
2883
+ }
2884
+ if (viewPosition !== void 0 && index !== void 0) {
2885
+ const dataLength = state.props.data.length;
2886
+ if (dataLength === 0) {
2887
+ return offset;
2683
2888
  }
2684
- settled = true;
2685
- cleanup();
2686
- finishScrollTo(ctx);
2687
- };
2688
- const onScroll2 = () => {
2689
- if (idleTimeout) {
2690
- clearTimeout(idleTimeout);
2889
+ const isOutOfBounds = index < 0 || index >= dataLength;
2890
+ const fallbackEstimatedSize = (_a3 = state.props.estimatedItemSize) != null ? _a3 : 0;
2891
+ const itemSize = isOutOfBounds ? fallbackEstimatedSize : getItemSize(ctx, getId(state, index), index, state.props.data[index]);
2892
+ const trailingInset = getContentInsetEnd(ctx);
2893
+ offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
2894
+ if (!isOutOfBounds && index === state.props.data.length - 1) {
2895
+ const footerSize = peek$(ctx, "footerSize") || 0;
2896
+ offset += footerSize;
2691
2897
  }
2692
- idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2693
- };
2694
- const onScrollEnd = () => finish("scrollend");
2695
- target.addEventListener("scroll", onScroll2);
2696
- if (supportsScrollEnd) {
2697
- target.addEventListener("scrollend", onScrollEnd);
2698
- } else {
2699
- idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2700
2898
  }
2899
+ return offset;
2900
+ }
2901
+
2902
+ // src/core/clampScrollOffset.ts
2903
+ function clampScrollOffset(ctx, offset, scrollTarget) {
2904
+ const state = ctx.state;
2905
+ const contentSize = getContentSize(ctx);
2906
+ let clampedOffset = offset;
2907
+ if (Number.isFinite(contentSize) && Number.isFinite(state.scrollLength) && (Platform.OS !== "android")) {
2908
+ const baseMaxOffset = Math.max(0, contentSize - state.scrollLength);
2909
+ const viewOffset = scrollTarget == null ? void 0 : scrollTarget.viewOffset;
2910
+ const extraEndOffset = typeof viewOffset === "number" && viewOffset < 0 ? -viewOffset : 0;
2911
+ const maxOffset = baseMaxOffset + extraEndOffset;
2912
+ clampedOffset = Math.min(offset, maxOffset);
2913
+ }
2914
+ clampedOffset = Math.max(0, clampedOffset);
2915
+ return clampedOffset;
2701
2916
  }
2702
2917
 
2703
2918
  // src/core/doMaintainScrollAtEnd.ts
@@ -2722,9 +2937,12 @@ function doMaintainScrollAtEnd(ctx) {
2722
2937
  state.scroll = 0;
2723
2938
  }
2724
2939
  if (!state.maintainingScrollAtEnd) {
2725
- state.maintainingScrollAtEnd = true;
2940
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
2941
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
2942
+ state.maintainingScrollAtEnd = pendingState;
2726
2943
  requestAnimationFrame(() => {
2727
2944
  if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
2945
+ state.maintainingScrollAtEnd = activeState;
2728
2946
  const scroller = refScroller.current;
2729
2947
  if (state.props.horizontal && isHorizontalRTL(state)) {
2730
2948
  const currentContentSize = getContentSize(ctx);
@@ -2742,12 +2960,14 @@ function doMaintainScrollAtEnd(ctx) {
2742
2960
  }
2743
2961
  setTimeout(
2744
2962
  () => {
2745
- state.maintainingScrollAtEnd = false;
2963
+ if (state.maintainingScrollAtEnd === activeState) {
2964
+ state.maintainingScrollAtEnd = void 0;
2965
+ }
2746
2966
  },
2747
2967
  maintainScrollAtEnd.animated ? 500 : 0
2748
2968
  );
2749
- } else {
2750
- state.maintainingScrollAtEnd = false;
2969
+ } else if (state.maintainingScrollAtEnd === pendingState) {
2970
+ state.maintainingScrollAtEnd = void 0;
2751
2971
  }
2752
2972
  });
2753
2973
  }
@@ -2756,30 +2976,6 @@ function doMaintainScrollAtEnd(ctx) {
2756
2976
  return false;
2757
2977
  }
2758
2978
 
2759
- // src/utils/requestAdjust.ts
2760
- function requestAdjust(ctx, positionDiff, dataChanged) {
2761
- const state = ctx.state;
2762
- if (Math.abs(positionDiff) > 0.1) {
2763
- const doit = () => {
2764
- {
2765
- state.scrollAdjustHandler.requestAdjust(positionDiff);
2766
- if (state.adjustingFromInitialMount) {
2767
- state.adjustingFromInitialMount--;
2768
- }
2769
- }
2770
- };
2771
- state.scroll += positionDiff;
2772
- state.scrollForNextCalculateItemsInView = void 0;
2773
- const readyToRender = peek$(ctx, "readyToRender");
2774
- if (readyToRender) {
2775
- doit();
2776
- } else {
2777
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2778
- requestAnimationFrame(doit);
2779
- }
2780
- }
2781
- }
2782
-
2783
2979
  // src/core/mvcp.ts
2784
2980
  var MVCP_POSITION_EPSILON = 0.1;
2785
2981
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2925,6 +3121,10 @@ function prepareMVCP(ctx, dataChanged) {
2925
3121
  const now = Date.now();
2926
3122
  const enableMVCPAnchorLock = (!!dataChanged || !!state.mvcpAnchorLock);
2927
3123
  const scrollingTo = state.scrollingTo;
3124
+ if (dataChanged && state.pendingScrollToEnd && scrollingTo === void 0) {
3125
+ state.mvcpAnchorLock = void 0;
3126
+ return void 0;
3127
+ }
2928
3128
  const anchorLock = resolveAnchorLock(state, enableMVCPAnchorLock, mvcpData, now) ;
2929
3129
  let prevPosition;
2930
3130
  let targetId;
@@ -3053,7 +3253,7 @@ function prepareMVCP(ctx, dataChanged) {
3053
3253
  return;
3054
3254
  }
3055
3255
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3056
- const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3256
+ const shouldSkipAdjustForMaintainedEnd = (state.maintainingScrollAtEnd === "pending-animated" || state.maintainingScrollAtEnd === "animated") && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3057
3257
  if (!shouldSkipAdjustForMaintainedEnd) {
3058
3258
  requestAdjust(ctx, positionDiff);
3059
3259
  }
@@ -3062,6 +3262,45 @@ function prepareMVCP(ctx, dataChanged) {
3062
3262
  }
3063
3263
  }
3064
3264
 
3265
+ // src/utils/getScrollVelocity.ts
3266
+ var getScrollVelocity = (state) => {
3267
+ const { scrollHistory } = state;
3268
+ const newestIndex = scrollHistory.length - 1;
3269
+ if (newestIndex < 1) {
3270
+ return 0;
3271
+ }
3272
+ const newest = scrollHistory[newestIndex];
3273
+ const now = Date.now();
3274
+ let direction = 0;
3275
+ for (let i = newestIndex; i > 0; i--) {
3276
+ const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
3277
+ if (delta !== 0) {
3278
+ direction = Math.sign(delta);
3279
+ break;
3280
+ }
3281
+ }
3282
+ if (direction === 0) {
3283
+ return 0;
3284
+ }
3285
+ let oldest = newest;
3286
+ for (let i = newestIndex - 1; i >= 0; i--) {
3287
+ const current = scrollHistory[i];
3288
+ const next = scrollHistory[i + 1];
3289
+ const delta = next.scroll - current.scroll;
3290
+ const deltaSign = Math.sign(delta);
3291
+ if (deltaSign !== 0 && deltaSign !== direction) {
3292
+ break;
3293
+ }
3294
+ if (now - current.time > 1e3) {
3295
+ break;
3296
+ }
3297
+ oldest = current;
3298
+ }
3299
+ const scrollDiff = newest.scroll - oldest.scroll;
3300
+ const timeDiff = newest.time - oldest.time;
3301
+ return timeDiff > 0 ? scrollDiff / timeDiff : 0;
3302
+ };
3303
+
3065
3304
  // src/core/updateScroll.ts
3066
3305
  function updateScroll(ctx, newScroll, forceUpdate, options) {
3067
3306
  var _a3;
@@ -3087,6 +3326,8 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
3087
3326
  if (scrollHistory.length > 5) {
3088
3327
  scrollHistory.shift();
3089
3328
  }
3329
+ const scrollVelocity = getScrollVelocity(state);
3330
+ updateAdaptiveRender(ctx, scrollVelocity);
3090
3331
  if (ignoreScrollFromMVCP && !scrollingTo) {
3091
3332
  const { lt, gt } = ignoreScrollFromMVCP;
3092
3333
  if (lt && newScroll < lt || gt && newScroll > gt) {
@@ -3110,7 +3351,7 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
3110
3351
  state.lastScrollDelta = scrollDelta;
3111
3352
  const runCalculateItems = () => {
3112
3353
  var _a4;
3113
- (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0 });
3354
+ (_a4 = state.triggerCalculateItemsInView) == null ? void 0 : _a4.call(state, { doMVCP: scrollingTo !== void 0, scrollVelocity });
3114
3355
  checkThresholds(ctx);
3115
3356
  };
3116
3357
  if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength && !state.pendingNativeMVCPAdjust) {
@@ -4190,9 +4431,32 @@ function initializeInitialScrollOnMount(ctx, options) {
4190
4431
  }
4191
4432
  function handleInitialScrollDataChange(ctx, options) {
4192
4433
  var _a3, _b, _c;
4193
- const { dataLength, didDataChange, initialScrollAtEnd, stylePaddingBottom, useBootstrapInitialScroll } = options;
4434
+ const {
4435
+ dataLength,
4436
+ didDataChange,
4437
+ initialScrollAtEnd,
4438
+ latestInitialScroll,
4439
+ latestInitialScrollSessionKind,
4440
+ stylePaddingBottom,
4441
+ useBootstrapInitialScroll
4442
+ } = options;
4194
4443
  const state = ctx.state;
4195
4444
  const previousDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
4445
+ const isFirstNonEmptyData = !state.hasHadNonEmptyData && dataLength > 0;
4446
+ if (dataLength > 0) {
4447
+ state.hasHadNonEmptyData = true;
4448
+ }
4449
+ if (isFirstNonEmptyData) {
4450
+ if (latestInitialScroll) {
4451
+ setInitialScrollTarget(state, latestInitialScroll);
4452
+ setInitialScrollSession(state, {
4453
+ kind: latestInitialScrollSessionKind,
4454
+ previousDataLength
4455
+ });
4456
+ } else {
4457
+ clearPreservedInitialScrollTarget(state);
4458
+ }
4459
+ }
4196
4460
  if (state.initialScrollSession) {
4197
4461
  state.initialScrollSession.previousDataLength = dataLength;
4198
4462
  }
@@ -4416,45 +4680,6 @@ function updateTotalSize(ctx) {
4416
4680
  }
4417
4681
  }
4418
4682
 
4419
- // src/utils/getScrollVelocity.ts
4420
- var getScrollVelocity = (state) => {
4421
- const { scrollHistory } = state;
4422
- const newestIndex = scrollHistory.length - 1;
4423
- if (newestIndex < 1) {
4424
- return 0;
4425
- }
4426
- const newest = scrollHistory[newestIndex];
4427
- const now = Date.now();
4428
- let direction = 0;
4429
- for (let i = newestIndex; i > 0; i--) {
4430
- const delta = scrollHistory[i].scroll - scrollHistory[i - 1].scroll;
4431
- if (delta !== 0) {
4432
- direction = Math.sign(delta);
4433
- break;
4434
- }
4435
- }
4436
- if (direction === 0) {
4437
- return 0;
4438
- }
4439
- let oldest = newest;
4440
- for (let i = newestIndex - 1; i >= 0; i--) {
4441
- const current = scrollHistory[i];
4442
- const next = scrollHistory[i + 1];
4443
- const delta = next.scroll - current.scroll;
4444
- const deltaSign = Math.sign(delta);
4445
- if (deltaSign !== 0 && deltaSign !== direction) {
4446
- break;
4447
- }
4448
- if (now - current.time > 1e3) {
4449
- break;
4450
- }
4451
- oldest = current;
4452
- }
4453
- const scrollDiff = newest.scroll - oldest.scroll;
4454
- const timeDiff = newest.time - oldest.time;
4455
- return timeDiff > 0 ? scrollDiff / timeDiff : 0;
4456
- };
4457
-
4458
4683
  // src/utils/updateSnapToOffsets.ts
4459
4684
  function updateSnapToOffsets(ctx) {
4460
4685
  const state = ctx.state;
@@ -4775,7 +5000,7 @@ function areViewabilityAmountTokensEqual(prev, next) {
4775
5000
  }
4776
5001
  function computeViewability(state, ctx, viewabilityConfig, containerId, key, scrollSize, item, index) {
4777
5002
  const { sizes, scroll: scrollState } = state;
4778
- const topPad = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
5003
+ const topPad = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "alignItemsAtEndPadding") || 0) + (peek$(ctx, "headerSize") || 0);
4779
5004
  const { itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold } = viewabilityConfig;
4780
5005
  const viewAreaMode = viewAreaCoveragePercentThreshold != null;
4781
5006
  const viewablePercentThreshold = viewAreaMode ? viewAreaCoveragePercentThreshold : itemVisiblePercentThreshold;
@@ -5190,7 +5415,7 @@ function updateViewabilityForCachedRange(ctx, viewabilityConfigCallbackPairs, sc
5190
5415
  function calculateItemsInView(ctx, params = {}) {
5191
5416
  const state = ctx.state;
5192
5417
  batchedUpdates(() => {
5193
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
5418
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
5194
5419
  const {
5195
5420
  columns,
5196
5421
  containerItemKeys,
@@ -5199,14 +5424,7 @@ function calculateItemsInView(ctx, params = {}) {
5199
5424
  indexByKey,
5200
5425
  minIndexSizeChanged,
5201
5426
  positions,
5202
- props: {
5203
- alwaysRenderIndicesArr,
5204
- alwaysRenderIndicesSet,
5205
- drawDistance,
5206
- getItemType,
5207
- keyExtractor,
5208
- onStickyHeaderChange
5209
- },
5427
+ props: { alwaysRenderIndicesArr, alwaysRenderIndicesSet, getItemType, keyExtractor, onStickyHeaderChange },
5210
5428
  scrollForNextCalculateItemsInView,
5211
5429
  scrollLength,
5212
5430
  sizes,
@@ -5218,6 +5436,7 @@ function calculateItemsInView(ctx, params = {}) {
5218
5436
  const stickyHeaderIndicesSet = state.props.stickyHeaderIndicesSet || /* @__PURE__ */ new Set();
5219
5437
  const alwaysRenderArr = alwaysRenderIndicesArr || [];
5220
5438
  const alwaysRenderSet = alwaysRenderIndicesSet || /* @__PURE__ */ new Set();
5439
+ const drawDistance = getEffectiveDrawDistance(ctx);
5221
5440
  const { dataChanged, doMVCP, forceFullItemPositions } = params;
5222
5441
  const bootstrapInitialScrollState = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "bootstrap" ? state.initialScrollSession.bootstrap : void 0;
5223
5442
  const suppressInitialScrollSideEffects = !!bootstrapInitialScrollState;
@@ -5226,12 +5445,12 @@ function calculateItemsInView(ctx, params = {}) {
5226
5445
  return;
5227
5446
  }
5228
5447
  let totalSize = getContentSize(ctx);
5229
- const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
5448
+ const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "alignItemsAtEndPadding") + peek$(ctx, "headerSize");
5230
5449
  const numColumns = peek$(ctx, "numColumns");
5231
- const speed = getScrollVelocity(state);
5450
+ const speed = (_b = params.scrollVelocity) != null ? _b : getScrollVelocity(state);
5232
5451
  const scrollExtra = 0;
5233
5452
  const { initialScroll, queuedInitialLayout } = state;
5234
- const scrollState = suppressInitialScrollSideEffects ? (_b = bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.scroll) != null ? _b : state.scroll : !queuedInitialLayout && hasActiveInitialScroll(state) && initialScroll ? (
5453
+ const scrollState = suppressInitialScrollSideEffects ? (_c = bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.scroll) != null ? _c : state.scroll : !queuedInitialLayout && hasActiveInitialScroll(state) && initialScroll ? (
5235
5454
  // Before the initial layout settles, keep viewport math anchored to the
5236
5455
  // current initial-scroll target instead of transient native adjustments.
5237
5456
  resolveInitialScrollOffset(ctx, initialScroll)
@@ -5255,19 +5474,25 @@ function calculateItemsInView(ctx, params = {}) {
5255
5474
  };
5256
5475
  updateScroll2(scrollState);
5257
5476
  const previousStickyIndex = peek$(ctx, "activeStickyIndex");
5258
- const currentStickyIdx = stickyHeaderIndicesArr.length > 0 ? findCurrentStickyIndex(stickyHeaderIndicesArr, scroll, state) : -1;
5259
- const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyHeaderIndicesArr[currentStickyIdx] : -1;
5260
- const stickyIndexDidChange = previousStickyIndex !== nextActiveStickyIndex;
5261
- if (currentStickyIdx >= 0 || previousStickyIndex >= 0) {
5262
- set$(ctx, "activeStickyIndex", nextActiveStickyIndex);
5263
- }
5264
- const shouldNotifyStickyHeaderChange = !!onStickyHeaderChange && stickyHeaderIndicesArr.length > 0 && stickyIndexDidChange;
5265
- const finishCalculateItemsInView = shouldNotifyStickyHeaderChange ? () => {
5266
- const item = data[nextActiveStickyIndex];
5267
- if (item !== void 0) {
5268
- onStickyHeaderChange == null ? void 0 : onStickyHeaderChange({ index: nextActiveStickyIndex, item });
5477
+ const resolveStickyState = () => {
5478
+ const currentStickyIdx = stickyHeaderIndicesArr.length > 0 ? findCurrentStickyIndex(stickyHeaderIndicesArr, scroll, state) : -1;
5479
+ const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyHeaderIndicesArr[currentStickyIdx] : -1;
5480
+ const stickyIndexDidChange = previousStickyIndex !== nextActiveStickyIndex;
5481
+ if (currentStickyIdx >= 0 || previousStickyIndex >= 0) {
5482
+ set$(ctx, "activeStickyIndex", nextActiveStickyIndex);
5269
5483
  }
5270
- } : void 0;
5484
+ const shouldNotifyStickyHeaderChange = !!onStickyHeaderChange && stickyHeaderIndicesArr.length > 0 && stickyIndexDidChange;
5485
+ return {
5486
+ currentStickyIdx,
5487
+ finishCalculateItemsInView: shouldNotifyStickyHeaderChange ? () => {
5488
+ const item = data[nextActiveStickyIndex];
5489
+ if (item !== void 0) {
5490
+ onStickyHeaderChange == null ? void 0 : onStickyHeaderChange({ index: nextActiveStickyIndex, item });
5491
+ }
5492
+ } : void 0
5493
+ };
5494
+ };
5495
+ let stickyState = dataChanged ? void 0 : resolveStickyState();
5271
5496
  let scrollBufferTop = drawDistance;
5272
5497
  let scrollBufferBottom = drawDistance;
5273
5498
  if (speed > 0 || speed === 0 && scroll < Math.max(50, drawDistance)) {
@@ -5300,7 +5525,7 @@ function calculateItemsInView(ctx, params = {}) {
5300
5525
  scrollBottom
5301
5526
  );
5302
5527
  }
5303
- finishCalculateItemsInView == null ? void 0 : finishCalculateItemsInView();
5528
+ (_d = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _d.call(stickyState);
5304
5529
  return;
5305
5530
  }
5306
5531
  }
@@ -5309,7 +5534,7 @@ function calculateItemsInView(ctx, params = {}) {
5309
5534
  if (dataChanged) {
5310
5535
  resetLayoutCachesForDataChange(state);
5311
5536
  }
5312
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
5537
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_e = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _e : 0;
5313
5538
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
5314
5539
  updateItemPositions(ctx, dataChanged, {
5315
5540
  doMVCP,
@@ -5335,21 +5560,24 @@ function calculateItemsInView(ctx, params = {}) {
5335
5560
  }
5336
5561
  }
5337
5562
  const scrollBeforeMVCP = state.scroll;
5338
- const scrollAdjustPendingBeforeMVCP = (_d = peek$(ctx, "scrollAdjustPending")) != null ? _d : 0;
5563
+ const scrollAdjustPendingBeforeMVCP = (_f = peek$(ctx, "scrollAdjustPending")) != null ? _f : 0;
5339
5564
  checkMVCP == null ? void 0 : checkMVCP();
5340
- const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0) !== scrollAdjustPendingBeforeMVCP);
5565
+ const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_g = peek$(ctx, "scrollAdjustPending")) != null ? _g : 0) !== scrollAdjustPendingBeforeMVCP);
5341
5566
  if (didMVCPAdjustScroll && initialScroll) {
5342
5567
  updateScroll2(state.scroll);
5343
5568
  updateScrollRange();
5344
5569
  }
5570
+ if (dataChanged) {
5571
+ stickyState = resolveStickyState();
5572
+ }
5345
5573
  let startBuffered = null;
5346
5574
  let startBufferedId = null;
5347
5575
  let endBuffered = null;
5348
- let loopStart = (_f = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _f : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
5576
+ let loopStart = (_h = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _h : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
5349
5577
  for (let i = loopStart; i >= 0; i--) {
5350
- const id = (_g = idCache[i]) != null ? _g : getId(state, i);
5578
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
5351
5579
  const top = positions[i];
5352
- const size = (_h = sizes.get(id)) != null ? _h : getItemSize(ctx, id, i, data[i]);
5580
+ const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
5353
5581
  const bottom = top + size;
5354
5582
  if (bottom > scrollTopBuffered) {
5355
5583
  loopStart = i;
@@ -5384,8 +5612,8 @@ function calculateItemsInView(ctx, params = {}) {
5384
5612
  };
5385
5613
  const dataLength = data.length;
5386
5614
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
5387
- const id = (_i = idCache[i]) != null ? _i : getId(state, i);
5388
- const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
5615
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
5616
+ const size = (_l = sizes.get(id)) != null ? _l : getItemSize(ctx, id, i, data[i]);
5389
5617
  const top = positions[i];
5390
5618
  if (!foundEnd) {
5391
5619
  trackVisibleRange(visibleRange, i, top, size, scroll, scrollBottom);
@@ -5441,7 +5669,7 @@ function calculateItemsInView(ctx, params = {}) {
5441
5669
  const needNewContainers = [];
5442
5670
  const needNewContainersSet = /* @__PURE__ */ new Set();
5443
5671
  for (let i = startBuffered; i <= endBuffered; i++) {
5444
- const id = (_k = idCache[i]) != null ? _k : getId(state, i);
5672
+ const id = (_m = idCache[i]) != null ? _m : getId(state, i);
5445
5673
  if (!containerItemKeys.has(id)) {
5446
5674
  needNewContainersSet.add(i);
5447
5675
  needNewContainers.push(i);
@@ -5450,7 +5678,7 @@ function calculateItemsInView(ctx, params = {}) {
5450
5678
  if (alwaysRenderArr.length > 0) {
5451
5679
  for (const index of alwaysRenderArr) {
5452
5680
  if (index < 0 || index >= dataLength) continue;
5453
- const id = (_l = idCache[index]) != null ? _l : getId(state, index);
5681
+ const id = (_n = idCache[index]) != null ? _n : getId(state, index);
5454
5682
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
5455
5683
  needNewContainersSet.add(index);
5456
5684
  needNewContainers.push(index);
@@ -5461,7 +5689,7 @@ function calculateItemsInView(ctx, params = {}) {
5461
5689
  handleStickyActivation(
5462
5690
  ctx,
5463
5691
  stickyHeaderIndicesArr,
5464
- currentStickyIdx,
5692
+ (_o = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _o : -1,
5465
5693
  needNewContainers,
5466
5694
  needNewContainersSet,
5467
5695
  startBuffered,
@@ -5487,7 +5715,7 @@ function calculateItemsInView(ctx, params = {}) {
5487
5715
  for (const allocation of availableContainerAllocations) {
5488
5716
  const i = allocation.itemIndex;
5489
5717
  const containerIndex = allocation.containerIndex;
5490
- const id = (_m = idCache[i]) != null ? _m : getId(state, i);
5718
+ const id = (_p = idCache[i]) != null ? _p : getId(state, i);
5491
5719
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
5492
5720
  if (oldKey && oldKey !== id) {
5493
5721
  containerItemKeys.delete(oldKey);
@@ -5498,7 +5726,7 @@ function calculateItemsInView(ctx, params = {}) {
5498
5726
  state.containerItemTypes.set(containerIndex, allocation.itemType);
5499
5727
  }
5500
5728
  containerItemKeys.set(id, containerIndex);
5501
- (_n = state.userScrollAnchorReset) == null ? void 0 : _n.keys.add(id);
5729
+ (_q = state.userScrollAnchorReset) == null ? void 0 : _q.keys.add(id);
5502
5730
  const containerSticky = `containerSticky${containerIndex}`;
5503
5731
  const isSticky = stickyHeaderIndicesSet.has(i);
5504
5732
  const isAlwaysRender = alwaysRenderSet.has(i);
@@ -5529,14 +5757,12 @@ function calculateItemsInView(ctx, params = {}) {
5529
5757
  if (state.userScrollAnchorReset) {
5530
5758
  if (state.userScrollAnchorReset.keys.size === 0) {
5531
5759
  state.userScrollAnchorReset = void 0;
5532
- } else {
5533
- state.userScrollAnchorReset.batchSize = state.userScrollAnchorReset.keys.size;
5534
5760
  }
5535
5761
  }
5536
5762
  if (alwaysRenderArr.length > 0) {
5537
5763
  for (const index of alwaysRenderArr) {
5538
5764
  if (index < 0 || index >= dataLength) continue;
5539
- const id = (_o = idCache[index]) != null ? _o : getId(state, index);
5765
+ const id = (_r = idCache[index]) != null ? _r : getId(state, index);
5540
5766
  const containerIndex = containerItemKeys.get(id);
5541
5767
  if (containerIndex !== void 0) {
5542
5768
  state.stickyContainerPool.add(containerIndex);
@@ -5550,7 +5776,7 @@ function calculateItemsInView(ctx, params = {}) {
5550
5776
  stickyHeaderIndicesArr,
5551
5777
  scroll,
5552
5778
  drawDistance,
5553
- currentStickyIdx,
5779
+ (_s = stickyState == null ? void 0 : stickyState.currentStickyIdx) != null ? _s : -1,
5554
5780
  pendingRemoval,
5555
5781
  alwaysRenderSet
5556
5782
  );
@@ -5611,7 +5837,7 @@ function calculateItemsInView(ctx, params = {}) {
5611
5837
  );
5612
5838
  }
5613
5839
  }
5614
- finishCalculateItemsInView == null ? void 0 : finishCalculateItemsInView();
5840
+ (_t = stickyState == null ? void 0 : stickyState.finishCalculateItemsInView) == null ? void 0 : _t.call(stickyState);
5615
5841
  });
5616
5842
  }
5617
5843
 
@@ -5694,8 +5920,9 @@ function doInitialAllocateContainers(ctx) {
5694
5920
  const state = ctx.state;
5695
5921
  const {
5696
5922
  scrollLength,
5697
- props: { data, drawDistance, getFixedItemSize, getItemType, numColumns, estimatedItemSize }
5923
+ props: { data, getFixedItemSize, getItemType, numColumns, estimatedItemSize }
5698
5924
  } = state;
5925
+ const drawDistance = getEffectiveDrawDistance(ctx);
5699
5926
  const hasContainers = peek$(ctx, "numContainers");
5700
5927
  if (scrollLength > 0 && data.length > 0 && !hasContainers) {
5701
5928
  let averageItemSize;
@@ -5706,12 +5933,12 @@ function doInitialAllocateContainers(ctx) {
5706
5933
  const item = data[i];
5707
5934
  if (item !== void 0) {
5708
5935
  const itemType = (_a3 = getItemType == null ? void 0 : getItemType(item, i)) != null ? _a3 : "";
5709
- totalSize += (_b = getFixedItemSize(item, i, itemType)) != null ? _b : estimatedItemSize;
5936
+ totalSize += ((_b = getFixedItemSize(item, i, itemType)) != null ? _b : estimatedItemSize) + ctx.scrollAxisGap;
5710
5937
  }
5711
5938
  }
5712
5939
  averageItemSize = totalSize / num;
5713
5940
  } else {
5714
- averageItemSize = estimatedItemSize;
5941
+ averageItemSize = estimatedItemSize + ctx.scrollAxisGap;
5715
5942
  }
5716
5943
  const numContainers = Math.max(
5717
5944
  1,
@@ -5770,6 +5997,7 @@ function handleLayout(ctx, layoutParam, setCanRender) {
5770
5997
  if (didChange) {
5771
5998
  state.scrollLength = scrollLength;
5772
5999
  state.otherAxisSize = otherAxisSize;
6000
+ updateContentMetricsState(ctx);
5773
6001
  state.lastBatchingAction = Date.now();
5774
6002
  state.scrollForNextCalculateItemsInView = void 0;
5775
6003
  if (scrollLength > 0) {
@@ -6006,28 +6234,12 @@ function updateContentInsetEndAdjustment(ctx, previousContentInsetEndAdjustment)
6006
6234
 
6007
6235
  // src/core/updateItemSize.ts
6008
6236
  function runOrScheduleMVCPRecalculate(ctx) {
6009
- var _a3, _b;
6237
+ var _a3;
6010
6238
  const state = ctx.state;
6011
6239
  if (state.userScrollAnchorReset !== void 0) {
6012
- const replacementBatchSize = (_a3 = state.userScrollAnchorReset.batchSize) != null ? _a3 : state.userScrollAnchorReset.keys.size;
6013
- const replacementMeasurementBatchThreshold = 3;
6014
- const shouldBatchReplacementMeasurements = replacementBatchSize > replacementMeasurementBatchThreshold;
6015
- if (shouldBatchReplacementMeasurements) {
6016
- if (state.queuedMVCPRecalculate === void 0) {
6017
- state.queuedMVCPRecalculate = requestAnimationFrame(() => {
6018
- var _a4;
6019
- state.queuedMVCPRecalculate = void 0;
6020
- calculateItemsInView(ctx);
6021
- if (((_a4 = state.userScrollAnchorReset) == null ? void 0 : _a4.keys.size) === 0) {
6022
- state.userScrollAnchorReset = void 0;
6023
- }
6024
- });
6025
- }
6026
- } else {
6027
- calculateItemsInView(ctx);
6028
- if (((_b = state.userScrollAnchorReset) == null ? void 0 : _b.keys.size) === 0) {
6029
- state.userScrollAnchorReset = void 0;
6030
- }
6240
+ calculateItemsInView(ctx);
6241
+ if (((_a3 = state.userScrollAnchorReset) == null ? void 0 : _a3.keys.size) === 0) {
6242
+ state.userScrollAnchorReset = void 0;
6031
6243
  }
6032
6244
  } else {
6033
6245
  if (!state.mvcpAnchorLock) {
@@ -6435,10 +6647,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6435
6647
  startBuffered: state.startBuffered
6436
6648
  }),
6437
6649
  reportContentInset: (inset) => {
6438
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
6439
- const previousInset = state.contentInsetOverride;
6440
- state.contentInsetOverride = inset != null ? inset : void 0;
6441
- 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);
6650
+ const didChange = setContentInsetOverride(ctx, inset);
6442
6651
  updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
6443
6652
  if (didChange) {
6444
6653
  retargetActiveInitialScrollAtEnd(ctx);
@@ -6490,6 +6699,9 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) {
6490
6699
  scrollTo(ctx, params);
6491
6700
  return true;
6492
6701
  }),
6702
+ setItemSize: (itemKey, size) => {
6703
+ updateItemSize(ctx, itemKey, size);
6704
+ },
6493
6705
  setScrollProcessingEnabled: (enabled) => {
6494
6706
  state.scrollProcessingEnabled = enabled;
6495
6707
  },
@@ -6714,7 +6926,7 @@ var LegendList = typedMemo(
6714
6926
  })
6715
6927
  );
6716
6928
  var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
6717
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i;
6929
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
6718
6930
  const noopOnScroll = useCallback((_event) => {
6719
6931
  }, []);
6720
6932
  if (props.recycleItems === void 0) {
@@ -6745,6 +6957,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6745
6957
  initialScrollAtEnd = false,
6746
6958
  initialScrollIndex: initialScrollIndexProp,
6747
6959
  initialScrollOffset: initialScrollOffsetProp,
6960
+ experimental_adaptiveRender,
6748
6961
  itemsAreEqual,
6749
6962
  keyExtractor: keyExtractorProp,
6750
6963
  ListEmptyComponent,
@@ -6794,10 +7007,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6794
7007
  ...restProps
6795
7008
  } = rest;
6796
7009
  const contentContainerStyleBase = StyleSheet.flatten(contentContainerStyleProp);
6797
- const shouldFlexGrow = alignItemsAtEnd && (horizontal ? (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minWidth) == null : (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minHeight) == null);
7010
+ const useAlignItemsAtEndPadding = alignItemsAtEnd && !horizontal && (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minHeight) == null && dataProp.length > 0;
7011
+ const shouldFlexGrow = alignItemsAtEnd && !useAlignItemsAtEndPadding && (horizontal ? (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minWidth) == null : (contentContainerStyleBase == null ? void 0 : contentContainerStyleBase.minHeight) == null);
6798
7012
  const contentContainerStyle = {
6799
7013
  ...contentContainerStyleBase,
6800
- ...alignItemsAtEnd ? {
7014
+ ...alignItemsAtEnd && !useAlignItemsAtEndPadding ? {
6801
7015
  display: "flex",
6802
7016
  flexDirection: horizontal ? "row" : "column",
6803
7017
  ...shouldFlexGrow ? { flexGrow: 1 } : {},
@@ -6840,6 +7054,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6840
7054
  const [, scheduleImperativeScrollCommit] = React3.useReducer((value) => value + 1, 0);
6841
7055
  const ctx = useStateContext();
6842
7056
  ctx.columnWrapperStyle = columnWrapperStyle || (contentContainerStyle ? createColumnWrapperStyle(contentContainerStyle) : void 0);
7057
+ 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;
7058
+ const nextScrollAxisGap = typeof scrollAxisGap === "number" && Number.isFinite(scrollAxisGap) ? scrollAxisGap : 0;
6843
7059
  const refScroller = useRef(null);
6844
7060
  const combinedRef = useCombinedRef(refScroller, refScrollView);
6845
7061
  const keyExtractor = keyExtractorProp != null ? keyExtractorProp : ((_item, index) => index.toString());
@@ -6853,8 +7069,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6853
7069
  anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex,
6854
7070
  alwaysRender == null ? void 0 : alwaysRender.top,
6855
7071
  alwaysRender == null ? void 0 : alwaysRender.bottom,
6856
- (_d = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _d.join(","),
6857
- (_e = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _e.join(","),
7072
+ (_j = alwaysRender == null ? void 0 : alwaysRender.indices) == null ? void 0 : _j.join(","),
7073
+ (_k = alwaysRender == null ? void 0 : alwaysRender.keys) == null ? void 0 : _k.join(","),
6858
7074
  dataProp,
6859
7075
  dataVersion,
6860
7076
  keyExtractor
@@ -6882,6 +7098,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6882
7098
  endNoBuffer: -1,
6883
7099
  endReachedSnapshot: void 0,
6884
7100
  firstFullyOnScreenIndex: -1,
7101
+ hasHadNonEmptyData: dataProp.length > 0,
6885
7102
  idCache: [],
6886
7103
  idsInView: [],
6887
7104
  indexByKey: /* @__PURE__ */ new Map(),
@@ -6924,6 +7141,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6924
7141
  startReachedSnapshotDataChangeEpoch: void 0,
6925
7142
  stickyContainerPool: /* @__PURE__ */ new Set(),
6926
7143
  stickyContainers: /* @__PURE__ */ new Map(),
7144
+ timeoutAdaptiveRender: void 0,
6927
7145
  timeouts: /* @__PURE__ */ new Set(),
6928
7146
  totalSize: 0,
6929
7147
  viewabilityConfigCallbackPairs: void 0
@@ -6942,11 +7160,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6942
7160
  const state = refState.current;
6943
7161
  const isFirstLocal = state.isFirst;
6944
7162
  const previousNumColumnsProp = state.props.numColumns;
6945
- state.didColumnsChange = numColumnsProp !== previousNumColumnsProp;
7163
+ const didScrollAxisGapChange = !isFirstLocal && ctx.scrollAxisGap !== nextScrollAxisGap;
7164
+ ctx.scrollAxisGap = nextScrollAxisGap;
7165
+ state.didColumnsChange = numColumnsProp !== previousNumColumnsProp || didScrollAxisGapChange;
6946
7166
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6947
7167
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6948
7168
  const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6949
- if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
7169
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_l = state.initialScroll) == null ? void 0 : _l.viewPosition) === 1 && state.props.data.length > 0) {
6950
7170
  clearPreservedInitialScrollTarget(state);
6951
7171
  }
6952
7172
  if (didDataChangeLocal) {
@@ -6958,14 +7178,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6958
7178
  const throttledOnScroll = useThrottledOnScroll(onScrollProp != null ? onScrollProp : noopOnScroll, scrollEventThrottle != null ? scrollEventThrottle : 0);
6959
7179
  const throttleScrollFn = scrollEventThrottle && onScrollProp ? throttledOnScroll : onScrollProp;
6960
7180
  const anchoredEndSpaceResolved = anchoredEndSpace ? { ...anchoredEndSpace, includeInEndInset: true } : anchoredEndSpace;
6961
- const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_g = state.props.anchoredEndSpace) == null ? void 0 : _g.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
7181
+ const didAnchoredEndSpaceAnchorIndexChange = !isFirstLocal && !didDataChangeLocal && ((_m = state.props.anchoredEndSpace) == null ? void 0 : _m.anchorIndex) !== (anchoredEndSpaceResolved == null ? void 0 : anchoredEndSpaceResolved.anchorIndex);
6962
7182
  state.props = {
7183
+ adaptiveRender: experimental_adaptiveRender,
6963
7184
  alignItemsAtEnd,
7185
+ alignItemsAtEndPaddingEnabled: useAlignItemsAtEndPadding,
6964
7186
  alwaysRender,
6965
7187
  alwaysRenderIndicesArr: alwaysRenderIndices.arr,
6966
7188
  alwaysRenderIndicesSet: alwaysRenderIndices.set,
6967
7189
  anchoredEndSpace: anchoredEndSpaceResolved,
6968
7190
  animatedProps: animatedPropsInternal,
7191
+ contentContainerAlignItems: contentContainerStyle.alignItems,
6969
7192
  contentInset,
6970
7193
  contentInsetEndAdjustment: contentInsetEndAdjustmentResolved,
6971
7194
  data: dataProp,
@@ -7018,6 +7241,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7018
7241
  const prevPaddingTop = peek$(ctx, "stylePaddingTop");
7019
7242
  setPaddingTop(ctx, { stylePaddingTop: stylePaddingTopState });
7020
7243
  refState.current.props.stylePaddingBottom = stylePaddingBottomState;
7244
+ updateContentMetricsState(ctx);
7021
7245
  let paddingDiff = stylePaddingTopState - prevPaddingTop;
7022
7246
  if (shouldAdjustPadding && maintainVisibleContentPositionConfig.size && paddingDiff && prevPaddingTop !== void 0 && Platform.OS === "ios") ;
7023
7247
  };
@@ -7049,7 +7273,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7049
7273
  useBootstrapInitialScroll: usesBootstrapInitialScroll
7050
7274
  });
7051
7275
  }, []);
7052
- if (isFirstLocal || didDataChangeLocal || numColumnsProp !== peek$(ctx, "numColumns")) {
7276
+ if (isFirstLocal || didDataChangeLocal || state.didColumnsChange) {
7053
7277
  refState.current.lastBatchingAction = Date.now();
7054
7278
  if (!keyExtractorProp && !isFirstLocal && didDataChangeLocal) {
7055
7279
  refState.current.sizes.clear();
@@ -7066,6 +7290,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7066
7290
  dataLength: dataProp.length,
7067
7291
  didDataChange: didDataChangeLocal,
7068
7292
  initialScrollAtEnd,
7293
+ latestInitialScroll: initialScrollProp,
7294
+ latestInitialScrollSessionKind: initialScrollUsesOffsetOnly ? "offset" : "bootstrap",
7069
7295
  stylePaddingBottom: stylePaddingBottomState,
7070
7296
  useBootstrapInitialScroll: usesBootstrapInitialScroll
7071
7297
  });
@@ -7136,7 +7362,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7136
7362
  }, [snapToIndices]);
7137
7363
  useLayoutEffect(
7138
7364
  () => initializeStateVars(true),
7139
- [dataVersion, memoizedLastItemKeys.join(","), numColumnsProp, stylePaddingBottomState, stylePaddingTopState]
7365
+ [
7366
+ dataVersion,
7367
+ memoizedLastItemKeys.join(","),
7368
+ numColumnsProp,
7369
+ nextScrollAxisGap,
7370
+ stylePaddingBottomState,
7371
+ stylePaddingTopState,
7372
+ useAlignItemsAtEndPadding
7373
+ ]
7140
7374
  );
7141
7375
  useLayoutEffect(() => {
7142
7376
  const {
@@ -7155,7 +7389,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7155
7389
  state.didColumnsChange = false;
7156
7390
  state.didDataChange = false;
7157
7391
  state.isFirst = false;
7158
- }, [dataProp, dataVersion, numColumnsProp]);
7392
+ }, [dataProp, dataVersion, numColumnsProp, nextScrollAxisGap]);
7159
7393
  useLayoutEffect(() => {
7160
7394
  var _a4;
7161
7395
  set$(ctx, "extraData", extraData);
@@ -7203,6 +7437,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7203
7437
  useInit(() => {
7204
7438
  });
7205
7439
  useImperativeHandle(forwardedRef, () => createImperativeHandle(ctx, scheduleImperativeScrollCommit), []);
7440
+ useEffect(() => {
7441
+ return () => {
7442
+ for (const timeout of state.timeouts) {
7443
+ clearTimeout(timeout);
7444
+ }
7445
+ state.timeouts.clear();
7446
+ };
7447
+ }, [state]);
7206
7448
  useLayoutEffect(() => {
7207
7449
  var _a4;
7208
7450
  (_a4 = state.runPendingScrollToEnd) == null ? void 0 : _a4.call(state);
@@ -7250,7 +7492,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7250
7492
  onScroll: onScrollHandler,
7251
7493
  recycleItems,
7252
7494
  refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React3.cloneElement(refreshControlElement, {
7253
- progressViewOffset: ((_h = refreshControlElement.props.progressViewOffset) != null ? _h : 0) + stylePaddingTopState
7495
+ progressViewOffset: ((_n = refreshControlElement.props.progressViewOffset) != null ? _n : 0) + stylePaddingTopState
7254
7496
  }) : refreshControlElement : onRefresh && /* @__PURE__ */ React3.createElement(
7255
7497
  RefreshControl,
7256
7498
  {
@@ -7261,7 +7503,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
7261
7503
  ),
7262
7504
  refScrollView: combinedRef,
7263
7505
  renderScrollComponent,
7264
- scrollAdjustHandler: (_i = refState.current) == null ? void 0 : _i.scrollAdjustHandler,
7506
+ scrollAdjustHandler: (_o = refState.current) == null ? void 0 : _o.scrollAdjustHandler,
7265
7507
  scrollEventThrottle: 0,
7266
7508
  snapToIndices,
7267
7509
  stickyHeaderIndices,
@@ -7317,4 +7559,4 @@ var internal = {
7317
7559
  var LegendList3 = LegendListRuntime;
7318
7560
  var internal2 = internal;
7319
7561
 
7320
- export { LegendList3 as LegendList, internal2 as internal, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };
7562
+ export { LegendList3 as LegendList, internal2 as internal, useAdaptiveRender, useAdaptiveRenderChange, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };