@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.mjs CHANGED
@@ -713,6 +713,7 @@ function getContainerPositionStyle({
713
713
  }
714
714
  var Container = typedMemo(function Container2({
715
715
  id,
716
+ itemKey,
716
717
  recycleItems,
717
718
  horizontal,
718
719
  getRenderedItem: getRenderedItem2,
@@ -724,11 +725,10 @@ var Container = typedMemo(function Container2({
724
725
  const { columnWrapperStyle, animatedScrollY } = ctx;
725
726
  const positionComponentInternal = ctx.state.props.positionComponentInternal;
726
727
  const stickyPositionComponentInternal = ctx.state.props.stickyPositionComponentInternal;
727
- const [column = 0, span = 1, data, itemKey, numColumns = 1, extraData, isSticky] = useArr$([
728
+ const [column = 0, span = 1, data, numColumns = 1, extraData, isSticky] = useArr$([
728
729
  `containerColumn${id}`,
729
730
  `containerSpan${id}`,
730
731
  `containerItemData${id}`,
731
- `containerItemKey${id}`,
732
732
  "numColumns",
733
733
  "extraData",
734
734
  `containerSticky${id}`
@@ -846,6 +846,39 @@ var Container = typedMemo(function Container2({
846
846
  );
847
847
  });
848
848
 
849
+ // src/components/ContainerSlot.tsx
850
+ function ContainerSlotBase({
851
+ id,
852
+ horizontal,
853
+ recycleItems,
854
+ ItemSeparatorComponent,
855
+ updateItemSize: updateItemSize2,
856
+ getRenderedItem: getRenderedItem2,
857
+ stickyHeaderConfig,
858
+ ContainerComponent = Container
859
+ }) {
860
+ const [itemKey] = useArr$([`containerItemKey${id}`]);
861
+ if (itemKey === void 0) {
862
+ return null;
863
+ }
864
+ return /* @__PURE__ */ React3.createElement(
865
+ ContainerComponent,
866
+ {
867
+ getRenderedItem: getRenderedItem2,
868
+ horizontal,
869
+ ItemSeparatorComponent,
870
+ id,
871
+ itemKey,
872
+ recycleItems,
873
+ stickyHeaderConfig,
874
+ updateItemSize: updateItemSize2
875
+ }
876
+ );
877
+ }
878
+ var ContainerSlot = typedMemo(function ContainerSlot2(props) {
879
+ return /* @__PURE__ */ React3.createElement(ContainerSlotBase, { ...props });
880
+ });
881
+
849
882
  // src/utils/reordering.ts
850
883
  var mapFn = (element) => {
851
884
  const indexStr = element.getAttribute("data-index");
@@ -965,9 +998,12 @@ var ContainersInner = typedMemo(function ContainersInner2({ horizontal, numColum
965
998
  const ref = useRef(null);
966
999
  const ctx = useStateContext();
967
1000
  const columnWrapperStyle = ctx.columnWrapperStyle;
968
- const [otherAxisSize, totalSize] = useArr$(["otherAxisSize", "totalSize"]);
1001
+ const [otherAxisSize, readyToRender, totalSize] = useArr$(["otherAxisSize", "readyToRender", "totalSize"]);
969
1002
  useDOMOrder(ref);
970
- const style = horizontal ? { minHeight: otherAxisSize, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, position: "relative" };
1003
+ const style = horizontal ? { minHeight: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative" };
1004
+ if (!readyToRender) {
1005
+ style.pointerEvents = "none";
1006
+ }
971
1007
  if (columnWrapperStyle && numColumns > 1) {
972
1008
  const { columnGap, rowGap, gap } = columnWrapperStyle;
973
1009
  const gapX = columnGap || gap || 0;
@@ -995,12 +1031,12 @@ var Containers = typedMemo(function Containers2({
995
1031
  getRenderedItem: getRenderedItem2,
996
1032
  stickyHeaderConfig
997
1033
  }) {
998
- const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1034
+ const [numContainersPooled, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
999
1035
  const containers = [];
1000
- for (let i = 0; i < numContainers; i++) {
1036
+ for (let i = 0; i < numContainersPooled; i++) {
1001
1037
  containers.push(
1002
1038
  /* @__PURE__ */ React3.createElement(
1003
- Container,
1039
+ ContainerSlot,
1004
1040
  {
1005
1041
  getRenderedItem: getRenderedItem2,
1006
1042
  horizontal,
@@ -1072,6 +1108,8 @@ function useRafCoalescer(callback) {
1072
1108
 
1073
1109
  // src/components/webConstants.ts
1074
1110
  var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
1111
+ var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
1112
+ var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
1075
1113
 
1076
1114
  // src/components/webScrollUtils.ts
1077
1115
  function getDocumentScrollerNode() {
@@ -1157,6 +1195,17 @@ function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll
1157
1195
  }
1158
1196
 
1159
1197
  // src/components/ListComponentScrollView.tsx
1198
+ var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
1199
+ 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;}`;
1200
+ function ensureScrollbarHiddenStyle() {
1201
+ if (typeof document === "undefined" || document.getElementById(SCROLLBAR_HIDDEN_STYLE_ID)) {
1202
+ return;
1203
+ }
1204
+ const styleElement = document.createElement("style");
1205
+ styleElement.id = SCROLLBAR_HIDDEN_STYLE_ID;
1206
+ styleElement.textContent = SCROLLBAR_HIDDEN_STYLE;
1207
+ document.head.appendChild(styleElement);
1208
+ }
1160
1209
  function getContentInsetEndAdjustmentEnd2(ctx) {
1161
1210
  var _a3, _b;
1162
1211
  const adjustment = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.props) == null ? void 0 : _b.contentInsetEndAdjustment;
@@ -1361,6 +1410,12 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
1361
1410
  }
1362
1411
  };
1363
1412
  }, [isWindowScroll, onLayout]);
1413
+ const hiddenScrollIndicatorClassName = !isWindowScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
1414
+ useLayoutEffect(() => {
1415
+ if (hiddenScrollIndicatorClassName) {
1416
+ ensureScrollbarHiddenStyle();
1417
+ }
1418
+ }, [hiddenScrollIndicatorClassName]);
1364
1419
  const scrollViewStyle = {
1365
1420
  ...isWindowScroll ? {} : {
1366
1421
  overflow: "auto",
@@ -1389,9 +1444,31 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
1389
1444
  scrollEventThrottle: _scrollEventThrottle,
1390
1445
  ScrollComponent: _ScrollComponent,
1391
1446
  useWindowScroll: _useWindowScroll,
1447
+ className: scrollViewClassNameProp,
1392
1448
  ...webProps
1393
1449
  } = props;
1394
- return /* @__PURE__ */ React3.createElement("div", { ref: scrollRef, ...webProps, style: scrollViewStyle }, refreshControl, /* @__PURE__ */ React3.createElement("div", { className, ref: contentRef, style: contentStyle }, children, contentInsetEndAdjustmentSpacerStyle ? /* @__PURE__ */ React3.createElement("div", { "aria-hidden": true, style: contentInsetEndAdjustmentSpacerStyle }) : null));
1450
+ const scrollViewClassName = hiddenScrollIndicatorClassName ? scrollViewClassNameProp ? `${scrollViewClassNameProp} ${hiddenScrollIndicatorClassName}` : hiddenScrollIndicatorClassName : scrollViewClassNameProp;
1451
+ if (IS_DEV) {
1452
+ if (/(?:^|\s)(?:[a-z0-9_-]+:)*gap(?:-[xy])?-(?:\[[^\]]+\]|[^\s]+)/.test(
1453
+ `${contentContainerClassName != null ? contentContainerClassName : ""} ${scrollViewClassNameProp != null ? scrollViewClassNameProp : ""}`
1454
+ )) {
1455
+ warnDevOnce(
1456
+ "className-gap",
1457
+ "className/contentContainerClassName gap classes are not supported in LegendList because it needs to use exact values internally. Use contentContainerStyle={{ gap: ... }} or columnWrapperStyle instead."
1458
+ );
1459
+ }
1460
+ }
1461
+ return /* @__PURE__ */ React3.createElement(
1462
+ "div",
1463
+ {
1464
+ className: scrollViewClassName,
1465
+ ref: scrollRef,
1466
+ ...webProps,
1467
+ style: scrollViewStyle
1468
+ },
1469
+ refreshControl,
1470
+ /* @__PURE__ */ React3.createElement("div", { className, ref: contentRef, style: contentStyle }, children, contentInsetEndAdjustmentSpacerStyle ? /* @__PURE__ */ React3.createElement("div", { "aria-hidden": true, style: contentInsetEndAdjustmentSpacerStyle }) : null)
1471
+ );
1395
1472
  });
1396
1473
  function useValueListener$(key, callback) {
1397
1474
  const ctx = useStateContext();
@@ -1419,11 +1496,18 @@ function getScrollAdjustAxis(horizontal) {
1419
1496
  y: 1
1420
1497
  };
1421
1498
  }
1422
- function resolveScrollAdjustContentNode(el, contentNode) {
1423
- if ((contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === el) {
1424
- return contentNode;
1499
+ function getScrollAdjustTarget(ctx, contentNode) {
1500
+ var _a3, _b, _c;
1501
+ const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1502
+ const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
1503
+ let resolvedContentNode = null;
1504
+ if (scrollElement) {
1505
+ resolvedContentNode = (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1425
1506
  }
1426
- return el.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1507
+ return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
1508
+ }
1509
+ function scrollAdjustBy(el, left, top) {
1510
+ el.scrollBy({ behavior: "auto", left, top });
1427
1511
  }
1428
1512
  function ScrollAdjust() {
1429
1513
  const ctx = useStateContext();
@@ -1432,43 +1516,43 @@ function ScrollAdjust() {
1432
1516
  const resetPaddingBaselineRef = React3.useRef(void 0);
1433
1517
  const contentNodeRef = React3.useRef(null);
1434
1518
  const callback = React3.useCallback(() => {
1435
- var _a3, _b;
1519
+ var _a3;
1436
1520
  const scrollAdjust = peek$(ctx, "scrollAdjust");
1437
1521
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
1438
1522
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
1439
- const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1440
- if (scrollView && scrollOffset !== lastScrollOffsetRef.current) {
1441
- const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1442
- if (scrollDelta !== 0) {
1443
- const axis = getScrollAdjustAxis(!!ctx.state.props.horizontal);
1444
- const prevScroll = scrollView.getCurrentScrollOffset();
1445
- const el = scrollView.getScrollableNode();
1446
- const contentNode = resolveScrollAdjustContentNode(el, contentNodeRef.current);
1523
+ const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1524
+ if (scrollDelta !== 0) {
1525
+ const target = getScrollAdjustTarget(ctx, contentNodeRef.current);
1526
+ if (target) {
1527
+ const horizontal = !!ctx.state.props.horizontal;
1528
+ const axis = getScrollAdjustAxis(horizontal);
1529
+ const { contentNode, scrollElement: el } = target;
1530
+ const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
1447
1531
  contentNodeRef.current = contentNode;
1448
- const scrollBy = () => scrollView.scrollBy(axis.x * scrollDelta, axis.y * scrollDelta);
1449
- if (!contentNode) {
1450
- scrollBy();
1451
- lastScrollOffsetRef.current = scrollOffset;
1452
- return;
1453
- }
1454
- const totalSize = contentNode[axis.contentSizeKey];
1455
- const viewportSize = el[axis.viewportSizeKey];
1456
- const nextScroll = prevScroll + scrollDelta;
1457
- if (scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize) {
1458
- const previousPaddingEnd = (_b = resetPaddingBaselineRef.current) != null ? _b : contentNode.style[axis.paddingEndProp];
1459
- resetPaddingBaselineRef.current = previousPaddingEnd;
1460
- const pad = (nextScroll + viewportSize - totalSize) * 2;
1461
- contentNode.style[axis.paddingEndProp] = `${pad}px`;
1462
- void contentNode.offsetHeight;
1463
- scrollBy();
1464
- if (resetPaddingRafRef.current !== void 0) {
1465
- cancelAnimationFrame(resetPaddingRafRef.current);
1532
+ if (contentNode) {
1533
+ const prevScroll = horizontal ? el.scrollLeft : el.scrollTop;
1534
+ const totalSize = contentNode[axis.contentSizeKey];
1535
+ const viewportSize = el[axis.viewportSizeKey];
1536
+ const nextScroll = prevScroll + scrollDelta;
1537
+ const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
1538
+ if (needsTemporaryPadding) {
1539
+ const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
1540
+ resetPaddingBaselineRef.current = previousPaddingEnd;
1541
+ const pad = (nextScroll + viewportSize - totalSize) * 2;
1542
+ contentNode.style[axis.paddingEndProp] = `${pad}px`;
1543
+ void contentNode.offsetHeight;
1544
+ scrollBy();
1545
+ if (resetPaddingRafRef.current !== void 0) {
1546
+ cancelAnimationFrame(resetPaddingRafRef.current);
1547
+ }
1548
+ resetPaddingRafRef.current = requestAnimationFrame(() => {
1549
+ resetPaddingRafRef.current = void 0;
1550
+ resetPaddingBaselineRef.current = void 0;
1551
+ contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1552
+ });
1553
+ } else {
1554
+ scrollBy();
1466
1555
  }
1467
- resetPaddingRafRef.current = requestAnimationFrame(() => {
1468
- resetPaddingRafRef.current = void 0;
1469
- resetPaddingBaselineRef.current = void 0;
1470
- contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1471
- });
1472
1556
  } else {
1473
1557
  scrollBy();
1474
1558
  }
@@ -1779,10 +1863,9 @@ var initialScrollWatchdog = {
1779
1863
  clear(state) {
1780
1864
  initialScrollWatchdog.set(state, void 0);
1781
1865
  },
1782
- didObserveProgress(newScroll, watchdog) {
1783
- const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
1866
+ didReachTarget(newScroll, watchdog) {
1784
1867
  const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
1785
- return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET || nextDistance + INITIAL_SCROLL_MIN_TARGET_OFFSET < previousDistance;
1868
+ return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET;
1786
1869
  },
1787
1870
  get(state) {
1788
1871
  var _a3, _b;
@@ -1807,19 +1890,19 @@ var initialScrollWatchdog = {
1807
1890
  }
1808
1891
  };
1809
1892
  function setInitialScrollSession(state, options = {}) {
1810
- var _a3, _b, _c;
1893
+ var _a3, _b, _c, _d;
1811
1894
  const existingSession = state.initialScrollSession;
1812
1895
  const kind = (_a3 = options.kind) != null ? _a3 : existingSession == null ? void 0 : existingSession.kind;
1813
1896
  const completion = existingSession == null ? void 0 : existingSession.completion;
1814
- const hasBootstrapOverride = Object.hasOwn(options, "bootstrap");
1815
- const bootstrap = kind === "bootstrap" ? hasBootstrapOverride ? options.bootstrap : (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0 : void 0;
1897
+ const existingBootstrap = (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0;
1898
+ const bootstrap = kind === "bootstrap" ? options.bootstrap === null ? void 0 : (_b = options.bootstrap) != null ? _b : existingBootstrap : void 0;
1816
1899
  if (!kind) {
1817
1900
  return clearInitialScrollSession(state);
1818
1901
  }
1819
1902
  if (!state.initialScroll && !bootstrap && !hasInitialScrollSessionCompletion(completion)) {
1820
1903
  return clearInitialScrollSession(state);
1821
1904
  }
1822
- const previousDataLength = (_c = (_b = options.previousDataLength) != null ? _b : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _c : 0;
1905
+ const previousDataLength = (_d = (_c = options.previousDataLength) != null ? _c : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _d : 0;
1823
1906
  state.initialScrollSession = createInitialScrollSession({
1824
1907
  bootstrap,
1825
1908
  completion,
@@ -2571,7 +2654,7 @@ function advanceMeasuredInitialScroll(ctx, options) {
2571
2654
  const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2572
2655
  const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2573
2656
  const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2574
- const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - activeInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - activeInitialTargetOffset) <= 1;
2657
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2575
2658
  if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2576
2659
  return false;
2577
2660
  }
@@ -2643,6 +2726,30 @@ function checkAllSizesKnown(state, indices) {
2643
2726
  });
2644
2727
  }
2645
2728
 
2729
+ // src/utils/requestAdjust.ts
2730
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2731
+ const state = ctx.state;
2732
+ if (Math.abs(positionDiff) > 0.1) {
2733
+ const doit = () => {
2734
+ {
2735
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2736
+ if (state.adjustingFromInitialMount) {
2737
+ state.adjustingFromInitialMount--;
2738
+ }
2739
+ }
2740
+ };
2741
+ state.scroll += positionDiff;
2742
+ state.scrollForNextCalculateItemsInView = void 0;
2743
+ const readyToRender = peek$(ctx, "readyToRender");
2744
+ if (readyToRender) {
2745
+ doit();
2746
+ } else {
2747
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2748
+ requestAnimationFrame(doit);
2749
+ }
2750
+ }
2751
+ }
2752
+
2646
2753
  // src/core/bootstrapInitialScroll.ts
2647
2754
  var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2648
2755
  var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
@@ -2736,7 +2843,7 @@ function clearBootstrapInitialScrollSession(state) {
2736
2843
  bootstrapInitialScroll.frameHandle = void 0;
2737
2844
  }
2738
2845
  setInitialScrollSession(state, {
2739
- bootstrap: void 0,
2846
+ bootstrap: null,
2740
2847
  kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2741
2848
  });
2742
2849
  }
@@ -2892,15 +2999,18 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2892
2999
  return;
2893
3000
  }
2894
3001
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2895
- if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
2896
- clearPendingInitialScrollFooterLayout(ctx, {
2897
- dataLength: state.props.data.length,
2898
- stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
2899
- target: initialScroll
2900
- });
2901
- return;
3002
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3003
+ if (!shouldKeepEndTargetAlive) {
3004
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3005
+ clearPendingInitialScrollFooterLayout(ctx, {
3006
+ dataLength: state.props.data.length,
3007
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3008
+ target: initialScroll
3009
+ });
3010
+ } else {
3011
+ clearFinishedViewportRetargetableInitialScroll(state);
3012
+ }
2902
3013
  }
2903
- clearFinishedViewportRetargetableInitialScroll(state);
2904
3014
  }
2905
3015
  }
2906
3016
  function startBootstrapInitialScrollOnMount(ctx, options) {
@@ -2939,7 +3049,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2939
3049
  }
2940
3050
  const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2941
3051
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2942
- const shouldClearFinishedResizePreservation = didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3052
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2943
3053
  if (shouldClearFinishedResizePreservation) {
2944
3054
  clearPreservedInitialScrollTarget(state);
2945
3055
  return;
@@ -3042,27 +3152,46 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3042
3152
  }
3043
3153
  }
3044
3154
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3155
+ var _a3, _b, _c, _d;
3045
3156
  const state = ctx.state;
3046
3157
  const initialScroll = state.initialScroll;
3047
- if (isOffsetInitialScrollSession(state) || state.props.data.length === 0 || !initialScroll) {
3048
- return;
3049
- }
3050
3158
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3051
- if (!bootstrapInitialScroll && initialScroll.viewPosition !== 1) {
3052
- return;
3053
- }
3054
- const didFinishInitialScroll = state.didFinishInitialScroll;
3055
- if (didFinishInitialScroll) {
3056
- setInitialScrollTarget(state, initialScroll, {
3057
- resetDidFinish: true
3058
- });
3059
- state.clearPreservedInitialScrollOnNextFinish = true;
3159
+ if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3160
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3161
+ const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
3162
+ if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
3163
+ const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3164
+ const offsetDiff = resolvedOffset - currentOffset;
3165
+ if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3166
+ if (scrollingTo) {
3167
+ const existingWatchdog = initialScrollWatchdog.get(state);
3168
+ scrollingTo.offset = resolvedOffset;
3169
+ scrollingTo.targetOffset = resolvedOffset;
3170
+ state.initialScroll = {
3171
+ ...initialScroll,
3172
+ contentOffset: resolvedOffset
3173
+ };
3174
+ state.hasScrolled = false;
3175
+ initialScrollWatchdog.set(state, {
3176
+ startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3177
+ targetOffset: resolvedOffset
3178
+ });
3179
+ }
3180
+ requestAdjust(ctx, offsetDiff);
3181
+ if (state.didFinishInitialScroll) {
3182
+ (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3183
+ }
3184
+ }
3185
+ if (state.didFinishInitialScroll) {
3186
+ clearFinishedViewportRetargetableInitialScroll(state);
3187
+ }
3188
+ } else {
3189
+ rearmBootstrapInitialScroll(ctx, {
3190
+ scroll: resolvedOffset,
3191
+ targetIndexSeed: initialScroll.index
3192
+ });
3193
+ }
3060
3194
  }
3061
- rearmBootstrapInitialScroll(ctx, {
3062
- scroll: resolveInitialScrollOffset(ctx, initialScroll),
3063
- seedContentOffset: didFinishInitialScroll && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3064
- targetIndexSeed: initialScroll.index
3065
- });
3066
3195
  }
3067
3196
  function evaluateBootstrapInitialScroll(ctx) {
3068
3197
  var _a3, _b;
@@ -3269,7 +3398,7 @@ function checkFinishedScrollFallback(ctx) {
3269
3398
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, delay);
3270
3399
  };
3271
3400
  const checkHasScrolled = () => {
3272
- var _c;
3401
+ var _c, _d;
3273
3402
  state.timeoutCheckFinishedScrollFallback = void 0;
3274
3403
  const isStillScrollingTo = state.scrollingTo;
3275
3404
  if (isStillScrollingTo) {
@@ -3282,11 +3411,13 @@ function checkFinishedScrollFallback(ctx) {
3282
3411
  isStillScrollingTo
3283
3412
  );
3284
3413
  const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
3285
- const canFinishAfterSilentNativeDispatch = silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
3286
- if (shouldFinishZeroTarget || state.hasScrolled || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3414
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android";
3415
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
3416
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
3417
+ if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3287
3418
  finishScrollTo(ctx);
3288
- } else if (isNativeInitialPending && numChecks <= maxChecks) {
3289
- const targetOffset = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : state.scrollPending;
3419
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
3420
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
3290
3421
  scrollToFallbackOffset(ctx, targetOffset);
3291
3422
  scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
3292
3423
  } else {
@@ -3381,30 +3512,6 @@ function handleInitialScrollDataChange(ctx, options) {
3381
3512
  advanceCurrentInitialScrollSession(ctx);
3382
3513
  }
3383
3514
 
3384
- // src/utils/requestAdjust.ts
3385
- function requestAdjust(ctx, positionDiff, dataChanged) {
3386
- const state = ctx.state;
3387
- if (Math.abs(positionDiff) > 0.1) {
3388
- const doit = () => {
3389
- {
3390
- state.scrollAdjustHandler.requestAdjust(positionDiff);
3391
- if (state.adjustingFromInitialMount) {
3392
- state.adjustingFromInitialMount--;
3393
- }
3394
- }
3395
- };
3396
- state.scroll += positionDiff;
3397
- state.scrollForNextCalculateItemsInView = void 0;
3398
- const readyToRender = peek$(ctx, "readyToRender");
3399
- if (readyToRender) {
3400
- doit();
3401
- } else {
3402
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
3403
- requestAnimationFrame(doit);
3404
- }
3405
- }
3406
- }
3407
-
3408
3515
  // src/core/mvcp.ts
3409
3516
  var MVCP_POSITION_EPSILON = 0.1;
3410
3517
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -3678,7 +3785,10 @@ function prepareMVCP(ctx, dataChanged) {
3678
3785
  return;
3679
3786
  }
3680
3787
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3681
- requestAdjust(ctx, positionDiff);
3788
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3789
+ if (!shouldSkipAdjustForMaintainedEnd) {
3790
+ requestAdjust(ctx, positionDiff);
3791
+ }
3682
3792
  }
3683
3793
  };
3684
3794
  }
@@ -4297,6 +4407,30 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
4297
4407
  var unstableBatchedUpdates = ReactDOM.unstable_batchedUpdates;
4298
4408
  var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
4299
4409
 
4410
+ // src/utils/containerPool.ts
4411
+ var MIN_INITIAL_CONTAINER_POOL_SIZE = 32;
4412
+ var MAX_INITIAL_SPARE_CONTAINERS = 64;
4413
+ function getInitialContainerPoolSize(dataLength, numContainers, initialContainerPoolRatio) {
4414
+ if (dataLength <= 0 || numContainers <= 0) {
4415
+ return 0;
4416
+ }
4417
+ const ratioPoolSize = Math.ceil(numContainers * initialContainerPoolRatio);
4418
+ const cappedSparePoolSize = numContainers + MAX_INITIAL_SPARE_CONTAINERS;
4419
+ const targetPoolSize = Math.max(
4420
+ numContainers,
4421
+ Math.min(ratioPoolSize, cappedSparePoolSize),
4422
+ Math.min(dataLength, MIN_INITIAL_CONTAINER_POOL_SIZE)
4423
+ );
4424
+ const maxUsefulPoolSize = Math.max(dataLength, numContainers);
4425
+ return Math.min(maxUsefulPoolSize, targetPoolSize);
4426
+ }
4427
+ function getExpandedContainerPoolSize(dataLength, numContainers) {
4428
+ if (dataLength <= 0 || numContainers <= 0) {
4429
+ return 0;
4430
+ }
4431
+ return Math.min(Math.max(dataLength, numContainers), Math.max(numContainers, Math.ceil(numContainers * 1.5)));
4432
+ }
4433
+
4300
4434
  // src/utils/findAvailableContainers.ts
4301
4435
  function findAvailableContainers(ctx, numNeeded, startBuffered, endBuffered, pendingRemoval, requiredItemTypes, needNewContainers, protectedKeys) {
4302
4436
  const numContainers = peek$(ctx, "numContainers");
@@ -4502,7 +4636,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4502
4636
  function calculateItemsInView(ctx, params = {}) {
4503
4637
  const state = ctx.state;
4504
4638
  batchedUpdates(() => {
4505
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
4639
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
4506
4640
  const {
4507
4641
  columns,
4508
4642
  columnSpans,
@@ -4549,12 +4683,22 @@ function calculateItemsInView(ctx, params = {}) {
4549
4683
  // current initial-scroll target instead of transient native adjustments.
4550
4684
  resolveInitialScrollOffset(ctx, initialScroll)
4551
4685
  ) : state.scroll;
4552
- const scrollAdjustPending = (_c = peek$(ctx, "scrollAdjustPending")) != null ? _c : 0;
4553
- const scrollAdjustPad = scrollAdjustPending - topPad;
4554
- let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
4555
- if (scroll + scrollLength > totalSize) {
4556
- scroll = Math.max(0, totalSize - scrollLength);
4557
- }
4686
+ let scrollAdjustPending = 0;
4687
+ let scrollAdjustPad = 0;
4688
+ let scroll = 0;
4689
+ let scrollTopBuffered = 0;
4690
+ let scrollBottom = 0;
4691
+ let scrollBottomBuffered = 0;
4692
+ const updateScroll2 = (nextScrollState) => {
4693
+ var _a4;
4694
+ scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4695
+ scrollAdjustPad = scrollAdjustPending - topPad;
4696
+ scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4697
+ if (scroll + scrollLength > totalSize) {
4698
+ scroll = Math.max(0, totalSize - scrollLength);
4699
+ }
4700
+ };
4701
+ updateScroll2(scrollState);
4558
4702
  const previousStickyIndex = peek$(ctx, "activeStickyIndex");
4559
4703
  const currentStickyIdx = stickyIndicesArr.length > 0 ? findCurrentStickyIndex(stickyIndicesArr, scroll, state) : -1;
4560
4704
  const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyIndicesArr[currentStickyIdx] : -1;
@@ -4570,9 +4714,12 @@ function calculateItemsInView(ctx, params = {}) {
4570
4714
  scrollBufferTop = drawDistance * 1.5;
4571
4715
  scrollBufferBottom = drawDistance * 0.5;
4572
4716
  }
4573
- const scrollTopBuffered = scroll - scrollBufferTop;
4574
- const scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4575
- const scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4717
+ const updateScrollRange = () => {
4718
+ scrollTopBuffered = scroll - scrollBufferTop;
4719
+ scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4720
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4721
+ };
4722
+ updateScrollRange();
4576
4723
  if (!suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4577
4724
  const { top, bottom } = scrollForNextCalculateItemsInView;
4578
4725
  if (top === null && bottom === null) {
@@ -4591,7 +4738,7 @@ function calculateItemsInView(ctx, params = {}) {
4591
4738
  columns.length = 0;
4592
4739
  columnSpans.length = 0;
4593
4740
  }
4594
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_d = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _d : 0;
4741
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
4595
4742
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4596
4743
  updateItemPositions(ctx, dataChanged, {
4597
4744
  doMVCP,
@@ -4616,21 +4763,25 @@ function calculateItemsInView(ctx, params = {}) {
4616
4763
  }
4617
4764
  }
4618
4765
  const scrollBeforeMVCP = state.scroll;
4619
- const scrollAdjustPendingBeforeMVCP = (_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0;
4766
+ const scrollAdjustPendingBeforeMVCP = (_d = peek$(ctx, "scrollAdjustPending")) != null ? _d : 0;
4620
4767
  checkMVCP == null ? void 0 : checkMVCP();
4621
- const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_f = peek$(ctx, "scrollAdjustPending")) != null ? _f : 0) !== scrollAdjustPendingBeforeMVCP);
4768
+ const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0) !== scrollAdjustPendingBeforeMVCP);
4769
+ if (didMVCPAdjustScroll && initialScroll) {
4770
+ updateScroll2(state.scroll);
4771
+ updateScrollRange();
4772
+ }
4622
4773
  let startNoBuffer = null;
4623
4774
  let startBuffered = null;
4624
4775
  let startBufferedId = null;
4625
4776
  let endNoBuffer = null;
4626
4777
  let endBuffered = null;
4627
- let loopStart = (_g = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _g : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4778
+ let loopStart = (_f = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _f : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4628
4779
  for (let i = loopStart; i >= 0; i--) {
4629
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
4780
+ const id = (_g = idCache[i]) != null ? _g : getId(state, i);
4630
4781
  const top = positions[i];
4631
- const size = (_i = sizes.get(id)) != null ? _i : getItemSize(ctx, id, i, data[i]);
4782
+ const size = (_h = sizes.get(id)) != null ? _h : getItemSize(ctx, id, i, data[i]);
4632
4783
  const bottom = top + size;
4633
- if (bottom > scroll - scrollBufferTop) {
4784
+ if (bottom > scrollTopBuffered) {
4634
4785
  loopStart = i;
4635
4786
  } else {
4636
4787
  break;
@@ -4659,8 +4810,8 @@ function calculateItemsInView(ctx, params = {}) {
4659
4810
  let firstFullyOnScreenIndex;
4660
4811
  const dataLength = data.length;
4661
4812
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
4662
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
4663
- const size = (_k = sizes.get(id)) != null ? _k : getItemSize(ctx, id, i, data[i]);
4813
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
4814
+ const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
4664
4815
  const top = positions[i];
4665
4816
  if (!foundEnd) {
4666
4817
  if (startNoBuffer === null && top + size > scroll) {
@@ -4699,7 +4850,7 @@ function calculateItemsInView(ctx, params = {}) {
4699
4850
  const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
4700
4851
  if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
4701
4852
  for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
4702
- const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4853
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
4703
4854
  idsInView.push(id);
4704
4855
  }
4705
4856
  }
@@ -4732,7 +4883,7 @@ function calculateItemsInView(ctx, params = {}) {
4732
4883
  const needNewContainers = [];
4733
4884
  const needNewContainersSet = /* @__PURE__ */ new Set();
4734
4885
  for (let i = startBuffered; i <= endBuffered; i++) {
4735
- const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4886
+ const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4736
4887
  if (!containerItemKeys.has(id)) {
4737
4888
  needNewContainersSet.add(i);
4738
4889
  needNewContainers.push(i);
@@ -4741,7 +4892,7 @@ function calculateItemsInView(ctx, params = {}) {
4741
4892
  if (alwaysRenderArr.length > 0) {
4742
4893
  for (const index of alwaysRenderArr) {
4743
4894
  if (index < 0 || index >= dataLength) continue;
4744
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4895
+ const id = (_m = idCache[index]) != null ? _m : getId(state, index);
4745
4896
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4746
4897
  needNewContainersSet.add(index);
4747
4898
  needNewContainers.push(index);
@@ -4780,7 +4931,7 @@ function calculateItemsInView(ctx, params = {}) {
4780
4931
  for (let idx = 0; idx < needNewContainers.length; idx++) {
4781
4932
  const i = needNewContainers[idx];
4782
4933
  const containerIndex = availableContainers[idx];
4783
- const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4934
+ const id = (_n = idCache[i]) != null ? _n : getId(state, i);
4784
4935
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4785
4936
  if (oldKey && oldKey !== id) {
4786
4937
  containerItemKeys.delete(oldKey);
@@ -4791,7 +4942,7 @@ function calculateItemsInView(ctx, params = {}) {
4791
4942
  state.containerItemTypes.set(containerIndex, requiredItemTypes[idx]);
4792
4943
  }
4793
4944
  containerItemKeys.set(id, containerIndex);
4794
- (_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.add(id);
4945
+ (_o = state.userScrollAnchorResetKeys) == null ? void 0 : _o.add(id);
4795
4946
  const containerSticky = `containerSticky${containerIndex}`;
4796
4947
  const isSticky = stickyIndicesSet.has(i);
4797
4948
  const isAlwaysRender = alwaysRenderSet.has(i);
@@ -4815,17 +4966,17 @@ function calculateItemsInView(ctx, params = {}) {
4815
4966
  if (numContainers !== prevNumContainers) {
4816
4967
  set$(ctx, "numContainers", numContainers);
4817
4968
  if (numContainers > peek$(ctx, "numContainersPooled")) {
4818
- set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
4969
+ set$(ctx, "numContainersPooled", getExpandedContainerPoolSize(dataLength, numContainers));
4819
4970
  }
4820
4971
  }
4821
4972
  }
4822
- if (((_q = state.userScrollAnchorResetKeys) == null ? void 0 : _q.size) === 0) {
4973
+ if (((_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.size) === 0) {
4823
4974
  state.userScrollAnchorResetKeys = void 0;
4824
4975
  }
4825
4976
  if (alwaysRenderArr.length > 0) {
4826
4977
  for (const index of alwaysRenderArr) {
4827
4978
  if (index < 0 || index >= dataLength) continue;
4828
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
4979
+ const id = (_q = idCache[index]) != null ? _q : getId(state, index);
4829
4980
  const containerIndex = containerItemKeys.get(id);
4830
4981
  if (containerIndex !== void 0) {
4831
4982
  state.stickyContainerPool.add(containerIndex);
@@ -4929,21 +5080,25 @@ function doMaintainScrollAtEnd(ctx) {
4929
5080
  if (contentSize < state.scrollLength) {
4930
5081
  state.scroll = 0;
4931
5082
  }
4932
- requestAnimationFrame(() => {
4933
- var _a3;
4934
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
4935
- state.maintainingScrollAtEnd = true;
4936
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
4937
- animated: maintainScrollAtEnd.animated
4938
- });
4939
- setTimeout(
4940
- () => {
4941
- state.maintainingScrollAtEnd = false;
4942
- },
4943
- maintainScrollAtEnd.animated ? 500 : 0
4944
- );
4945
- }
4946
- });
5083
+ if (!state.maintainingScrollAtEnd) {
5084
+ state.maintainingScrollAtEnd = true;
5085
+ requestAnimationFrame(() => {
5086
+ var _a3;
5087
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
5088
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
5089
+ animated: maintainScrollAtEnd.animated
5090
+ });
5091
+ setTimeout(
5092
+ () => {
5093
+ state.maintainingScrollAtEnd = false;
5094
+ },
5095
+ maintainScrollAtEnd.animated ? 500 : 0
5096
+ );
5097
+ } else {
5098
+ state.maintainingScrollAtEnd = false;
5099
+ }
5100
+ });
5101
+ }
4947
5102
  return true;
4948
5103
  }
4949
5104
  return false;
@@ -5055,14 +5210,21 @@ function doInitialAllocateContainers(ctx) {
5055
5210
  } else {
5056
5211
  averageItemSize = estimatedItemSize;
5057
5212
  }
5058
- const numContainers = Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns);
5213
+ const numContainers = Math.max(
5214
+ 1,
5215
+ Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns)
5216
+ );
5059
5217
  for (let i = 0; i < numContainers; i++) {
5060
5218
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
5061
5219
  set$(ctx, `containerColumn${i}`, -1);
5062
5220
  set$(ctx, `containerSpan${i}`, 1);
5063
5221
  }
5064
5222
  set$(ctx, "numContainers", numContainers);
5065
- set$(ctx, "numContainersPooled", numContainers * state.props.initialContainerPoolRatio);
5223
+ set$(
5224
+ ctx,
5225
+ "numContainersPooled",
5226
+ getInitialContainerPoolSize(data.length, numContainers, state.props.initialContainerPoolRatio)
5227
+ );
5066
5228
  if (state.lastLayout) {
5067
5229
  if (state.initialScroll) {
5068
5230
  requestAnimationFrame(() => {
@@ -5213,8 +5375,8 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
5213
5375
  // src/core/onScroll.ts
5214
5376
  function trackInitialScrollNativeProgress(state, newScroll) {
5215
5377
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);
5216
- const didInitialScrollProgress = !!initialNativeScrollWatchdog && initialScrollWatchdog.didObserveProgress(newScroll, initialNativeScrollWatchdog);
5217
- if (didInitialScrollProgress) {
5378
+ const didInitialScrollReachTarget = !!initialNativeScrollWatchdog && initialScrollWatchdog.didReachTarget(newScroll, initialNativeScrollWatchdog);
5379
+ if (didInitialScrollReachTarget) {
5218
5380
  initialScrollWatchdog.clear(state);
5219
5381
  return;
5220
5382
  }
@@ -5352,16 +5514,20 @@ function maybeUpdateAnchoredEndSpace(ctx) {
5352
5514
  let contentBelowAnchor = 0;
5353
5515
  const footerSize = ctx.values.get("footerSize") || 0;
5354
5516
  const stylePaddingBottom = state.props.stylePaddingBottom || 0;
5517
+ let hasUnknownTailSize = false;
5355
5518
  for (let index = anchorIndex; index < data.length; index++) {
5356
5519
  const itemKey = getId(state, index);
5357
5520
  const size = itemKey ? state.sizesKnown.get(itemKey) : void 0;
5358
5521
  const effectiveSize = index === anchorIndex && anchorMaxSize !== void 0 ? Math.min(size || 0, Math.max(0, anchorMaxSize)) : size;
5522
+ if (size === void 0) {
5523
+ hasUnknownTailSize = true;
5524
+ }
5359
5525
  if (effectiveSize !== null && effectiveSize !== void 0 && effectiveSize > 0) {
5360
5526
  contentBelowAnchor += effectiveSize;
5361
5527
  }
5362
5528
  }
5363
5529
  contentBelowAnchor += footerSize + stylePaddingBottom;
5364
- nextSize = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5530
+ nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5365
5531
  }
5366
5532
  }
5367
5533
  if (previousSize !== nextSize) {
@@ -5433,15 +5599,7 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5433
5599
  const {
5434
5600
  didContainersLayout,
5435
5601
  sizesKnown,
5436
- props: {
5437
- getFixedItemSize,
5438
- getItemType,
5439
- horizontal,
5440
- suggestEstimatedItemSize,
5441
- onItemSizeChanged,
5442
- data,
5443
- maintainScrollAtEnd
5444
- }
5602
+ props: { getFixedItemSize, getItemType, horizontal, onItemSizeChanged, data, maintainScrollAtEnd }
5445
5603
  } = state;
5446
5604
  if (!data) return;
5447
5605
  const index = state.indexByKey.get(itemKey);
@@ -5492,18 +5650,6 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5492
5650
  if (minIndexSizeChanged !== void 0) {
5493
5651
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5494
5652
  }
5495
- if (IS_DEV && suggestEstimatedItemSize && minIndexSizeChanged !== void 0) {
5496
- if (state.timeoutSizeMessage) clearTimeout(state.timeoutSizeMessage);
5497
- state.timeoutSizeMessage = setTimeout(() => {
5498
- var _a4;
5499
- state.timeoutSizeMessage = void 0;
5500
- const num = state.sizesKnown.size;
5501
- const avg = (_a4 = state.averageSizes[""]) == null ? void 0 : _a4.avg;
5502
- console.warn(
5503
- `[legend-list] Based on the ${num} items rendered so far, the optimal estimated size is ${avg}.`
5504
- );
5505
- }, 1e3);
5506
- }
5507
5653
  const cur = peek$(ctx, "otherAxisSize");
5508
5654
  if (!cur || maxOtherAxisSize > cur) {
5509
5655
  set$(ctx, "otherAxisSize", maxOtherAxisSize);
@@ -5608,12 +5754,47 @@ function createColumnWrapperStyle(contentContainerStyle) {
5608
5754
  }
5609
5755
 
5610
5756
  // src/utils/createImperativeHandle.ts
5757
+ var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
5758
+ function getAverageItemSizes(state) {
5759
+ const averageItemSizes = {};
5760
+ for (const itemType in state.averageSizes) {
5761
+ const averageSize = state.averageSizes[itemType];
5762
+ if (averageSize) {
5763
+ averageItemSizes[itemType || DEFAULT_AVERAGE_ITEM_SIZE_TYPE] = {
5764
+ average: averageSize.avg,
5765
+ count: averageSize.num
5766
+ };
5767
+ }
5768
+ }
5769
+ return averageItemSizes;
5770
+ }
5611
5771
  function createImperativeHandle(ctx) {
5612
5772
  const state = ctx.state;
5613
5773
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
5614
5774
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
5615
5775
  let imperativeScrollToken = 0;
5616
5776
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.queuedMVCPRecalculate !== void 0 || state.ignoreScrollFromMVCP !== void 0;
5777
+ const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
5778
+ var _a3;
5779
+ const props = state.props;
5780
+ const dataLength = props.data.length;
5781
+ const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5782
+ if (targetIndex < 0) {
5783
+ return allowEmpty;
5784
+ }
5785
+ if (targetIndex >= dataLength) {
5786
+ return false;
5787
+ }
5788
+ if (anchorIndex === void 0 || anchorIndex < 0 || anchorIndex >= dataLength || targetIndex < anchorIndex || props.getFixedItemSize) {
5789
+ return true;
5790
+ }
5791
+ for (let index = anchorIndex; index < dataLength; index++) {
5792
+ if (!state.sizesKnown.has(getId(state, index))) {
5793
+ return false;
5794
+ }
5795
+ }
5796
+ return true;
5797
+ };
5617
5798
  const runWhenReady = (token, run, isReady) => {
5618
5799
  const startedAt = Date.now();
5619
5800
  let stableFrames = 0;
@@ -5635,11 +5816,10 @@ function createImperativeHandle(ctx) {
5635
5816
  };
5636
5817
  requestAnimationFrame(check);
5637
5818
  };
5638
- const runScrollWithPromise = (run, options) => new Promise((resolve) => {
5639
- var _a3, _b;
5819
+ const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
5820
+ var _a3;
5640
5821
  const token = ++imperativeScrollToken;
5641
- const isReady = (_a3 = options == null ? void 0 : options.isReady) != null ? _a3 : (() => true);
5642
- (_b = state.pendingScrollResolve) == null ? void 0 : _b.call(state);
5822
+ (_a3 = state.pendingScrollResolve) == null ? void 0 : _a3.call(state);
5643
5823
  state.pendingScrollResolve = resolve;
5644
5824
  const runNow = () => {
5645
5825
  if (token !== imperativeScrollToken) {
@@ -5714,6 +5894,7 @@ function createImperativeHandle(ctx) {
5714
5894
  },
5715
5895
  end: state.endNoBuffer,
5716
5896
  endBuffered: state.endBuffered,
5897
+ getAverageItemSizes: () => getAverageItemSizes(state),
5717
5898
  isAtEnd: peek$(ctx, "isAtEnd"),
5718
5899
  isAtStart: peek$(ctx, "isAtStart"),
5719
5900
  isEndReached: state.isEndReached,
@@ -5756,40 +5937,34 @@ function createImperativeHandle(ctx) {
5756
5937
  }
5757
5938
  return false;
5758
5939
  }),
5759
- scrollToEnd: (options) => runScrollWithPromise(() => {
5760
- const data = state.props.data;
5761
- const stylePaddingBottom = state.props.stylePaddingBottom;
5762
- const index = data.length - 1;
5763
- if (index !== -1) {
5764
- const paddingBottom = stylePaddingBottom || 0;
5765
- const footerSize = peek$(ctx, "footerSize") || 0;
5766
- scrollToIndex(ctx, {
5767
- ...options,
5768
- index,
5769
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5770
- viewPosition: 1
5771
- });
5772
- return true;
5773
- }
5774
- return false;
5775
- }),
5776
- scrollToIndex: (params) => {
5777
- const shouldWaitForOutOfRangeTarget = params.index >= 0 && params.index >= state.props.data.length;
5778
- const options = shouldWaitForOutOfRangeTarget ? {
5779
- isReady: () => {
5780
- var _a3;
5781
- const props = state.props;
5782
- const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5783
- const lastIndex = props.data.length - 1;
5784
- const isInRange = params.index < props.data.length;
5785
- const shouldWaitForAnchorSize = isInRange && anchorIndex !== void 0 && anchorIndex >= 0 && params.index >= anchorIndex && !props.getFixedItemSize && !state.sizesKnown.has(getId(state, lastIndex));
5786
- return isInRange && !shouldWaitForAnchorSize;
5940
+ scrollToEnd: (options) => runScrollWithPromise(
5941
+ () => {
5942
+ const data = state.props.data;
5943
+ const stylePaddingBottom = state.props.stylePaddingBottom;
5944
+ const index = data.length - 1;
5945
+ if (index !== -1) {
5946
+ const paddingBottom = stylePaddingBottom || 0;
5947
+ const footerSize = peek$(ctx, "footerSize") || 0;
5948
+ scrollToIndex(ctx, {
5949
+ ...options,
5950
+ index,
5951
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5952
+ viewPosition: 1
5953
+ });
5954
+ return true;
5787
5955
  }
5788
- } : void 0;
5789
- return runScrollWithPromise(() => {
5790
- scrollToIndex(ctx, params);
5791
- return true;
5792
- }, options);
5956
+ return false;
5957
+ },
5958
+ () => isScrollToIndexReady(state.props.data.length - 1, true)
5959
+ ),
5960
+ scrollToIndex: (params) => {
5961
+ return runScrollWithPromise(
5962
+ () => {
5963
+ scrollToIndex(ctx, params);
5964
+ return true;
5965
+ },
5966
+ params.index >= 0 ? () => isScrollToIndexReady(params.index) : void 0
5967
+ );
5793
5968
  },
5794
5969
  scrollToItem: ({ item, ...props }) => runScrollWithPromise(() => {
5795
5970
  const data = state.props.data;
@@ -6053,7 +6228,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6053
6228
  getFixedItemSize,
6054
6229
  getItemType,
6055
6230
  horizontal,
6056
- initialContainerPoolRatio = 2,
6231
+ initialContainerPoolRatio = 3,
6057
6232
  initialScrollAtEnd = false,
6058
6233
  initialScrollIndex: initialScrollIndexProp,
6059
6234
  initialScrollOffset: initialScrollOffsetProp,
@@ -6094,7 +6269,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6094
6269
  stickyIndices: stickyIndicesDeprecated,
6095
6270
  // TODOV3: Remove from v3 release
6096
6271
  style: styleProp,
6097
- suggestEstimatedItemSize,
6098
6272
  useWindowScroll = false,
6099
6273
  viewabilityConfig,
6100
6274
  viewabilityConfigCallbackPairs,
@@ -6235,7 +6409,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6235
6409
  startReachedSnapshotDataChangeEpoch: void 0,
6236
6410
  stickyContainerPool: /* @__PURE__ */ new Set(),
6237
6411
  stickyContainers: /* @__PURE__ */ new Map(),
6238
- timeoutSizeMessage: 0,
6239
6412
  timeouts: /* @__PURE__ */ new Set(),
6240
6413
  totalSize: 0,
6241
6414
  viewabilityConfigCallbackPairs: void 0
@@ -6255,7 +6428,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6255
6428
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6256
6429
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6257
6430
  const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6258
- if (didDataChangeLocal && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6431
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6259
6432
  clearPreservedInitialScrollTarget(state);
6260
6433
  }
6261
6434
  if (didDataChangeLocal) {
@@ -6310,7 +6483,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6310
6483
  stickyPositionComponentInternal,
6311
6484
  stylePaddingBottom: stylePaddingBottomState,
6312
6485
  stylePaddingTop: stylePaddingTopState,
6313
- suggestEstimatedItemSize: !!suggestEstimatedItemSize,
6314
6486
  useWindowScroll: useWindowScrollResolved
6315
6487
  };
6316
6488
  state.refScroller = refScroller;