@legendapp/list 3.3.10 → 3.3.11

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.
@@ -980,6 +980,7 @@ function supersedeInitialScroll(ctx) {
980
980
  function finishInitialScroll(ctx, options) {
981
981
  var _a3, _b, _c;
982
982
  const state = ctx.state;
983
+ const freshDataTransitionEpoch = state.freshDataTransitionEpoch;
983
984
  if ((options == null ? void 0 : options.resolvedOffset) !== void 0) {
984
985
  syncInitialScrollOffset(state, options.resolvedOffset);
985
986
  } else if ((options == null ? void 0 : options.syncObservedOffset) && ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset") {
@@ -989,9 +990,13 @@ function finishInitialScroll(ctx, options) {
989
990
  }
990
991
  }
991
992
  const complete = () => {
992
- var _a4, _b2, _c2, _d, _e;
993
- const shouldReleaseDeferredPublicOnScroll = ((_a4 = state.initialScrollSession) == null ? void 0 : _a4.kind) === "bootstrap";
994
- const finalScrollOffset = (_d = (_c2 = (_b2 = options == null ? void 0 : options.resolvedOffset) != null ? _b2 : state.scrollPending) != null ? _c2 : state.scroll) != null ? _d : 0;
993
+ var _a4, _b2, _c2, _d, _e, _f;
994
+ if (freshDataTransitionEpoch !== state.freshDataTransitionEpoch) {
995
+ (_a4 = options == null ? void 0 : options.onFinished) == null ? void 0 : _a4.call(options);
996
+ return;
997
+ }
998
+ const shouldReleaseDeferredPublicOnScroll = ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap";
999
+ const finalScrollOffset = (_e = (_d = (_c2 = options == null ? void 0 : options.resolvedOffset) != null ? _c2 : state.scrollPending) != null ? _d : state.scroll) != null ? _e : 0;
995
1000
  initialScrollWatchdog.clear(state);
996
1001
  if ((options == null ? void 0 : options.preserveTarget) && state.initialScroll) {
997
1002
  state.clearPreservedInitialScrollOnNextFinish = void 0;
@@ -1020,7 +1025,7 @@ function finishInitialScroll(ctx, options) {
1020
1025
  if (shouldReleaseDeferredPublicOnScroll) {
1021
1026
  releaseDeferredPublicOnScroll(ctx, finalScrollOffset);
1022
1027
  }
1023
- (_e = options == null ? void 0 : options.onFinished) == null ? void 0 : _e.call(options);
1028
+ (_f = options == null ? void 0 : options.onFinished) == null ? void 0 : _f.call(options);
1024
1029
  };
1025
1030
  if (options == null ? void 0 : options.waitForCompletionFrame) {
1026
1031
  requestAnimationFrame(complete);
@@ -1302,153 +1307,21 @@ function clampScrollOffset(ctx, offset, scrollTarget) {
1302
1307
  return clampedOffset;
1303
1308
  }
1304
1309
 
1305
- // src/core/finishScrollTo.ts
1306
- function finishScrollTo(ctx) {
1307
- var _a3, _b;
1308
- const state = ctx.state;
1309
- if (state == null ? void 0 : state.scrollingTo) {
1310
- cancelScrollCompletionChecks(state);
1311
- const resolvePendingScroll = state.pendingScrollResolve;
1312
- state.pendingScrollResolve = void 0;
1313
- const scrollingTo = state.scrollingTo;
1314
- state.scrollHistory.length = 0;
1315
- state.scrollingTo = void 0;
1316
- state.scrollTargetPinnedRange = void 0;
1317
- if (state.pendingTotalSize !== void 0) {
1318
- addTotalSize(ctx, null, state.pendingTotalSize);
1319
- }
1320
- {
1321
- state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1322
- }
1323
- if (scrollingTo.isInitialScroll || state.initialScroll) {
1324
- const isOffsetSession = ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset";
1325
- const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_b = state.initialScroll) == null ? void 0 : _b.viewPosition) === 1;
1326
- finishInitialScroll(ctx, {
1327
- onFinished: () => {
1328
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1329
- },
1330
- preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1331
- recalculateItems: true,
1332
- schedulePreservedTargetClear: shouldPreserveResizeTarget,
1333
- syncObservedOffset: isOffsetSession,
1334
- waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1335
- });
1336
- return;
1337
- }
1338
- recalculateSettledScroll(ctx);
1339
- resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1340
- }
1341
- }
1342
-
1343
- // src/core/doScrollTo.ts
1344
- var SCROLL_END_IDLE_MS = 80;
1345
- var SCROLL_END_MAX_MS = 1500;
1346
- var SMOOTH_SCROLL_DURATION_MS = 320;
1347
- var SCROLL_END_TARGET_EPSILON = 1;
1348
- function doScrollTo(ctx, params) {
1349
- var _a3, _b;
1350
- const state = ctx.state;
1351
- const { animated, horizontal, offset } = params;
1352
- state.scheduledWork.cancel("platformScrollCompletion");
1353
- const scroller = state.refScroller.current;
1354
- const node = scroller == null ? void 0 : scroller.getScrollableNode();
1355
- if (!scroller || !node) {
1356
- return;
1357
- }
1358
- const isAnimated = !!animated;
1359
- const isHorizontal = !!horizontal;
1360
- const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
1361
- const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
1362
- const top = isHorizontal ? 0 : offset;
1363
- scroller.scrollTo({ animated: isAnimated, x: left, y: top });
1364
- if (isAnimated) {
1365
- const target = (_b = (_a3 = scroller.getScrollEventTarget) == null ? void 0 : _a3.call(scroller)) != null ? _b : null;
1366
- listenForScrollEnd(ctx, {
1367
- readOffset: () => scroller.getCurrentScrollOffset(),
1368
- target,
1369
- targetOffset: offset
1370
- });
1371
- } else {
1372
- state.scroll = offset;
1373
- const targetToken = state.scrollingTo;
1374
- state.scheduledWork.timeout(
1375
- () => {
1376
- if (targetToken === state.scrollingTo) {
1377
- finishScrollTo(ctx);
1378
- }
1379
- },
1380
- 100,
1381
- "platformScrollCompletion"
1382
- );
1383
- }
1384
- }
1385
- function listenForScrollEnd(ctx, params) {
1386
- const { readOffset, target, targetOffset } = params;
1387
- if (!target) {
1388
- finishScrollTo(ctx);
1389
- return;
1390
- }
1391
- const supportsScrollEnd = "onscrollend" in target;
1392
- let idleTimeout;
1393
- let settled = false;
1394
- const { scheduledWork, scrollingTo: targetToken } = ctx.state;
1395
- const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
1396
- const cleanup = () => {
1397
- target.removeEventListener("scroll", onScroll2);
1398
- if (supportsScrollEnd) {
1399
- target.removeEventListener("scrollend", onScrollEnd);
1400
- }
1401
- if (idleTimeout !== void 0) {
1402
- clearTimeout(idleTimeout);
1403
- }
1404
- clearTimeout(maxTimeout);
1405
- };
1406
- const cancel = () => {
1407
- if (!settled) {
1408
- settled = true;
1409
- cleanup();
1410
- }
1411
- };
1412
- const finish = (reason) => {
1413
- if (settled) return;
1414
- if (targetToken !== ctx.state.scrollingTo) {
1415
- scheduledWork.cancel("platformScrollCompletion");
1416
- return;
1417
- }
1418
- const currentOffset = readOffset();
1419
- const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
1420
- if (reason === "scrollend" && !isNearTarget) {
1421
- return;
1422
- }
1423
- scheduledWork.cancel("platformScrollCompletion");
1424
- finishScrollTo(ctx);
1425
- };
1426
- const onScroll2 = () => {
1427
- if (idleTimeout !== void 0) {
1428
- clearTimeout(idleTimeout);
1429
- }
1430
- idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
1431
- };
1432
- const onScrollEnd = () => finish("scrollend");
1433
- target.addEventListener("scroll", onScroll2);
1434
- if (supportsScrollEnd) {
1435
- target.addEventListener("scrollend", onScrollEnd);
1436
- } else {
1437
- idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
1438
- }
1439
- scheduledWork.register("platformScrollCompletion", cancel);
1440
- }
1441
-
1442
1310
  // src/core/scrollRequestTracker.ts
1443
1311
  function getScrollRequestTracker(ctx) {
1444
1312
  if (!ctx.scrollRequestTracker) {
1445
1313
  let currentToken = 0;
1446
- const start = (resolve) => {
1314
+ let waitingToken;
1315
+ const start = (resolve, isInternal = false) => {
1447
1316
  const state = ctx.state;
1317
+ if (!isInternal && (state.maintainingScrollAtEnd || state.pendingMaintainScrollAtEnd)) {
1318
+ finishMaintainScrollAtEnd(ctx);
1319
+ }
1448
1320
  state.scheduledWork.cancel("imperativeScrollReady");
1449
1321
  const token = ++currentToken;
1450
1322
  settlePendingImperativeScroll(state);
1451
1323
  state.pendingScrollResolve = resolve;
1324
+ waitingToken = token;
1452
1325
  return token;
1453
1326
  };
1454
1327
  const runNow = (token, resolve, run) => {
@@ -1456,6 +1329,7 @@ function getScrollRequestTracker(ctx) {
1456
1329
  if (token !== currentToken) {
1457
1330
  return;
1458
1331
  }
1332
+ waitingToken = void 0;
1459
1333
  const didStartScroll = run();
1460
1334
  if (!didStartScroll || !state.scrollingTo) {
1461
1335
  if (state.pendingScrollResolve === resolve) {
@@ -1466,6 +1340,7 @@ function getScrollRequestTracker(ctx) {
1466
1340
  };
1467
1341
  ctx.scrollRequestTracker = {
1468
1342
  isCurrent: (token) => token === currentToken,
1343
+ isWaitingToRun: () => waitingToken === currentToken && !!ctx.state.pendingScrollResolve,
1469
1344
  runNow,
1470
1345
  runNowIfIdle: (run) => {
1471
1346
  const state = ctx.state;
@@ -1473,7 +1348,7 @@ function getScrollRequestTracker(ctx) {
1473
1348
  return Promise.resolve();
1474
1349
  }
1475
1350
  return new Promise((resolve) => {
1476
- const token = start(resolve);
1351
+ const token = start(resolve, true);
1477
1352
  runNow(token, resolve, run);
1478
1353
  });
1479
1354
  },
@@ -1507,87 +1382,6 @@ function requestAdjust(ctx, positionDiff, source) {
1507
1382
  }
1508
1383
  }
1509
1384
 
1510
- // src/core/doMaintainScrollAtEnd.ts
1511
- function finishMaintainScrollAtEnd(ctx) {
1512
- var _a3;
1513
- const { state } = ctx;
1514
- const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1515
- const nextPadding = getAlignItemsAtEndPadding(ctx);
1516
- state.maintainingScrollAtEnd = void 0;
1517
- state.pendingMaintainScrollAtEnd = false;
1518
- if (currentPadding !== nextPadding) {
1519
- set$(ctx, "alignItemsAtEndPadding", nextPadding);
1520
- state.scrollForNextCalculateItemsInView = void 0;
1521
- (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1522
- const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1523
- requestAdjust(ctx, nextScroll - state.scroll);
1524
- }
1525
- }
1526
- function doMaintainScrollAtEnd(ctx) {
1527
- const state = ctx.state;
1528
- const {
1529
- didContainersLayout,
1530
- didFinishInitialScroll,
1531
- pendingNativeMVCPAdjust,
1532
- props: { maintainScrollAtEnd }
1533
- } = state;
1534
- const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1535
- const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1536
- const shouldMaintainScrollAtEnd = !!((isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain) && maintainScrollAtEnd && didFinishInitialScroll);
1537
- if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1538
- state.pendingMaintainScrollAtEnd = true;
1539
- return false;
1540
- }
1541
- if (pendingNativeMVCPAdjust) {
1542
- state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1543
- return false;
1544
- }
1545
- if (shouldMaintainScrollAtEnd && didContainersLayout) {
1546
- state.pendingMaintainScrollAtEnd = false;
1547
- const contentSize = getContentSize(ctx);
1548
- if (contentSize < state.scrollLength) {
1549
- state.scroll = 0;
1550
- }
1551
- if (!state.maintainingScrollAtEnd) {
1552
- const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1553
- const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1554
- const scrollAtRequest = state.scroll;
1555
- state.maintainingScrollAtEnd = pendingState;
1556
- requestAnimationFrame(() => {
1557
- if (state.maintainingScrollAtEnd !== pendingState) {
1558
- return;
1559
- }
1560
- const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1561
- const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1562
- if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1563
- state.maintainingScrollAtEnd = activeState;
1564
- const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1565
- () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1566
- );
1567
- void scrollPromise.then(() => {
1568
- if (state.maintainingScrollAtEnd !== activeState) {
1569
- return;
1570
- }
1571
- if (state.pendingMaintainScrollAtEnd) {
1572
- state.maintainingScrollAtEnd = void 0;
1573
- doMaintainScrollAtEnd(ctx);
1574
- } else {
1575
- finishMaintainScrollAtEnd(ctx);
1576
- }
1577
- });
1578
- } else if (state.maintainingScrollAtEnd === pendingState) {
1579
- finishMaintainScrollAtEnd(ctx);
1580
- }
1581
- });
1582
- } else {
1583
- state.pendingMaintainScrollAtEnd = true;
1584
- }
1585
- return true;
1586
- }
1587
- finishMaintainScrollAtEnd(ctx);
1588
- return false;
1589
- }
1590
-
1591
1385
  // src/core/mvcp.ts
1592
1386
  var MVCP_POSITION_EPSILON = 0.1;
1593
1387
  var MVCP_ANCHOR_LOCK_TTL_MS = 300;
@@ -2025,6 +1819,271 @@ function updateScroll(ctx, newScroll, forceUpdate, options) {
2025
1819
  }
2026
1820
  }
2027
1821
 
1822
+ // src/core/doMaintainScrollAtEnd.ts
1823
+ function interruptMaintainScrollAtEnd(ctx) {
1824
+ var _a3;
1825
+ const { state } = ctx;
1826
+ const maintaining = state.maintainingScrollAtEnd;
1827
+ const cancelActiveScroll = !!(maintaining === "animated" || maintaining === "instant" || maintaining && ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isScrollToEnd));
1828
+ if (cancelActiveScroll) {
1829
+ cancelImperativeScroll(state);
1830
+ }
1831
+ if (maintaining || state.pendingMaintainScrollAtEnd) {
1832
+ finishMaintainScrollAtEnd(ctx);
1833
+ }
1834
+ return cancelActiveScroll;
1835
+ }
1836
+ function finishMaintainScrollAtEnd(ctx) {
1837
+ var _a3;
1838
+ const { state } = ctx;
1839
+ const currentPadding = peek$(ctx, "alignItemsAtEndPadding") || 0;
1840
+ const nextPadding = getAlignItemsAtEndPadding(ctx);
1841
+ state.maintainingScrollAtEnd = void 0;
1842
+ state.pendingMaintainScrollAtEnd = false;
1843
+ if (currentPadding !== nextPadding) {
1844
+ set$(ctx, "alignItemsAtEndPadding", nextPadding);
1845
+ state.scrollForNextCalculateItemsInView = void 0;
1846
+ (_a3 = state.triggerCalculateItemsInView) == null ? void 0 : _a3.call(state, { forceFullItemPositions: true });
1847
+ const nextScroll = clampScrollOffset(ctx, state.scroll + nextPadding - currentPadding);
1848
+ requestAdjust(ctx, nextScroll - state.scroll);
1849
+ }
1850
+ }
1851
+ function doMaintainScrollAtEnd(ctx) {
1852
+ var _a3, _b, _c, _d, _e;
1853
+ const state = ctx.state;
1854
+ const {
1855
+ didContainersLayout,
1856
+ didFinishInitialScroll,
1857
+ pendingNativeMVCPAdjust,
1858
+ props: { maintainScrollAtEnd }
1859
+ } = state;
1860
+ const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1861
+ const isReplayingPendingMaintain = !!state.pendingMaintainScrollAtEnd;
1862
+ const shouldMaintainScrollAtEnd = !!(!((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) && (!state.scrollingTo || state.scrollingTo.isScrollToEnd) && (isWithinMaintainScrollAtEndThreshold || isReplayingPendingMaintain || ((_b = state.scrollingTo) == null ? void 0 : _b.isScrollToEnd)) && maintainScrollAtEnd && didFinishInitialScroll);
1863
+ if (shouldMaintainScrollAtEnd && !state.scrollingTo) {
1864
+ const scroller = state.refScroller.current;
1865
+ const nativeMax = (_c = scroller == null ? void 0 : scroller.getMaxScrollOffset) == null ? void 0 : _c.call(scroller);
1866
+ const observed = (_d = scroller == null ? void 0 : scroller.getCurrentScrollOffset) == null ? void 0 : _d.call(scroller);
1867
+ if (nativeMax !== void 0 && observed !== void 0 && nativeMax < state.scroll - 1 && Math.abs(observed - nativeMax) <= 1) {
1868
+ state.pendingMaintainScrollAtEnd = true;
1869
+ state.scrollPending = observed;
1870
+ updateScroll(ctx, observed, true, { markHasScrolled: false });
1871
+ }
1872
+ }
1873
+ if (shouldMaintainScrollAtEnd && !didContainersLayout) {
1874
+ state.pendingMaintainScrollAtEnd = true;
1875
+ return false;
1876
+ }
1877
+ if (pendingNativeMVCPAdjust) {
1878
+ state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd;
1879
+ return false;
1880
+ }
1881
+ if (shouldMaintainScrollAtEnd && (state.scrollingTo || state.pendingScrollResolve)) {
1882
+ state.pendingMaintainScrollAtEnd = true;
1883
+ (_e = state.maintainingScrollAtEnd) != null ? _e : state.maintainingScrollAtEnd = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1884
+ return false;
1885
+ }
1886
+ if (shouldMaintainScrollAtEnd && didContainersLayout) {
1887
+ state.pendingMaintainScrollAtEnd = false;
1888
+ const contentSize = getContentSize(ctx);
1889
+ if (contentSize < state.scrollLength) {
1890
+ state.scroll = 0;
1891
+ }
1892
+ if (!state.maintainingScrollAtEnd) {
1893
+ const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant";
1894
+ const activeState = maintainScrollAtEnd.animated ? "animated" : "instant";
1895
+ const scrollAtRequest = state.scroll;
1896
+ state.maintainingScrollAtEnd = pendingState;
1897
+ requestAnimationFrame(() => {
1898
+ if (state.maintainingScrollAtEnd !== pendingState) {
1899
+ return;
1900
+ }
1901
+ const isStillWithinThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold");
1902
+ const didScrollSinceRequest = state.scroll !== scrollAtRequest;
1903
+ if (isReplayingPendingMaintain || isStillWithinThreshold || !didScrollSinceRequest) {
1904
+ if (state.scrollingTo || state.pendingScrollResolve) {
1905
+ state.pendingMaintainScrollAtEnd = true;
1906
+ return;
1907
+ }
1908
+ state.maintainingScrollAtEnd = activeState;
1909
+ const scrollPromise = getScrollRequestTracker(ctx).runNowIfIdle(
1910
+ () => ctx.scrollToEnd({ animated: maintainScrollAtEnd.animated })
1911
+ );
1912
+ void scrollPromise.then(() => {
1913
+ if (state.maintainingScrollAtEnd !== activeState) {
1914
+ return;
1915
+ }
1916
+ if (state.pendingMaintainScrollAtEnd) {
1917
+ state.maintainingScrollAtEnd = void 0;
1918
+ doMaintainScrollAtEnd(ctx);
1919
+ } else {
1920
+ finishMaintainScrollAtEnd(ctx);
1921
+ }
1922
+ });
1923
+ } else if (state.maintainingScrollAtEnd === pendingState) {
1924
+ finishMaintainScrollAtEnd(ctx);
1925
+ }
1926
+ });
1927
+ } else {
1928
+ state.pendingMaintainScrollAtEnd = true;
1929
+ }
1930
+ return true;
1931
+ }
1932
+ finishMaintainScrollAtEnd(ctx);
1933
+ return false;
1934
+ }
1935
+
1936
+ // src/core/finishScrollTo.ts
1937
+ function finishScrollTo(ctx) {
1938
+ var _a3, _b, _c;
1939
+ const state = ctx.state;
1940
+ if (state == null ? void 0 : state.scrollingTo) {
1941
+ cancelScrollCompletionChecks(state);
1942
+ const resolvePendingScroll = ((_a3 = ctx.scrollRequestTracker) == null ? void 0 : _a3.isWaitingToRun()) ? void 0 : state.pendingScrollResolve;
1943
+ if (resolvePendingScroll) {
1944
+ state.pendingScrollResolve = void 0;
1945
+ }
1946
+ const scrollingTo = state.scrollingTo;
1947
+ state.scrollHistory.length = 0;
1948
+ state.scrollingTo = void 0;
1949
+ state.scrollTargetPinnedRange = void 0;
1950
+ if (state.pendingTotalSize !== void 0) {
1951
+ addTotalSize(ctx, null, state.pendingTotalSize);
1952
+ }
1953
+ {
1954
+ state.scrollAdjustHandler.commitPendingAdjust(scrollingTo);
1955
+ }
1956
+ if (scrollingTo.isInitialScroll || state.initialScroll) {
1957
+ const isOffsetSession = ((_b = state.initialScrollSession) == null ? void 0 : _b.kind) === "offset";
1958
+ const shouldPreserveResizeTarget = !!scrollingTo.isInitialScroll && !state.clearPreservedInitialScrollOnNextFinish && state.props.data.length > 0 && ((_c = state.initialScroll) == null ? void 0 : _c.viewPosition) === 1;
1959
+ finishInitialScroll(ctx, {
1960
+ onFinished: () => {
1961
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1962
+ },
1963
+ preserveTarget: isOffsetSession && state.props.data.length === 0 || shouldPreserveResizeTarget,
1964
+ recalculateItems: true,
1965
+ schedulePreservedTargetClear: shouldPreserveResizeTarget,
1966
+ syncObservedOffset: isOffsetSession,
1967
+ waitForCompletionFrame: !!scrollingTo.waitForInitialScrollCompletionFrame
1968
+ });
1969
+ return;
1970
+ }
1971
+ recalculateSettledScroll(ctx);
1972
+ resolvePendingScroll == null ? void 0 : resolvePendingScroll();
1973
+ if (state.pendingMaintainScrollAtEnd) {
1974
+ state.maintainingScrollAtEnd = void 0;
1975
+ doMaintainScrollAtEnd(ctx);
1976
+ }
1977
+ }
1978
+ }
1979
+
1980
+ // src/core/doScrollTo.ts
1981
+ var SCROLL_END_IDLE_MS = 80;
1982
+ var SCROLL_END_MAX_MS = 1500;
1983
+ var SMOOTH_SCROLL_DURATION_MS = 320;
1984
+ var SCROLL_END_TARGET_EPSILON = 1;
1985
+ function doScrollTo(ctx, params) {
1986
+ var _a3, _b, _c, _d, _e, _f;
1987
+ const state = ctx.state;
1988
+ const { animated, horizontal, offset } = params;
1989
+ state.scheduledWork.cancel("platformScrollCompletion");
1990
+ const scroller = state.refScroller.current;
1991
+ const node = scroller == null ? void 0 : scroller.getScrollableNode();
1992
+ if (!scroller || !node) {
1993
+ return;
1994
+ }
1995
+ const isAnimated = !!animated;
1996
+ const isHorizontal = !!horizontal;
1997
+ const contentSize = isHorizontal ? getContentSize(ctx) : void 0;
1998
+ const left = isHorizontal ? toNativeHorizontalOffset(state, offset, contentSize) : 0;
1999
+ const top = isHorizontal ? 0 : offset;
2000
+ scroller.scrollTo({ animated: isAnimated, x: left, y: top });
2001
+ if (isAnimated) {
2002
+ const targetOffset = Math.max(0, Math.min(offset, (_b = (_a3 = scroller.getMaxScrollOffset) == null ? void 0 : _a3.call(scroller)) != null ? _b : offset));
2003
+ if (Math.abs(((_d = (_c = scroller.getCurrentScrollOffset) == null ? void 0 : _c.call(scroller)) != null ? _d : Number.NaN) - targetOffset) <= SCROLL_END_TARGET_EPSILON) {
2004
+ const targetToken = state.scrollingTo;
2005
+ state.scheduledWork.frame(() => {
2006
+ if (targetToken === state.scrollingTo) finishScrollTo(ctx);
2007
+ }, "platformScrollCompletion");
2008
+ return;
2009
+ }
2010
+ const target = (_f = (_e = scroller.getScrollEventTarget) == null ? void 0 : _e.call(scroller)) != null ? _f : null;
2011
+ listenForScrollEnd(ctx, {
2012
+ readOffset: () => scroller.getCurrentScrollOffset(),
2013
+ target,
2014
+ targetOffset
2015
+ });
2016
+ } else {
2017
+ state.scroll = offset;
2018
+ const targetToken = state.scrollingTo;
2019
+ state.scheduledWork.timeout(
2020
+ () => {
2021
+ if (targetToken === state.scrollingTo) {
2022
+ finishScrollTo(ctx);
2023
+ }
2024
+ },
2025
+ 100,
2026
+ "platformScrollCompletion"
2027
+ );
2028
+ }
2029
+ }
2030
+ function listenForScrollEnd(ctx, params) {
2031
+ const { readOffset, target, targetOffset } = params;
2032
+ if (!target) {
2033
+ finishScrollTo(ctx);
2034
+ return;
2035
+ }
2036
+ const supportsScrollEnd = "onscrollend" in target;
2037
+ let idleTimeout;
2038
+ let settled = false;
2039
+ const { scheduledWork, scrollingTo: targetToken } = ctx.state;
2040
+ const maxTimeout = setTimeout(() => finish("max"), SCROLL_END_MAX_MS);
2041
+ const cleanup = () => {
2042
+ target.removeEventListener("scroll", onScroll2);
2043
+ if (supportsScrollEnd) {
2044
+ target.removeEventListener("scrollend", onScrollEnd);
2045
+ }
2046
+ if (idleTimeout !== void 0) {
2047
+ clearTimeout(idleTimeout);
2048
+ }
2049
+ clearTimeout(maxTimeout);
2050
+ };
2051
+ const cancel = () => {
2052
+ if (!settled) {
2053
+ settled = true;
2054
+ cleanup();
2055
+ }
2056
+ };
2057
+ const finish = (reason) => {
2058
+ if (settled) return;
2059
+ if (targetToken !== ctx.state.scrollingTo) {
2060
+ scheduledWork.cancel("platformScrollCompletion");
2061
+ return;
2062
+ }
2063
+ const currentOffset = readOffset();
2064
+ const isNearTarget = Math.abs(currentOffset - targetOffset) <= SCROLL_END_TARGET_EPSILON;
2065
+ if (reason === "scrollend" && !isNearTarget) {
2066
+ return;
2067
+ }
2068
+ scheduledWork.cancel("platformScrollCompletion");
2069
+ finishScrollTo(ctx);
2070
+ };
2071
+ const onScroll2 = () => {
2072
+ if (idleTimeout !== void 0) {
2073
+ clearTimeout(idleTimeout);
2074
+ }
2075
+ idleTimeout = setTimeout(() => finish("idle"), SCROLL_END_IDLE_MS);
2076
+ };
2077
+ const onScrollEnd = () => finish("scrollend");
2078
+ target.addEventListener("scroll", onScroll2);
2079
+ if (supportsScrollEnd) {
2080
+ target.addEventListener("scrollend", onScrollEnd);
2081
+ } else {
2082
+ idleTimeout = setTimeout(() => finish("idle"), SMOOTH_SCROLL_DURATION_MS);
2083
+ }
2084
+ scheduledWork.register("platformScrollCompletion", cancel);
2085
+ }
2086
+
2028
2087
  // src/core/scrollTo.ts
2029
2088
  function getAverageSizeSnapshot(state) {
2030
2089
  if (Object.keys(state.averageSizes).length === 0) {
@@ -3220,6 +3279,11 @@ function handleInitialScrollDataChange(ctx, options) {
3220
3279
  const state = ctx.state;
3221
3280
  const previousInitialScrollDataLength = (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.previousDataLength) != null ? _b : 0;
3222
3281
  const shouldUseLatestInitialScroll = dataLength > 0 && (!state.hasHadNonEmptyData || didStartFreshData);
3282
+ if (didStartFreshData) {
3283
+ cancelImperativeScroll(state);
3284
+ state.maintainingScrollAtEnd = void 0;
3285
+ state.pendingMaintainScrollAtEnd = false;
3286
+ }
3223
3287
  if (dataLength > 0) {
3224
3288
  state.hasHadNonEmptyData = true;
3225
3289
  }
@@ -4764,6 +4828,7 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4764
4828
  const nextAnchorIndex = anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex;
4765
4829
  let nextAnchorKey;
4766
4830
  let isReady = true;
4831
+ let canUpdateSize = true;
4767
4832
  let nextSize = 0;
4768
4833
  if (anchoredEndSpace) {
4769
4834
  const { anchorIndex, anchorMaxSize, anchorOffset = 0 } = anchoredEndSpace;
@@ -4785,25 +4850,30 @@ function maybeUpdateAnchoredEndSpace(ctx) {
4785
4850
  contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap);
4786
4851
  contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props);
4787
4852
  isReady = !hasUnknownTailSize;
4788
- nextSize = hasUnknownTailSize ? previousSize || 0 : Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4853
+ const knownSizeBound = Math.max(0, state.scrollLength - contentBelowAnchor - anchorOffset);
4854
+ nextSize = hasUnknownTailSize ? Math.min(previousSize || 0, knownSizeBound) : knownSizeBound;
4789
4855
  } else if (anchorIndex >= 0) {
4790
4856
  isReady = false;
4857
+ canUpdateSize = false;
4791
4858
  }
4792
4859
  }
4793
4860
  const didSizeChange = previousSize !== nextSize && (previousSize !== void 0 || anchoredEndSpace !== void 0);
4794
4861
  const didEffectiveSizeChange = (previousSize || 0) !== nextSize;
4862
+ const canApplySizeChange = canUpdateSize && (isReady || previousSize !== void 0);
4795
4863
  const didReadyAnchorChange = previousReadyAnchorIndex !== nextAnchorIndex || previousReadyAnchorKey !== nextAnchorKey;
4796
- if (isReady && (didSizeChange || didReadyAnchorChange)) {
4864
+ const didBecomeReady = isReady && state.anchoredEndSpacePendingReady;
4865
+ state.anchoredEndSpacePendingReady = !isReady;
4866
+ if (canApplySizeChange && didSizeChange) {
4867
+ set$(ctx, "anchoredEndSpaceSize", nextSize);
4868
+ (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4869
+ }
4870
+ if (canApplySizeChange && didEffectiveSizeChange) {
4871
+ updateContentMetricsState(ctx);
4872
+ updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4873
+ }
4874
+ if (isReady && (didSizeChange || didReadyAnchorChange || didBecomeReady)) {
4797
4875
  state.anchoredEndSpaceReadyAnchorIndex = nextAnchorIndex;
4798
4876
  state.anchoredEndSpaceReadyAnchorKey = nextAnchorKey;
4799
- if (didSizeChange) {
4800
- set$(ctx, "anchoredEndSpaceSize", nextSize);
4801
- (_a3 = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onSizeChanged) == null ? void 0 : _a3.call(anchoredEndSpace, nextSize);
4802
- }
4803
- if (didEffectiveSizeChange) {
4804
- updateContentMetricsState(ctx);
4805
- updateScroll(ctx, state.scroll, true, { markHasScrolled: false });
4806
- }
4807
4877
  (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize });
4808
4878
  }
4809
4879
  return nextSize;
@@ -5803,7 +5873,9 @@ function sortDOMElements(container, indexByElement) {
5803
5873
  break;
5804
5874
  }
5805
5875
  }
5806
- if (nextStableElement) {
5876
+ if ("moveBefore" in container && typeof container.moveBefore === "function" && container.isConnected) {
5877
+ container.moveBefore(element, nextStableElement);
5878
+ } else if (nextStableElement) {
5807
5879
  container.insertBefore(element, nextStableElement);
5808
5880
  } else {
5809
5881
  container.appendChild(element);
@@ -5850,13 +5922,18 @@ function findLIS(arr) {
5850
5922
  // src/hooks/useDOMOrder.ts
5851
5923
  function useDOMOrder(ref) {
5852
5924
  const ctx = useStateContext();
5853
- const debounceRef = React3.useRef(void 0);
5854
5925
  React3.useEffect(() => {
5926
+ let timeoutId;
5927
+ let lastUpdateTime = 0;
5928
+ let delay = 500;
5855
5929
  const unsubscribe = listen$(ctx, "lastPositionUpdate", () => {
5856
- if (debounceRef.current !== void 0) {
5857
- clearTimeout(debounceRef.current);
5858
- }
5859
- debounceRef.current = setTimeout(() => {
5930
+ const container = ref.current;
5931
+ const now = Date.now();
5932
+ delay = container && "moveBefore" in container && typeof container.moveBefore === "function" && container.isConnected && (now - lastUpdateTime >= 1e3 || timeoutId !== void 0 && delay === 0) && now - ctx.state.scrollTime >= 500 ? 0 : 500;
5933
+ lastUpdateTime = now;
5934
+ clearTimeout(timeoutId);
5935
+ timeoutId = setTimeout(() => {
5936
+ timeoutId = void 0;
5860
5937
  const parent = ref.current;
5861
5938
  if (parent) {
5862
5939
  const indexByElement = /* @__PURE__ */ new Map();
@@ -5869,14 +5946,11 @@ function useDOMOrder(ref) {
5869
5946
  }
5870
5947
  sortDOMElements(parent, indexByElement);
5871
5948
  }
5872
- debounceRef.current = void 0;
5873
- }, 500);
5949
+ }, delay);
5874
5950
  });
5875
5951
  return () => {
5876
5952
  unsubscribe();
5877
- if (debounceRef.current !== void 0) {
5878
- clearTimeout(debounceRef.current);
5879
- }
5953
+ clearTimeout(timeoutId);
5880
5954
  };
5881
5955
  }, [ctx]);
5882
5956
  }
@@ -6246,6 +6320,7 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6246
6320
  return (_a4 = scrollRef.current) == null ? void 0 : _a4.getBoundingClientRect();
6247
6321
  },
6248
6322
  getCurrentScrollOffset,
6323
+ getMaxScrollOffset,
6249
6324
  getScrollableNode: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
6250
6325
  getScrollEventTarget: () => getScrollTarget(),
6251
6326
  getScrollResponder: () => resolveScrollableNode(scrollRef.current, isWindowScroll),
@@ -6351,6 +6426,22 @@ var ListComponentScrollView = React3.forwardRef(function ListComponentScrollView
6351
6426
  scrollEventCoalescer.cancel();
6352
6427
  };
6353
6428
  }, [emitScrollEnd, getScrollTarget, handleScroll, scrollEventCoalescer]);
6429
+ React3.useEffect(() => {
6430
+ const target = getScrollTarget();
6431
+ if (!target) return;
6432
+ const onWheel = (nativeEvent) => {
6433
+ const event = nativeEvent;
6434
+ const delta = horizontal ? event.deltaX || (event.shiftKey ? event.deltaY : 0) : event.deltaY;
6435
+ if (delta < 0 && !event.ctrlKey && !event.defaultPrevented && (ctx.state.maintainingScrollAtEnd || ctx.state.pendingMaintainScrollAtEnd)) {
6436
+ const offset = getCurrentScrollOffset();
6437
+ if (interruptMaintainScrollAtEnd(ctx)) {
6438
+ scrollToLocalOffset(offset, false);
6439
+ }
6440
+ }
6441
+ };
6442
+ target.addEventListener("wheel", onWheel, { passive: true });
6443
+ return () => target.removeEventListener("wheel", onWheel);
6444
+ }, [ctx, getCurrentScrollOffset, getScrollTarget, horizontal, scrollToLocalOffset]);
6354
6445
  React3.useEffect(() => {
6355
6446
  const doScroll = () => {
6356
6447
  if (contentOffset) {
@@ -6560,8 +6651,11 @@ function ScrollAdjust() {
6560
6651
  window.getComputedStyle(contentNode)[axis.paddingEndProp]
6561
6652
  );
6562
6653
  const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`;
6563
- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd };
6564
6654
  contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd;
6655
+ temporaryPaddingRef.current = {
6656
+ baseline: baselinePaddingEnd,
6657
+ value: contentNode.style[axis.paddingEndProp]
6658
+ };
6565
6659
  void contentNode.offsetHeight;
6566
6660
  scrollBy();
6567
6661
  if (resetPaddingRafRef.current !== void 0) {
@@ -7299,6 +7393,9 @@ function scrollToEnd(ctx, options) {
7299
7393
  viewOffset: -paddingBottom - footerSize + ((options == null ? void 0 : options.viewOffset) || 0),
7300
7394
  viewPosition: 1
7301
7395
  });
7396
+ if (state.scrollingTo) {
7397
+ state.scrollingTo.isScrollToEnd = true;
7398
+ }
7302
7399
  return true;
7303
7400
  }
7304
7401
 
@@ -8016,6 +8113,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8016
8113
  endReachedSnapshot: void 0,
8017
8114
  firstFullyOnScreenIndex: -1,
8018
8115
  freshDataTransitionEpoch: 0,
8116
+ handledDataChangeEpoch: 0,
8117
+ handledFreshDataTransitionEpoch: 0,
8019
8118
  hasHadNonEmptyData: dataProp.length > 0,
8020
8119
  idCache: [],
8021
8120
  idsInView: [],
@@ -8075,8 +8174,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8075
8174
  refState.current = ctx.state;
8076
8175
  }
8077
8176
  const state = refState.current;
8078
- const handledDataChangeEpochRef = React3.useRef(state.dataChangeEpoch);
8079
- const handledFreshDataTransitionEpochRef = React3.useRef(state.freshDataTransitionEpoch);
8080
8177
  const isFirstLocal = state.isFirst;
8081
8178
  const previousAdaptiveRender = state.props.adaptiveRender;
8082
8179
  const previousHideItemsUntilMeasured = state.props.hideItemsUntilMeasured;
@@ -8226,10 +8323,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8226
8323
  useDevChecks(props);
8227
8324
  }
8228
8325
  React3.useLayoutEffect(() => {
8229
- const didDataChange = handledDataChangeEpochRef.current !== dataChangeEpoch;
8230
- const didStartFreshData = handledFreshDataTransitionEpochRef.current !== freshDataTransitionEpoch;
8231
- handledDataChangeEpochRef.current = dataChangeEpoch;
8232
- handledFreshDataTransitionEpochRef.current = freshDataTransitionEpoch;
8326
+ const didDataChange = state.handledDataChangeEpoch !== dataChangeEpoch;
8327
+ const didStartFreshData = state.handledFreshDataTransitionEpoch !== freshDataTransitionEpoch;
8328
+ state.handledDataChangeEpoch = dataChangeEpoch;
8329
+ state.handledFreshDataTransitionEpoch = freshDataTransitionEpoch;
8233
8330
  if (didStartFreshData) {
8234
8331
  resetInitialRenderState(ctx, {
8235
8332
  resetInitialScroll: !!initialScrollProp,
@@ -8430,13 +8527,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
8430
8527
  onScroll: (event) => onScroll(ctx, event),
8431
8528
  onScrollBeginDrag: (event) => {
8432
8529
  var _a4, _b2;
8433
- const maintainingScrollAtEnd = state.maintainingScrollAtEnd;
8434
- if (maintainingScrollAtEnd === "animated" || maintainingScrollAtEnd === "instant") {
8435
- cancelImperativeScroll(state);
8436
- }
8437
- if (maintainingScrollAtEnd) {
8438
- finishMaintainScrollAtEnd(ctx);
8439
- }
8530
+ interruptMaintainScrollAtEnd(ctx);
8440
8531
  prepareReachedEdgeForNextUserScroll(ctx);
8441
8532
  (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event);
8442
8533
  },