@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/react.mjs CHANGED
@@ -716,6 +716,7 @@ function getContainerPositionStyle({
716
716
  }
717
717
  var Container = typedMemo(function Container2({
718
718
  id,
719
+ itemKey,
719
720
  recycleItems,
720
721
  horizontal,
721
722
  getRenderedItem: getRenderedItem2,
@@ -727,11 +728,10 @@ var Container = typedMemo(function Container2({
727
728
  const { columnWrapperStyle, animatedScrollY } = ctx;
728
729
  const positionComponentInternal = ctx.state.props.positionComponentInternal;
729
730
  const stickyPositionComponentInternal = ctx.state.props.stickyPositionComponentInternal;
730
- const [column = 0, span = 1, data, itemKey, numColumns = 1, extraData, isSticky] = useArr$([
731
+ const [column = 0, span = 1, data, numColumns = 1, extraData, isSticky] = useArr$([
731
732
  `containerColumn${id}`,
732
733
  `containerSpan${id}`,
733
734
  `containerItemData${id}`,
734
- `containerItemKey${id}`,
735
735
  "numColumns",
736
736
  "extraData",
737
737
  `containerSticky${id}`
@@ -849,6 +849,39 @@ var Container = typedMemo(function Container2({
849
849
  );
850
850
  });
851
851
 
852
+ // src/components/ContainerSlot.tsx
853
+ function ContainerSlotBase({
854
+ id,
855
+ horizontal,
856
+ recycleItems,
857
+ ItemSeparatorComponent,
858
+ updateItemSize: updateItemSize2,
859
+ getRenderedItem: getRenderedItem2,
860
+ stickyHeaderConfig,
861
+ ContainerComponent = Container
862
+ }) {
863
+ const [itemKey] = useArr$([`containerItemKey${id}`]);
864
+ if (itemKey === void 0) {
865
+ return null;
866
+ }
867
+ return /* @__PURE__ */ React3.createElement(
868
+ ContainerComponent,
869
+ {
870
+ getRenderedItem: getRenderedItem2,
871
+ horizontal,
872
+ ItemSeparatorComponent,
873
+ id,
874
+ itemKey,
875
+ recycleItems,
876
+ stickyHeaderConfig,
877
+ updateItemSize: updateItemSize2
878
+ }
879
+ );
880
+ }
881
+ var ContainerSlot = typedMemo(function ContainerSlot2(props) {
882
+ return /* @__PURE__ */ React3.createElement(ContainerSlotBase, { ...props });
883
+ });
884
+
852
885
  // src/utils/reordering.ts
853
886
  var mapFn = (element) => {
854
887
  const indexStr = element.getAttribute("data-index");
@@ -968,9 +1001,12 @@ var ContainersInner = typedMemo(function ContainersInner2({ horizontal, numColum
968
1001
  const ref = useRef(null);
969
1002
  const ctx = useStateContext();
970
1003
  const columnWrapperStyle = ctx.columnWrapperStyle;
971
- const [otherAxisSize, totalSize] = useArr$(["otherAxisSize", "totalSize"]);
1004
+ const [otherAxisSize, readyToRender, totalSize] = useArr$(["otherAxisSize", "readyToRender", "totalSize"]);
972
1005
  useDOMOrder(ref);
973
- const style = horizontal ? { minHeight: otherAxisSize, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, position: "relative" };
1006
+ const style = horizontal ? { minHeight: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative", width: totalSize } : { height: totalSize, minWidth: otherAxisSize, opacity: readyToRender ? 1 : 0, position: "relative" };
1007
+ if (!readyToRender) {
1008
+ style.pointerEvents = "none";
1009
+ }
974
1010
  if (columnWrapperStyle && numColumns > 1) {
975
1011
  const { columnGap, rowGap, gap } = columnWrapperStyle;
976
1012
  const gapX = columnGap || gap || 0;
@@ -998,12 +1034,12 @@ var Containers = typedMemo(function Containers2({
998
1034
  getRenderedItem: getRenderedItem2,
999
1035
  stickyHeaderConfig
1000
1036
  }) {
1001
- const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1037
+ const [numContainersPooled, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
1002
1038
  const containers = [];
1003
- for (let i = 0; i < numContainers; i++) {
1039
+ for (let i = 0; i < numContainersPooled; i++) {
1004
1040
  containers.push(
1005
1041
  /* @__PURE__ */ React3.createElement(
1006
- Container,
1042
+ ContainerSlot,
1007
1043
  {
1008
1044
  getRenderedItem: getRenderedItem2,
1009
1045
  horizontal,
@@ -1075,6 +1111,8 @@ function useRafCoalescer(callback) {
1075
1111
 
1076
1112
  // src/components/webConstants.ts
1077
1113
  var LEGEND_LIST_CONTENT_CONTAINER_CLASS = "legend-list-content-container";
1114
+ var LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS = "legend-list-scrollbar-x-hidden";
1115
+ var LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS = "legend-list-scrollbar-y-hidden";
1078
1116
 
1079
1117
  // src/components/webScrollUtils.ts
1080
1118
  function getDocumentScrollerNode() {
@@ -1160,6 +1198,17 @@ function resolveWindowScrollTarget({ clampedOffset, horizontal, listPos, scroll
1160
1198
  }
1161
1199
 
1162
1200
  // src/components/ListComponentScrollView.tsx
1201
+ var SCROLLBAR_HIDDEN_STYLE_ID = "legend-list-scrollbar-axis-hidden-style";
1202
+ 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;}`;
1203
+ function ensureScrollbarHiddenStyle() {
1204
+ if (typeof document === "undefined" || document.getElementById(SCROLLBAR_HIDDEN_STYLE_ID)) {
1205
+ return;
1206
+ }
1207
+ const styleElement = document.createElement("style");
1208
+ styleElement.id = SCROLLBAR_HIDDEN_STYLE_ID;
1209
+ styleElement.textContent = SCROLLBAR_HIDDEN_STYLE;
1210
+ document.head.appendChild(styleElement);
1211
+ }
1163
1212
  function getContentInsetEndAdjustmentEnd2(ctx) {
1164
1213
  var _a3, _b;
1165
1214
  const adjustment = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.props) == null ? void 0 : _b.contentInsetEndAdjustment;
@@ -1364,6 +1413,12 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
1364
1413
  }
1365
1414
  };
1366
1415
  }, [isWindowScroll, onLayout]);
1416
+ const hiddenScrollIndicatorClassName = !isWindowScroll && (horizontal ? !showsHorizontalScrollIndicator && LEGEND_LIST_SCROLLBAR_X_HIDDEN_CLASS : !showsVerticalScrollIndicator && LEGEND_LIST_SCROLLBAR_Y_HIDDEN_CLASS);
1417
+ useLayoutEffect(() => {
1418
+ if (hiddenScrollIndicatorClassName) {
1419
+ ensureScrollbarHiddenStyle();
1420
+ }
1421
+ }, [hiddenScrollIndicatorClassName]);
1367
1422
  const scrollViewStyle = {
1368
1423
  ...isWindowScroll ? {} : {
1369
1424
  overflow: "auto",
@@ -1392,9 +1447,31 @@ var ListComponentScrollView = forwardRef(function ListComponentScrollView2({
1392
1447
  scrollEventThrottle: _scrollEventThrottle,
1393
1448
  ScrollComponent: _ScrollComponent,
1394
1449
  useWindowScroll: _useWindowScroll,
1450
+ className: scrollViewClassNameProp,
1395
1451
  ...webProps
1396
1452
  } = props;
1397
- 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));
1453
+ const scrollViewClassName = hiddenScrollIndicatorClassName ? scrollViewClassNameProp ? `${scrollViewClassNameProp} ${hiddenScrollIndicatorClassName}` : hiddenScrollIndicatorClassName : scrollViewClassNameProp;
1454
+ if (IS_DEV) {
1455
+ if (/(?:^|\s)(?:[a-z0-9_-]+:)*gap(?:-[xy])?-(?:\[[^\]]+\]|[^\s]+)/.test(
1456
+ `${contentContainerClassName != null ? contentContainerClassName : ""} ${scrollViewClassNameProp != null ? scrollViewClassNameProp : ""}`
1457
+ )) {
1458
+ warnDevOnce(
1459
+ "className-gap",
1460
+ "className/contentContainerClassName gap classes are not supported in LegendList because it needs to use exact values internally. Use contentContainerStyle={{ gap: ... }} or columnWrapperStyle instead."
1461
+ );
1462
+ }
1463
+ }
1464
+ return /* @__PURE__ */ React3.createElement(
1465
+ "div",
1466
+ {
1467
+ className: scrollViewClassName,
1468
+ ref: scrollRef,
1469
+ ...webProps,
1470
+ style: scrollViewStyle
1471
+ },
1472
+ refreshControl,
1473
+ /* @__PURE__ */ React3.createElement("div", { className, ref: contentRef, style: contentStyle }, children, contentInsetEndAdjustmentSpacerStyle ? /* @__PURE__ */ React3.createElement("div", { "aria-hidden": true, style: contentInsetEndAdjustmentSpacerStyle }) : null)
1474
+ );
1398
1475
  });
1399
1476
  function useValueListener$(key, callback) {
1400
1477
  const ctx = useStateContext();
@@ -1422,11 +1499,18 @@ function getScrollAdjustAxis(horizontal) {
1422
1499
  y: 1
1423
1500
  };
1424
1501
  }
1425
- function resolveScrollAdjustContentNode(el, contentNode) {
1426
- if ((contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === el) {
1427
- return contentNode;
1502
+ function getScrollAdjustTarget(ctx, contentNode) {
1503
+ var _a3, _b, _c;
1504
+ const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1505
+ const scrollElement = (_c = (_b = scrollView == null ? void 0 : scrollView.getScrollableNode) == null ? void 0 : _b.call(scrollView)) != null ? _c : null;
1506
+ let resolvedContentNode = null;
1507
+ if (scrollElement) {
1508
+ resolvedContentNode = (contentNode == null ? void 0 : contentNode.isConnected) && contentNode.parentElement === scrollElement ? contentNode : scrollElement.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1428
1509
  }
1429
- return el.querySelector(`:scope > .${LEGEND_LIST_CONTENT_CONTAINER_CLASS}`);
1510
+ return scrollElement ? { contentNode: resolvedContentNode, scrollElement } : null;
1511
+ }
1512
+ function scrollAdjustBy(el, left, top) {
1513
+ el.scrollBy({ behavior: "auto", left, top });
1430
1514
  }
1431
1515
  function ScrollAdjust() {
1432
1516
  const ctx = useStateContext();
@@ -1435,43 +1519,43 @@ function ScrollAdjust() {
1435
1519
  const resetPaddingBaselineRef = React3.useRef(void 0);
1436
1520
  const contentNodeRef = React3.useRef(null);
1437
1521
  const callback = React3.useCallback(() => {
1438
- var _a3, _b;
1522
+ var _a3;
1439
1523
  const scrollAdjust = peek$(ctx, "scrollAdjust");
1440
1524
  const scrollAdjustUserOffset = peek$(ctx, "scrollAdjustUserOffset");
1441
1525
  const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0);
1442
- const scrollView = (_a3 = ctx.state) == null ? void 0 : _a3.refScroller.current;
1443
- if (scrollView && scrollOffset !== lastScrollOffsetRef.current) {
1444
- const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1445
- if (scrollDelta !== 0) {
1446
- const axis = getScrollAdjustAxis(!!ctx.state.props.horizontal);
1447
- const prevScroll = scrollView.getCurrentScrollOffset();
1448
- const el = scrollView.getScrollableNode();
1449
- const contentNode = resolveScrollAdjustContentNode(el, contentNodeRef.current);
1526
+ const scrollDelta = scrollOffset - lastScrollOffsetRef.current;
1527
+ if (scrollDelta !== 0) {
1528
+ const target = getScrollAdjustTarget(ctx, contentNodeRef.current);
1529
+ if (target) {
1530
+ const horizontal = !!ctx.state.props.horizontal;
1531
+ const axis = getScrollAdjustAxis(horizontal);
1532
+ const { contentNode, scrollElement: el } = target;
1533
+ const scrollBy = () => scrollAdjustBy(el, axis.x * scrollDelta, axis.y * scrollDelta);
1450
1534
  contentNodeRef.current = contentNode;
1451
- const scrollBy = () => scrollView.scrollBy(axis.x * scrollDelta, axis.y * scrollDelta);
1452
- if (!contentNode) {
1453
- scrollBy();
1454
- lastScrollOffsetRef.current = scrollOffset;
1455
- return;
1456
- }
1457
- const totalSize = contentNode[axis.contentSizeKey];
1458
- const viewportSize = el[axis.viewportSizeKey];
1459
- const nextScroll = prevScroll + scrollDelta;
1460
- if (scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize) {
1461
- const previousPaddingEnd = (_b = resetPaddingBaselineRef.current) != null ? _b : contentNode.style[axis.paddingEndProp];
1462
- resetPaddingBaselineRef.current = previousPaddingEnd;
1463
- const pad = (nextScroll + viewportSize - totalSize) * 2;
1464
- contentNode.style[axis.paddingEndProp] = `${pad}px`;
1465
- void contentNode.offsetHeight;
1466
- scrollBy();
1467
- if (resetPaddingRafRef.current !== void 0) {
1468
- cancelAnimationFrame(resetPaddingRafRef.current);
1535
+ if (contentNode) {
1536
+ const prevScroll = horizontal ? el.scrollLeft : el.scrollTop;
1537
+ const totalSize = contentNode[axis.contentSizeKey];
1538
+ const viewportSize = el[axis.viewportSizeKey];
1539
+ const nextScroll = prevScroll + scrollDelta;
1540
+ const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize;
1541
+ if (needsTemporaryPadding) {
1542
+ const previousPaddingEnd = (_a3 = resetPaddingBaselineRef.current) != null ? _a3 : contentNode.style[axis.paddingEndProp];
1543
+ resetPaddingBaselineRef.current = previousPaddingEnd;
1544
+ const pad = (nextScroll + viewportSize - totalSize) * 2;
1545
+ contentNode.style[axis.paddingEndProp] = `${pad}px`;
1546
+ void contentNode.offsetHeight;
1547
+ scrollBy();
1548
+ if (resetPaddingRafRef.current !== void 0) {
1549
+ cancelAnimationFrame(resetPaddingRafRef.current);
1550
+ }
1551
+ resetPaddingRafRef.current = requestAnimationFrame(() => {
1552
+ resetPaddingRafRef.current = void 0;
1553
+ resetPaddingBaselineRef.current = void 0;
1554
+ contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1555
+ });
1556
+ } else {
1557
+ scrollBy();
1469
1558
  }
1470
- resetPaddingRafRef.current = requestAnimationFrame(() => {
1471
- resetPaddingRafRef.current = void 0;
1472
- resetPaddingBaselineRef.current = void 0;
1473
- contentNode.style[axis.paddingEndProp] = previousPaddingEnd;
1474
- });
1475
1559
  } else {
1476
1560
  scrollBy();
1477
1561
  }
@@ -1782,10 +1866,9 @@ var initialScrollWatchdog = {
1782
1866
  clear(state) {
1783
1867
  initialScrollWatchdog.set(state, void 0);
1784
1868
  },
1785
- didObserveProgress(newScroll, watchdog) {
1786
- const previousDistance = Math.abs(watchdog.startScroll - watchdog.targetOffset);
1869
+ didReachTarget(newScroll, watchdog) {
1787
1870
  const nextDistance = Math.abs(newScroll - watchdog.targetOffset);
1788
- return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET || nextDistance + INITIAL_SCROLL_MIN_TARGET_OFFSET < previousDistance;
1871
+ return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET;
1789
1872
  },
1790
1873
  get(state) {
1791
1874
  var _a3, _b;
@@ -1810,19 +1893,19 @@ var initialScrollWatchdog = {
1810
1893
  }
1811
1894
  };
1812
1895
  function setInitialScrollSession(state, options = {}) {
1813
- var _a3, _b, _c;
1896
+ var _a3, _b, _c, _d;
1814
1897
  const existingSession = state.initialScrollSession;
1815
1898
  const kind = (_a3 = options.kind) != null ? _a3 : existingSession == null ? void 0 : existingSession.kind;
1816
1899
  const completion = existingSession == null ? void 0 : existingSession.completion;
1817
- const hasBootstrapOverride = Object.hasOwn(options, "bootstrap");
1818
- const bootstrap = kind === "bootstrap" ? hasBootstrapOverride ? options.bootstrap : (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0 : void 0;
1900
+ const existingBootstrap = (existingSession == null ? void 0 : existingSession.kind) === "bootstrap" ? existingSession.bootstrap : void 0;
1901
+ const bootstrap = kind === "bootstrap" ? options.bootstrap === null ? void 0 : (_b = options.bootstrap) != null ? _b : existingBootstrap : void 0;
1819
1902
  if (!kind) {
1820
1903
  return clearInitialScrollSession(state);
1821
1904
  }
1822
1905
  if (!state.initialScroll && !bootstrap && !hasInitialScrollSessionCompletion(completion)) {
1823
1906
  return clearInitialScrollSession(state);
1824
1907
  }
1825
- const previousDataLength = (_c = (_b = options.previousDataLength) != null ? _b : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _c : 0;
1908
+ const previousDataLength = (_d = (_c = options.previousDataLength) != null ? _c : existingSession == null ? void 0 : existingSession.previousDataLength) != null ? _d : 0;
1826
1909
  state.initialScrollSession = createInitialScrollSession({
1827
1910
  bootstrap,
1828
1911
  completion,
@@ -2574,7 +2657,7 @@ function advanceMeasuredInitialScroll(ctx, options) {
2574
2657
  const activeInitialTargetOffset = scrollingTo ? (_a3 = scrollingTo.targetOffset) != null ? _a3 : scrollingTo.offset : void 0;
2575
2658
  const didOffsetChange = initialScroll.contentOffset === void 0 || Math.abs(initialScroll.contentOffset - resolvedOffset) > 1;
2576
2659
  const didActiveInitialTargetChange = activeInitialTargetOffset !== void 0 && Math.abs(activeInitialTargetOffset - resolvedOffset) > 1;
2577
- const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - activeInitialTargetOffset) <= 1 && Math.abs(state.scrollPending - activeInitialTargetOffset) <= 1;
2660
+ const isAlreadyAtDesiredInitialTarget = activeInitialTargetOffset !== void 0 && Math.abs(state.scroll - resolvedOffset) <= 1 && Math.abs(state.scrollPending - resolvedOffset) <= 1;
2578
2661
  if (!(options == null ? void 0 : options.forceScroll) && !didOffsetChange && isInitialScrollInProgress && !didActiveInitialTargetChange) {
2579
2662
  return false;
2580
2663
  }
@@ -2646,6 +2729,30 @@ function checkAllSizesKnown(state, indices) {
2646
2729
  });
2647
2730
  }
2648
2731
 
2732
+ // src/utils/requestAdjust.ts
2733
+ function requestAdjust(ctx, positionDiff, dataChanged) {
2734
+ const state = ctx.state;
2735
+ if (Math.abs(positionDiff) > 0.1) {
2736
+ const doit = () => {
2737
+ {
2738
+ state.scrollAdjustHandler.requestAdjust(positionDiff);
2739
+ if (state.adjustingFromInitialMount) {
2740
+ state.adjustingFromInitialMount--;
2741
+ }
2742
+ }
2743
+ };
2744
+ state.scroll += positionDiff;
2745
+ state.scrollForNextCalculateItemsInView = void 0;
2746
+ const readyToRender = peek$(ctx, "readyToRender");
2747
+ if (readyToRender) {
2748
+ doit();
2749
+ } else {
2750
+ state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
2751
+ requestAnimationFrame(doit);
2752
+ }
2753
+ }
2754
+ }
2755
+
2649
2756
  // src/core/bootstrapInitialScroll.ts
2650
2757
  var DEFAULT_BOOTSTRAP_REVEAL_EPSILON = 1;
2651
2758
  var DEFAULT_BOOTSTRAP_REVEAL_MAX_FRAMES = 8;
@@ -2739,7 +2846,7 @@ function clearBootstrapInitialScrollSession(state) {
2739
2846
  bootstrapInitialScroll.frameHandle = void 0;
2740
2847
  }
2741
2848
  setInitialScrollSession(state, {
2742
- bootstrap: void 0,
2849
+ bootstrap: null,
2743
2850
  kind: (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind
2744
2851
  });
2745
2852
  }
@@ -2895,15 +3002,18 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2895
3002
  return;
2896
3003
  }
2897
3004
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
2898
- if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
2899
- clearPendingInitialScrollFooterLayout(ctx, {
2900
- dataLength: state.props.data.length,
2901
- stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
2902
- target: initialScroll
2903
- });
2904
- return;
3005
+ const shouldKeepEndTargetAlive = isRetargetableBottomAlignedInitialScrollTarget(initialScroll) && peek$(ctx, "isAtEnd");
3006
+ if (!shouldKeepEndTargetAlive) {
3007
+ if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) {
3008
+ clearPendingInitialScrollFooterLayout(ctx, {
3009
+ dataLength: state.props.data.length,
3010
+ stylePaddingBottom: (_b = state.props.stylePaddingBottom) != null ? _b : 0,
3011
+ target: initialScroll
3012
+ });
3013
+ } else {
3014
+ clearFinishedViewportRetargetableInitialScroll(state);
3015
+ }
2905
3016
  }
2906
- clearFinishedViewportRetargetableInitialScroll(state);
2907
3017
  }
2908
3018
  }
2909
3019
  function startBootstrapInitialScrollOnMount(ctx, options) {
@@ -2942,7 +3052,7 @@ function handleBootstrapInitialScrollDataChange(ctx, options) {
2942
3052
  }
2943
3053
  const shouldResetDidFinish = !!(state.didFinishInitialScroll && previousDataLength === 0 && dataLength > 0 && initialScroll.index !== void 0);
2944
3054
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
2945
- const shouldClearFinishedResizePreservation = didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
3055
+ const shouldClearFinishedResizePreservation = !initialScrollAtEnd && didDataChange && dataLength > 0 && state.didFinishInitialScroll && !bootstrapInitialScroll && !shouldResetDidFinish;
2946
3056
  if (shouldClearFinishedResizePreservation) {
2947
3057
  clearPreservedInitialScrollTarget(state);
2948
3058
  return;
@@ -3045,27 +3155,46 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3045
3155
  }
3046
3156
  }
3047
3157
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3158
+ var _a3, _b, _c, _d;
3048
3159
  const state = ctx.state;
3049
3160
  const initialScroll = state.initialScroll;
3050
- if (isOffsetInitialScrollSession(state) || state.props.data.length === 0 || !initialScroll) {
3051
- return;
3052
- }
3053
3161
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
3054
- if (!bootstrapInitialScroll && initialScroll.viewPosition !== 1) {
3055
- return;
3056
- }
3057
- const didFinishInitialScroll = state.didFinishInitialScroll;
3058
- if (didFinishInitialScroll) {
3059
- setInitialScrollTarget(state, initialScroll, {
3060
- resetDidFinish: true
3061
- });
3062
- state.clearPreservedInitialScrollOnNextFinish = true;
3162
+ if (initialScroll && state.props.data.length > 0 && !isOffsetInitialScrollSession(state) && (bootstrapInitialScroll || initialScroll.viewPosition === 1)) {
3163
+ const resolvedOffset = resolveInitialScrollOffset(ctx, initialScroll);
3164
+ const scrollingTo = ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) ? state.scrollingTo : void 0;
3165
+ if (!bootstrapInitialScroll && (scrollingTo || state.didFinishInitialScroll)) {
3166
+ const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3167
+ const offsetDiff = resolvedOffset - currentOffset;
3168
+ if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3169
+ if (scrollingTo) {
3170
+ const existingWatchdog = initialScrollWatchdog.get(state);
3171
+ scrollingTo.offset = resolvedOffset;
3172
+ scrollingTo.targetOffset = resolvedOffset;
3173
+ state.initialScroll = {
3174
+ ...initialScroll,
3175
+ contentOffset: resolvedOffset
3176
+ };
3177
+ state.hasScrolled = false;
3178
+ initialScrollWatchdog.set(state, {
3179
+ startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3180
+ targetOffset: resolvedOffset
3181
+ });
3182
+ }
3183
+ requestAdjust(ctx, offsetDiff);
3184
+ if (state.didFinishInitialScroll) {
3185
+ (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3186
+ }
3187
+ }
3188
+ if (state.didFinishInitialScroll) {
3189
+ clearFinishedViewportRetargetableInitialScroll(state);
3190
+ }
3191
+ } else {
3192
+ rearmBootstrapInitialScroll(ctx, {
3193
+ scroll: resolvedOffset,
3194
+ targetIndexSeed: initialScroll.index
3195
+ });
3196
+ }
3063
3197
  }
3064
- rearmBootstrapInitialScroll(ctx, {
3065
- scroll: resolveInitialScrollOffset(ctx, initialScroll),
3066
- seedContentOffset: didFinishInitialScroll && !bootstrapInitialScroll ? getObservedBootstrapInitialScrollOffset(state) : void 0,
3067
- targetIndexSeed: initialScroll.index
3068
- });
3069
3198
  }
3070
3199
  function evaluateBootstrapInitialScroll(ctx) {
3071
3200
  var _a3, _b;
@@ -3272,7 +3401,7 @@ function checkFinishedScrollFallback(ctx) {
3272
3401
  state.timeoutCheckFinishedScrollFallback = setTimeout(checkHasScrolled, delay);
3273
3402
  };
3274
3403
  const checkHasScrolled = () => {
3275
- var _c;
3404
+ var _c, _d;
3276
3405
  state.timeoutCheckFinishedScrollFallback = void 0;
3277
3406
  const isStillScrollingTo = state.scrollingTo;
3278
3407
  if (isStillScrollingTo) {
@@ -3285,11 +3414,13 @@ function checkFinishedScrollFallback(ctx) {
3285
3414
  isStillScrollingTo
3286
3415
  );
3287
3416
  const completionState = getResolvedScrollCompletionState(ctx, isStillScrollingTo);
3288
- const canFinishAfterSilentNativeDispatch = silentInitialDispatch && completionState.isAtResolvedTarget && numChecks >= 1;
3289
- if (shouldFinishZeroTarget || state.hasScrolled || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3417
+ const canFinishAfterSilentNativeDispatch = Platform.OS === "android";
3418
+ const shouldFinishAfterObservedScroll = state.hasScrolled && (!isStillScrollingTo.isInitialScroll || completionState.isAtResolvedTarget);
3419
+ const shouldRetryUnalignedInitialScroll = isStillScrollingTo.isInitialScroll && !completionState.isAtResolvedTarget && numChecks <= maxChecks;
3420
+ if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) {
3290
3421
  finishScrollTo(ctx);
3291
- } else if (isNativeInitialPending && numChecks <= maxChecks) {
3292
- const targetOffset = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : state.scrollPending;
3422
+ } else if ((isNativeInitialPending || shouldRetryUnalignedInitialScroll) && numChecks <= maxChecks) {
3423
+ const targetOffset = (_d = (_c = getInitialScrollWatchdogTargetOffset(state)) != null ? _c : isStillScrollingTo.targetOffset) != null ? _d : state.scrollPending;
3293
3424
  scrollToFallbackOffset(ctx, targetOffset);
3294
3425
  scheduleFallbackCheck(silentInitialDispatch ? SILENT_INITIAL_SCROLL_RETRY_DELAY_MS : 100);
3295
3426
  } else {
@@ -3384,30 +3515,6 @@ function handleInitialScrollDataChange(ctx, options) {
3384
3515
  advanceCurrentInitialScrollSession(ctx);
3385
3516
  }
3386
3517
 
3387
- // src/utils/requestAdjust.ts
3388
- function requestAdjust(ctx, positionDiff, dataChanged) {
3389
- const state = ctx.state;
3390
- if (Math.abs(positionDiff) > 0.1) {
3391
- const doit = () => {
3392
- {
3393
- state.scrollAdjustHandler.requestAdjust(positionDiff);
3394
- if (state.adjustingFromInitialMount) {
3395
- state.adjustingFromInitialMount--;
3396
- }
3397
- }
3398
- };
3399
- state.scroll += positionDiff;
3400
- state.scrollForNextCalculateItemsInView = void 0;
3401
- const readyToRender = peek$(ctx, "readyToRender");
3402
- if (readyToRender) {
3403
- doit();
3404
- } else {
3405
- state.adjustingFromInitialMount = (state.adjustingFromInitialMount || 0) + 1;
3406
- requestAnimationFrame(doit);
3407
- }
3408
- }
3409
- }
3410
-
3411
3518
  // src/core/mvcp.ts
3412
3519
  var MVCP_POSITION_EPSILON = 0.1;
3413
3520
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -3681,7 +3788,10 @@ function prepareMVCP(ctx, dataChanged) {
3681
3788
  return;
3682
3789
  }
3683
3790
  if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
3684
- requestAdjust(ctx, positionDiff);
3791
+ const shouldSkipAdjustForMaintainedEnd = state.maintainingScrollAtEnd && peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
3792
+ if (!shouldSkipAdjustForMaintainedEnd) {
3793
+ requestAdjust(ctx, positionDiff);
3794
+ }
3685
3795
  }
3686
3796
  };
3687
3797
  }
@@ -4300,6 +4410,30 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
4300
4410
  var unstableBatchedUpdates = ReactDOM.unstable_batchedUpdates;
4301
4411
  var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
4302
4412
 
4413
+ // src/utils/containerPool.ts
4414
+ var MIN_INITIAL_CONTAINER_POOL_SIZE = 32;
4415
+ var MAX_INITIAL_SPARE_CONTAINERS = 64;
4416
+ function getInitialContainerPoolSize(dataLength, numContainers, initialContainerPoolRatio) {
4417
+ if (dataLength <= 0 || numContainers <= 0) {
4418
+ return 0;
4419
+ }
4420
+ const ratioPoolSize = Math.ceil(numContainers * initialContainerPoolRatio);
4421
+ const cappedSparePoolSize = numContainers + MAX_INITIAL_SPARE_CONTAINERS;
4422
+ const targetPoolSize = Math.max(
4423
+ numContainers,
4424
+ Math.min(ratioPoolSize, cappedSparePoolSize),
4425
+ Math.min(dataLength, MIN_INITIAL_CONTAINER_POOL_SIZE)
4426
+ );
4427
+ const maxUsefulPoolSize = Math.max(dataLength, numContainers);
4428
+ return Math.min(maxUsefulPoolSize, targetPoolSize);
4429
+ }
4430
+ function getExpandedContainerPoolSize(dataLength, numContainers) {
4431
+ if (dataLength <= 0 || numContainers <= 0) {
4432
+ return 0;
4433
+ }
4434
+ return Math.min(Math.max(dataLength, numContainers), Math.max(numContainers, Math.ceil(numContainers * 1.5)));
4435
+ }
4436
+
4303
4437
  // src/utils/findAvailableContainers.ts
4304
4438
  function findAvailableContainers(ctx, numNeeded, startBuffered, endBuffered, pendingRemoval, requiredItemTypes, needNewContainers, protectedKeys) {
4305
4439
  const numContainers = peek$(ctx, "numContainers");
@@ -4505,7 +4639,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
4505
4639
  function calculateItemsInView(ctx, params = {}) {
4506
4640
  const state = ctx.state;
4507
4641
  batchedUpdates(() => {
4508
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
4642
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
4509
4643
  const {
4510
4644
  columns,
4511
4645
  columnSpans,
@@ -4552,12 +4686,22 @@ function calculateItemsInView(ctx, params = {}) {
4552
4686
  // current initial-scroll target instead of transient native adjustments.
4553
4687
  resolveInitialScrollOffset(ctx, initialScroll)
4554
4688
  ) : state.scroll;
4555
- const scrollAdjustPending = (_c = peek$(ctx, "scrollAdjustPending")) != null ? _c : 0;
4556
- const scrollAdjustPad = scrollAdjustPending - topPad;
4557
- let scroll = Math.round(scrollState + scrollExtra + scrollAdjustPad);
4558
- if (scroll + scrollLength > totalSize) {
4559
- scroll = Math.max(0, totalSize - scrollLength);
4560
- }
4689
+ let scrollAdjustPending = 0;
4690
+ let scrollAdjustPad = 0;
4691
+ let scroll = 0;
4692
+ let scrollTopBuffered = 0;
4693
+ let scrollBottom = 0;
4694
+ let scrollBottomBuffered = 0;
4695
+ const updateScroll2 = (nextScrollState) => {
4696
+ var _a4;
4697
+ scrollAdjustPending = (_a4 = peek$(ctx, "scrollAdjustPending")) != null ? _a4 : 0;
4698
+ scrollAdjustPad = scrollAdjustPending - topPad;
4699
+ scroll = Math.round(nextScrollState + scrollExtra + scrollAdjustPad);
4700
+ if (scroll + scrollLength > totalSize) {
4701
+ scroll = Math.max(0, totalSize - scrollLength);
4702
+ }
4703
+ };
4704
+ updateScroll2(scrollState);
4561
4705
  const previousStickyIndex = peek$(ctx, "activeStickyIndex");
4562
4706
  const currentStickyIdx = stickyIndicesArr.length > 0 ? findCurrentStickyIndex(stickyIndicesArr, scroll, state) : -1;
4563
4707
  const nextActiveStickyIndex = currentStickyIdx >= 0 ? stickyIndicesArr[currentStickyIdx] : -1;
@@ -4573,9 +4717,12 @@ function calculateItemsInView(ctx, params = {}) {
4573
4717
  scrollBufferTop = drawDistance * 1.5;
4574
4718
  scrollBufferBottom = drawDistance * 0.5;
4575
4719
  }
4576
- const scrollTopBuffered = scroll - scrollBufferTop;
4577
- const scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4578
- const scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4720
+ const updateScrollRange = () => {
4721
+ scrollTopBuffered = scroll - scrollBufferTop;
4722
+ scrollBottom = scroll + scrollLength + (scroll < 0 ? -scroll : 0);
4723
+ scrollBottomBuffered = scrollBottom + scrollBufferBottom;
4724
+ };
4725
+ updateScrollRange();
4579
4726
  if (!suppressInitialScrollSideEffects && !dataChanged && !forceFullItemPositions && scrollForNextCalculateItemsInView) {
4580
4727
  const { top, bottom } = scrollForNextCalculateItemsInView;
4581
4728
  if (top === null && bottom === null) {
@@ -4594,7 +4741,7 @@ function calculateItemsInView(ctx, params = {}) {
4594
4741
  columns.length = 0;
4595
4742
  columnSpans.length = 0;
4596
4743
  }
4597
- const startIndex = forceFullItemPositions || dataChanged ? 0 : (_d = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _d : 0;
4744
+ const startIndex = forceFullItemPositions || dataChanged ? 0 : (_c = minIndexSizeChanged != null ? minIndexSizeChanged : state.startBuffered) != null ? _c : 0;
4598
4745
  const optimizeForVisibleWindow = !forceFullItemPositions && !dataChanged && numColumns > 1 && minIndexSizeChanged !== void 0;
4599
4746
  updateItemPositions(ctx, dataChanged, {
4600
4747
  doMVCP,
@@ -4619,21 +4766,25 @@ function calculateItemsInView(ctx, params = {}) {
4619
4766
  }
4620
4767
  }
4621
4768
  const scrollBeforeMVCP = state.scroll;
4622
- const scrollAdjustPendingBeforeMVCP = (_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0;
4769
+ const scrollAdjustPendingBeforeMVCP = (_d = peek$(ctx, "scrollAdjustPending")) != null ? _d : 0;
4623
4770
  checkMVCP == null ? void 0 : checkMVCP();
4624
- const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_f = peek$(ctx, "scrollAdjustPending")) != null ? _f : 0) !== scrollAdjustPendingBeforeMVCP);
4771
+ const didMVCPAdjustScroll = !!checkMVCP && (state.scroll !== scrollBeforeMVCP || ((_e = peek$(ctx, "scrollAdjustPending")) != null ? _e : 0) !== scrollAdjustPendingBeforeMVCP);
4772
+ if (didMVCPAdjustScroll && initialScroll) {
4773
+ updateScroll2(state.scroll);
4774
+ updateScrollRange();
4775
+ }
4625
4776
  let startNoBuffer = null;
4626
4777
  let startBuffered = null;
4627
4778
  let startBufferedId = null;
4628
4779
  let endNoBuffer = null;
4629
4780
  let endBuffered = null;
4630
- let loopStart = (_g = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _g : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4781
+ let loopStart = (_f = suppressInitialScrollSideEffects ? bootstrapInitialScrollState == null ? void 0 : bootstrapInitialScrollState.targetIndexSeed : void 0) != null ? _f : !dataChanged && startBufferedIdOrig ? indexByKey.get(startBufferedIdOrig) || 0 : 0;
4631
4782
  for (let i = loopStart; i >= 0; i--) {
4632
- const id = (_h = idCache[i]) != null ? _h : getId(state, i);
4783
+ const id = (_g = idCache[i]) != null ? _g : getId(state, i);
4633
4784
  const top = positions[i];
4634
- const size = (_i = sizes.get(id)) != null ? _i : getItemSize(ctx, id, i, data[i]);
4785
+ const size = (_h = sizes.get(id)) != null ? _h : getItemSize(ctx, id, i, data[i]);
4635
4786
  const bottom = top + size;
4636
- if (bottom > scroll - scrollBufferTop) {
4787
+ if (bottom > scrollTopBuffered) {
4637
4788
  loopStart = i;
4638
4789
  } else {
4639
4790
  break;
@@ -4662,8 +4813,8 @@ function calculateItemsInView(ctx, params = {}) {
4662
4813
  let firstFullyOnScreenIndex;
4663
4814
  const dataLength = data.length;
4664
4815
  for (let i = Math.max(0, loopStart); i < dataLength && (!foundEnd || i <= maxIndexRendered); i++) {
4665
- const id = (_j = idCache[i]) != null ? _j : getId(state, i);
4666
- const size = (_k = sizes.get(id)) != null ? _k : getItemSize(ctx, id, i, data[i]);
4816
+ const id = (_i = idCache[i]) != null ? _i : getId(state, i);
4817
+ const size = (_j = sizes.get(id)) != null ? _j : getItemSize(ctx, id, i, data[i]);
4667
4818
  const top = positions[i];
4668
4819
  if (!foundEnd) {
4669
4820
  if (startNoBuffer === null && top + size > scroll) {
@@ -4702,7 +4853,7 @@ function calculateItemsInView(ctx, params = {}) {
4702
4853
  const firstVisibleAnchorIndex = firstFullyOnScreenIndex != null ? firstFullyOnScreenIndex : startNoBuffer;
4703
4854
  if (firstVisibleAnchorIndex !== null && firstVisibleAnchorIndex !== void 0 && endNoBuffer !== null) {
4704
4855
  for (let i = firstVisibleAnchorIndex; i <= endNoBuffer; i++) {
4705
- const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4856
+ const id = (_k = idCache[i]) != null ? _k : getId(state, i);
4706
4857
  idsInView.push(id);
4707
4858
  }
4708
4859
  }
@@ -4735,7 +4886,7 @@ function calculateItemsInView(ctx, params = {}) {
4735
4886
  const needNewContainers = [];
4736
4887
  const needNewContainersSet = /* @__PURE__ */ new Set();
4737
4888
  for (let i = startBuffered; i <= endBuffered; i++) {
4738
- const id = (_m = idCache[i]) != null ? _m : getId(state, i);
4889
+ const id = (_l = idCache[i]) != null ? _l : getId(state, i);
4739
4890
  if (!containerItemKeys.has(id)) {
4740
4891
  needNewContainersSet.add(i);
4741
4892
  needNewContainers.push(i);
@@ -4744,7 +4895,7 @@ function calculateItemsInView(ctx, params = {}) {
4744
4895
  if (alwaysRenderArr.length > 0) {
4745
4896
  for (const index of alwaysRenderArr) {
4746
4897
  if (index < 0 || index >= dataLength) continue;
4747
- const id = (_n = idCache[index]) != null ? _n : getId(state, index);
4898
+ const id = (_m = idCache[index]) != null ? _m : getId(state, index);
4748
4899
  if (id && !containerItemKeys.has(id) && !needNewContainersSet.has(index)) {
4749
4900
  needNewContainersSet.add(index);
4750
4901
  needNewContainers.push(index);
@@ -4783,7 +4934,7 @@ function calculateItemsInView(ctx, params = {}) {
4783
4934
  for (let idx = 0; idx < needNewContainers.length; idx++) {
4784
4935
  const i = needNewContainers[idx];
4785
4936
  const containerIndex = availableContainers[idx];
4786
- const id = (_o = idCache[i]) != null ? _o : getId(state, i);
4937
+ const id = (_n = idCache[i]) != null ? _n : getId(state, i);
4787
4938
  const oldKey = peek$(ctx, `containerItemKey${containerIndex}`);
4788
4939
  if (oldKey && oldKey !== id) {
4789
4940
  containerItemKeys.delete(oldKey);
@@ -4794,7 +4945,7 @@ function calculateItemsInView(ctx, params = {}) {
4794
4945
  state.containerItemTypes.set(containerIndex, requiredItemTypes[idx]);
4795
4946
  }
4796
4947
  containerItemKeys.set(id, containerIndex);
4797
- (_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.add(id);
4948
+ (_o = state.userScrollAnchorResetKeys) == null ? void 0 : _o.add(id);
4798
4949
  const containerSticky = `containerSticky${containerIndex}`;
4799
4950
  const isSticky = stickyIndicesSet.has(i);
4800
4951
  const isAlwaysRender = alwaysRenderSet.has(i);
@@ -4818,17 +4969,17 @@ function calculateItemsInView(ctx, params = {}) {
4818
4969
  if (numContainers !== prevNumContainers) {
4819
4970
  set$(ctx, "numContainers", numContainers);
4820
4971
  if (numContainers > peek$(ctx, "numContainersPooled")) {
4821
- set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
4972
+ set$(ctx, "numContainersPooled", getExpandedContainerPoolSize(dataLength, numContainers));
4822
4973
  }
4823
4974
  }
4824
4975
  }
4825
- if (((_q = state.userScrollAnchorResetKeys) == null ? void 0 : _q.size) === 0) {
4976
+ if (((_p = state.userScrollAnchorResetKeys) == null ? void 0 : _p.size) === 0) {
4826
4977
  state.userScrollAnchorResetKeys = void 0;
4827
4978
  }
4828
4979
  if (alwaysRenderArr.length > 0) {
4829
4980
  for (const index of alwaysRenderArr) {
4830
4981
  if (index < 0 || index >= dataLength) continue;
4831
- const id = (_r = idCache[index]) != null ? _r : getId(state, index);
4982
+ const id = (_q = idCache[index]) != null ? _q : getId(state, index);
4832
4983
  const containerIndex = containerItemKeys.get(id);
4833
4984
  if (containerIndex !== void 0) {
4834
4985
  state.stickyContainerPool.add(containerIndex);
@@ -4932,21 +5083,25 @@ function doMaintainScrollAtEnd(ctx) {
4932
5083
  if (contentSize < state.scrollLength) {
4933
5084
  state.scroll = 0;
4934
5085
  }
4935
- requestAnimationFrame(() => {
4936
- var _a3;
4937
- if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
4938
- state.maintainingScrollAtEnd = true;
4939
- (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
4940
- animated: maintainScrollAtEnd.animated
4941
- });
4942
- setTimeout(
4943
- () => {
4944
- state.maintainingScrollAtEnd = false;
4945
- },
4946
- maintainScrollAtEnd.animated ? 500 : 0
4947
- );
4948
- }
4949
- });
5086
+ if (!state.maintainingScrollAtEnd) {
5087
+ state.maintainingScrollAtEnd = true;
5088
+ requestAnimationFrame(() => {
5089
+ var _a3;
5090
+ if (peek$(ctx, "isWithinMaintainScrollAtEndThreshold")) {
5091
+ (_a3 = refScroller.current) == null ? void 0 : _a3.scrollToEnd({
5092
+ animated: maintainScrollAtEnd.animated
5093
+ });
5094
+ setTimeout(
5095
+ () => {
5096
+ state.maintainingScrollAtEnd = false;
5097
+ },
5098
+ maintainScrollAtEnd.animated ? 500 : 0
5099
+ );
5100
+ } else {
5101
+ state.maintainingScrollAtEnd = false;
5102
+ }
5103
+ });
5104
+ }
4950
5105
  return true;
4951
5106
  }
4952
5107
  return false;
@@ -5058,14 +5213,21 @@ function doInitialAllocateContainers(ctx) {
5058
5213
  } else {
5059
5214
  averageItemSize = estimatedItemSize;
5060
5215
  }
5061
- const numContainers = Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns);
5216
+ const numContainers = Math.max(
5217
+ 1,
5218
+ Math.ceil((scrollLength + drawDistance * 2) / averageItemSize * numColumns)
5219
+ );
5062
5220
  for (let i = 0; i < numContainers; i++) {
5063
5221
  set$(ctx, `containerPosition${i}`, POSITION_OUT_OF_VIEW);
5064
5222
  set$(ctx, `containerColumn${i}`, -1);
5065
5223
  set$(ctx, `containerSpan${i}`, 1);
5066
5224
  }
5067
5225
  set$(ctx, "numContainers", numContainers);
5068
- set$(ctx, "numContainersPooled", numContainers * state.props.initialContainerPoolRatio);
5226
+ set$(
5227
+ ctx,
5228
+ "numContainersPooled",
5229
+ getInitialContainerPoolSize(data.length, numContainers, state.props.initialContainerPoolRatio)
5230
+ );
5069
5231
  if (state.lastLayout) {
5070
5232
  if (state.initialScroll) {
5071
5233
  requestAnimationFrame(() => {
@@ -5216,8 +5378,8 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
5216
5378
  // src/core/onScroll.ts
5217
5379
  function trackInitialScrollNativeProgress(state, newScroll) {
5218
5380
  const initialNativeScrollWatchdog = initialScrollWatchdog.get(state);
5219
- const didInitialScrollProgress = !!initialNativeScrollWatchdog && initialScrollWatchdog.didObserveProgress(newScroll, initialNativeScrollWatchdog);
5220
- if (didInitialScrollProgress) {
5381
+ const didInitialScrollReachTarget = !!initialNativeScrollWatchdog && initialScrollWatchdog.didReachTarget(newScroll, initialNativeScrollWatchdog);
5382
+ if (didInitialScrollReachTarget) {
5221
5383
  initialScrollWatchdog.clear(state);
5222
5384
  return;
5223
5385
  }
@@ -5355,16 +5517,20 @@ function maybeUpdateAnchoredEndSpace(ctx) {
5355
5517
  let contentBelowAnchor = 0;
5356
5518
  const footerSize = ctx.values.get("footerSize") || 0;
5357
5519
  const stylePaddingBottom = state.props.stylePaddingBottom || 0;
5520
+ let hasUnknownTailSize = false;
5358
5521
  for (let index = anchorIndex; index < data.length; index++) {
5359
5522
  const itemKey = getId(state, index);
5360
5523
  const size = itemKey ? state.sizesKnown.get(itemKey) : void 0;
5361
5524
  const effectiveSize = index === anchorIndex && anchorMaxSize !== void 0 ? Math.min(size || 0, Math.max(0, anchorMaxSize)) : size;
5525
+ if (size === void 0) {
5526
+ hasUnknownTailSize = true;
5527
+ }
5362
5528
  if (effectiveSize !== null && effectiveSize !== void 0 && effectiveSize > 0) {
5363
5529
  contentBelowAnchor += effectiveSize;
5364
5530
  }
5365
5531
  }
5366
5532
  contentBelowAnchor += footerSize + stylePaddingBottom;
5367
- nextSize = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5533
+ nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
5368
5534
  }
5369
5535
  }
5370
5536
  if (previousSize !== nextSize) {
@@ -5436,15 +5602,7 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5436
5602
  const {
5437
5603
  didContainersLayout,
5438
5604
  sizesKnown,
5439
- props: {
5440
- getFixedItemSize,
5441
- getItemType,
5442
- horizontal,
5443
- suggestEstimatedItemSize,
5444
- onItemSizeChanged,
5445
- data,
5446
- maintainScrollAtEnd
5447
- }
5605
+ props: { getFixedItemSize, getItemType, horizontal, onItemSizeChanged, data, maintainScrollAtEnd }
5448
5606
  } = state;
5449
5607
  if (!data) return;
5450
5608
  const index = state.indexByKey.get(itemKey);
@@ -5495,18 +5653,6 @@ function updateItemSize(ctx, itemKey, sizeObj) {
5495
5653
  if (minIndexSizeChanged !== void 0) {
5496
5654
  state.minIndexSizeChanged = state.minIndexSizeChanged !== void 0 ? Math.min(state.minIndexSizeChanged, minIndexSizeChanged) : minIndexSizeChanged;
5497
5655
  }
5498
- if (IS_DEV && suggestEstimatedItemSize && minIndexSizeChanged !== void 0) {
5499
- if (state.timeoutSizeMessage) clearTimeout(state.timeoutSizeMessage);
5500
- state.timeoutSizeMessage = setTimeout(() => {
5501
- var _a4;
5502
- state.timeoutSizeMessage = void 0;
5503
- const num = state.sizesKnown.size;
5504
- const avg = (_a4 = state.averageSizes[""]) == null ? void 0 : _a4.avg;
5505
- console.warn(
5506
- `[legend-list] Based on the ${num} items rendered so far, the optimal estimated size is ${avg}.`
5507
- );
5508
- }, 1e3);
5509
- }
5510
5656
  const cur = peek$(ctx, "otherAxisSize");
5511
5657
  if (!cur || maxOtherAxisSize > cur) {
5512
5658
  set$(ctx, "otherAxisSize", maxOtherAxisSize);
@@ -5611,12 +5757,47 @@ function createColumnWrapperStyle(contentContainerStyle) {
5611
5757
  }
5612
5758
 
5613
5759
  // src/utils/createImperativeHandle.ts
5760
+ var DEFAULT_AVERAGE_ITEM_SIZE_TYPE = "default";
5761
+ function getAverageItemSizes(state) {
5762
+ const averageItemSizes = {};
5763
+ for (const itemType in state.averageSizes) {
5764
+ const averageSize = state.averageSizes[itemType];
5765
+ if (averageSize) {
5766
+ averageItemSizes[itemType || DEFAULT_AVERAGE_ITEM_SIZE_TYPE] = {
5767
+ average: averageSize.avg,
5768
+ count: averageSize.num
5769
+ };
5770
+ }
5771
+ }
5772
+ return averageItemSizes;
5773
+ }
5614
5774
  function createImperativeHandle(ctx) {
5615
5775
  const state = ctx.state;
5616
5776
  const IMPERATIVE_SCROLL_SETTLE_MAX_WAIT_MS = 800;
5617
5777
  const IMPERATIVE_SCROLL_SETTLE_STABLE_FRAMES = 2;
5618
5778
  let imperativeScrollToken = 0;
5619
5779
  const isSettlingAfterDataChange = () => !!state.didDataChange || !!state.didColumnsChange || state.queuedMVCPRecalculate !== void 0 || state.ignoreScrollFromMVCP !== void 0;
5780
+ const isScrollToIndexReady = (targetIndex, allowEmpty = false) => {
5781
+ var _a3;
5782
+ const props = state.props;
5783
+ const dataLength = props.data.length;
5784
+ const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5785
+ if (targetIndex < 0) {
5786
+ return allowEmpty;
5787
+ }
5788
+ if (targetIndex >= dataLength) {
5789
+ return false;
5790
+ }
5791
+ if (anchorIndex === void 0 || anchorIndex < 0 || anchorIndex >= dataLength || targetIndex < anchorIndex || props.getFixedItemSize) {
5792
+ return true;
5793
+ }
5794
+ for (let index = anchorIndex; index < dataLength; index++) {
5795
+ if (!state.sizesKnown.has(getId(state, index))) {
5796
+ return false;
5797
+ }
5798
+ }
5799
+ return true;
5800
+ };
5620
5801
  const runWhenReady = (token, run, isReady) => {
5621
5802
  const startedAt = Date.now();
5622
5803
  let stableFrames = 0;
@@ -5638,11 +5819,10 @@ function createImperativeHandle(ctx) {
5638
5819
  };
5639
5820
  requestAnimationFrame(check);
5640
5821
  };
5641
- const runScrollWithPromise = (run, options) => new Promise((resolve) => {
5642
- var _a3, _b;
5822
+ const runScrollWithPromise = (run, isReady = () => true) => new Promise((resolve) => {
5823
+ var _a3;
5643
5824
  const token = ++imperativeScrollToken;
5644
- const isReady = (_a3 = options == null ? void 0 : options.isReady) != null ? _a3 : (() => true);
5645
- (_b = state.pendingScrollResolve) == null ? void 0 : _b.call(state);
5825
+ (_a3 = state.pendingScrollResolve) == null ? void 0 : _a3.call(state);
5646
5826
  state.pendingScrollResolve = resolve;
5647
5827
  const runNow = () => {
5648
5828
  if (token !== imperativeScrollToken) {
@@ -5717,6 +5897,7 @@ function createImperativeHandle(ctx) {
5717
5897
  },
5718
5898
  end: state.endNoBuffer,
5719
5899
  endBuffered: state.endBuffered,
5900
+ getAverageItemSizes: () => getAverageItemSizes(state),
5720
5901
  isAtEnd: peek$(ctx, "isAtEnd"),
5721
5902
  isAtStart: peek$(ctx, "isAtStart"),
5722
5903
  isEndReached: state.isEndReached,
@@ -5759,40 +5940,34 @@ function createImperativeHandle(ctx) {
5759
5940
  }
5760
5941
  return false;
5761
5942
  }),
5762
- scrollToEnd: (options) => runScrollWithPromise(() => {
5763
- const data = state.props.data;
5764
- const stylePaddingBottom = state.props.stylePaddingBottom;
5765
- const index = data.length - 1;
5766
- if (index !== -1) {
5767
- const paddingBottom = stylePaddingBottom || 0;
5768
- const footerSize = peek$(ctx, "footerSize") || 0;
5769
- scrollToIndex(ctx, {
5770
- ...options,
5771
- index,
5772
- viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5773
- viewPosition: 1
5774
- });
5775
- return true;
5776
- }
5777
- return false;
5778
- }),
5779
- scrollToIndex: (params) => {
5780
- const shouldWaitForOutOfRangeTarget = params.index >= 0 && params.index >= state.props.data.length;
5781
- const options = shouldWaitForOutOfRangeTarget ? {
5782
- isReady: () => {
5783
- var _a3;
5784
- const props = state.props;
5785
- const anchorIndex = (_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.anchorIndex;
5786
- const lastIndex = props.data.length - 1;
5787
- const isInRange = params.index < props.data.length;
5788
- const shouldWaitForAnchorSize = isInRange && anchorIndex !== void 0 && anchorIndex >= 0 && params.index >= anchorIndex && !props.getFixedItemSize && !state.sizesKnown.has(getId(state, lastIndex));
5789
- return isInRange && !shouldWaitForAnchorSize;
5943
+ scrollToEnd: (options) => runScrollWithPromise(
5944
+ () => {
5945
+ const data = state.props.data;
5946
+ const stylePaddingBottom = state.props.stylePaddingBottom;
5947
+ const index = data.length - 1;
5948
+ if (index !== -1) {
5949
+ const paddingBottom = stylePaddingBottom || 0;
5950
+ const footerSize = peek$(ctx, "footerSize") || 0;
5951
+ scrollToIndex(ctx, {
5952
+ ...options,
5953
+ index,
5954
+ viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
5955
+ viewPosition: 1
5956
+ });
5957
+ return true;
5790
5958
  }
5791
- } : void 0;
5792
- return runScrollWithPromise(() => {
5793
- scrollToIndex(ctx, params);
5794
- return true;
5795
- }, options);
5959
+ return false;
5960
+ },
5961
+ () => isScrollToIndexReady(state.props.data.length - 1, true)
5962
+ ),
5963
+ scrollToIndex: (params) => {
5964
+ return runScrollWithPromise(
5965
+ () => {
5966
+ scrollToIndex(ctx, params);
5967
+ return true;
5968
+ },
5969
+ params.index >= 0 ? () => isScrollToIndexReady(params.index) : void 0
5970
+ );
5796
5971
  },
5797
5972
  scrollToItem: ({ item, ...props }) => runScrollWithPromise(() => {
5798
5973
  const data = state.props.data;
@@ -6056,7 +6231,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6056
6231
  getFixedItemSize,
6057
6232
  getItemType,
6058
6233
  horizontal,
6059
- initialContainerPoolRatio = 2,
6234
+ initialContainerPoolRatio = 3,
6060
6235
  initialScrollAtEnd = false,
6061
6236
  initialScrollIndex: initialScrollIndexProp,
6062
6237
  initialScrollOffset: initialScrollOffsetProp,
@@ -6097,7 +6272,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6097
6272
  stickyIndices: stickyIndicesDeprecated,
6098
6273
  // TODOV3: Remove from v3 release
6099
6274
  style: styleProp,
6100
- suggestEstimatedItemSize,
6101
6275
  useWindowScroll = false,
6102
6276
  viewabilityConfig,
6103
6277
  viewabilityConfigCallbackPairs,
@@ -6238,7 +6412,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6238
6412
  startReachedSnapshotDataChangeEpoch: void 0,
6239
6413
  stickyContainerPool: /* @__PURE__ */ new Set(),
6240
6414
  stickyContainers: /* @__PURE__ */ new Map(),
6241
- timeoutSizeMessage: 0,
6242
6415
  timeouts: /* @__PURE__ */ new Set(),
6243
6416
  totalSize: 0,
6244
6417
  viewabilityConfigCallbackPairs: void 0
@@ -6258,7 +6431,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6258
6431
  const didDataReferenceChangeLocal = state.props.data !== dataProp;
6259
6432
  const didDataVersionChangeLocal = state.props.dataVersion !== dataVersion;
6260
6433
  const didDataChangeLocal = didDataVersionChangeLocal || didDataReferenceChangeLocal && checkStructuralDataChange(state, dataProp, state.props.data);
6261
- if (didDataChangeLocal && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6434
+ if (didDataChangeLocal && !initialScrollAtEnd && state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.props.data.length > 0) {
6262
6435
  clearPreservedInitialScrollTarget(state);
6263
6436
  }
6264
6437
  if (didDataChangeLocal) {
@@ -6313,7 +6486,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
6313
6486
  stickyPositionComponentInternal,
6314
6487
  stylePaddingBottom: stylePaddingBottomState,
6315
6488
  stylePaddingTop: stylePaddingTopState,
6316
- suggestEstimatedItemSize: !!suggestEstimatedItemSize,
6317
6489
  useWindowScroll: useWindowScrollResolved
6318
6490
  };
6319
6491
  state.refScroller = refScroller;