@legendapp/list 3.0.0-beta.35 → 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/react-native.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as React2 from 'react';
2
2
  import React2__default, { useReducer, useEffect, createContext, useRef, useState, useMemo, useCallback, useLayoutEffect, useImperativeHandle, memo, forwardRef, useContext } from 'react';
3
- import { Animated, View as View$1, Text as Text$1, Platform, StyleSheet as StyleSheet$1, RefreshControl, Dimensions, unstable_batchedUpdates } from 'react-native';
3
+ import * as ReactNative from 'react-native';
4
+ import { Animated, View as View$1, Text as Text$1, Platform, StyleSheet as StyleSheet$1, RefreshControl, Dimensions } from 'react-native';
4
5
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
5
6
 
6
7
  // src/components/LegendList.tsx
@@ -814,6 +815,7 @@ var Containers = typedMemo(function Containers2({
814
815
  updateItemSize: updateItemSize2,
815
816
  getRenderedItem: getRenderedItem2
816
817
  }) {
818
+ var _a3;
817
819
  const ctx = useStateContext();
818
820
  const columnWrapperStyle = ctx.columnWrapperStyle;
819
821
  const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
@@ -821,11 +823,13 @@ var Containers = typedMemo(function Containers2({
821
823
  // Use a microtask if increasing the size significantly, otherwise use a timeout
822
824
  // If this is the initial scroll, we don't want to delay because we want to update the size immediately
823
825
  delay: (value, prevValue) => {
824
- var _a3;
825
- return !((_a3 = ctx.state) == null ? void 0 : _a3.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
826
+ var _a4;
827
+ return !((_a4 = ctx.state) == null ? void 0 : _a4.initialScroll) ? !prevValue || value - prevValue > 20 ? 0 : 200 : void 0;
826
828
  }
827
829
  });
828
- const animOpacity = waitForInitialLayout && !IsNewArchitecture ? useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 }) : void 0;
830
+ const readyToRenderOpacity = useValue$("readyToRender", { getValue: (value) => value ? 1 : 0 });
831
+ const shouldWaitForReadyToRender = !!waitForInitialLayout && (!IsNewArchitecture || !!((_a3 = ctx.state) == null ? void 0 : _a3.initialScroll));
832
+ const animOpacity = shouldWaitForReadyToRender ? readyToRenderOpacity : void 0;
829
833
  const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
830
834
  const containers = [];
831
835
  for (let i = 0; i < numContainers; i++) {
@@ -918,8 +922,8 @@ var ListComponent = typedMemo2(function ListComponent2({
918
922
  updateItemSize: updateItemSize2,
919
923
  refScrollView,
920
924
  renderScrollComponent,
925
+ onLayoutFooter,
921
926
  scrollAdjustHandler,
922
- onLayoutHeader,
923
927
  snapToIndices,
924
928
  stickyHeaderConfig,
925
929
  stickyHeaderIndices,
@@ -943,6 +947,21 @@ var ListComponent = typedMemo2(function ListComponent2({
943
947
  set$(ctx, "footerSize", 0);
944
948
  }
945
949
  }, [ListHeaderComponent, ListFooterComponent, ctx]);
950
+ const onLayoutHeader = useCallback(
951
+ (rect) => {
952
+ const size = rect[horizontal ? "width" : "height"];
953
+ set$(ctx, "headerSize", size);
954
+ },
955
+ [ctx, horizontal]
956
+ );
957
+ const onLayoutFooterInternal = useCallback(
958
+ (rect, fromLayoutEffect) => {
959
+ const size = rect[horizontal ? "width" : "height"];
960
+ set$(ctx, "footerSize", size);
961
+ onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect);
962
+ },
963
+ [ctx, horizontal, onLayoutFooter]
964
+ );
946
965
  return /* @__PURE__ */ React2.createElement(
947
966
  SnapOrScroll,
948
967
  {
@@ -978,17 +997,7 @@ var ListComponent = typedMemo2(function ListComponent2({
978
997
  waitForInitialLayout
979
998
  }
980
999
  ),
981
- ListFooterComponent && /* @__PURE__ */ React2.createElement(
982
- LayoutView,
983
- {
984
- onLayoutChange: (layout) => {
985
- const size = layout[horizontal ? "width" : "height"];
986
- set$(ctx, "footerSize", size);
987
- },
988
- style: ListFooterComponentStyle
989
- },
990
- getComponent(ListFooterComponent)
991
- ),
1000
+ ListFooterComponent && /* @__PURE__ */ React2.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)),
992
1001
  IS_DEV && ENABLE_DEVMODE
993
1002
  );
994
1003
  });
@@ -996,19 +1005,12 @@ var ListComponent = typedMemo2(function ListComponent2({
996
1005
  // src/core/calculateOffsetForIndex.ts
997
1006
  function calculateOffsetForIndex(ctx, index) {
998
1007
  const state = ctx.state;
999
- let position = 0;
1000
- if (index !== void 0) {
1001
- position = state.positions[index] || 0;
1002
- const paddingTop = peek$(ctx, "stylePaddingTop");
1003
- if (paddingTop) {
1004
- position += paddingTop;
1005
- }
1006
- const headerSize = peek$(ctx, "headerSize");
1007
- if (headerSize) {
1008
- position += headerSize;
1009
- }
1010
- }
1011
- return position;
1008
+ return index !== void 0 ? state.positions[index] || 0 : 0;
1009
+ }
1010
+
1011
+ // src/core/getTopOffsetAdjustment.ts
1012
+ function getTopOffsetAdjustment(ctx) {
1013
+ return (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
1012
1014
  }
1013
1015
 
1014
1016
  // src/utils/getId.ts
@@ -1116,10 +1118,20 @@ function calculateOffsetWithOffsetPosition(ctx, offsetParam, params) {
1116
1118
  if (viewOffset) {
1117
1119
  offset -= viewOffset;
1118
1120
  }
1121
+ if (index !== void 0) {
1122
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1123
+ if (topOffsetAdjustment) {
1124
+ offset += topOffsetAdjustment;
1125
+ }
1126
+ }
1119
1127
  if (viewPosition !== void 0 && index !== void 0) {
1120
1128
  const itemSize = getItemSize(ctx, getId(state, index), index, state.props.data[index]);
1121
1129
  const trailingInset = getContentInsetEnd(state);
1122
1130
  offset -= viewPosition * (state.scrollLength - trailingInset - itemSize);
1131
+ if (index === state.props.data.length - 1) {
1132
+ const footerSize = peek$(ctx, "footerSize") || 0;
1133
+ offset += footerSize;
1134
+ }
1123
1135
  }
1124
1136
  return offset;
1125
1137
  }
@@ -1320,6 +1332,7 @@ function finishScrollTo(ctx) {
1320
1332
  state.initialScroll = void 0;
1321
1333
  state.initialAnchor = void 0;
1322
1334
  state.scrollingTo = void 0;
1335
+ state.stableTarget = void 0;
1323
1336
  if (state.pendingTotalSize !== void 0) {
1324
1337
  addTotalSize(ctx, null, state.pendingTotalSize);
1325
1338
  }
@@ -1336,6 +1349,14 @@ function finishScrollTo(ctx) {
1336
1349
  }
1337
1350
 
1338
1351
  // src/core/checkFinishedScroll.ts
1352
+ function getCurrentTargetOffset(ctx, scrollingTo) {
1353
+ if (scrollingTo.index !== void 0) {
1354
+ const baseOffset = calculateOffsetForIndex(ctx, scrollingTo.index);
1355
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, scrollingTo);
1356
+ return clampScrollOffset(ctx, resolvedOffset, scrollingTo);
1357
+ }
1358
+ return clampScrollOffset(ctx, scrollingTo.offset, scrollingTo);
1359
+ }
1339
1360
  function checkFinishedScroll(ctx) {
1340
1361
  ctx.state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
1341
1362
  }
@@ -1345,20 +1366,30 @@ function checkFinishedScrollFrame(ctx) {
1345
1366
  const { state } = ctx;
1346
1367
  state.animFrameCheckFinishedScroll = void 0;
1347
1368
  const scroll = state.scrollPending;
1348
- const adjust = state.scrollAdjustHandler.getAdjust();
1349
- const clampedTargetOffset = clampScrollOffset(
1350
- ctx,
1351
- scrollingTo.offset - (scrollingTo.viewOffset || 0),
1352
- scrollingTo
1353
- );
1369
+ const clampedTargetOffset = getCurrentTargetOffset(ctx, scrollingTo);
1370
+ if (Math.abs(scrollingTo.offset - clampedTargetOffset) >= 1) {
1371
+ state.scrollingTo = { ...scrollingTo, offset: clampedTargetOffset };
1372
+ }
1354
1373
  const maxOffset = clampScrollOffset(ctx, scroll, scrollingTo);
1355
1374
  const diff1 = Math.abs(scroll - clampedTargetOffset);
1356
- const diff2 = Math.abs(diff1 - adjust);
1357
1375
  const isNotOverscrolled = Math.abs(scroll - maxOffset) < 1;
1358
- const isAtTarget = diff1 < 1 || !scrollingTo.animated && diff2 < 1;
1376
+ const isAtTarget = diff1 < 1;
1377
+ const previousStableTarget = state.stableTarget;
1378
+ const hasStableTargetFrame = !!previousStableTarget && Math.abs(previousStableTarget.target - clampedTargetOffset) < 1 && Math.abs(previousStableTarget.scroll - scroll) < 1;
1379
+ if (isAtTarget && !hasStableTargetFrame) {
1380
+ state.stableTarget = { scroll, target: clampedTargetOffset };
1381
+ state.animFrameCheckFinishedScroll = requestAnimationFrame(() => checkFinishedScrollFrame(ctx));
1382
+ return;
1383
+ }
1384
+ if (!isAtTarget) {
1385
+ state.stableTarget = void 0;
1386
+ }
1359
1387
  if (isNotOverscrolled && isAtTarget) {
1388
+ state.stableTarget = void 0;
1360
1389
  finishScrollTo(ctx);
1361
1390
  }
1391
+ } else {
1392
+ ctx.state.stableTarget = void 0;
1362
1393
  }
1363
1394
  }
1364
1395
  function checkFinishedScrollFallback(ctx) {
@@ -1424,8 +1455,9 @@ function scrollTo(ctx, params) {
1424
1455
  let offset = precomputedWithViewOffset ? scrollTargetOffset : calculateOffsetWithOffsetPosition(ctx, scrollTargetOffset, scrollTarget);
1425
1456
  offset = clampScrollOffset(ctx, offset, scrollTarget);
1426
1457
  state.scrollHistory.length = 0;
1458
+ state.stableTarget = void 0;
1427
1459
  if (!noScrollingTo) {
1428
- state.scrollingTo = scrollTarget;
1460
+ state.scrollingTo = { ...scrollTarget, offset };
1429
1461
  }
1430
1462
  state.scrollPending = offset;
1431
1463
  if (forceScroll || !isInitialScroll || Platform2.OS === "android") {
@@ -1573,7 +1605,8 @@ function ensureInitialAnchor(ctx) {
1573
1605
  return;
1574
1606
  }
1575
1607
  const availableSpace = Math.max(0, scrollLength - size);
1576
- const desiredOffset = calculateOffsetForIndex(ctx, anchor.index) - ((_a3 = anchor.viewOffset) != null ? _a3 : 0) - ((_b = anchor.viewPosition) != null ? _b : 0) * availableSpace;
1608
+ const topOffsetAdjustment = getTopOffsetAdjustment(ctx);
1609
+ const desiredOffset = calculateOffsetForIndex(ctx, anchor.index) + topOffsetAdjustment - ((_a3 = anchor.viewOffset) != null ? _a3 : 0) - ((_b = anchor.viewPosition) != null ? _b : 0) * availableSpace;
1577
1610
  const clampedDesiredOffset = clampScrollOffset(ctx, desiredOffset, anchor);
1578
1611
  const delta = clampedDesiredOffset - scroll;
1579
1612
  if (Math.abs(delta) <= INITIAL_ANCHOR_TOLERANCE) {
@@ -2290,6 +2323,8 @@ function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
2290
2323
  const cb = ctx.mapViewabilityCallbacks.get(key);
2291
2324
  cb == null ? void 0 : cb(viewToken);
2292
2325
  }
2326
+ var unstableBatchedUpdates = ReactNative.unstable_batchedUpdates;
2327
+ var batchedUpdates = typeof unstableBatchedUpdates === "function" ? unstableBatchedUpdates : (fn) => fn();
2293
2328
 
2294
2329
  // src/utils/checkAllSizesKnown.ts
2295
2330
  function isNullOrUndefined2(value) {
@@ -2542,7 +2577,7 @@ function handleStickyRecycling(ctx, stickyArray, scroll, drawDistance, currentSt
2542
2577
  }
2543
2578
  function calculateItemsInView(ctx, params = {}) {
2544
2579
  const state = ctx.state;
2545
- unstable_batchedUpdates(() => {
2580
+ batchedUpdates(() => {
2546
2581
  var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2547
2582
  const {
2548
2583
  columns,
@@ -3893,7 +3928,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
3893
3928
  const maintainVisibleContentPositionConfig = normalizeMaintainVisibleContentPosition(
3894
3929
  maintainVisibleContentPositionProp
3895
3930
  );
3896
- const [renderNum, setRenderNum] = useState(0);
3897
3931
  const initialScrollProp = initialScrollAtEnd ? { index: Math.max(0, dataProp.length - 1), viewOffset: -stylePaddingBottomState, viewPosition: 1 } : initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? {
3898
3932
  index: initialScrollIndexProp.index || 0,
3899
3933
  viewOffset: initialScrollIndexProp.viewOffset || (initialScrollIndexProp.viewPosition === 1 ? -stylePaddingBottomState : 0),
@@ -4098,6 +4132,11 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4098
4132
  true
4099
4133
  );
4100
4134
  }
4135
+ const resolveInitialScrollOffset = useCallback((initialScroll) => {
4136
+ const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4137
+ const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4138
+ return clampScrollOffset(ctx, resolvedOffset, initialScroll);
4139
+ }, []);
4101
4140
  const initialContentOffset = useMemo(() => {
4102
4141
  var _a4;
4103
4142
  let value;
@@ -4115,9 +4154,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4115
4154
  if (initialScroll.contentOffset !== void 0) {
4116
4155
  value = initialScroll.contentOffset;
4117
4156
  } else {
4118
- const baseOffset = initialScroll.index !== void 0 ? calculateOffsetForIndex(ctx, initialScroll.index) : 0;
4119
- const resolvedOffset = calculateOffsetWithOffsetPosition(ctx, baseOffset, initialScroll);
4120
- const clampedOffset = clampScrollOffset(ctx, resolvedOffset, initialScroll);
4157
+ const clampedOffset = resolveInitialScrollOffset(initialScroll);
4121
4158
  const updatedInitialScroll = { ...initialScroll, contentOffset: clampedOffset };
4122
4159
  refState.current.initialScroll = updatedInitialScroll;
4123
4160
  state.initialScroll = updatedInitialScroll;
@@ -4131,7 +4168,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4131
4168
  setInitialRenderState(ctx, { didInitialScroll: true });
4132
4169
  }
4133
4170
  return value;
4134
- }, [renderNum]);
4171
+ }, []);
4135
4172
  if (isFirstLocal || didDataChangeLocal || numColumnsProp !== peek$(ctx, "numColumns")) {
4136
4173
  refState.current.lastBatchingAction = Date.now();
4137
4174
  if (!keyExtractorProp && !isFirstLocal && didDataChangeLocal) {
@@ -4145,32 +4182,45 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4145
4182
  set$(ctx, "totalSize", 0);
4146
4183
  }
4147
4184
  }
4148
- const onLayoutHeader = useCallback((rect, fromLayoutEffect) => {
4149
- const { initialScroll } = refState.current;
4150
- const size = rect[horizontal ? "width" : "height"];
4151
- set$(ctx, "headerSize", size);
4152
- if ((initialScroll == null ? void 0 : initialScroll.index) !== void 0) {
4153
- if (IsNewArchitecture && Platform2.OS !== "android") {
4154
- if (fromLayoutEffect) {
4155
- setRenderNum((v) => v + 1);
4156
- }
4157
- } else {
4158
- setTimeout(doInitialScroll, 17);
4159
- }
4160
- }
4161
- }, []);
4162
4185
  const doInitialScroll = useCallback(() => {
4163
4186
  const { initialScroll, didFinishInitialScroll, queuedInitialLayout, scrollingTo } = state;
4164
4187
  if (initialScroll && !queuedInitialLayout && !didFinishInitialScroll && !scrollingTo) {
4188
+ const offset = resolveInitialScrollOffset(initialScroll);
4189
+ const updatedInitialScroll = { ...initialScroll, contentOffset: offset };
4190
+ refState.current.initialScroll = updatedInitialScroll;
4191
+ state.initialScroll = updatedInitialScroll;
4165
4192
  scrollTo(ctx, {
4166
4193
  animated: false,
4167
- index: initialScroll == null ? void 0 : initialScroll.index,
4194
+ index: initialScroll.index,
4168
4195
  isInitialScroll: true,
4169
- offset: initialContentOffset,
4196
+ offset,
4170
4197
  precomputedWithViewOffset: true
4171
4198
  });
4172
4199
  }
4173
- }, [initialContentOffset]);
4200
+ }, []);
4201
+ const onLayoutFooter = useCallback(
4202
+ (layout) => {
4203
+ if (!initialScrollAtEnd) {
4204
+ return;
4205
+ }
4206
+ const { initialScroll } = state;
4207
+ if (!initialScroll) {
4208
+ return;
4209
+ }
4210
+ const lastIndex = Math.max(0, dataProp.length - 1);
4211
+ if (initialScroll.index !== lastIndex || initialScroll.viewPosition !== 1) {
4212
+ return;
4213
+ }
4214
+ const footerSize = layout[horizontal ? "width" : "height"];
4215
+ const viewOffset = -stylePaddingBottomState - footerSize;
4216
+ if (initialScroll.viewOffset !== viewOffset) {
4217
+ const updatedInitialScroll = { ...initialScroll, viewOffset };
4218
+ refState.current.initialScroll = updatedInitialScroll;
4219
+ state.initialScroll = updatedInitialScroll;
4220
+ }
4221
+ },
4222
+ [dataProp.length, horizontal, initialScrollAtEnd, stylePaddingBottomState]
4223
+ );
4174
4224
  const onLayoutChange = useCallback((layout) => {
4175
4225
  doInitialScroll();
4176
4226
  handleLayout(ctx, layout, setCanRender);
@@ -4284,7 +4334,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4284
4334
  ListEmptyComponent: dataProp.length === 0 ? ListEmptyComponent : void 0,
4285
4335
  ListHeaderComponent,
4286
4336
  onLayout,
4287
- onLayoutHeader,
4337
+ onLayoutFooter,
4288
4338
  onMomentumScrollEnd: fns.onMomentumScrollEnd,
4289
4339
  onScroll: onScrollHandler,
4290
4340
  recycleItems,
@@ -4314,5 +4364,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
4314
4364
 
4315
4365
  // src/react-native.ts
4316
4366
  var LegendList3 = LegendList;
4367
+ var internal = {
4368
+ getComponent,
4369
+ IsNewArchitecture,
4370
+ POSITION_OUT_OF_VIEW,
4371
+ useArr$,
4372
+ useCombinedRef
4373
+ };
4317
4374
 
4318
- export { LegendList3 as LegendList, typedForwardRef, typedMemo2 as typedMemo, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };
4375
+ export { LegendList3 as LegendList, internal, typedForwardRef, typedMemo2 as typedMemo, useIsLastItem, useListScrollSize, useRecyclingEffect, useRecyclingState, useSyncLayout, useViewability, useViewabilityAmount };
package/react.d.ts CHANGED
@@ -591,6 +591,10 @@ interface InternalState {
591
591
  scrollLastCalculate?: number;
592
592
  scrollLength: number;
593
593
  scrollPending: number;
594
+ stableTarget?: {
595
+ scroll: number;
596
+ target: number;
597
+ };
594
598
  scrollPrev: number;
595
599
  scrollPrevTime: number;
596
600
  scrollProcessingEnabled: boolean;
package/react.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,