@legendapp/list 3.0.0-beta.36 → 3.0.0-beta.37

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.d.ts CHANGED
@@ -571,6 +571,10 @@ interface InternalState$1 {
571
571
  scrollLastCalculate?: number;
572
572
  scrollLength: number;
573
573
  scrollPending: number;
574
+ stableTarget?: {
575
+ scroll: number;
576
+ target: number;
577
+ };
574
578
  scrollPrev: number;
575
579
  scrollPrevTime: number;
576
580
  scrollProcessingEnabled: boolean;
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var React3 = require('react');
4
4
  var shim = require('use-sync-external-store/shim');
5
- var reactDom = require('react-dom');
5
+ var ReactDOM = require('react-dom');
6
6
 
7
7
  function _interopNamespace(e) {
8
8
  if (e && e.__esModule) return e;
@@ -23,6 +23,7 @@ function _interopNamespace(e) {
23
23
  }
24
24
 
25
25
  var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
26
+ var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
26
27
 
27
28
  // src/components/LegendList.tsx
28
29
  React3.forwardRef(function AnimatedView2(props, ref) {
@@ -1382,8 +1383,8 @@ var ListComponent = typedMemo(function ListComponent2({
1382
1383
  updateItemSize: updateItemSize2,
1383
1384
  refScrollView,
1384
1385
  renderScrollComponent,
1386
+ onLayoutFooter,
1385
1387
  scrollAdjustHandler,
1386
- onLayoutHeader,
1387
1388
  snapToIndices,
1388
1389
  stickyHeaderConfig,
1389
1390
  stickyHeaderIndices,
@@ -1407,6 +1408,21 @@ var ListComponent = typedMemo(function ListComponent2({
1407
1408
  set$(ctx, "footerSize", 0);
1408
1409
  }
1409
1410
  }, [ListHeaderComponent, ListFooterComponent, ctx]);
1411
+ const onLayoutHeader = React3.useCallback(
1412
+ (rect) => {
1413
+ const size = rect[horizontal ? "width" : "height"];
1414
+ set$(ctx, "headerSize", size);
1415
+ },
1416
+ [ctx, horizontal]
1417
+ );
1418
+ const onLayoutFooterInternal = React3.useCallback(
1419
+ (rect, fromLayoutEffect) => {
1420
+ const size = rect[horizontal ? "width" : "height"];
1421
+ set$(ctx, "footerSize", size);
1422
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
1423
+ },
1424
+ [ctx, horizontal, onLayoutFooter]
1425
+ );
1410
1426
  return /* @__PURE__ */ React3__namespace.createElement(
1411
1427
  SnapOrScroll,
1412
1428
  {
@@ -1442,17 +1458,7 @@ var ListComponent = typedMemo(function ListComponent2({
1442
1458
  waitForInitialLayout
1443
1459
  }
1444
1460
  ),
1445
- ListFooterComponent && /* @__PURE__ */ React3__namespace.createElement(
1446
- LayoutView,
1447
- {
1448
- onLayoutChange: (layout) => {
1449
- const size = layout[horizontal ? "width" : "height"];
1450
- set$(ctx, "footerSize", size);
1451
- },
1452
- style: ListFooterComponentStyle
1453
- },
1454
- getComponent(ListFooterComponent)
1455
- ),
1461
+ ListFooterComponent && /* @__PURE__ */ React3__namespace.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
1456
1462
  IS_DEV && ENABLE_DEVMODE
1457
1463
  );
1458
1464
  });
@@ -1460,19 +1466,12 @@ var ListComponent = typedMemo(function ListComponent2({
1460
1466
  // src/core/calculateOffsetForIndex.ts
1461
1467
  function calculateOffsetForIndex(ctx, index) {
1462
1468
  const state = ctx.state;
1463
- let position = 0;
1464
- if (index !== void 0) {
1465
- position = state.positions[index] || 0;
1466
- const paddingTop = peek$(ctx, "stylePaddingTop");
1467
- if (paddingTop) {
1468
- position += paddingTop;
1469
- }
1470
- const headerSize = peek$(ctx, "headerSize");
1471
- if (headerSize) {
1472
- position += headerSize;
1473
- }
1474
- }
1475
- return position;
1469
+ return index !== void 0 ? state.positions[index] || 0 : 0;
1470
+ }
1471
+
1472
+ // src/core/getTopOffsetAdjustment.ts
1473
+ function getTopOffsetAdjustment(ctx) {
1474
+ return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
1476
1475
  }
1477
1476
 
1478
1477
  // src/utils/getId.ts
@@ -1578,10 +1577,20 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1578
1577
  if (viewOffset) {
1579
1578
  offset -= viewOffset;
1580
1579
  }
1580
+ if (index !== void 0) {
1581
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1582
+ if (topOffsetAdjustment) {
1583
+ offset += topOffsetAdjustment;
1584
+ }
1585
+ }
1581
1586
  if (viewPosition !== void 0 && index !== void 0) {
1582
1587
  const itemSize = getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1583
1588
  const trailingInset = getContentInsetEnd(state);
1584
1589
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1590
+ if (index === state.props.data.length - 1) {
1591
+ const footerSize = peek$(ctx, "footerSize") || 0;
1592
+ offset += footerSize;
1593
+ }
1585
1594
  }
1586
1595
  return offset;
1587
1596
  }
@@ -1782,6 +1791,7 @@ function finishScrollTo(ctx) {
1782
1791
  state.initialScroll = void 0;
1783
1792
  state.initialAnchor = void 0;
1784
1793
  state.scrollingTo = void 0;
1794
+ state.stableTarget = void 0;
1785
1795
  if (state.pendingTotalSize !== void 0) {
1786
1796
  addTotalSize(ctx, null, state.pendingTotalSize);
1787
1797
  }
@@ -1898,8 +1908,9 @@ function scrollTo(ctx, params) {
1898
1908
  let offset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
1899
1909
  offset = clampScrollOffset(ctx, offset, scrollTarget);
1900
1910
  state.scrollHistory.length = 0;
1911
+ state.stableTarget = void 0;
1901
1912
  if (!noScrollingTo) {
1902
- state.scrollingTo = scrollTarget;
1913
+ state.scrollingTo = { ...scrollTarget, offset };
1903
1914
  }
1904
1915
  state.scrollPending = offset;
1905
1916
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
@@ -1957,7 +1968,7 @@ function updateScroll(ctx, newScroll, forceUpdate) {
1957
1968
  checkThresholds(ctx);
1958
1969
  };
1959
1970
  if (scrollLength > 0 && scrollingTo === void 0 && scrollDelta > scrollLength) {
1960
- reactDom.flushSync(runCalculateItems);
1971
+ ReactDOM.flushSync(runCalculateItems);
1961
1972
  } else {
1962
1973
  runCalculateItems();
1963
1974
  }
@@ -2674,6 +2685,8 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
2674
2685
  const cb = ctx.mapViewabilityCallbacks.get(key);
2675
2686
  cb == null ? void 0 : cb(viewToken);
2676
2687
  }
2688
+ var unstableBatchedUpdates = ReactDOM__namespace.unstable_batchedUpdates;
2689
+ var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
2677
2690
 
2678
2691
  // src/utils/checkAllSizesKnown.ts
2679
2692
  function isNullOrUndefined2(value) {
@@ -2926,7 +2939,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
2926
2939
  }
2927
2940
  function calculateItemsInView(ctx, params = {}) {
2928
2941
  const state = ctx.state;
2929
- reactDom.unstable_batchedUpdates(() => {
2942
+ batchedUpdates(() => {
2930
2943
  var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2931
2944
  const {
2932
2945
  columns,
@@ -3338,6 +3351,14 @@ function checkActualChange(state, dataProp, previousData) {
3338
3351
  }
3339
3352
 
3340
3353
  // src/core/checkFinishedScroll.ts
3354
+ function getCurrentTargetOffset(ctx, scrollingTo) {
3355
+ if (scrollingTo.index !== void 0) {
3356
+ const baseOffset = calculateOffsetForIndex(ctx, scrollingTo.index);
3357
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, scrollingTo);
3358
+ return clampScrollOffset(ctx, resolvedOffset, scrollingTo);
3359
+ }
3360
+ return clampScrollOffset(ctx, scrollingTo.offset, scrollingTo);
3361
+ }
3341
3362
  function checkFinishedScroll(ctx) {
3342
3363
  ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3343
3364
  }
@@ -3347,20 +3368,30 @@ function checkFinishedScrollFrame(ctx) {
3347
3368
  const { state } = ctx;
3348
3369
  state.animFrameCheckFinishedScroll = void 0;
3349
3370
  const scroll = state.scrollPending;
3350
- const adjust = state.scrollAdjustHandler.getAdjust();
3351
- const clampedTargetOffset = clampScrollOffset(
3352
- ctx,
3353
- scrollingTo.offset - (scrollingTo.viewOffset || 0),
3354
- scrollingTo
3355
- );
3371
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
3372
+ if (Math.abs(scrollingTo.offset - clampedTargetOffset) >= 1) {
3373
+ state.scrollingTo = { ...scrollingTo, offset: clampedTargetOffset };
3374
+ }
3356
3375
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
3357
3376
  const diff1 = Math.abs(scroll - clampedTargetOffset);
3358
- const diff2 = Math.abs(diff1 - adjust);
3359
3377
  const isNotOverscrolled = Math.abs(scroll - maxOffset) < 1;
3360
- const isAtTarget = diff1 < 1 || !scrollingTo.animated && diff2 < 1;
3378
+ const isAtTarget = diff1 < 1;
3379
+ const previousStableTarget = state.stableTarget;
3380
+ const hasStableTargetFrame = !!previousStableTarget && Math.abs(previousStableTarget.target - clampedTargetOffset) < 1 && Math.abs(previousStableTarget.scroll - scroll) < 1;
3381
+ if (isAtTarget && !hasStableTargetFrame) {
3382
+ state.stableTarget = { scroll, target: clampedTargetOffset };
3383
+ state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3384
+ return;
3385
+ }
3386
+ if (!isAtTarget) {
3387
+ state.stableTarget = void 0;
3388
+ }
3361
3389
  if (isNotOverscrolled && isAtTarget) {
3390
+ state.stableTarget = void 0;
3362
3391
  finishScrollTo(ctx);
3363
3392
  }
3393
+ } else {
3394
+ ctx.state.stableTarget = void 0;
3364
3395
  }
3365
3396
  }
3366
3397
  function checkFinishedScrollFallback(ctx) {
@@ -4297,7 +4328,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4297
4328
  const maintainVisibleContentPositionConfig = normalizeMaintainVisibleContentPosition(
4298
4329
  maintainVisibleContentPositionProp
4299
4330
  );
4300
- const [renderNum, setRenderNum] = React3.useState(0);
4301
4331
  const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? {
4302
4332
  index: initialScrollIndexProp.index || 0,
4303
4333
  viewOffset: initialScrollIndexProp.viewOffset || (initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0),
@@ -4497,6 +4527,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4497
4527
  true
4498
4528
  );
4499
4529
  }
4530
+ const resolveInitialScrollOffset = React3.useCallback((initialScroll) => {
4531
+ const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4532
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4533
+ return clampScrollOffset(ctx, resolvedOffset, initialScroll);
4534
+ }, []);
4500
4535
  const initialContentOffset = React3.useMemo(() => {
4501
4536
  let value;
4502
4537
  const { initialScroll, initialAnchor } = refState.current;
@@ -4504,9 +4539,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4504
4539
  if (initialScroll.contentOffset !== void 0) {
4505
4540
  value = initialScroll.contentOffset;
4506
4541
  } else {
4507
- const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4508
- const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4509
- const clampedOffset = clampScrollOffset(ctx, resolvedOffset, initialScroll);
4542
+ const clampedOffset = resolveInitialScrollOffset(initialScroll);
4510
4543
  const updatedInitialScroll = { ...initialScroll, contentOffset: clampedOffset };
4511
4544
  refState.current.initialScroll = updatedInitialScroll;
4512
4545
  state.initialScroll = updatedInitialScroll;
@@ -4520,7 +4553,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4520
4553
  setInitialRenderState(ctx, { didInitialScroll: true });
4521
4554
  }
4522
4555
  return value;
4523
- }, [renderNum]);
4556
+ }, []);
4524
4557
  if (isFirstLocal || didDataChangeLocal || numColumnsProp !== peek$(ctx, "numColumns")) {
4525
4558
  refState.current.lastBatchingAction = Date.now();
4526
4559
  if (!keyExtractorProp && !isFirstLocal && didDataChangeLocal) {
@@ -4534,30 +4567,45 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4534
4567
  set$(ctx, "totalSize", 0);
4535
4568
  }
4536
4569
  }
4537
- const onLayoutHeader = React3.useCallback((rect, fromLayoutEffect) => {
4538
- const { initialScroll } = refState.current;
4539
- const size = rect[horizontal ? "width" : "height"];
4540
- set$(ctx, "headerSize", size);
4541
- if ((initialScroll == null ? void 0 : initialScroll.index) !== void 0) {
4542
- {
4543
- if (fromLayoutEffect) {
4544
- setRenderNum((v) => v + 1);
4545
- }
4546
- }
4547
- }
4548
- }, []);
4549
4570
  const doInitialScroll = React3.useCallback(() => {
4550
4571
  const { initialScroll, didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4551
4572
  if (initialScroll && !queuedInitialLayout && !didFinishInitialScroll && !scrollingTo) {
4573
+ const offset = resolveInitialScrollOffset(initialScroll);
4574
+ const updatedInitialScroll = { ...initialScroll, contentOffset: offset };
4575
+ refState.current.initialScroll = updatedInitialScroll;
4576
+ state.initialScroll = updatedInitialScroll;
4552
4577
  scrollTo(ctx, {
4553
4578
  animated: false,
4554
- index: initialScroll == null ? void 0 : initialScroll.index,
4579
+ index: initialScroll.index,
4555
4580
  isInitialScroll: true,
4556
- offset: initialContentOffset,
4581
+ offset,
4557
4582
  precomputedWithViewOffset: true
4558
4583
  });
4559
4584
  }
4560
- }, [initialContentOffset]);
4585
+ }, []);
4586
+ const onLayoutFooter = React3.useCallback(
4587
+ (layout) => {
4588
+ if (!initialScrollAtEnd) {
4589
+ return;
4590
+ }
4591
+ const { initialScroll } = state;
4592
+ if (!initialScroll) {
4593
+ return;
4594
+ }
4595
+ const lastIndex = Math.max(0, dataProp.length - 1);
4596
+ if (initialScroll.index !== lastIndex || initialScroll.viewPosition !== 1) {
4597
+ return;
4598
+ }
4599
+ const footerSize = layout[horizontal ? "width" : "height"];
4600
+ const viewOffset = -stylePaddingBottomState - footerSize;
4601
+ if (initialScroll.viewOffset !== viewOffset) {
4602
+ const updatedInitialScroll = { ...initialScroll, viewOffset };
4603
+ refState.current.initialScroll = updatedInitialScroll;
4604
+ state.initialScroll = updatedInitialScroll;
4605
+ }
4606
+ },
4607
+ [dataProp.length, horizontal, initialScrollAtEnd, stylePaddingBottomState]
4608
+ );
4561
4609
  const onLayoutChange = React3.useCallback((layout) => {
4562
4610
  doInitialScroll();
4563
4611
  handleLayout(ctx, layout, setCanRender);
@@ -4666,7 +4714,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4666
4714
  ListEmptyComponent: dataProp.length === 0 ? ListEmptyComponent : void 0,
4667
4715
  ListHeaderComponent,
4668
4716
  onLayout,
4669
- onLayoutHeader,
4717
+ onLayoutFooter,
4670
4718
  onMomentumScrollEnd: fns.onMomentumScrollEnd,
4671
4719
  onScroll: onScrollHandler,
4672
4720
  recycleItems,
package/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as React3 from 'react';
2
2
  import React3__default, { forwardRef, useReducer, useEffect, createContext, useRef, useState, useMemo, useCallback, useImperativeHandle, useLayoutEffect, memo, useContext } from 'react';
3
3
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
4
- import { unstable_batchedUpdates, flushSync } from 'react-dom';
4
+ import * as ReactDOM from 'react-dom';
5
+ import { flushSync } from 'react-dom';
5
6
 
6
7
  // src/components/LegendList.tsx
7
8
  forwardRef(function AnimatedView2(props, ref) {
@@ -1361,8 +1362,8 @@ var ListComponent = typedMemo(function ListComponent2({
1361
1362
  updateItemSize: updateItemSize2,
1362
1363
  refScrollView,
1363
1364
  renderScrollComponent,
1365
+ onLayoutFooter,
1364
1366
  scrollAdjustHandler,
1365
- onLayoutHeader,
1366
1367
  snapToIndices,
1367
1368
  stickyHeaderConfig,
1368
1369
  stickyHeaderIndices,
@@ -1386,6 +1387,21 @@ var ListComponent = typedMemo(function ListComponent2({
1386
1387
  set$(ctx, "footerSize", 0);
1387
1388
  }
1388
1389
  }, [ListHeaderComponent, ListFooterComponent, ctx]);
1390
+ const onLayoutHeader = useCallback(
1391
+ (rect) => {
1392
+ const size = rect[horizontal ? "width" : "height"];
1393
+ set$(ctx, "headerSize", size);
1394
+ },
1395
+ [ctx, horizontal]
1396
+ );
1397
+ const onLayoutFooterInternal = useCallback(
1398
+ (rect, fromLayoutEffect) => {
1399
+ const size = rect[horizontal ? "width" : "height"];
1400
+ set$(ctx, "footerSize", size);
1401
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
1402
+ },
1403
+ [ctx, horizontal, onLayoutFooter]
1404
+ );
1389
1405
  return /* @__PURE__ */ React3.createElement(
1390
1406
  SnapOrScroll,
1391
1407
  {
@@ -1421,17 +1437,7 @@ var ListComponent = typedMemo(function ListComponent2({
1421
1437
  waitForInitialLayout
1422
1438
  }
1423
1439
  ),
1424
- ListFooterComponent && /* @__PURE__ */ React3.createElement(
1425
- LayoutView,
1426
- {
1427
- onLayoutChange: (layout) => {
1428
- const size = layout[horizontal ? "width" : "height"];
1429
- set$(ctx, "footerSize", size);
1430
- },
1431
- style: ListFooterComponentStyle
1432
- },
1433
- getComponent(ListFooterComponent)
1434
- ),
1440
+ ListFooterComponent && /* @__PURE__ */ React3.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
1435
1441
  IS_DEV && ENABLE_DEVMODE
1436
1442
  );
1437
1443
  });
@@ -1439,19 +1445,12 @@ var ListComponent = typedMemo(function ListComponent2({
1439
1445
  // src/core/calculateOffsetForIndex.ts
1440
1446
  function calculateOffsetForIndex(ctx, index) {
1441
1447
  const state = ctx.state;
1442
- let position = 0;
1443
- if (index !== void 0) {
1444
- position = state.positions[index] || 0;
1445
- const paddingTop = peek$(ctx, "stylePaddingTop");
1446
- if (paddingTop) {
1447
- position += paddingTop;
1448
- }
1449
- const headerSize = peek$(ctx, "headerSize");
1450
- if (headerSize) {
1451
- position += headerSize;
1452
- }
1453
- }
1454
- return position;
1448
+ return index !== void 0 ? state.positions[index] || 0 : 0;
1449
+ }
1450
+
1451
+ // src/core/getTopOffsetAdjustment.ts
1452
+ function getTopOffsetAdjustment(ctx) {
1453
+ return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
1455
1454
  }
1456
1455
 
1457
1456
  // src/utils/getId.ts
@@ -1557,10 +1556,20 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1557
1556
  if (viewOffset) {
1558
1557
  offset -= viewOffset;
1559
1558
  }
1559
+ if (index !== void 0) {
1560
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1561
+ if (topOffsetAdjustment) {
1562
+ offset += topOffsetAdjustment;
1563
+ }
1564
+ }
1560
1565
  if (viewPosition !== void 0 && index !== void 0) {
1561
1566
  const itemSize = getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1562
1567
  const trailingInset = getContentInsetEnd(state);
1563
1568
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1569
+ if (index === state.props.data.length - 1) {
1570
+ const footerSize = peek$(ctx, "footerSize") || 0;
1571
+ offset += footerSize;
1572
+ }
1564
1573
  }
1565
1574
  return offset;
1566
1575
  }
@@ -1761,6 +1770,7 @@ function finishScrollTo(ctx) {
1761
1770
  state.initialScroll = void 0;
1762
1771
  state.initialAnchor = void 0;
1763
1772
  state.scrollingTo = void 0;
1773
+ state.stableTarget = void 0;
1764
1774
  if (state.pendingTotalSize !== void 0) {
1765
1775
  addTotalSize(ctx, null, state.pendingTotalSize);
1766
1776
  }
@@ -1877,8 +1887,9 @@ function scrollTo(ctx, params) {
1877
1887
  let offset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
1878
1888
  offset = clampScrollOffset(ctx, offset, scrollTarget);
1879
1889
  state.scrollHistory.length = 0;
1890
+ state.stableTarget = void 0;
1880
1891
  if (!noScrollingTo) {
1881
- state.scrollingTo = scrollTarget;
1892
+ state.scrollingTo = { ...scrollTarget, offset };
1882
1893
  }
1883
1894
  state.scrollPending = offset;
1884
1895
  if (forceScroll || !isInitialScroll || Platform.OS === "android") {
@@ -2653,6 +2664,8 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
2653
2664
  const cb = ctx.mapViewabilityCallbacks.get(key);
2654
2665
  cb == null ? void 0 : cb(viewToken);
2655
2666
  }
2667
+ var unstableBatchedUpdates = ReactDOM.unstable_batchedUpdates;
2668
+ var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
2656
2669
 
2657
2670
  // src/utils/checkAllSizesKnown.ts
2658
2671
  function isNullOrUndefined2(value) {
@@ -2905,7 +2918,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
2905
2918
  }
2906
2919
  function calculateItemsInView(ctx, params = {}) {
2907
2920
  const state = ctx.state;
2908
- unstable_batchedUpdates(() => {
2921
+ batchedUpdates(() => {
2909
2922
  var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2910
2923
  const {
2911
2924
  columns,
@@ -3317,6 +3330,14 @@ function checkActualChange(state, dataProp, previousData) {
3317
3330
  }
3318
3331
 
3319
3332
  // src/core/checkFinishedScroll.ts
3333
+ function getCurrentTargetOffset(ctx, scrollingTo) {
3334
+ if (scrollingTo.index !== void 0) {
3335
+ const baseOffset = calculateOffsetForIndex(ctx, scrollingTo.index);
3336
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, scrollingTo);
3337
+ return clampScrollOffset(ctx, resolvedOffset, scrollingTo);
3338
+ }
3339
+ return clampScrollOffset(ctx, scrollingTo.offset, scrollingTo);
3340
+ }
3320
3341
  function checkFinishedScroll(ctx) {
3321
3342
  ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3322
3343
  }
@@ -3326,20 +3347,30 @@ function checkFinishedScrollFrame(ctx) {
3326
3347
  const { state } = ctx;
3327
3348
  state.animFrameCheckFinishedScroll = void 0;
3328
3349
  const scroll = state.scrollPending;
3329
- const adjust = state.scrollAdjustHandler.getAdjust();
3330
- const clampedTargetOffset = clampScrollOffset(
3331
- ctx,
3332
- scrollingTo.offset - (scrollingTo.viewOffset || 0),
3333
- scrollingTo
3334
- );
3350
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
3351
+ if (Math.abs(scrollingTo.offset - clampedTargetOffset) >= 1) {
3352
+ state.scrollingTo = { ...scrollingTo, offset: clampedTargetOffset };
3353
+ }
3335
3354
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
3336
3355
  const diff1 = Math.abs(scroll - clampedTargetOffset);
3337
- const diff2 = Math.abs(diff1 - adjust);
3338
3356
  const isNotOverscrolled = Math.abs(scroll - maxOffset) < 1;
3339
- const isAtTarget = diff1 < 1 || !scrollingTo.animated && diff2 < 1;
3357
+ const isAtTarget = diff1 < 1;
3358
+ const previousStableTarget = state.stableTarget;
3359
+ const hasStableTargetFrame = !!previousStableTarget && Math.abs(previousStableTarget.target - clampedTargetOffset) < 1 && Math.abs(previousStableTarget.scroll - scroll) < 1;
3360
+ if (isAtTarget && !hasStableTargetFrame) {
3361
+ state.stableTarget = { scroll, target: clampedTargetOffset };
3362
+ state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
3363
+ return;
3364
+ }
3365
+ if (!isAtTarget) {
3366
+ state.stableTarget = void 0;
3367
+ }
3340
3368
  if (isNotOverscrolled && isAtTarget) {
3369
+ state.stableTarget = void 0;
3341
3370
  finishScrollTo(ctx);
3342
3371
  }
3372
+ } else {
3373
+ ctx.state.stableTarget = void 0;
3343
3374
  }
3344
3375
  }
3345
3376
  function checkFinishedScrollFallback(ctx) {
@@ -4276,7 +4307,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4276
4307
  const maintainVisibleContentPositionConfig = normalizeMaintainVisibleContentPosition(
4277
4308
  maintainVisibleContentPositionProp
4278
4309
  );
4279
- const [renderNum, setRenderNum] = useState(0);
4280
4310
  const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? {
4281
4311
  index: initialScrollIndexProp.index || 0,
4282
4312
  viewOffset: initialScrollIndexProp.viewOffset || (initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0),
@@ -4476,6 +4506,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4476
4506
  true
4477
4507
  );
4478
4508
  }
4509
+ const resolveInitialScrollOffset = useCallback((initialScroll) => {
4510
+ const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4511
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4512
+ return clampScrollOffset(ctx, resolvedOffset, initialScroll);
4513
+ }, []);
4479
4514
  const initialContentOffset = useMemo(() => {
4480
4515
  let value;
4481
4516
  const { initialScroll, initialAnchor } = refState.current;
@@ -4483,9 +4518,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4483
4518
  if (initialScroll.contentOffset !== void 0) {
4484
4519
  value = initialScroll.contentOffset;
4485
4520
  } else {
4486
- const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4487
- const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4488
- const clampedOffset = clampScrollOffset(ctx, resolvedOffset, initialScroll);
4521
+ const clampedOffset = resolveInitialScrollOffset(initialScroll);
4489
4522
  const updatedInitialScroll = { ...initialScroll, contentOffset: clampedOffset };
4490
4523
  refState.current.initialScroll = updatedInitialScroll;
4491
4524
  state.initialScroll = updatedInitialScroll;
@@ -4499,7 +4532,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4499
4532
  setInitialRenderState(ctx, { didInitialScroll: true });
4500
4533
  }
4501
4534
  return value;
4502
- }, [renderNum]);
4535
+ }, []);
4503
4536
  if (isFirstLocal || didDataChangeLocal || numColumnsProp !== peek$(ctx, "numColumns")) {
4504
4537
  refState.current.lastBatchingAction = Date.now();
4505
4538
  if (!keyExtractorProp && !isFirstLocal && didDataChangeLocal) {
@@ -4513,30 +4546,45 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4513
4546
  set$(ctx, "totalSize", 0);
4514
4547
  }
4515
4548
  }
4516
- const onLayoutHeader = useCallback((rect, fromLayoutEffect) => {
4517
- const { initialScroll } = refState.current;
4518
- const size = rect[horizontal ? "width" : "height"];
4519
- set$(ctx, "headerSize", size);
4520
- if ((initialScroll == null ? void 0 : initialScroll.index) !== void 0) {
4521
- {
4522
- if (fromLayoutEffect) {
4523
- setRenderNum((v) => v + 1);
4524
- }
4525
- }
4526
- }
4527
- }, []);
4528
4549
  const doInitialScroll = useCallback(() => {
4529
4550
  const { initialScroll, didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4530
4551
  if (initialScroll && !queuedInitialLayout && !didFinishInitialScroll && !scrollingTo) {
4552
+ const offset = resolveInitialScrollOffset(initialScroll);
4553
+ const updatedInitialScroll = { ...initialScroll, contentOffset: offset };
4554
+ refState.current.initialScroll = updatedInitialScroll;
4555
+ state.initialScroll = updatedInitialScroll;
4531
4556
  scrollTo(ctx, {
4532
4557
  animated: false,
4533
- index: initialScroll == null ? void 0 : initialScroll.index,
4558
+ index: initialScroll.index,
4534
4559
  isInitialScroll: true,
4535
- offset: initialContentOffset,
4560
+ offset,
4536
4561
  precomputedWithViewOffset: true
4537
4562
  });
4538
4563
  }
4539
- }, [initialContentOffset]);
4564
+ }, []);
4565
+ const onLayoutFooter = useCallback(
4566
+ (layout) => {
4567
+ if (!initialScrollAtEnd) {
4568
+ return;
4569
+ }
4570
+ const { initialScroll } = state;
4571
+ if (!initialScroll) {
4572
+ return;
4573
+ }
4574
+ const lastIndex = Math.max(0, dataProp.length - 1);
4575
+ if (initialScroll.index !== lastIndex || initialScroll.viewPosition !== 1) {
4576
+ return;
4577
+ }
4578
+ const footerSize = layout[horizontal ? "width" : "height"];
4579
+ const viewOffset = -stylePaddingBottomState - footerSize;
4580
+ if (initialScroll.viewOffset !== viewOffset) {
4581
+ const updatedInitialScroll = { ...initialScroll, viewOffset };
4582
+ refState.current.initialScroll = updatedInitialScroll;
4583
+ state.initialScroll = updatedInitialScroll;
4584
+ }
4585
+ },
4586
+ [dataProp.length, horizontal, initialScrollAtEnd, stylePaddingBottomState]
4587
+ );
4540
4588
  const onLayoutChange = useCallback((layout) => {
4541
4589
  doInitialScroll();
4542
4590
  handleLayout(ctx, layout, setCanRender);
@@ -4645,7 +4693,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4645
4693
  ListEmptyComponent: dataProp.length === 0 ? ListEmptyComponent : void 0,
4646
4694
  ListHeaderComponent,
4647
4695
  onLayout,
4648
- onLayoutHeader,
4696
+ onLayoutFooter,
4649
4697
  onMomentumScrollEnd: fns.onMomentumScrollEnd,
4650
4698
  onScroll: onScrollHandler,
4651
4699
  recycleItems,