@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.
package/index.js CHANGED
@@ -734,6 +734,7 @@ function getContainerPositionStyle({
734
734
  }
735
735
  var Container = typedMemo(function Container2({
736
736
  id,
737
+ itemKey,
737
738
  recycleItems,
738
739
  horizontal,
739
740
  getRenderedItem: getRenderedItem2,
@@ -745,11 +746,10 @@ var Container = typedMemo(function Container2({
745
746
  const { columnWrapperStyle, animatedScrollY } = ctx;
746
747
  const positionComponentInternal = ctx.state.props.positionComponentInternal;
747
748
  const stickyPositionComponentInternal = ctx.state.props.stickyPositionComponentInternal;
748
- const [column = 0, span = 1, data, itemKey, numColumns = 1, extraData, isSticky] = useArr$([
749
+ const [column = 0, span = 1, data, numColumns = 1, extraData, isSticky] = useArr$([
749
750
  `containerColumn${id}`,
750
751
  `containerSpan${id}`,
751
752
  `containerItemData${id}`,
752
- `containerItemKey${id}`,
753
753
  "numColumns",
754
754
  "extraData",
755
755
  `containerSticky${id}`
@@ -867,6 +867,39 @@ var Container = typedMemo(function Container2({
867
867
  );
868
868
  });
869
869
 
870
+ // src/components/ContainerSlot.tsx
871
+ function ContainerSlotBase({
872
+ id,
873
+ horizontal,
874
+ recycleItems,
875
+ ItemSeparatorComponent,
876
+ updateItemSize: updateItemSize2,
877
+ getRenderedItem: getRenderedItem2,
878
+ stickyHeaderConfig,
879
+ ContainerComponent = Container
880
+ }) {
881
+ const [itemKey] = useArr$([`containerItemKey${id}`]);
882
+ if (itemKey === void 0) {
883
+ return null;
884
+ }
885
+ return /* @__PURE__ */ React3__namespace.createElement(
886
+ ContainerComponent,
887
+ {
888
+ getRenderedItem: getRenderedItem2,
889
+ horizontal,
890
+ ItemSeparatorComponent,
891
+ id,
892
+ itemKey,
893
+ recycleItems,
894
+ stickyHeaderConfig,
895
+ updateItemSize: updateItemSize2
896
+ }
897
+ );
898
+ }
899
+ var ContainerSlot = typedMemo(function ContainerSlot2(props) {
900
+ return /* @__PURE__ */ React3__namespace.createElement(ContainerSlotBase, { ...props });
901
+ });
902
+
870
903
  // src/utils/reordering.ts
871
904
  var mapFn = (element) => {
872
905
  const indexStr = element.getAttribute("data-index");
@@ -986,9 +1019,12 @@ var ContainersInner = typedMemo(function ContainersInner2({ horizontal, numColum
986
1019
  const ref = React3.useRef(null);
987
1020
  const ctx = useStateContext();
988
1021
  const columnWrapperStyle = ctx.columnWrapperStyle;
989
- const [otherAxisSize, totalSize] = useArr$(["otherAxisSize", "totalSize"]);
1022
+ const [otherAxisSize, readyToRender, totalSize] = useArr$(["otherAxisSize", "readyToRender", "totalSize"]);
990
1023
  useDOMOrder(ref);
991
- const style = horizontal ? { minHeight: otherAxisSize, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, position: "relative" };
1024
+ const style = horizontal ? { minHeight: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative" };
1025
+ if (!readyToRender) {
1026
+ style.pointerEvents = "none";
1027
+ }
992
1028
  if (columnWrapperStyle && numColumns > 1) {
993
1029
  const { columnGap, rowGap, gap } = columnWrapperStyle;
994
1030
  const gapX = columnGap || gap || 0;
@@ -1016,12 +1052,12 @@ var Containers = typedMemo(function Containers2({
1016
1052
  getRenderedItem: getRenderedItem2,
1017
1053
  stickyHeaderConfig
1018
1054
  }) {
1019
- const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1055
+ const [numContainersPooled, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1020
1056
  const containers = [];
1021
- for (let i = 0; i < numContainers; i++) {
1057
+ for (let i = 0; i < numContainersPooled; i++) {
1022
1058
  containers.push(
1023
1059
  /* @__PURE__ */ React3__namespace.createElement(
1024
- Container,
1060
+ ContainerSlot,
1025
1061
  {
1026
1062
  getRenderedItem: getRenderedItem2,
1027
1063
  horizontal,
@@ -1093,6 +1129,8 @@ function useRafCoalescer(callback) {
1093
1129
 
1094
1130
  // src/components/webConstants.ts
1095
1131
  var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
1132
+ var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
1133
+ var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
1096
1134
 
1097
1135
  // src/components/webScrollUtils.ts
1098
1136
  function getDocumentScrollerNode() {
@@ -1178,6 +1216,17 @@ function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll
1178
1216
  }
1179
1217
 
1180
1218
  // src/components/ListComponentScrollView.tsx
1219
+ var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
1220
+ 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;}`;
1221
+ function ensureScrollbarHiddenStyle() {
1222
+ if (typeof document === "undefined" || document.getElementById(SCROLLBAR_HIDDEN_STYLE_ID)) {
1223
+ return;
1224
+ }
1225
+ const styleElement = document.createElement("style");
1226
+ styleElement.id = SCROLLBAR_HIDDEN_STYLE_ID;
1227
+ styleElement.textContent = SCROLLBAR_HIDDEN_STYLE;
1228
+ document.head.appendChild(styleElement);
1229
+ }
1181
1230
  function getContentInsetEndAdjustmentEnd2(ctx) {
1182
1231
  var _a3, _b;
1183
1232
  const adjustment = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.props) == null ? void 0 : _b.contentInsetEndAdjustment;
@@ -1382,6 +1431,12 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1382
1431
  }
1383
1432
  };
1384
1433
  }, [isWindowScroll, onLayout]);
1434
+ const hiddenScrollIndicatorClassName = !isWindowScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
1435
+ React3.useLayoutEffect(() => {
1436
+ if (hiddenScrollIndicatorClassName) {
1437
+ ensureScrollbarHiddenStyle();
1438
+ }
1439
+ }, [hiddenScrollIndicatorClassName]);
1385
1440
  const scrollViewStyle = {
1386
1441
  ...isWindowScroll ? {} : {
1387
1442
  overflow: "auto",
@@ -1410,9 +1465,31 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
1410
1465
  scrollEventThrottle: _scrollEventThrottle,
1411
1466
  ScrollComponent: _ScrollComponent,
1412
1467
  useWindowScroll: _useWindowScroll,
1468
+ className: scrollViewClassNameProp,
1413
1469
  ...webProps
1414
1470
  } = props;
1415
- 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));
1471
+ const scrollViewClassName = hiddenScrollIndicatorClassName ? scrollViewClassNameProp ? `${scrollViewClassNameProp} ${hiddenScrollIndicatorClassName}` : hiddenScrollIndicatorClassName : scrollViewClassNameProp;
1472
+ if (IS_DEV) {
1473
+ if (/(?:^|\s)(?:[a-z0-9_-]+:)*gap(?:-[xy])?-(?:\[[^\]]+\]|[^\s]+)/.test(
1474
+ `${contentContainerClassName != null ? contentContainerClassName : ""} ${scrollViewClassNameProp != null ? scrollViewClassNameProp : ""}`
1475
+ )) {
1476
+ warnDevOnce(
1477
+ "className-gap",
1478
+ "className/contentContainerClassName gap classes are not supported in LegendList because it needs to use exact values internally. Use contentContainerStyle={{ gap: ... }} or columnWrapperStyle instead."
1479
+ );
1480
+ }
1481
+ }
1482
+ return /* @__PURE__ */ React3__namespace.createElement(
1483
+ "div",
1484
+ {
1485
+ className: scrollViewClassName,
1486
+ ref: scrollRef,
1487
+ ...webProps,
1488
+ style: scrollViewStyle
1489
+ },
1490
+ refreshControl,
1491
+ /* @__PURE__ */ React3__namespace.createElement("div", { className, ref: contentRef, style: contentStyle }, children, contentInsetEndAdjustmentSpacerStyle ? /* @__PURE__ */ React3__namespace.createElement("div", { "aria-hidden": true, style: contentInsetEndAdjustmentSpacerStyle }) : null)
1492
+ );
1416
1493
  });
1417
1494
  function useValueListener$(key, callback) {
1418
1495
  const ctx = useStateContext();
@@ -1440,11 +1517,18 @@ function getScrollAdjustAxis(horizontal) {
1440
1517
  y: 1
1441
1518
  };
1442
1519
  }
1443
- function resolveScrollAdjustContentNode(el, contentNode) {
1444
- if ((contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === el) {
1445
- return contentNode;
1520
+ function getScrollAdjustTarget(ctx, contentNode) {
1521
+ var _a3, _b, _c;
1522
+ const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1523
+ const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
1524
+ let resolvedContentNode = null;
1525
+ if (scrollElement) {
1526
+ resolvedContentNode = (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1446
1527
  }
1447
- return el.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1528
+ return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
1529
+ }
1530
+ function scrollAdjustBy(el, left, top) {
1531
+ el.scrollBy({ behavior: "auto", left, top });
1448
1532
  }
1449
1533
  function ScrollAdjust() {
1450
1534
  const ctx = useStateContext();
@@ -1453,43 +1537,43 @@ function ScrollAdjust() {
1453
1537
  const resetPaddingBaselineRef = React3__namespace.useRef(void 0);
1454
1538
  const contentNodeRef = React3__namespace.useRef(null);
1455
1539
  const callback = React3__namespace.useCallback(() => {
1456
- var _a3, _b;
1540
+ var _a3;
1457
1541
  const scrollAdjust = peek$(ctx, "scrollAdjust");
1458
1542
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
1459
1543
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
1460
- const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1461
- if (scrollView && scrollOffset !== lastScrollOffsetRef.current) {
1462
- const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1463
- if (scrollDelta !== 0) {
1464
- const axis = getScrollAdjustAxis(!!ctx.state.props.horizontal);
1465
- const prevScroll = scrollView.getCurrentScrollOffset();
1466
- const el = scrollView.getScrollableNode();
1467
- const contentNode = resolveScrollAdjustContentNode(el, contentNodeRef.current);
1544
+ const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1545
+ if (scrollDelta !== 0) {
1546
+ const target = getScrollAdjustTarget(ctx, contentNodeRef.current);
1547
+ if (target) {
1548
+ const horizontal = !!ctx.state.props.horizontal;
1549
+ const axis = getScrollAdjustAxis(horizontal);
1550
+ const { contentNode, scrollElement: el } = target;
1551
+ const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
1468
1552
  contentNodeRef.current = contentNode;
1469
- const scrollBy = () => scrollView.scrollBy(axis.x * scrollDelta, axis.y * scrollDelta);
1470
- if (!contentNode) {
1471
- scrollBy();
1472
- lastScrollOffsetRef.current = scrollOffset;
1473
- return;
1474
- }
1475
- const totalSize = contentNode[axis.contentSizeKey];
1476
- const viewportSize = el[axis.viewportSizeKey];
1477
- const nextScroll = prevScroll + scrollDelta;
1478
- if (scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize) {
1479
- const previousPaddingEnd = (_b = resetPaddingBaselineRef.current) != null ? _b : contentNode.style[axis.paddingEndProp];
1480
- resetPaddingBaselineRef.current = previousPaddingEnd;
1481
- const pad = (nextScroll + viewportSize - totalSize) * 2;
1482
- contentNode.style[axis.paddingEndProp] = `${pad}px`;
1483
- void contentNode.offsetHeight;
1484
- scrollBy();
1485
- if (resetPaddingRafRef.current !== void 0) {
1486
- cancelAnimationFrame(resetPaddingRafRef.current);
1553
+ if (contentNode) {
1554
+ const prevScroll = horizontal ? el.scrollLeft : el.scrollTop;
1555
+ const totalSize = contentNode[axis.contentSizeKey];
1556
+ const viewportSize = el[axis.viewportSizeKey];
1557
+ const nextScroll = prevScroll + scrollDelta;
1558
+ const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
1559
+ if (needsTemporaryPadding) {
1560
+ const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
1561
+ resetPaddingBaselineRef.current = previousPaddingEnd;
1562
+ const pad = (nextScroll + viewportSize - totalSize) * 2;
1563
+ contentNode.style[axis.paddingEndProp] = `${pad}px`;
1564
+ void contentNode.offsetHeight;
1565
+ scrollBy();
1566
+ if (resetPaddingRafRef.current !== void 0) {
1567
+ cancelAnimationFrame(resetPaddingRafRef.current);
1568
+ }
1569
+ resetPaddingRafRef.current = requestAnimationFrame(() => {
1570
+ resetPaddingRafRef.current = void 0;
1571
+ resetPaddingBaselineRef.current = void 0;
1572
+ contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1573
+ });
1574
+ } else {
1575
+ scrollBy();
1487
1576
  }
1488
- resetPaddingRafRef.current = requestAnimationFrame(() => {
1489
- resetPaddingRafRef.current = void 0;
1490
- resetPaddingBaselineRef.current = void 0;
1491
- contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1492
- });
1493
1577
  } else {
1494
1578
  scrollBy();
1495
1579
  }
@@ -1800,10 +1884,9 @@ var initialScrollWatchdog = {
1800
1884
  clear(state) {
1801
1885
  initialScrollWatchdog.set(state, void 0);
1802
1886
  },
1803
- didObserveProgress(newScroll, watchdog) {
1804
- const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
1887
+ didReachTarget(newScroll, watchdog) {
1805
1888
  const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
1806
- return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET || nextDistance + INITIAL_SCROLL_MIN_TARGET_OFFSET < previousDistance;
1889
+ return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET;
1807
1890
  },
1808
1891
  get(state) {
1809
1892
  var _a3, _b;
@@ -1828,19 +1911,19 @@ var initialScrollWatchdog = {
1828
1911
  }
1829
1912
  };
1830
1913
  function setInitialScrollSession(state, options = {}) {
1831
- var _a3, _b, _c;
1914
+ var _a3, _b, _c, _d;
1832
1915
  const existingSession = state.initialScrollSession;
1833
1916
  const kind = (_a3 = options.kind) != null ? _a3 : existingSession == null ? void 0 : existingSession.kind;
1834
1917
  const completion = existingSession == null ? void 0 : existingSession.completion;
1835
- const hasBootstrapOverride = Object.hasOwn(options, "bootstrap");
1836
- const bootstrap = kind === "bootstrap" ? hasBootstrapOverride ? options.bootstrap : (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0 : void 0;
1918
+ const existingBootstrap = (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0;
1919
+ const bootstrap = kind === "bootstrap" ? options.bootstrap === null ? void 0 : (_b = options.bootstrap) != null ? _b : existingBootstrap : void 0;
1837
1920
  if (!kind) {
1838
1921
  return clearInitialScrollSession(state);
1839
1922
  }
1840
1923
  if (!state.initialScroll && !bootstrap && !hasInitialScrollSessionCompletion(completion)) {
1841
1924
  return clearInitialScrollSession(state);
1842
1925
  }
1843
- const previousDataLength = (_c = (_b = options.previousDataLength) != null ? _b : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _c : 0;
1926
+ const previousDataLength = (_d = (_c = options.previousDataLength) != null ? _c : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _d : 0;
1844
1927
  state.initialScrollSession = createInitialScrollSession({
1845
1928
  bootstrap,
1846
1929
  completion,
@@ -2592,7 +2675,7 @@ function advanceMeasuredInitialScroll(ctx, options) {
2592
2675
  const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2593
2676
  const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2594
2677
  const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2595
- const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - activeInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - activeInitialTargetOffset) <= 1;
2678
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2596
2679
  if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2597
2680
  return false;
2598
2681
  }
@@ -2664,6 +2747,30 @@ function checkAllSizesKnown(state, indices) {
2664
2747
  });
2665
2748
  }
2666
2749
 
2750
+ // src/utils/requestAdjust.ts
2751
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2752
+ const state = ctx.state;
2753
+ if (Math.abs(positionDiff) > 0.1) {
2754
+ const doit = () => {
2755
+ {
2756
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2757
+ if (state.adjustingFromInitialMount) {
2758
+ state.adjustingFromInitialMount--;
2759
+ }
2760
+ }
2761
+ };
2762
+ state.scroll += positionDiff;
2763
+ state.scrollForNextCalculateItemsInView = void 0;
2764
+ const readyToRender = peek$(ctx, "readyToRender");
2765
+ if (readyToRender) {
2766
+ doit();
2767
+ } else {
2768
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2769
+ requestAnimationFrame(doit);
2770
+ }
2771
+ }
2772
+ }
2773
+
2667
2774
  // src/core/bootstrapInitialScroll.ts
2668
2775
  var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2669
2776
  var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
@@ -2757,7 +2864,7 @@ function clearBootstrapInitialScrollSession(state) {
2757
2864
  bootstrapInitialScroll.frameHandle = void 0;
2758
2865
  }
2759
2866
  setInitialScrollSession(state, {
2760
- bootstrap: void 0,
2867
+ bootstrap: null,
2761
2868
  kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2762
2869
  });
2763
2870
  }
@@ -2913,15 +3020,18 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2913
3020
  return;
2914
3021
  }
2915
3022
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2916
- if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
2917
- clearPendingInitialScrollFooterLayout(ctx, {
2918
- dataLength: state.props.data.length,
2919
- stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
2920
- target: initialScroll
2921
- });
2922
- return;
3023
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3024
+ if (!shouldKeepEndTargetAlive) {
3025
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3026
+ clearPendingInitialScrollFooterLayout(ctx, {
3027
+ dataLength: state.props.data.length,
3028
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3029
+ target: initialScroll
3030
+ });
3031
+ } else {
3032
+ clearFinishedViewportRetargetableInitialScroll(state);
3033
+ }
2923
3034
  }
2924
- clearFinishedViewportRetargetableInitialScroll(state);
2925
3035
  }
2926
3036
  }
2927
3037
  function startBootstrapInitialScrollOnMount(ctx, options) {
@@ -2960,7 +3070,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2960
3070
  }
2961
3071
  const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2962
3072
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2963
- const shouldClearFinishedResizePreservation = didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3073
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2964
3074
  if (shouldClearFinishedResizePreservation) {
2965
3075
  clearPreservedInitialScrollTarget(state);
2966
3076
  return;
@@ -3063,27 +3173,46 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3063
3173
  }
3064
3174
  }
3065
3175
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3176
+ var _a3, _b, _c, _d;
3066
3177
  const state = ctx.state;
3067
3178
  const initialScroll = state.initialScroll;
3068
- if (isOffsetInitialScrollSession(state) || state.props.data.length === 0 || !initialScroll) {
3069
- return;
3070
- }
3071
3179
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3072
- if (!bootstrapInitialScroll && initialScroll.viewPosition !== 1) {
3073
- return;
3074
- }
3075
- const didFinishInitialScroll = state.didFinishInitialScroll;
3076
- if (didFinishInitialScroll) {
3077
- setInitialScrollTarget(state, initialScroll, {
3078
- resetDidFinish: true
3079
- });
3080
- state.clearPreservedInitialScrollOnNextFinish = true;
3180
+ if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3181
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3182
+ const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
3183
+ if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
3184
+ const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3185
+ const offsetDiff = resolvedOffset - currentOffset;
3186
+ if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3187
+ if (scrollingTo) {
3188
+ const existingWatchdog = initialScrollWatchdog.get(state);
3189
+ scrollingTo.offset = resolvedOffset;
3190
+ scrollingTo.targetOffset = resolvedOffset;
3191
+ state.initialScroll = {
3192
+ ...initialScroll,
3193
+ contentOffset: resolvedOffset
3194
+ };
3195
+ state.hasScrolled = false;
3196
+ initialScrollWatchdog.set(state, {
3197
+ startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3198
+ targetOffset: resolvedOffset
3199
+ });
3200
+ }
3201
+ requestAdjust(ctx, offsetDiff);
3202
+ if (state.didFinishInitialScroll) {
3203
+ (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3204
+ }
3205
+ }
3206
+ if (state.didFinishInitialScroll) {
3207
+ clearFinishedViewportRetargetableInitialScroll(state);
3208
+ }
3209
+ } else {
3210
+ rearmBootstrapInitialScroll(ctx, {
3211
+ scroll: resolvedOffset,
3212
+ targetIndexSeed: initialScroll.index
3213
+ });
3214
+ }
3081
3215
  }
3082
- rearmBootstrapInitialScroll(ctx, {
3083
- scroll: resolveInitialScrollOffset(ctx, initialScroll),
3084
- seedContentOffset: didFinishInitialScroll && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3085
- targetIndexSeed: initialScroll.index
3086
- });
3087
3216
  }
3088
3217
  function evaluateBootstrapInitialScroll(ctx) {
3089
3218
  var _a3, _b;
@@ -3290,7 +3419,7 @@ function checkFinishedScrollFallback(ctx) {
3290
3419
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, delay);
3291
3420
  };
3292
3421
  const checkHasScrolled = () => {
3293
- var _c;
3422
+ var _c, _d;
3294
3423
  state.timeoutCheckFinishedScrollFallback = void 0;
3295
3424
  const isStillScrollingTo = state.scrollingTo;
3296
3425
  if (isStillScrollingTo) {
@@ -3303,11 +3432,13 @@ function checkFinishedScrollFallback(ctx) {
3303
3432
  isStillScrollingTo
3304
3433
  );
3305
3434
  const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
3306
- const canFinishAfterSilentNativeDispatch = silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
3307
- if (shouldFinishZeroTarget || state.hasScrolled || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3435
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android";
3436
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
3437
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
3438
+ if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3308
3439
  finishScrollTo(ctx);
3309
- } else if (isNativeInitialPending && numChecks <= maxChecks) {
3310
- const targetOffset = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : state.scrollPending;
3440
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
3441
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
3311
3442
  scrollToFallbackOffset(ctx, targetOffset);
3312
3443
  scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
3313
3444
  } else {
@@ -3402,30 +3533,6 @@ function handleInitialScrollDataChange(ctx, options) {
3402
3533
  advanceCurrentInitialScrollSession(ctx);
3403
3534
  }
3404
3535
 
3405
- // src/utils/requestAdjust.ts
3406
- function requestAdjust(ctx, positionDiff, dataChanged) {
3407
- const state = ctx.state;
3408
- if (Math.abs(positionDiff) > 0.1) {
3409
- const doit = () => {
3410
- {
3411
- state.scrollAdjustHandler.requestAdjust(positionDiff);
3412
- if (state.adjustingFromInitialMount) {
3413
- state.adjustingFromInitialMount--;
3414
- }
3415
- }
3416
- };
3417
- state.scroll += positionDiff;
3418
- state.scrollForNextCalculateItemsInView = void 0;
3419
- const readyToRender = peek$(ctx, "readyToRender");
3420
- if (readyToRender) {
3421
- doit();
3422
- } else {
3423
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
3424
- requestAnimationFrame(doit);
3425
- }
3426
- }
3427
- }
3428
-
3429
3536
  // src/core/mvcp.ts
3430
3537
  var MVCP_POSITION_EPSILON = 0.1;
3431
3538
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -3699,7 +3806,10 @@ function prepareMVCP(ctx, dataChanged) {
3699
3806
  return;
3700
3807
  }
3701
3808
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3702
- requestAdjust(ctx, positionDiff);
3809
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3810
+ if (!shouldSkipAdjustForMaintainedEnd) {
3811
+ requestAdjust(ctx, positionDiff);
3812
+ }
3703
3813
  }
3704
3814
  };
3705
3815
  }
@@ -4318,6 +4428,30 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
4318
4428
  var unstableBatchedUpdates = ReactDOM__namespace.unstable_batchedUpdates;
4319
4429
  var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
4320
4430
 
4431
+ // src/utils/containerPool.ts
4432
+ var MIN_INITIAL_CONTAINER_POOL_SIZE = 32;
4433
+ var MAX_INITIAL_SPARE_CONTAINERS = 64;
4434
+ function getInitialContainerPoolSize(dataLength, numContainers, initialContainerPoolRatio) {
4435
+ if (dataLength <= 0 || numContainers <= 0) {
4436
+ return 0;
4437
+ }
4438
+ const ratioPoolSize = Math.ceil(numContainers * initialContainerPoolRatio);
4439
+ const cappedSparePoolSize = numContainers + MAX_INITIAL_SPARE_CONTAINERS;
4440
+ const targetPoolSize = Math.max(
4441
+ numContainers,
4442
+ Math.min(ratioPoolSize, cappedSparePoolSize),
4443
+ Math.min(dataLength, MIN_INITIAL_CONTAINER_POOL_SIZE)
4444
+ );
4445
+ const maxUsefulPoolSize = Math.max(dataLength, numContainers);
4446
+ return Math.min(maxUsefulPoolSize, targetPoolSize);
4447
+ }
4448
+ function getExpandedContainerPoolSize(dataLength, numContainers) {
4449
+ if (dataLength <= 0 || numContainers <= 0) {
4450
+ return 0;
4451
+ }
4452
+ return Math.min(Math.max(dataLength, numContainers), Math.max(numContainers, Math.ceil(numContainers * 1.5)));
4453
+ }
4454
+
4321
4455
  // src/utils/findAvailableContainers.ts
4322
4456
  function findAvailableContainers(ctx, numNeeded, startBuffered, endBuffered, pendingRemoval, requiredItemTypes, needNewContainers, protectedKeys) {
4323
4457
  const numContainers = peek$(ctx, "numContainers");
@@ -4523,7 +4657,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4523
4657
  function calculateItemsInView(ctx, params = {}) {
4524
4658
  const state = ctx.state;
4525
4659
  batchedUpdates(() => {
4526
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
4660
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
4527
4661
  const {
4528
4662
  columns,
4529
4663
  columnSpans,
@@ -4570,12 +4704,22 @@ function calculateItemsInView(ctx, params = {}) {
4570
4704
  // current initial-scroll target instead of transient native adjustments.
4571
4705
  resolveInitialScrollOffset(ctx, initialScroll)
4572
4706
  ) : state.scroll;
4573
- const scrollAdjustPending = (_c = peek$(ctx, "scrollAdjustPending")) != null ? _c : 0;
4574
- const scrollAdjustPad = scrollAdjustPending - topPad;
4575
- let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
4576
- if (scroll + scrollLength > totalSize) {
4577
- scroll = Math.max(0, totalSize - scrollLength);
4578
- }
4707
+ let scrollAdjustPending = 0;
4708
+ let scrollAdjustPad = 0;
4709
+ let scroll = 0;
4710
+ let scrollTopBuffered = 0;
4711
+ let scrollBottom = 0;
4712
+ let scrollBottomBuffered = 0;
4713
+ const updateScroll2 = (nextScrollState) => {
4714
+ var _a4;
4715
+ scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4716
+ scrollAdjustPad = scrollAdjustPending - topPad;
4717
+ scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4718
+ if (scroll + scrollLength > totalSize) {
4719
+ scroll = Math.max(0, totalSize - scrollLength);
4720
+ }
4721
+ };
4722
+ updateScroll2(scrollState);
4579
4723
  const previousStickyIndex = peek$(ctx, "activeStickyIndex");
4580
4724
  const currentStickyIdx = stickyIndicesArr.length > 0 ? findCurrentStickyIndex(stickyIndicesArr, scroll, state) : -1;
4581
4725
  const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyIndicesArr[currentStickyIdx] : -1;
@@ -4591,9 +4735,12 @@ function calculateItemsInView(ctx, params = {}) {
4591
4735
  scrollBufferTop = drawDistance * 1.5;
4592
4736
  scrollBufferBottom = drawDistance * 0.5;
4593
4737
  }
4594
- const scrollTopBuffered = scroll - scrollBufferTop;
4595
- const scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4596
- const scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4738
+ const updateScrollRange = () => {
4739
+ scrollTopBuffered = scroll - scrollBufferTop;
4740
+ scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4741
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4742
+ };
4743
+ updateScrollRange();
4597
4744
  if (!suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4598
4745
  const { top, bottom } = scrollForNextCalculateItemsInView;
4599
4746
  if (top === null && bottom === null) {
@@ -4612,7 +4759,7 @@ function calculateItemsInView(ctx, params = {}) {
4612
4759
  columns.length = 0;
4613
4760
  columnSpans.length = 0;
4614
4761
  }
4615
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_d = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _d : 0;
4762
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
4616
4763
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4617
4764
  updateItemPositions(ctx, dataChanged, {
4618
4765
  doMVCP,
@@ -4637,21 +4784,25 @@ function calculateItemsInView(ctx, params = {}) {
4637
4784
  }
4638
4785
  }
4639
4786
  const scrollBeforeMVCP = state.scroll;
4640
- const scrollAdjustPendingBeforeMVCP = (_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0;
4787
+ const scrollAdjustPendingBeforeMVCP = (_d = peek$(ctx, "scrollAdjustPending")) != null ? _d : 0;
4641
4788
  checkMVCP == null ? void 0 : checkMVCP();
4642
- const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_f = peek$(ctx, "scrollAdjustPending")) != null ? _f : 0) !== scrollAdjustPendingBeforeMVCP);
4789
+ const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0) !== scrollAdjustPendingBeforeMVCP);
4790
+ if (didMVCPAdjustScroll && initialScroll) {
4791
+ updateScroll2(state.scroll);
4792
+ updateScrollRange();
4793
+ }
4643
4794
  let startNoBuffer = null;
4644
4795
  let startBuffered = null;
4645
4796
  let startBufferedId = null;
4646
4797
  let endNoBuffer = null;
4647
4798
  let endBuffered = null;
4648
- let loopStart = (_g = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _g : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4799
+ let loopStart = (_f = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _f : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4649
4800
  for (let i = loopStart; i >= 0; i--) {
4650
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
4801
+ const id = (_g = idCache[i]) != null ? _g : getId(state, i);
4651
4802
  const top = positions[i];
4652
- const size = (_i = sizes.get(id)) != null ? _i : getItemSize(ctx, id, i, data[i]);
4803
+ const size = (_h = sizes.get(id)) != null ? _h : getItemSize(ctx, id, i, data[i]);
4653
4804
  const bottom = top + size;
4654
- if (bottom > scroll - scrollBufferTop) {
4805
+ if (bottom > scrollTopBuffered) {
4655
4806
  loopStart = i;
4656
4807
  } else {
4657
4808
  break;
@@ -4680,8 +4831,8 @@ function calculateItemsInView(ctx, params = {}) {
4680
4831
  let firstFullyOnScreenIndex;
4681
4832
  const dataLength = data.length;
4682
4833
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
4683
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
4684
- const size = (_k = sizes.get(id)) != null ? _k : getItemSize(ctx, id, i, data[i]);
4834
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
4835
+ const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
4685
4836
  const top = positions[i];
4686
4837
  if (!foundEnd) {
4687
4838
  if (startNoBuffer === null && top + size > scroll) {
@@ -4720,7 +4871,7 @@ function calculateItemsInView(ctx, params = {}) {
4720
4871
  const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
4721
4872
  if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
4722
4873
  for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
4723
- const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4874
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
4724
4875
  idsInView.push(id);
4725
4876
  }
4726
4877
  }
@@ -4753,7 +4904,7 @@ function calculateItemsInView(ctx, params = {}) {
4753
4904
  const needNewContainers = [];
4754
4905
  const needNewContainersSet = /* @__PURE__ */ new Set();
4755
4906
  for (let i = startBuffered; i <= endBuffered; i++) {
4756
- const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4907
+ const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4757
4908
  if (!containerItemKeys.has(id)) {
4758
4909
  needNewContainersSet.add(i);
4759
4910
  needNewContainers.push(i);
@@ -4762,7 +4913,7 @@ function calculateItemsInView(ctx, params = {}) {
4762
4913
  if (alwaysRenderArr.length > 0) {
4763
4914
  for (const index of alwaysRenderArr) {
4764
4915
  if (index < 0 || index >= dataLength) continue;
4765
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4916
+ const id = (_m = idCache[index]) != null ? _m : getId(state, index);
4766
4917
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4767
4918
  needNewContainersSet.add(index);
4768
4919
  needNewContainers.push(index);
@@ -4801,7 +4952,7 @@ function calculateItemsInView(ctx, params = {}) {
4801
4952
  for (let idx = 0; idx < needNewContainers.length; idx++) {
4802
4953
  const i = needNewContainers[idx];
4803
4954
  const containerIndex = availableContainers[idx];
4804
- const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4955
+ const id = (_n = idCache[i]) != null ? _n : getId(state, i);
4805
4956
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4806
4957
  if (oldKey && oldKey !== id) {
4807
4958
  containerItemKeys.delete(oldKey);
@@ -4812,7 +4963,7 @@ function calculateItemsInView(ctx, params = {}) {
4812
4963
  state.containerItemTypes.set(containerIndex, requiredItemTypes[idx]);
4813
4964
  }
4814
4965
  containerItemKeys.set(id, containerIndex);
4815
- (_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.add(id);
4966
+ (_o = state.userScrollAnchorResetKeys) == null ? void 0 : _o.add(id);
4816
4967
  const containerSticky = `containerSticky${containerIndex}`;
4817
4968
  const isSticky = stickyIndicesSet.has(i);
4818
4969
  const isAlwaysRender = alwaysRenderSet.has(i);
@@ -4836,17 +4987,17 @@ function calculateItemsInView(ctx, params = {}) {
4836
4987
  if (numContainers !== prevNumContainers) {
4837
4988
  set$(ctx, "numContainers", numContainers);
4838
4989
  if (numContainers > peek$(ctx, "numContainersPooled")) {
4839
- set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
4990
+ set$(ctx, "numContainersPooled", getExpandedContainerPoolSize(dataLength, numContainers));
4840
4991
  }
4841
4992
  }
4842
4993
  }
4843
- if (((_q = state.userScrollAnchorResetKeys) == null ? void 0 : _q.size) === 0) {
4994
+ if (((_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.size) === 0) {
4844
4995
  state.userScrollAnchorResetKeys = void 0;
4845
4996
  }
4846
4997
  if (alwaysRenderArr.length > 0) {
4847
4998
  for (const index of alwaysRenderArr) {
4848
4999
  if (index < 0 || index >= dataLength) continue;
4849
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
5000
+ const id = (_q = idCache[index]) != null ? _q : getId(state, index);
4850
5001
  const containerIndex = containerItemKeys.get(id);
4851
5002
  if (containerIndex !== void 0) {
4852
5003
  state.stickyContainerPool.add(containerIndex);
@@ -4950,21 +5101,25 @@ function doMaintainScrollAtEnd(ctx) {
4950
5101
  if (contentSize < state.scrollLength) {
4951
5102
  state.scroll = 0;
4952
5103
  }
4953
- requestAnimationFrame(() => {
4954
- var _a3;
4955
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
4956
- state.maintainingScrollAtEnd = true;
4957
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
4958
- animated: maintainScrollAtEnd.animated
4959
- });
4960
- setTimeout(
4961
- () => {
4962
- state.maintainingScrollAtEnd = false;
4963
- },
4964
- maintainScrollAtEnd.animated ? 500 : 0
4965
- );
4966
- }
4967
- });
5104
+ if (!state.maintainingScrollAtEnd) {
5105
+ state.maintainingScrollAtEnd = true;
5106
+ requestAnimationFrame(() => {
5107
+ var _a3;
5108
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
5109
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
5110
+ animated: maintainScrollAtEnd.animated
5111
+ });
5112
+ setTimeout(
5113
+ () => {
5114
+ state.maintainingScrollAtEnd = false;
5115
+ },
5116
+ maintainScrollAtEnd.animated ? 500 : 0
5117
+ );
5118
+ } else {
5119
+ state.maintainingScrollAtEnd = false;
5120
+ }
5121
+ });
5122
+ }
4968
5123
  return true;
4969
5124
  }
4970
5125
  return false;
@@ -5076,14 +5231,21 @@ function doInitialAllocateContainers(ctx) {
5076
5231
  } else {
5077
5232
  averageItemSize = estimatedItemSize;
5078
5233
  }
5079
- const numContainers = Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns);
5234
+ const numContainers = Math.max(
5235
+ 1,
5236
+ Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns)
5237
+ );
5080
5238
  for (let i = 0; i < numContainers; i++) {
5081
5239
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
5082
5240
  set$(ctx, `containerColumn${i}`, -1);
5083
5241
  set$(ctx, `containerSpan${i}`, 1);
5084
5242
  }
5085
5243
  set$(ctx, "numContainers", numContainers);
5086
- set$(ctx, "numContainersPooled", numContainers * state.props.initialContainerPoolRatio);
5244
+ set$(
5245
+ ctx,
5246
+ "numContainersPooled",
5247
+ getInitialContainerPoolSize(data.length, numContainers, state.props.initialContainerPoolRatio)
5248
+ );
5087
5249
  if (state.lastLayout) {
5088
5250
  if (state.initialScroll) {
5089
5251
  requestAnimationFrame(() => {
@@ -5234,8 +5396,8 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
5234
5396
  // src/core/onScroll.ts
5235
5397
  function trackInitialScrollNativeProgress(state, newScroll) {
5236
5398
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);
5237
- const didInitialScrollProgress = !!initialNativeScrollWatchdog && initialScrollWatchdog.didObserveProgress(newScroll, initialNativeScrollWatchdog);
5238
- if (didInitialScrollProgress) {
5399
+ const didInitialScrollReachTarget = !!initialNativeScrollWatchdog && initialScrollWatchdog.didReachTarget(newScroll, initialNativeScrollWatchdog);
5400
+ if (didInitialScrollReachTarget) {
5239
5401
  initialScrollWatchdog.clear(state);
5240
5402
  return;
5241
5403
  }
@@ -5373,16 +5535,20 @@ function maybeUpdateAnchoredEndSpace(ctx) {
5373
5535
  let contentBelowAnchor = 0;
5374
5536
  const footerSize = ctx.values.get("footerSize") || 0;
5375
5537
  const stylePaddingBottom = state.props.stylePaddingBottom || 0;
5538
+ let hasUnknownTailSize = false;
5376
5539
  for (let index = anchorIndex; index < data.length; index++) {
5377
5540
  const itemKey = getId(state, index);
5378
5541
  const size = itemKey ? state.sizesKnown.get(itemKey) : void 0;
5379
5542
  const effectiveSize = index === anchorIndex && anchorMaxSize !== void 0 ? Math.min(size || 0, Math.max(0, anchorMaxSize)) : size;
5543
+ if (size === void 0) {
5544
+ hasUnknownTailSize = true;
5545
+ }
5380
5546
  if (effectiveSize !== null && effectiveSize !== void 0 && effectiveSize > 0) {
5381
5547
  contentBelowAnchor += effectiveSize;
5382
5548
  }
5383
5549
  }
5384
5550
  contentBelowAnchor += footerSize + stylePaddingBottom;
5385
- nextSize = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5551
+ nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5386
5552
  }
5387
5553
  }
5388
5554
  if (previousSize !== nextSize) {
@@ -5454,15 +5620,7 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5454
5620
  const {
5455
5621
  didContainersLayout,
5456
5622
  sizesKnown,
5457
- props: {
5458
- getFixedItemSize,
5459
- getItemType,
5460
- horizontal,
5461
- suggestEstimatedItemSize,
5462
- onItemSizeChanged,
5463
- data,
5464
- maintainScrollAtEnd
5465
- }
5623
+ props: { getFixedItemSize, getItemType, horizontal, onItemSizeChanged, data, maintainScrollAtEnd }
5466
5624
  } = state;
5467
5625
  if (!data) return;
5468
5626
  const index = state.indexByKey.get(itemKey);
@@ -5513,18 +5671,6 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5513
5671
  if (minIndexSizeChanged !== void 0) {
5514
5672
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5515
5673
  }
5516
- if (IS_DEV && suggestEstimatedItemSize && minIndexSizeChanged !== void 0) {
5517
- if (state.timeoutSizeMessage) clearTimeout(state.timeoutSizeMessage);
5518
- state.timeoutSizeMessage = setTimeout(() => {
5519
- var _a4;
5520
- state.timeoutSizeMessage = void 0;
5521
- const num = state.sizesKnown.size;
5522
- const avg = (_a4 = state.averageSizes[""]) == null ? void 0 : _a4.avg;
5523
- console.warn(
5524
- `[legend-list] Based on the ${num} items rendered so far, the optimal estimated size is ${avg}.`
5525
- );
5526
- }, 1e3);
5527
- }
5528
5674
  const cur = peek$(ctx, "otherAxisSize");
5529
5675
  if (!cur || maxOtherAxisSize > cur) {
5530
5676
  set$(ctx, "otherAxisSize", maxOtherAxisSize);
@@ -5629,12 +5775,47 @@ function createColumnWrapperStyle(contentContainerStyle) {
5629
5775
  }
5630
5776
 
5631
5777
  // src/utils/createImperativeHandle.ts
5778
+ var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
5779
+ function getAverageItemSizes(state) {
5780
+ const averageItemSizes = {};
5781
+ for (const itemType in state.averageSizes) {
5782
+ const averageSize = state.averageSizes[itemType];
5783
+ if (averageSize) {
5784
+ averageItemSizes[itemType || DEFAULT_AVERAGE_ITEM_SIZE_TYPE] = {
5785
+ average: averageSize.avg,
5786
+ count: averageSize.num
5787
+ };
5788
+ }
5789
+ }
5790
+ return averageItemSizes;
5791
+ }
5632
5792
  function createImperativeHandle(ctx) {
5633
5793
  const state = ctx.state;
5634
5794
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
5635
5795
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
5636
5796
  let imperativeScrollToken = 0;
5637
5797
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.queuedMVCPRecalculate !== void 0 || state.ignoreScrollFromMVCP !== void 0;
5798
+ const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
5799
+ var _a3;
5800
+ const props = state.props;
5801
+ const dataLength = props.data.length;
5802
+ const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5803
+ if (targetIndex < 0) {
5804
+ return allowEmpty;
5805
+ }
5806
+ if (targetIndex >= dataLength) {
5807
+ return false;
5808
+ }
5809
+ if (anchorIndex === void 0 || anchorIndex < 0 || anchorIndex >= dataLength || targetIndex < anchorIndex || props.getFixedItemSize) {
5810
+ return true;
5811
+ }
5812
+ for (let index = anchorIndex; index < dataLength; index++) {
5813
+ if (!state.sizesKnown.has(getId(state, index))) {
5814
+ return false;
5815
+ }
5816
+ }
5817
+ return true;
5818
+ };
5638
5819
  const runWhenReady = (token, run, isReady) => {
5639
5820
  const startedAt = Date.now();
5640
5821
  let stableFrames = 0;
@@ -5656,11 +5837,10 @@ function createImperativeHandle(ctx) {
5656
5837
  };
5657
5838
  requestAnimationFrame(check);
5658
5839
  };
5659
- const runScrollWithPromise = (run, options) => new Promise((resolve) => {
5660
- var _a3, _b;
5840
+ const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
5841
+ var _a3;
5661
5842
  const token = ++imperativeScrollToken;
5662
- const isReady = (_a3 = options == null ? void 0 : options.isReady) != null ? _a3 : (() => true);
5663
- (_b = state.pendingScrollResolve) == null ? void 0 : _b.call(state);
5843
+ (_a3 = state.pendingScrollResolve) == null ? void 0 : _a3.call(state);
5664
5844
  state.pendingScrollResolve = resolve;
5665
5845
  const runNow = () => {
5666
5846
  if (token !== imperativeScrollToken) {
@@ -5735,6 +5915,7 @@ function createImperativeHandle(ctx) {
5735
5915
  },
5736
5916
  end: state.endNoBuffer,
5737
5917
  endBuffered: state.endBuffered,
5918
+ getAverageItemSizes: () => getAverageItemSizes(state),
5738
5919
  isAtEnd: peek$(ctx, "isAtEnd"),
5739
5920
  isAtStart: peek$(ctx, "isAtStart"),
5740
5921
  isEndReached: state.isEndReached,
@@ -5777,40 +5958,34 @@ function createImperativeHandle(ctx) {
5777
5958
  }
5778
5959
  return false;
5779
5960
  }),
5780
- scrollToEnd: (options) => runScrollWithPromise(() => {
5781
- const data = state.props.data;
5782
- const stylePaddingBottom = state.props.stylePaddingBottom;
5783
- const index = data.length - 1;
5784
- if (index !== -1) {
5785
- const paddingBottom = stylePaddingBottom || 0;
5786
- const footerSize = peek$(ctx, "footerSize") || 0;
5787
- scrollToIndex(ctx, {
5788
- ...options,
5789
- index,
5790
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5791
- viewPosition: 1
5792
- });
5793
- return true;
5794
- }
5795
- return false;
5796
- }),
5797
- scrollToIndex: (params) => {
5798
- const shouldWaitForOutOfRangeTarget = params.index >= 0 && params.index >= state.props.data.length;
5799
- const options = shouldWaitForOutOfRangeTarget ? {
5800
- isReady: () => {
5801
- var _a3;
5802
- const props = state.props;
5803
- const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5804
- const lastIndex = props.data.length - 1;
5805
- const isInRange = params.index < props.data.length;
5806
- const shouldWaitForAnchorSize = isInRange && anchorIndex !== void 0 && anchorIndex >= 0 && params.index >= anchorIndex && !props.getFixedItemSize && !state.sizesKnown.has(getId(state, lastIndex));
5807
- return isInRange && !shouldWaitForAnchorSize;
5961
+ scrollToEnd: (options) => runScrollWithPromise(
5962
+ () => {
5963
+ const data = state.props.data;
5964
+ const stylePaddingBottom = state.props.stylePaddingBottom;
5965
+ const index = data.length - 1;
5966
+ if (index !== -1) {
5967
+ const paddingBottom = stylePaddingBottom || 0;
5968
+ const footerSize = peek$(ctx, "footerSize") || 0;
5969
+ scrollToIndex(ctx, {
5970
+ ...options,
5971
+ index,
5972
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5973
+ viewPosition: 1
5974
+ });
5975
+ return true;
5808
5976
  }
5809
- } : void 0;
5810
- return runScrollWithPromise(() => {
5811
- scrollToIndex(ctx, params);
5812
- return true;
5813
- }, options);
5977
+ return false;
5978
+ },
5979
+ () => isScrollToIndexReady(state.props.data.length - 1, true)
5980
+ ),
5981
+ scrollToIndex: (params) => {
5982
+ return runScrollWithPromise(
5983
+ () => {
5984
+ scrollToIndex(ctx, params);
5985
+ return true;
5986
+ },
5987
+ params.index >= 0 ? () => isScrollToIndexReady(params.index) : void 0
5988
+ );
5814
5989
  },
5815
5990
  scrollToItem: ({ item, ...props }) => runScrollWithPromise(() => {
5816
5991
  const data = state.props.data;
@@ -6074,7 +6249,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6074
6249
  getFixedItemSize,
6075
6250
  getItemType,
6076
6251
  horizontal,
6077
- initialContainerPoolRatio = 2,
6252
+ initialContainerPoolRatio = 3,
6078
6253
  initialScrollAtEnd = false,
6079
6254
  initialScrollIndex: initialScrollIndexProp,
6080
6255
  initialScrollOffset: initialScrollOffsetProp,
@@ -6115,7 +6290,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6115
6290
  stickyIndices: stickyIndicesDeprecated,
6116
6291
  // TODOV3: Remove from v3 release
6117
6292
  style: styleProp,
6118
- suggestEstimatedItemSize,
6119
6293
  useWindowScroll = false,
6120
6294
  viewabilityConfig,
6121
6295
  viewabilityConfigCallbackPairs,
@@ -6256,7 +6430,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6256
6430
  startReachedSnapshotDataChangeEpoch: void 0,
6257
6431
  stickyContainerPool: /* @__PURE__ */ new Set(),
6258
6432
  stickyContainers: /* @__PURE__ */ new Map(),
6259
- timeoutSizeMessage: 0,
6260
6433
  timeouts: /* @__PURE__ */ new Set(),
6261
6434
  totalSize: 0,
6262
6435
  viewabilityConfigCallbackPairs: void 0
@@ -6276,7 +6449,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6276
6449
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6277
6450
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6278
6451
  const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6279
- if (didDataChangeLocal && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6452
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6280
6453
  clearPreservedInitialScrollTarget(state);
6281
6454
  }
6282
6455
  if (didDataChangeLocal) {
@@ -6331,7 +6504,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6331
6504
  stickyPositionComponentInternal,
6332
6505
  stylePaddingBottom: stylePaddingBottomState,
6333
6506
  stylePaddingTop: stylePaddingTopState,
6334
- suggestEstimatedItemSize: !!suggestEstimatedItemSize,
6335
6507
  useWindowScroll: useWindowScrollResolved
6336
6508
  };
6337
6509
  state.refScroller = refScroller;