@legendapp/list 3.0.0-beta.53 → 3.0.0-beta.54

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.
@@ -737,6 +737,7 @@ function getContainerPositionStyle({
737
737
  }
738
738
  var Container = typedMemo(function Container2({
739
739
  id,
740
+ itemKey,
740
741
  recycleItems,
741
742
  horizontal,
742
743
  getRenderedItem: getRenderedItem2,
@@ -748,11 +749,10 @@ var Container = typedMemo(function Container2({
748
749
  const { columnWrapperStyle, animatedScrollY } = ctx;
749
750
  const positionComponentInternal = ctx.state.props.positionComponentInternal;
750
751
  const stickyPositionComponentInternal = ctx.state.props.stickyPositionComponentInternal;
751
- const [column = 0, span = 1, data, itemKey, numColumns = 1, extraData, isSticky] = useArr$([
752
+ const [column = 0, span = 1, data, numColumns = 1, extraData, isSticky] = useArr$([
752
753
  `containerColumn${id}`,
753
754
  `containerSpan${id}`,
754
755
  `containerItemData${id}`,
755
- `containerItemKey${id}`,
756
756
  "numColumns",
757
757
  "extraData",
758
758
  `containerSticky${id}`
@@ -870,6 +870,39 @@ var Container = typedMemo(function Container2({
870
870
  );
871
871
  });
872
872
 
873
+ // src/components/ContainerSlot.tsx
874
+ function ContainerSlotBase({
875
+ id,
876
+ horizontal,
877
+ recycleItems,
878
+ ItemSeparatorComponent,
879
+ updateItemSize: updateItemSize2,
880
+ getRenderedItem: getRenderedItem2,
881
+ stickyHeaderConfig,
882
+ ContainerComponent = Container
883
+ }) {
884
+ const [itemKey] = useArr$([`containerItemKey${id}`]);
885
+ if (itemKey === void 0) {
886
+ return null;
887
+ }
888
+ return /* @__PURE__ */ React3__namespace.createElement(
889
+ ContainerComponent,
890
+ {
891
+ getRenderedItem: getRenderedItem2,
892
+ horizontal,
893
+ ItemSeparatorComponent,
894
+ id,
895
+ itemKey,
896
+ recycleItems,
897
+ stickyHeaderConfig,
898
+ updateItemSize: updateItemSize2
899
+ }
900
+ );
901
+ }
902
+ var ContainerSlot = typedMemo(function ContainerSlot2(props) {
903
+ return /* @__PURE__ */ React3__namespace.createElement(ContainerSlotBase, { ...props });
904
+ });
905
+
873
906
  // src/utils/reordering.ts
874
907
  var mapFn = (element) => {
875
908
  const indexStr = element.getAttribute("data-index");
@@ -989,9 +1022,12 @@ var ContainersInner = typedMemo(function ContainersInner2({ horizontal, numColum
989
1022
  const ref = React3.useRef(null);
990
1023
  const ctx = useStateContext();
991
1024
  const columnWrapperStyle = ctx.columnWrapperStyle;
992
- const [otherAxisSize, totalSize] = useArr$(["otherAxisSize", "totalSize"]);
1025
+ const [otherAxisSize, readyToRender, totalSize] = useArr$(["otherAxisSize", "readyToRender", "totalSize"]);
993
1026
  useDOMOrder(ref);
994
- const style = horizontal ? { minHeight: otherAxisSize, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, position: "relative" };
1027
+ const style = horizontal ? { minHeight: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative" };
1028
+ if (!readyToRender) {
1029
+ style.pointerEvents = "none";
1030
+ }
995
1031
  if (columnWrapperStyle && numColumns > 1) {
996
1032
  const { columnGap, rowGap, gap } = columnWrapperStyle;
997
1033
  const gapX = columnGap || gap || 0;
@@ -1019,12 +1055,12 @@ var Containers = typedMemo(function Containers2({
1019
1055
  getRenderedItem: getRenderedItem2,
1020
1056
  stickyHeaderConfig
1021
1057
  }) {
1022
- const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1058
+ const [numContainersPooled, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1023
1059
  const containers = [];
1024
- for (let i = 0; i < numContainers; i++) {
1060
+ for (let i = 0; i < numContainersPooled; i++) {
1025
1061
  containers.push(
1026
1062
  /* @__PURE__ */ React3__namespace.createElement(
1027
- Container,
1063
+ ContainerSlot,
1028
1064
  {
1029
1065
  getRenderedItem: getRenderedItem2,
1030
1066
  horizontal,
@@ -1096,6 +1132,8 @@ function useRafCoalescer(callback) {
1096
1132
 
1097
1133
  // src/components/webConstants.ts
1098
1134
  var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
1135
+ var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
1136
+ var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
1099
1137
 
1100
1138
  // src/components/webScrollUtils.ts
1101
1139
  function getDocumentScrollerNode() {
@@ -1181,6 +1219,17 @@ function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll
1181
1219
  }
1182
1220
 
1183
1221
  // src/components/ListComponentScrollView.tsx
1222
+ var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
1223
+ var SCROLLBAR_HIDDEN_STYLE = `.${LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS}::-webkit-scrollbar:vertical{width:0;display:none;}.${LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS}::-webkit-scrollbar:horizontal{height:0;display:none;}`;
1224
+ function ensureScrollbarHiddenStyle() {
1225
+ if (typeof document === "undefined" || document.getElementById(SCROLLBAR_HIDDEN_STYLE_ID)) {
1226
+ return;
1227
+ }
1228
+ const styleElement = document.createElement("style");
1229
+ styleElement.id = SCROLLBAR_HIDDEN_STYLE_ID;
1230
+ styleElement.textContent = SCROLLBAR_HIDDEN_STYLE;
1231
+ document.head.appendChild(styleElement);
1232
+ }
1184
1233
  function getContentInsetEndAdjustmentEnd2(ctx) {
1185
1234
  var _a3, _b;
1186
1235
  const adjustment = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.props) == null ? void 0 : _b.contentInsetEndAdjustment;
@@ -1385,6 +1434,12 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1385
1434
  }
1386
1435
  };
1387
1436
  }, [isWindowScroll, onLayout]);
1437
+ const hiddenScrollIndicatorClassName = !isWindowScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
1438
+ React3.useLayoutEffect(() => {
1439
+ if (hiddenScrollIndicatorClassName) {
1440
+ ensureScrollbarHiddenStyle();
1441
+ }
1442
+ }, [hiddenScrollIndicatorClassName]);
1388
1443
  const scrollViewStyle = {
1389
1444
  ...isWindowScroll ? {} : {
1390
1445
  overflow: "auto",
@@ -1413,9 +1468,31 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1413
1468
  scrollEventThrottle: _scrollEventThrottle,
1414
1469
  ScrollComponent: _ScrollComponent,
1415
1470
  useWindowScroll: _useWindowScroll,
1471
+ className: scrollViewClassNameProp,
1416
1472
  ...webProps
1417
1473
  } = props;
1418
- return /* @__PURE__ */ React3__namespace.createElement("div", { ref: scrollRef, ...webProps, style: scrollViewStyle }, refreshControl, /* @__PURE__ */ React3__namespace.createElement("div", { className, ref: contentRef, style: contentStyle }, children, contentInsetEndAdjustmentSpacerStyle ? /* @__PURE__ */ React3__namespace.createElement("div", { "aria-hidden": true, style: contentInsetEndAdjustmentSpacerStyle }) : null));
1474
+ const scrollViewClassName = hiddenScrollIndicatorClassName ? scrollViewClassNameProp ? `${scrollViewClassNameProp} ${hiddenScrollIndicatorClassName}` : hiddenScrollIndicatorClassName : scrollViewClassNameProp;
1475
+ if (IS_DEV) {
1476
+ if (/(?:^|\s)(?:[a-z0-9_-]+:)*gap(?:-[xy])?-(?:\[[^\]]+\]|[^\s]+)/.test(
1477
+ `${contentContainerClassName != null ? contentContainerClassName : ""} ${scrollViewClassNameProp != null ? scrollViewClassNameProp : ""}`
1478
+ )) {
1479
+ warnDevOnce(
1480
+ "className-gap",
1481
+ "className/contentContainerClassName gap classes are not supported in LegendList because it needs to use exact values internally. Use contentContainerStyle={{ gap: ... }} or columnWrapperStyle instead."
1482
+ );
1483
+ }
1484
+ }
1485
+ return /* @__PURE__ */ React3__namespace.createElement(
1486
+ "div",
1487
+ {
1488
+ className: scrollViewClassName,
1489
+ ref: scrollRef,
1490
+ ...webProps,
1491
+ style: scrollViewStyle
1492
+ },
1493
+ refreshControl,
1494
+ /* @__PURE__ */ React3__namespace.createElement("div", { className, ref: contentRef, style: contentStyle }, children, contentInsetEndAdjustmentSpacerStyle ? /* @__PURE__ */ React3__namespace.createElement("div", { "aria-hidden": true, style: contentInsetEndAdjustmentSpacerStyle }) : null)
1495
+ );
1419
1496
  });
1420
1497
  function useValueListener$(key, callback) {
1421
1498
  const ctx = useStateContext();
@@ -1443,11 +1520,18 @@ function getScrollAdjustAxis(horizontal) {
1443
1520
  y: 1
1444
1521
  };
1445
1522
  }
1446
- function resolveScrollAdjustContentNode(el, contentNode) {
1447
- if ((contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === el) {
1448
- return contentNode;
1523
+ function getScrollAdjustTarget(ctx, contentNode) {
1524
+ var _a3, _b, _c;
1525
+ const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1526
+ const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
1527
+ let resolvedContentNode = null;
1528
+ if (scrollElement) {
1529
+ resolvedContentNode = (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1449
1530
  }
1450
- return el.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1531
+ return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
1532
+ }
1533
+ function scrollAdjustBy(el, left, top) {
1534
+ el.scrollBy({ behavior: "auto", left, top });
1451
1535
  }
1452
1536
  function ScrollAdjust() {
1453
1537
  const ctx = useStateContext();
@@ -1456,43 +1540,43 @@ function ScrollAdjust() {
1456
1540
  const resetPaddingBaselineRef = React3__namespace.useRef(void 0);
1457
1541
  const contentNodeRef = React3__namespace.useRef(null);
1458
1542
  const callback = React3__namespace.useCallback(() => {
1459
- var _a3, _b;
1543
+ var _a3;
1460
1544
  const scrollAdjust = peek$(ctx, "scrollAdjust");
1461
1545
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
1462
1546
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
1463
- const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1464
- if (scrollView && scrollOffset !== lastScrollOffsetRef.current) {
1465
- const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1466
- if (scrollDelta !== 0) {
1467
- const axis = getScrollAdjustAxis(!!ctx.state.props.horizontal);
1468
- const prevScroll = scrollView.getCurrentScrollOffset();
1469
- const el = scrollView.getScrollableNode();
1470
- const contentNode = resolveScrollAdjustContentNode(el, contentNodeRef.current);
1547
+ const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1548
+ if (scrollDelta !== 0) {
1549
+ const target = getScrollAdjustTarget(ctx, contentNodeRef.current);
1550
+ if (target) {
1551
+ const horizontal = !!ctx.state.props.horizontal;
1552
+ const axis = getScrollAdjustAxis(horizontal);
1553
+ const { contentNode, scrollElement: el } = target;
1554
+ const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
1471
1555
  contentNodeRef.current = contentNode;
1472
- const scrollBy = () => scrollView.scrollBy(axis.x * scrollDelta, axis.y * scrollDelta);
1473
- if (!contentNode) {
1474
- scrollBy();
1475
- lastScrollOffsetRef.current = scrollOffset;
1476
- return;
1477
- }
1478
- const totalSize = contentNode[axis.contentSizeKey];
1479
- const viewportSize = el[axis.viewportSizeKey];
1480
- const nextScroll = prevScroll + scrollDelta;
1481
- if (scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize) {
1482
- const previousPaddingEnd = (_b = resetPaddingBaselineRef.current) != null ? _b : contentNode.style[axis.paddingEndProp];
1483
- resetPaddingBaselineRef.current = previousPaddingEnd;
1484
- const pad = (nextScroll + viewportSize - totalSize) * 2;
1485
- contentNode.style[axis.paddingEndProp] = `${pad}px`;
1486
- void contentNode.offsetHeight;
1487
- scrollBy();
1488
- if (resetPaddingRafRef.current !== void 0) {
1489
- cancelAnimationFrame(resetPaddingRafRef.current);
1556
+ if (contentNode) {
1557
+ const prevScroll = horizontal ? el.scrollLeft : el.scrollTop;
1558
+ const totalSize = contentNode[axis.contentSizeKey];
1559
+ const viewportSize = el[axis.viewportSizeKey];
1560
+ const nextScroll = prevScroll + scrollDelta;
1561
+ const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
1562
+ if (needsTemporaryPadding) {
1563
+ const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
1564
+ resetPaddingBaselineRef.current = previousPaddingEnd;
1565
+ const pad = (nextScroll + viewportSize - totalSize) * 2;
1566
+ contentNode.style[axis.paddingEndProp] = `${pad}px`;
1567
+ void contentNode.offsetHeight;
1568
+ scrollBy();
1569
+ if (resetPaddingRafRef.current !== void 0) {
1570
+ cancelAnimationFrame(resetPaddingRafRef.current);
1571
+ }
1572
+ resetPaddingRafRef.current = requestAnimationFrame(() => {
1573
+ resetPaddingRafRef.current = void 0;
1574
+ resetPaddingBaselineRef.current = void 0;
1575
+ contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1576
+ });
1577
+ } else {
1578
+ scrollBy();
1490
1579
  }
1491
- resetPaddingRafRef.current = requestAnimationFrame(() => {
1492
- resetPaddingRafRef.current = void 0;
1493
- resetPaddingBaselineRef.current = void 0;
1494
- contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1495
- });
1496
1580
  } else {
1497
1581
  scrollBy();
1498
1582
  }
@@ -1803,10 +1887,9 @@ var initialScrollWatchdog = {
1803
1887
  clear(state) {
1804
1888
  initialScrollWatchdog.set(state, void 0);
1805
1889
  },
1806
- didObserveProgress(newScroll, watchdog) {
1807
- const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
1890
+ didReachTarget(newScroll, watchdog) {
1808
1891
  const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
1809
- return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET || nextDistance + INITIAL_SCROLL_MIN_TARGET_OFFSET < previousDistance;
1892
+ return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET;
1810
1893
  },
1811
1894
  get(state) {
1812
1895
  var _a3, _b;
@@ -1831,19 +1914,19 @@ var initialScrollWatchdog = {
1831
1914
  }
1832
1915
  };
1833
1916
  function setInitialScrollSession(state, options = {}) {
1834
- var _a3, _b, _c;
1917
+ var _a3, _b, _c, _d;
1835
1918
  const existingSession = state.initialScrollSession;
1836
1919
  const kind = (_a3 = options.kind) != null ? _a3 : existingSession == null ? void 0 : existingSession.kind;
1837
1920
  const completion = existingSession == null ? void 0 : existingSession.completion;
1838
- const hasBootstrapOverride = Object.hasOwn(options, "bootstrap");
1839
- const bootstrap = kind === "bootstrap" ? hasBootstrapOverride ? options.bootstrap : (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0 : void 0;
1921
+ const existingBootstrap = (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0;
1922
+ const bootstrap = kind === "bootstrap" ? options.bootstrap === null ? void 0 : (_b = options.bootstrap) != null ? _b : existingBootstrap : void 0;
1840
1923
  if (!kind) {
1841
1924
  return clearInitialScrollSession(state);
1842
1925
  }
1843
1926
  if (!state.initialScroll && !bootstrap && !hasInitialScrollSessionCompletion(completion)) {
1844
1927
  return clearInitialScrollSession(state);
1845
1928
  }
1846
- const previousDataLength = (_c = (_b = options.previousDataLength) != null ? _b : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _c : 0;
1929
+ const previousDataLength = (_d = (_c = options.previousDataLength) != null ? _c : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _d : 0;
1847
1930
  state.initialScrollSession = createInitialScrollSession({
1848
1931
  bootstrap,
1849
1932
  completion,
@@ -2595,7 +2678,7 @@ function advanceMeasuredInitialScroll(ctx, options) {
2595
2678
  const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2596
2679
  const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2597
2680
  const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2598
- const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - activeInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - activeInitialTargetOffset) <= 1;
2681
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2599
2682
  if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2600
2683
  return false;
2601
2684
  }
@@ -2667,6 +2750,30 @@ function checkAllSizesKnown(state, indices) {
2667
2750
  });
2668
2751
  }
2669
2752
 
2753
+ // src/utils/requestAdjust.ts
2754
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2755
+ const state = ctx.state;
2756
+ if (Math.abs(positionDiff) > 0.1) {
2757
+ const doit = () => {
2758
+ {
2759
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2760
+ if (state.adjustingFromInitialMount) {
2761
+ state.adjustingFromInitialMount--;
2762
+ }
2763
+ }
2764
+ };
2765
+ state.scroll += positionDiff;
2766
+ state.scrollForNextCalculateItemsInView = void 0;
2767
+ const readyToRender = peek$(ctx, "readyToRender");
2768
+ if (readyToRender) {
2769
+ doit();
2770
+ } else {
2771
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2772
+ requestAnimationFrame(doit);
2773
+ }
2774
+ }
2775
+ }
2776
+
2670
2777
  // src/core/bootstrapInitialScroll.ts
2671
2778
  var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2672
2779
  var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
@@ -2760,7 +2867,7 @@ function clearBootstrapInitialScrollSession(state) {
2760
2867
  bootstrapInitialScroll.frameHandle = void 0;
2761
2868
  }
2762
2869
  setInitialScrollSession(state, {
2763
- bootstrap: void 0,
2870
+ bootstrap: null,
2764
2871
  kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2765
2872
  });
2766
2873
  }
@@ -2916,15 +3023,18 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2916
3023
  return;
2917
3024
  }
2918
3025
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2919
- if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
2920
- clearPendingInitialScrollFooterLayout(ctx, {
2921
- dataLength: state.props.data.length,
2922
- stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
2923
- target: initialScroll
2924
- });
2925
- return;
3026
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3027
+ if (!shouldKeepEndTargetAlive) {
3028
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3029
+ clearPendingInitialScrollFooterLayout(ctx, {
3030
+ dataLength: state.props.data.length,
3031
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3032
+ target: initialScroll
3033
+ });
3034
+ } else {
3035
+ clearFinishedViewportRetargetableInitialScroll(state);
3036
+ }
2926
3037
  }
2927
- clearFinishedViewportRetargetableInitialScroll(state);
2928
3038
  }
2929
3039
  }
2930
3040
  function startBootstrapInitialScrollOnMount(ctx, options) {
@@ -2963,7 +3073,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2963
3073
  }
2964
3074
  const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2965
3075
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2966
- const shouldClearFinishedResizePreservation = didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3076
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2967
3077
  if (shouldClearFinishedResizePreservation) {
2968
3078
  clearPreservedInitialScrollTarget(state);
2969
3079
  return;
@@ -3066,27 +3176,46 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3066
3176
  }
3067
3177
  }
3068
3178
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3179
+ var _a3, _b, _c, _d;
3069
3180
  const state = ctx.state;
3070
3181
  const initialScroll = state.initialScroll;
3071
- if (isOffsetInitialScrollSession(state) || state.props.data.length === 0 || !initialScroll) {
3072
- return;
3073
- }
3074
3182
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3075
- if (!bootstrapInitialScroll && initialScroll.viewPosition !== 1) {
3076
- return;
3077
- }
3078
- const didFinishInitialScroll = state.didFinishInitialScroll;
3079
- if (didFinishInitialScroll) {
3080
- setInitialScrollTarget(state, initialScroll, {
3081
- resetDidFinish: true
3082
- });
3083
- state.clearPreservedInitialScrollOnNextFinish = true;
3183
+ if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3184
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3185
+ const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
3186
+ if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
3187
+ const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3188
+ const offsetDiff = resolvedOffset - currentOffset;
3189
+ if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3190
+ if (scrollingTo) {
3191
+ const existingWatchdog = initialScrollWatchdog.get(state);
3192
+ scrollingTo.offset = resolvedOffset;
3193
+ scrollingTo.targetOffset = resolvedOffset;
3194
+ state.initialScroll = {
3195
+ ...initialScroll,
3196
+ contentOffset: resolvedOffset
3197
+ };
3198
+ state.hasScrolled = false;
3199
+ initialScrollWatchdog.set(state, {
3200
+ startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3201
+ targetOffset: resolvedOffset
3202
+ });
3203
+ }
3204
+ requestAdjust(ctx, offsetDiff);
3205
+ if (state.didFinishInitialScroll) {
3206
+ (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3207
+ }
3208
+ }
3209
+ if (state.didFinishInitialScroll) {
3210
+ clearFinishedViewportRetargetableInitialScroll(state);
3211
+ }
3212
+ } else {
3213
+ rearmBootstrapInitialScroll(ctx, {
3214
+ scroll: resolvedOffset,
3215
+ targetIndexSeed: initialScroll.index
3216
+ });
3217
+ }
3084
3218
  }
3085
- rearmBootstrapInitialScroll(ctx, {
3086
- scroll: resolveInitialScrollOffset(ctx, initialScroll),
3087
- seedContentOffset: didFinishInitialScroll && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3088
- targetIndexSeed: initialScroll.index
3089
- });
3090
3219
  }
3091
3220
  function evaluateBootstrapInitialScroll(ctx) {
3092
3221
  var _a3, _b;
@@ -3293,7 +3422,7 @@ function checkFinishedScrollFallback(ctx) {
3293
3422
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, delay);
3294
3423
  };
3295
3424
  const checkHasScrolled = () => {
3296
- var _c;
3425
+ var _c, _d;
3297
3426
  state.timeoutCheckFinishedScrollFallback = void 0;
3298
3427
  const isStillScrollingTo = state.scrollingTo;
3299
3428
  if (isStillScrollingTo) {
@@ -3306,11 +3435,13 @@ function checkFinishedScrollFallback(ctx) {
3306
3435
  isStillScrollingTo
3307
3436
  );
3308
3437
  const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
3309
- const canFinishAfterSilentNativeDispatch = silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
3310
- if (shouldFinishZeroTarget || state.hasScrolled || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3438
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android";
3439
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
3440
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
3441
+ if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3311
3442
  finishScrollTo(ctx);
3312
- } else if (isNativeInitialPending && numChecks <= maxChecks) {
3313
- const targetOffset = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : state.scrollPending;
3443
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
3444
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
3314
3445
  scrollToFallbackOffset(ctx, targetOffset);
3315
3446
  scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
3316
3447
  } else {
@@ -3405,30 +3536,6 @@ function handleInitialScrollDataChange(ctx, options) {
3405
3536
  advanceCurrentInitialScrollSession(ctx);
3406
3537
  }
3407
3538
 
3408
- // src/utils/requestAdjust.ts
3409
- function requestAdjust(ctx, positionDiff, dataChanged) {
3410
- const state = ctx.state;
3411
- if (Math.abs(positionDiff) > 0.1) {
3412
- const doit = () => {
3413
- {
3414
- state.scrollAdjustHandler.requestAdjust(positionDiff);
3415
- if (state.adjustingFromInitialMount) {
3416
- state.adjustingFromInitialMount--;
3417
- }
3418
- }
3419
- };
3420
- state.scroll += positionDiff;
3421
- state.scrollForNextCalculateItemsInView = void 0;
3422
- const readyToRender = peek$(ctx, "readyToRender");
3423
- if (readyToRender) {
3424
- doit();
3425
- } else {
3426
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
3427
- requestAnimationFrame(doit);
3428
- }
3429
- }
3430
- }
3431
-
3432
3539
  // src/core/mvcp.ts
3433
3540
  var MVCP_POSITION_EPSILON = 0.1;
3434
3541
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -3702,7 +3809,10 @@ function prepareMVCP(ctx, dataChanged) {
3702
3809
  return;
3703
3810
  }
3704
3811
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3705
- requestAdjust(ctx, positionDiff);
3812
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3813
+ if (!shouldSkipAdjustForMaintainedEnd) {
3814
+ requestAdjust(ctx, positionDiff);
3815
+ }
3706
3816
  }
3707
3817
  };
3708
3818
  }
@@ -4321,6 +4431,30 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
4321
4431
  var unstableBatchedUpdates = ReactDOM__namespace.unstable_batchedUpdates;
4322
4432
  var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
4323
4433
 
4434
+ // src/utils/containerPool.ts
4435
+ var MIN_INITIAL_CONTAINER_POOL_SIZE = 32;
4436
+ var MAX_INITIAL_SPARE_CONTAINERS = 64;
4437
+ function getInitialContainerPoolSize(dataLength, numContainers, initialContainerPoolRatio) {
4438
+ if (dataLength <= 0 || numContainers <= 0) {
4439
+ return 0;
4440
+ }
4441
+ const ratioPoolSize = Math.ceil(numContainers * initialContainerPoolRatio);
4442
+ const cappedSparePoolSize = numContainers + MAX_INITIAL_SPARE_CONTAINERS;
4443
+ const targetPoolSize = Math.max(
4444
+ numContainers,
4445
+ Math.min(ratioPoolSize, cappedSparePoolSize),
4446
+ Math.min(dataLength, MIN_INITIAL_CONTAINER_POOL_SIZE)
4447
+ );
4448
+ const maxUsefulPoolSize = Math.max(dataLength, numContainers);
4449
+ return Math.min(maxUsefulPoolSize, targetPoolSize);
4450
+ }
4451
+ function getExpandedContainerPoolSize(dataLength, numContainers) {
4452
+ if (dataLength <= 0 || numContainers <= 0) {
4453
+ return 0;
4454
+ }
4455
+ return Math.min(Math.max(dataLength, numContainers), Math.max(numContainers, Math.ceil(numContainers * 1.5)));
4456
+ }
4457
+
4324
4458
  // src/utils/findAvailableContainers.ts
4325
4459
  function findAvailableContainers(ctx, numNeeded, startBuffered, endBuffered, pendingRemoval, requiredItemTypes, needNewContainers, protectedKeys) {
4326
4460
  const numContainers = peek$(ctx, "numContainers");
@@ -4526,7 +4660,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4526
4660
  function calculateItemsInView(ctx, params = {}) {
4527
4661
  const state = ctx.state;
4528
4662
  batchedUpdates(() => {
4529
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
4663
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
4530
4664
  const {
4531
4665
  columns,
4532
4666
  columnSpans,
@@ -4573,12 +4707,22 @@ function calculateItemsInView(ctx, params = {}) {
4573
4707
  // current initial-scroll target instead of transient native adjustments.
4574
4708
  resolveInitialScrollOffset(ctx, initialScroll)
4575
4709
  ) : state.scroll;
4576
- const scrollAdjustPending = (_c = peek$(ctx, "scrollAdjustPending")) != null ? _c : 0;
4577
- const scrollAdjustPad = scrollAdjustPending - topPad;
4578
- let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
4579
- if (scroll + scrollLength > totalSize) {
4580
- scroll = Math.max(0, totalSize - scrollLength);
4581
- }
4710
+ let scrollAdjustPending = 0;
4711
+ let scrollAdjustPad = 0;
4712
+ let scroll = 0;
4713
+ let scrollTopBuffered = 0;
4714
+ let scrollBottom = 0;
4715
+ let scrollBottomBuffered = 0;
4716
+ const updateScroll2 = (nextScrollState) => {
4717
+ var _a4;
4718
+ scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4719
+ scrollAdjustPad = scrollAdjustPending - topPad;
4720
+ scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4721
+ if (scroll + scrollLength > totalSize) {
4722
+ scroll = Math.max(0, totalSize - scrollLength);
4723
+ }
4724
+ };
4725
+ updateScroll2(scrollState);
4582
4726
  const previousStickyIndex = peek$(ctx, "activeStickyIndex");
4583
4727
  const currentStickyIdx = stickyIndicesArr.length > 0 ? findCurrentStickyIndex(stickyIndicesArr, scroll, state) : -1;
4584
4728
  const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyIndicesArr[currentStickyIdx] : -1;
@@ -4594,9 +4738,12 @@ function calculateItemsInView(ctx, params = {}) {
4594
4738
  scrollBufferTop = drawDistance * 1.5;
4595
4739
  scrollBufferBottom = drawDistance * 0.5;
4596
4740
  }
4597
- const scrollTopBuffered = scroll - scrollBufferTop;
4598
- const scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4599
- const scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4741
+ const updateScrollRange = () => {
4742
+ scrollTopBuffered = scroll - scrollBufferTop;
4743
+ scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4744
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4745
+ };
4746
+ updateScrollRange();
4600
4747
  if (!suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4601
4748
  const { top, bottom } = scrollForNextCalculateItemsInView;
4602
4749
  if (top === null && bottom === null) {
@@ -4615,7 +4762,7 @@ function calculateItemsInView(ctx, params = {}) {
4615
4762
  columns.length = 0;
4616
4763
  columnSpans.length = 0;
4617
4764
  }
4618
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_d = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _d : 0;
4765
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
4619
4766
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4620
4767
  updateItemPositions(ctx, dataChanged, {
4621
4768
  doMVCP,
@@ -4640,21 +4787,25 @@ function calculateItemsInView(ctx, params = {}) {
4640
4787
  }
4641
4788
  }
4642
4789
  const scrollBeforeMVCP = state.scroll;
4643
- const scrollAdjustPendingBeforeMVCP = (_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0;
4790
+ const scrollAdjustPendingBeforeMVCP = (_d = peek$(ctx, "scrollAdjustPending")) != null ? _d : 0;
4644
4791
  checkMVCP == null ? void 0 : checkMVCP();
4645
- const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_f = peek$(ctx, "scrollAdjustPending")) != null ? _f : 0) !== scrollAdjustPendingBeforeMVCP);
4792
+ const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0) !== scrollAdjustPendingBeforeMVCP);
4793
+ if (didMVCPAdjustScroll && initialScroll) {
4794
+ updateScroll2(state.scroll);
4795
+ updateScrollRange();
4796
+ }
4646
4797
  let startNoBuffer = null;
4647
4798
  let startBuffered = null;
4648
4799
  let startBufferedId = null;
4649
4800
  let endNoBuffer = null;
4650
4801
  let endBuffered = null;
4651
- let loopStart = (_g = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _g : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4802
+ let loopStart = (_f = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _f : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4652
4803
  for (let i = loopStart; i >= 0; i--) {
4653
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
4804
+ const id = (_g = idCache[i]) != null ? _g : getId(state, i);
4654
4805
  const top = positions[i];
4655
- const size = (_i = sizes.get(id)) != null ? _i : getItemSize(ctx, id, i, data[i]);
4806
+ const size = (_h = sizes.get(id)) != null ? _h : getItemSize(ctx, id, i, data[i]);
4656
4807
  const bottom = top + size;
4657
- if (bottom > scroll - scrollBufferTop) {
4808
+ if (bottom > scrollTopBuffered) {
4658
4809
  loopStart = i;
4659
4810
  } else {
4660
4811
  break;
@@ -4683,8 +4834,8 @@ function calculateItemsInView(ctx, params = {}) {
4683
4834
  let firstFullyOnScreenIndex;
4684
4835
  const dataLength = data.length;
4685
4836
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
4686
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
4687
- const size = (_k = sizes.get(id)) != null ? _k : getItemSize(ctx, id, i, data[i]);
4837
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
4838
+ const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
4688
4839
  const top = positions[i];
4689
4840
  if (!foundEnd) {
4690
4841
  if (startNoBuffer === null && top + size > scroll) {
@@ -4723,7 +4874,7 @@ function calculateItemsInView(ctx, params = {}) {
4723
4874
  const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
4724
4875
  if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
4725
4876
  for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
4726
- const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4877
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
4727
4878
  idsInView.push(id);
4728
4879
  }
4729
4880
  }
@@ -4756,7 +4907,7 @@ function calculateItemsInView(ctx, params = {}) {
4756
4907
  const needNewContainers = [];
4757
4908
  const needNewContainersSet = /* @__PURE__ */ new Set();
4758
4909
  for (let i = startBuffered; i <= endBuffered; i++) {
4759
- const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4910
+ const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4760
4911
  if (!containerItemKeys.has(id)) {
4761
4912
  needNewContainersSet.add(i);
4762
4913
  needNewContainers.push(i);
@@ -4765,7 +4916,7 @@ function calculateItemsInView(ctx, params = {}) {
4765
4916
  if (alwaysRenderArr.length > 0) {
4766
4917
  for (const index of alwaysRenderArr) {
4767
4918
  if (index < 0 || index >= dataLength) continue;
4768
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4919
+ const id = (_m = idCache[index]) != null ? _m : getId(state, index);
4769
4920
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4770
4921
  needNewContainersSet.add(index);
4771
4922
  needNewContainers.push(index);
@@ -4804,7 +4955,7 @@ function calculateItemsInView(ctx, params = {}) {
4804
4955
  for (let idx = 0; idx < needNewContainers.length; idx++) {
4805
4956
  const i = needNewContainers[idx];
4806
4957
  const containerIndex = availableContainers[idx];
4807
- const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4958
+ const id = (_n = idCache[i]) != null ? _n : getId(state, i);
4808
4959
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4809
4960
  if (oldKey && oldKey !== id) {
4810
4961
  containerItemKeys.delete(oldKey);
@@ -4815,7 +4966,7 @@ function calculateItemsInView(ctx, params = {}) {
4815
4966
  state.containerItemTypes.set(containerIndex, requiredItemTypes[idx]);
4816
4967
  }
4817
4968
  containerItemKeys.set(id, containerIndex);
4818
- (_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.add(id);
4969
+ (_o = state.userScrollAnchorResetKeys) == null ? void 0 : _o.add(id);
4819
4970
  const containerSticky = `containerSticky${containerIndex}`;
4820
4971
  const isSticky = stickyIndicesSet.has(i);
4821
4972
  const isAlwaysRender = alwaysRenderSet.has(i);
@@ -4839,17 +4990,17 @@ function calculateItemsInView(ctx, params = {}) {
4839
4990
  if (numContainers !== prevNumContainers) {
4840
4991
  set$(ctx, "numContainers", numContainers);
4841
4992
  if (numContainers > peek$(ctx, "numContainersPooled")) {
4842
- set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
4993
+ set$(ctx, "numContainersPooled", getExpandedContainerPoolSize(dataLength, numContainers));
4843
4994
  }
4844
4995
  }
4845
4996
  }
4846
- if (((_q = state.userScrollAnchorResetKeys) == null ? void 0 : _q.size) === 0) {
4997
+ if (((_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.size) === 0) {
4847
4998
  state.userScrollAnchorResetKeys = void 0;
4848
4999
  }
4849
5000
  if (alwaysRenderArr.length > 0) {
4850
5001
  for (const index of alwaysRenderArr) {
4851
5002
  if (index < 0 || index >= dataLength) continue;
4852
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
5003
+ const id = (_q = idCache[index]) != null ? _q : getId(state, index);
4853
5004
  const containerIndex = containerItemKeys.get(id);
4854
5005
  if (containerIndex !== void 0) {
4855
5006
  state.stickyContainerPool.add(containerIndex);
@@ -4953,21 +5104,25 @@ function doMaintainScrollAtEnd(ctx) {
4953
5104
  if (contentSize < state.scrollLength) {
4954
5105
  state.scroll = 0;
4955
5106
  }
4956
- requestAnimationFrame(() => {
4957
- var _a3;
4958
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
4959
- state.maintainingScrollAtEnd = true;
4960
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
4961
- animated: maintainScrollAtEnd.animated
4962
- });
4963
- setTimeout(
4964
- () => {
4965
- state.maintainingScrollAtEnd = false;
4966
- },
4967
- maintainScrollAtEnd.animated ? 500 : 0
4968
- );
4969
- }
4970
- });
5107
+ if (!state.maintainingScrollAtEnd) {
5108
+ state.maintainingScrollAtEnd = true;
5109
+ requestAnimationFrame(() => {
5110
+ var _a3;
5111
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
5112
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
5113
+ animated: maintainScrollAtEnd.animated
5114
+ });
5115
+ setTimeout(
5116
+ () => {
5117
+ state.maintainingScrollAtEnd = false;
5118
+ },
5119
+ maintainScrollAtEnd.animated ? 500 : 0
5120
+ );
5121
+ } else {
5122
+ state.maintainingScrollAtEnd = false;
5123
+ }
5124
+ });
5125
+ }
4971
5126
  return true;
4972
5127
  }
4973
5128
  return false;
@@ -5079,14 +5234,21 @@ function doInitialAllocateContainers(ctx) {
5079
5234
  } else {
5080
5235
  averageItemSize = estimatedItemSize;
5081
5236
  }
5082
- const numContainers = Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns);
5237
+ const numContainers = Math.max(
5238
+ 1,
5239
+ Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns)
5240
+ );
5083
5241
  for (let i = 0; i < numContainers; i++) {
5084
5242
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
5085
5243
  set$(ctx, `containerColumn${i}`, -1);
5086
5244
  set$(ctx, `containerSpan${i}`, 1);
5087
5245
  }
5088
5246
  set$(ctx, "numContainers", numContainers);
5089
- set$(ctx, "numContainersPooled", numContainers * state.props.initialContainerPoolRatio);
5247
+ set$(
5248
+ ctx,
5249
+ "numContainersPooled",
5250
+ getInitialContainerPoolSize(data.length, numContainers, state.props.initialContainerPoolRatio)
5251
+ );
5090
5252
  if (state.lastLayout) {
5091
5253
  if (state.initialScroll) {
5092
5254
  requestAnimationFrame(() => {
@@ -5237,8 +5399,8 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
5237
5399
  // src/core/onScroll.ts
5238
5400
  function trackInitialScrollNativeProgress(state, newScroll) {
5239
5401
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);
5240
- const didInitialScrollProgress = !!initialNativeScrollWatchdog && initialScrollWatchdog.didObserveProgress(newScroll, initialNativeScrollWatchdog);
5241
- if (didInitialScrollProgress) {
5402
+ const didInitialScrollReachTarget = !!initialNativeScrollWatchdog && initialScrollWatchdog.didReachTarget(newScroll, initialNativeScrollWatchdog);
5403
+ if (didInitialScrollReachTarget) {
5242
5404
  initialScrollWatchdog.clear(state);
5243
5405
  return;
5244
5406
  }
@@ -5376,16 +5538,20 @@ function maybeUpdateAnchoredEndSpace(ctx) {
5376
5538
  let contentBelowAnchor = 0;
5377
5539
  const footerSize = ctx.values.get("footerSize") || 0;
5378
5540
  const stylePaddingBottom = state.props.stylePaddingBottom || 0;
5541
+ let hasUnknownTailSize = false;
5379
5542
  for (let index = anchorIndex; index < data.length; index++) {
5380
5543
  const itemKey = getId(state, index);
5381
5544
  const size = itemKey ? state.sizesKnown.get(itemKey) : void 0;
5382
5545
  const effectiveSize = index === anchorIndex && anchorMaxSize !== void 0 ? Math.min(size || 0, Math.max(0, anchorMaxSize)) : size;
5546
+ if (size === void 0) {
5547
+ hasUnknownTailSize = true;
5548
+ }
5383
5549
  if (effectiveSize !== null && effectiveSize !== void 0 && effectiveSize > 0) {
5384
5550
  contentBelowAnchor += effectiveSize;
5385
5551
  }
5386
5552
  }
5387
5553
  contentBelowAnchor += footerSize + stylePaddingBottom;
5388
- nextSize = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5554
+ nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5389
5555
  }
5390
5556
  }
5391
5557
  if (previousSize !== nextSize) {
@@ -5457,15 +5623,7 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5457
5623
  const {
5458
5624
  didContainersLayout,
5459
5625
  sizesKnown,
5460
- props: {
5461
- getFixedItemSize,
5462
- getItemType,
5463
- horizontal,
5464
- suggestEstimatedItemSize,
5465
- onItemSizeChanged,
5466
- data,
5467
- maintainScrollAtEnd
5468
- }
5626
+ props: { getFixedItemSize, getItemType, horizontal, onItemSizeChanged, data, maintainScrollAtEnd }
5469
5627
  } = state;
5470
5628
  if (!data) return;
5471
5629
  const index = state.indexByKey.get(itemKey);
@@ -5516,18 +5674,6 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5516
5674
  if (minIndexSizeChanged !== void 0) {
5517
5675
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5518
5676
  }
5519
- if (IS_DEV && suggestEstimatedItemSize && minIndexSizeChanged !== void 0) {
5520
- if (state.timeoutSizeMessage) clearTimeout(state.timeoutSizeMessage);
5521
- state.timeoutSizeMessage = setTimeout(() => {
5522
- var _a4;
5523
- state.timeoutSizeMessage = void 0;
5524
- const num = state.sizesKnown.size;
5525
- const avg = (_a4 = state.averageSizes[""]) == null ? void 0 : _a4.avg;
5526
- console.warn(
5527
- `[legend-list] Based on the ${num} items rendered so far, the optimal estimated size is ${avg}.`
5528
- );
5529
- }, 1e3);
5530
- }
5531
5677
  const cur = peek$(ctx, "otherAxisSize");
5532
5678
  if (!cur || maxOtherAxisSize > cur) {
5533
5679
  set$(ctx, "otherAxisSize", maxOtherAxisSize);
@@ -5632,12 +5778,47 @@ function createColumnWrapperStyle(contentContainerStyle) {
5632
5778
  }
5633
5779
 
5634
5780
  // src/utils/createImperativeHandle.ts
5781
+ var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
5782
+ function getAverageItemSizes(state) {
5783
+ const averageItemSizes = {};
5784
+ for (const itemType in state.averageSizes) {
5785
+ const averageSize = state.averageSizes[itemType];
5786
+ if (averageSize) {
5787
+ averageItemSizes[itemType || DEFAULT_AVERAGE_ITEM_SIZE_TYPE] = {
5788
+ average: averageSize.avg,
5789
+ count: averageSize.num
5790
+ };
5791
+ }
5792
+ }
5793
+ return averageItemSizes;
5794
+ }
5635
5795
  function createImperativeHandle(ctx) {
5636
5796
  const state = ctx.state;
5637
5797
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
5638
5798
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
5639
5799
  let imperativeScrollToken = 0;
5640
5800
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.queuedMVCPRecalculate !== void 0 || state.ignoreScrollFromMVCP !== void 0;
5801
+ const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
5802
+ var _a3;
5803
+ const props = state.props;
5804
+ const dataLength = props.data.length;
5805
+ const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5806
+ if (targetIndex < 0) {
5807
+ return allowEmpty;
5808
+ }
5809
+ if (targetIndex >= dataLength) {
5810
+ return false;
5811
+ }
5812
+ if (anchorIndex === void 0 || anchorIndex < 0 || anchorIndex >= dataLength || targetIndex < anchorIndex || props.getFixedItemSize) {
5813
+ return true;
5814
+ }
5815
+ for (let index = anchorIndex; index < dataLength; index++) {
5816
+ if (!state.sizesKnown.has(getId(state, index))) {
5817
+ return false;
5818
+ }
5819
+ }
5820
+ return true;
5821
+ };
5641
5822
  const runWhenReady = (token, run, isReady) => {
5642
5823
  const startedAt = Date.now();
5643
5824
  let stableFrames = 0;
@@ -5659,11 +5840,10 @@ function createImperativeHandle(ctx) {
5659
5840
  };
5660
5841
  requestAnimationFrame(check);
5661
5842
  };
5662
- const runScrollWithPromise = (run, options) => new Promise((resolve) => {
5663
- var _a3, _b;
5843
+ const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
5844
+ var _a3;
5664
5845
  const token = ++imperativeScrollToken;
5665
- const isReady = (_a3 = options == null ? void 0 : options.isReady) != null ? _a3 : (() => true);
5666
- (_b = state.pendingScrollResolve) == null ? void 0 : _b.call(state);
5846
+ (_a3 = state.pendingScrollResolve) == null ? void 0 : _a3.call(state);
5667
5847
  state.pendingScrollResolve = resolve;
5668
5848
  const runNow = () => {
5669
5849
  if (token !== imperativeScrollToken) {
@@ -5738,6 +5918,7 @@ function createImperativeHandle(ctx) {
5738
5918
  },
5739
5919
  end: state.endNoBuffer,
5740
5920
  endBuffered: state.endBuffered,
5921
+ getAverageItemSizes: () => getAverageItemSizes(state),
5741
5922
  isAtEnd: peek$(ctx, "isAtEnd"),
5742
5923
  isAtStart: peek$(ctx, "isAtStart"),
5743
5924
  isEndReached: state.isEndReached,
@@ -5780,40 +5961,34 @@ function createImperativeHandle(ctx) {
5780
5961
  }
5781
5962
  return false;
5782
5963
  }),
5783
- scrollToEnd: (options) => runScrollWithPromise(() => {
5784
- const data = state.props.data;
5785
- const stylePaddingBottom = state.props.stylePaddingBottom;
5786
- const index = data.length - 1;
5787
- if (index !== -1) {
5788
- const paddingBottom = stylePaddingBottom || 0;
5789
- const footerSize = peek$(ctx, "footerSize") || 0;
5790
- scrollToIndex(ctx, {
5791
- ...options,
5792
- index,
5793
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5794
- viewPosition: 1
5795
- });
5796
- return true;
5797
- }
5798
- return false;
5799
- }),
5800
- scrollToIndex: (params) => {
5801
- const shouldWaitForOutOfRangeTarget = params.index >= 0 && params.index >= state.props.data.length;
5802
- const options = shouldWaitForOutOfRangeTarget ? {
5803
- isReady: () => {
5804
- var _a3;
5805
- const props = state.props;
5806
- const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5807
- const lastIndex = props.data.length - 1;
5808
- const isInRange = params.index < props.data.length;
5809
- const shouldWaitForAnchorSize = isInRange && anchorIndex !== void 0 && anchorIndex >= 0 && params.index >= anchorIndex && !props.getFixedItemSize && !state.sizesKnown.has(getId(state, lastIndex));
5810
- return isInRange && !shouldWaitForAnchorSize;
5964
+ scrollToEnd: (options) => runScrollWithPromise(
5965
+ () => {
5966
+ const data = state.props.data;
5967
+ const stylePaddingBottom = state.props.stylePaddingBottom;
5968
+ const index = data.length - 1;
5969
+ if (index !== -1) {
5970
+ const paddingBottom = stylePaddingBottom || 0;
5971
+ const footerSize = peek$(ctx, "footerSize") || 0;
5972
+ scrollToIndex(ctx, {
5973
+ ...options,
5974
+ index,
5975
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5976
+ viewPosition: 1
5977
+ });
5978
+ return true;
5811
5979
  }
5812
- } : void 0;
5813
- return runScrollWithPromise(() => {
5814
- scrollToIndex(ctx, params);
5815
- return true;
5816
- }, options);
5980
+ return false;
5981
+ },
5982
+ () => isScrollToIndexReady(state.props.data.length - 1, true)
5983
+ ),
5984
+ scrollToIndex: (params) => {
5985
+ return runScrollWithPromise(
5986
+ () => {
5987
+ scrollToIndex(ctx, params);
5988
+ return true;
5989
+ },
5990
+ params.index >= 0 ? () => isScrollToIndexReady(params.index) : void 0
5991
+ );
5817
5992
  },
5818
5993
  scrollToItem: ({ item, ...props }) => runScrollWithPromise(() => {
5819
5994
  const data = state.props.data;
@@ -6077,7 +6252,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6077
6252
  getFixedItemSize,
6078
6253
  getItemType,
6079
6254
  horizontal,
6080
- initialContainerPoolRatio = 2,
6255
+ initialContainerPoolRatio = 3,
6081
6256
  initialScrollAtEnd = false,
6082
6257
  initialScrollIndex: initialScrollIndexProp,
6083
6258
  initialScrollOffset: initialScrollOffsetProp,
@@ -6118,7 +6293,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6118
6293
  stickyIndices: stickyIndicesDeprecated,
6119
6294
  // TODOV3: Remove from v3 release
6120
6295
  style: styleProp,
6121
- suggestEstimatedItemSize,
6122
6296
  useWindowScroll = false,
6123
6297
  viewabilityConfig,
6124
6298
  viewabilityConfigCallbackPairs,
@@ -6259,7 +6433,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6259
6433
  startReachedSnapshotDataChangeEpoch: void 0,
6260
6434
  stickyContainerPool: /* @__PURE__ */ new Set(),
6261
6435
  stickyContainers: /* @__PURE__ */ new Map(),
6262
- timeoutSizeMessage: 0,
6263
6436
  timeouts: /* @__PURE__ */ new Set(),
6264
6437
  totalSize: 0,
6265
6438
  viewabilityConfigCallbackPairs: void 0
@@ -6279,7 +6452,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6279
6452
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6280
6453
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6281
6454
  const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6282
- if (didDataChangeLocal && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6455
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6283
6456
  clearPreservedInitialScrollTarget(state);
6284
6457
  }
6285
6458
  if (didDataChangeLocal) {
@@ -6334,7 +6507,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6334
6507
  stickyPositionComponentInternal,
6335
6508
  stylePaddingBottom: stylePaddingBottomState,
6336
6509
  stylePaddingTop: stylePaddingTopState,
6337
- suggestEstimatedItemSize: !!suggestEstimatedItemSize,
6338
6510
  useWindowScroll: useWindowScrollResolved
6339
6511
  };
6340
6512
  state.refScroller = refScroller;