@legendapp/list 3.0.0-beta.55 → 3.0.0-beta.56

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.
@@ -1236,10 +1236,16 @@ var StyleSheet = {
1236
1236
  create: (styles) => styles,
1237
1237
  flatten: (style) => flattenStyles(style)
1238
1238
  };
1239
+ function useLatestRef(value) {
1240
+ const ref = React3__namespace.useRef(value);
1241
+ ref.current = value;
1242
+ return ref;
1243
+ }
1244
+
1245
+ // src/utils/useRafCoalescer.ts
1239
1246
  function useRafCoalescer(callback) {
1240
- const callbackRef = React3.useRef(callback);
1247
+ const callbackRef = useLatestRef(callback);
1241
1248
  const rafIdRef = React3.useRef(void 0);
1242
- callbackRef.current = callback;
1243
1249
  const coalescer = React3.useMemo(
1244
1250
  () => ({
1245
1251
  cancel() {
@@ -1801,6 +1807,19 @@ function WebAnchoredEndSpace({ horizontal }) {
1801
1807
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1802
1808
  return /* @__PURE__ */ React3__namespace.createElement("div", { style }, null);
1803
1809
  }
1810
+ function useStableRenderComponent(renderComponent, mapProps) {
1811
+ const renderComponentRef = useLatestRef(renderComponent);
1812
+ const mapPropsRef = useLatestRef(mapProps);
1813
+ return React3__namespace.useMemo(
1814
+ () => React3__namespace.forwardRef(
1815
+ (props, ref) => {
1816
+ var _a3, _b;
1817
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1818
+ }
1819
+ ),
1820
+ [mapPropsRef, renderComponentRef]
1821
+ );
1822
+ }
1804
1823
  var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1805
1824
  const localRef = React3.useRef(null);
1806
1825
  const ref = refView != null ? refView : localRef;
@@ -1845,14 +1864,11 @@ var ListComponent = typedMemo(function ListComponent2({
1845
1864
  needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1846
1865
  otherAxisSize
1847
1866
  });
1848
- const ScrollComponent = React3.useMemo(() => {
1849
- if (!renderScrollComponent) {
1850
- return ListComponentScrollView;
1851
- }
1852
- return React3__namespace.forwardRef(
1853
- (props, ref) => renderScrollComponent({ ...props, ref })
1854
- );
1855
- }, [renderScrollComponent]);
1867
+ const CustomScrollComponent = useStableRenderComponent(
1868
+ renderScrollComponent,
1869
+ (props, ref) => ({ ...props, ref })
1870
+ );
1871
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1856
1872
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1857
1873
  React3.useLayoutEffect(() => {
1858
1874
  if (!ListHeaderComponent) {
@@ -3212,11 +3228,51 @@ function getObservedBootstrapInitialScrollOffset(state) {
3212
3228
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3213
3229
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3214
3230
  }
3231
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3232
+ var _a3;
3233
+ const state = ctx.state;
3234
+ const initialScroll = state.initialScroll;
3235
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3236
+ return;
3237
+ }
3238
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3239
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3240
+ }
3241
+ function schedulePreservedEndAnchorCorrection(ctx) {
3242
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3243
+ return false;
3244
+ }
3245
+ const correction = {};
3246
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3247
+ return true;
3248
+ }
3249
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3250
+ const state = ctx.state;
3251
+ state.preservedEndAnchorCorrection = correction;
3252
+ requestAnimationFrame(() => {
3253
+ var _a3;
3254
+ const activeCorrection = state.preservedEndAnchorCorrection;
3255
+ if (activeCorrection !== correction) {
3256
+ return;
3257
+ }
3258
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3259
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3260
+ state.preservedEndAnchorCorrection = void 0;
3261
+ return;
3262
+ }
3263
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3264
+ if (hasObservedNativeScrollAfterRequest) {
3265
+ activeCorrection.lastRequestTime = Date.now();
3266
+ requestAdjust(ctx, offsetDiff);
3267
+ }
3268
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3269
+ });
3270
+ }
3215
3271
  function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3216
3272
  var _a3, _b;
3217
3273
  const state = ctx.state;
3218
3274
  const initialScroll = state.initialScroll;
3219
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
3275
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3220
3276
  return;
3221
3277
  }
3222
3278
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -3373,7 +3429,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3373
3429
  }
3374
3430
  }
3375
3431
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3376
- var _a3, _b, _c, _d;
3432
+ var _a3, _b, _c;
3377
3433
  const state = ctx.state;
3378
3434
  const initialScroll = state.initialScroll;
3379
3435
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -3384,7 +3440,9 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3384
3440
  const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3385
3441
  const offsetDiff = resolvedOffset - currentOffset;
3386
3442
  if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3387
- if (scrollingTo) {
3443
+ if (state.didFinishInitialScroll) {
3444
+ schedulePreservedEndAnchorCorrection(ctx);
3445
+ } else if (scrollingTo) {
3388
3446
  const existingWatchdog = initialScrollWatchdog.get(state);
3389
3447
  scrollingTo.offset = resolvedOffset;
3390
3448
  scrollingTo.targetOffset = resolvedOffset;
@@ -3397,14 +3455,8 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3397
3455
  startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3398
3456
  targetOffset: resolvedOffset
3399
3457
  });
3458
+ requestAdjust(ctx, offsetDiff);
3400
3459
  }
3401
- requestAdjust(ctx, offsetDiff);
3402
- if (state.didFinishInitialScroll) {
3403
- (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3404
- }
3405
- }
3406
- if (state.didFinishInitialScroll) {
3407
- clearFinishedViewportRetargetableInitialScroll(state);
3408
3460
  }
3409
3461
  } else {
3410
3462
  rearmBootstrapInitialScroll(ctx, {
@@ -3655,7 +3707,10 @@ function retargetActiveInitialScrollAtEnd(ctx) {
3655
3707
  var _a3;
3656
3708
  const state = ctx.state;
3657
3709
  const initialScroll = state.initialScroll;
3658
- if (!initialScroll || state.didFinishInitialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3710
+ if (state.didFinishInitialScroll) {
3711
+ return schedulePreservedEndAnchorCorrection(ctx);
3712
+ }
3713
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3659
3714
  return false;
3660
3715
  }
3661
3716
  return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
@@ -5658,7 +5713,7 @@ function cloneScrollEvent(event) {
5658
5713
  };
5659
5714
  }
5660
5715
  function onScroll(ctx, event) {
5661
- var _a3, _b, _c, _d, _e;
5716
+ var _a3, _b, _c, _d, _e, _f;
5662
5717
  const state = ctx.state;
5663
5718
  const { scrollProcessingEnabled } = state;
5664
5719
  if (scrollProcessingEnabled === false) {
@@ -5680,6 +5735,9 @@ function onScroll(ctx, event) {
5680
5735
  if (state.props.horizontal) {
5681
5736
  newScroll = toLogicalHorizontalOffset(state, newScroll, (_e = event.nativeEvent.contentSize) == null ? void 0 : _e.width);
5682
5737
  }
5738
+ state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
5739
+ state.lastNativeScroll = newScroll;
5740
+ state.lastNativeScrollTime = Date.now();
5683
5741
  if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
5684
5742
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
5685
5743
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
@@ -6305,6 +6363,8 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
6305
6363
  indices.sort(sortAsc);
6306
6364
  return indices;
6307
6365
  }
6366
+
6367
+ // src/utils/getRenderedItem.ts
6308
6368
  function getRenderedItem(ctx, key) {
6309
6369
  var _a3;
6310
6370
  const state = ctx.state;
@@ -6330,7 +6390,7 @@ function getRenderedItem(ctx, key) {
6330
6390
  item,
6331
6391
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
6332
6392
  };
6333
- renderedItem = React3__namespace.default.createElement(renderItem, itemProps);
6393
+ renderedItem = renderItem(itemProps);
6334
6394
  }
6335
6395
  return { index, item: data[index], renderedItem };
6336
6396
  }
@@ -7056,6 +7116,8 @@ var internal = {
7056
7116
  typedMemo,
7057
7117
  useArr$,
7058
7118
  useCombinedRef,
7119
+ useLatestRef,
7120
+ useStableRenderComponent,
7059
7121
  useStateContext
7060
7122
  };
7061
7123
 
@@ -1,5 +1,5 @@
1
1
  import * as React3 from 'react';
2
- import React3__default, { forwardRef, useReducer, useEffect, createContext, useRef, useState, useMemo, useCallback, useImperativeHandle, useLayoutEffect, useContext } from 'react';
2
+ import { forwardRef, useReducer, useEffect, createContext, useRef, useState, useMemo, useCallback, useImperativeHandle, useLayoutEffect, useContext } from 'react';
3
3
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
4
4
  import * as ReactDOM from 'react-dom';
5
5
  import { flushSync } from 'react-dom';
@@ -1215,10 +1215,16 @@ var StyleSheet = {
1215
1215
  create: (styles) => styles,
1216
1216
  flatten: (style) => flattenStyles(style)
1217
1217
  };
1218
+ function useLatestRef(value) {
1219
+ const ref = React3.useRef(value);
1220
+ ref.current = value;
1221
+ return ref;
1222
+ }
1223
+
1224
+ // src/utils/useRafCoalescer.ts
1218
1225
  function useRafCoalescer(callback) {
1219
- const callbackRef = useRef(callback);
1226
+ const callbackRef = useLatestRef(callback);
1220
1227
  const rafIdRef = useRef(void 0);
1221
- callbackRef.current = callback;
1222
1228
  const coalescer = useMemo(
1223
1229
  () => ({
1224
1230
  cancel() {
@@ -1780,6 +1786,19 @@ function WebAnchoredEndSpace({ horizontal }) {
1780
1786
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1781
1787
  return /* @__PURE__ */ React3.createElement("div", { style }, null);
1782
1788
  }
1789
+ function useStableRenderComponent(renderComponent, mapProps) {
1790
+ const renderComponentRef = useLatestRef(renderComponent);
1791
+ const mapPropsRef = useLatestRef(mapProps);
1792
+ return React3.useMemo(
1793
+ () => React3.forwardRef(
1794
+ (props, ref) => {
1795
+ var _a3, _b;
1796
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1797
+ }
1798
+ ),
1799
+ [mapPropsRef, renderComponentRef]
1800
+ );
1801
+ }
1783
1802
  var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1784
1803
  const localRef = useRef(null);
1785
1804
  const ref = refView != null ? refView : localRef;
@@ -1824,14 +1843,11 @@ var ListComponent = typedMemo(function ListComponent2({
1824
1843
  needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1825
1844
  otherAxisSize
1826
1845
  });
1827
- const ScrollComponent = useMemo(() => {
1828
- if (!renderScrollComponent) {
1829
- return ListComponentScrollView;
1830
- }
1831
- return React3.forwardRef(
1832
- (props, ref) => renderScrollComponent({ ...props, ref })
1833
- );
1834
- }, [renderScrollComponent]);
1846
+ const CustomScrollComponent = useStableRenderComponent(
1847
+ renderScrollComponent,
1848
+ (props, ref) => ({ ...props, ref })
1849
+ );
1850
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1835
1851
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1836
1852
  useLayoutEffect(() => {
1837
1853
  if (!ListHeaderComponent) {
@@ -3191,11 +3207,51 @@ function getObservedBootstrapInitialScrollOffset(state) {
3191
3207
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3192
3208
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3193
3209
  }
3210
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3211
+ var _a3;
3212
+ const state = ctx.state;
3213
+ const initialScroll = state.initialScroll;
3214
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3215
+ return;
3216
+ }
3217
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3218
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3219
+ }
3220
+ function schedulePreservedEndAnchorCorrection(ctx) {
3221
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3222
+ return false;
3223
+ }
3224
+ const correction = {};
3225
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3226
+ return true;
3227
+ }
3228
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3229
+ const state = ctx.state;
3230
+ state.preservedEndAnchorCorrection = correction;
3231
+ requestAnimationFrame(() => {
3232
+ var _a3;
3233
+ const activeCorrection = state.preservedEndAnchorCorrection;
3234
+ if (activeCorrection !== correction) {
3235
+ return;
3236
+ }
3237
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3238
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3239
+ state.preservedEndAnchorCorrection = void 0;
3240
+ return;
3241
+ }
3242
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3243
+ if (hasObservedNativeScrollAfterRequest) {
3244
+ activeCorrection.lastRequestTime = Date.now();
3245
+ requestAdjust(ctx, offsetDiff);
3246
+ }
3247
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3248
+ });
3249
+ }
3194
3250
  function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3195
3251
  var _a3, _b;
3196
3252
  const state = ctx.state;
3197
3253
  const initialScroll = state.initialScroll;
3198
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
3254
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3199
3255
  return;
3200
3256
  }
3201
3257
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -3352,7 +3408,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3352
3408
  }
3353
3409
  }
3354
3410
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3355
- var _a3, _b, _c, _d;
3411
+ var _a3, _b, _c;
3356
3412
  const state = ctx.state;
3357
3413
  const initialScroll = state.initialScroll;
3358
3414
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -3363,7 +3419,9 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3363
3419
  const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3364
3420
  const offsetDiff = resolvedOffset - currentOffset;
3365
3421
  if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3366
- if (scrollingTo) {
3422
+ if (state.didFinishInitialScroll) {
3423
+ schedulePreservedEndAnchorCorrection(ctx);
3424
+ } else if (scrollingTo) {
3367
3425
  const existingWatchdog = initialScrollWatchdog.get(state);
3368
3426
  scrollingTo.offset = resolvedOffset;
3369
3427
  scrollingTo.targetOffset = resolvedOffset;
@@ -3376,14 +3434,8 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3376
3434
  startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3377
3435
  targetOffset: resolvedOffset
3378
3436
  });
3437
+ requestAdjust(ctx, offsetDiff);
3379
3438
  }
3380
- requestAdjust(ctx, offsetDiff);
3381
- if (state.didFinishInitialScroll) {
3382
- (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3383
- }
3384
- }
3385
- if (state.didFinishInitialScroll) {
3386
- clearFinishedViewportRetargetableInitialScroll(state);
3387
3439
  }
3388
3440
  } else {
3389
3441
  rearmBootstrapInitialScroll(ctx, {
@@ -3634,7 +3686,10 @@ function retargetActiveInitialScrollAtEnd(ctx) {
3634
3686
  var _a3;
3635
3687
  const state = ctx.state;
3636
3688
  const initialScroll = state.initialScroll;
3637
- if (!initialScroll || state.didFinishInitialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3689
+ if (state.didFinishInitialScroll) {
3690
+ return schedulePreservedEndAnchorCorrection(ctx);
3691
+ }
3692
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3638
3693
  return false;
3639
3694
  }
3640
3695
  return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
@@ -5637,7 +5692,7 @@ function cloneScrollEvent(event) {
5637
5692
  };
5638
5693
  }
5639
5694
  function onScroll(ctx, event) {
5640
- var _a3, _b, _c, _d, _e;
5695
+ var _a3, _b, _c, _d, _e, _f;
5641
5696
  const state = ctx.state;
5642
5697
  const { scrollProcessingEnabled } = state;
5643
5698
  if (scrollProcessingEnabled === false) {
@@ -5659,6 +5714,9 @@ function onScroll(ctx, event) {
5659
5714
  if (state.props.horizontal) {
5660
5715
  newScroll = toLogicalHorizontalOffset(state, newScroll, (_e = event.nativeEvent.contentSize) == null ? void 0 : _e.width);
5661
5716
  }
5717
+ state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
5718
+ state.lastNativeScroll = newScroll;
5719
+ state.lastNativeScrollTime = Date.now();
5662
5720
  if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
5663
5721
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
5664
5722
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
@@ -6284,6 +6342,8 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
6284
6342
  indices.sort(sortAsc);
6285
6343
  return indices;
6286
6344
  }
6345
+
6346
+ // src/utils/getRenderedItem.ts
6287
6347
  function getRenderedItem(ctx, key) {
6288
6348
  var _a3;
6289
6349
  const state = ctx.state;
@@ -6309,7 +6369,7 @@ function getRenderedItem(ctx, key) {
6309
6369
  item,
6310
6370
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
6311
6371
  };
6312
- renderedItem = React3__default.createElement(renderItem, itemProps);
6372
+ renderedItem = renderItem(itemProps);
6313
6373
  }
6314
6374
  return { index, item: data[index], renderedItem };
6315
6375
  }
@@ -7035,6 +7095,8 @@ var internal = {
7035
7095
  typedMemo,
7036
7096
  useArr$,
7037
7097
  useCombinedRef,
7098
+ useLatestRef,
7099
+ useStableRenderComponent,
7038
7100
  useStateContext
7039
7101
  };
7040
7102
 
package/react.d.ts CHANGED
@@ -120,13 +120,11 @@ interface DataModeProps<ItemT, TItemType extends string | undefined> {
120
120
  */
121
121
  data: ReadonlyArray<ItemT>;
122
122
  /**
123
- * Function or React component to render each item in the list.
124
- * Can be either:
125
- * - A function: (props: LegendListRenderItemProps<ItemT>) => ReactNode
126
- * - A React component: React.ComponentType<LegendListRenderItemProps<ItemT>>
123
+ * Callback to render each item in the list.
124
+ * To use hooks in an item component, return that component from this callback.
127
125
  * @required when using data mode
128
126
  */
129
- renderItem: ((props: LegendListRenderItemProps<ItemT, TItemType>) => React.ReactNode) | React.ComponentType<LegendListRenderItemProps<ItemT, TItemType>>;
127
+ renderItem: (props: LegendListRenderItemProps<ItemT, TItemType>) => React.ReactNode;
130
128
  children?: never;
131
129
  }
132
130
  interface ChildrenModeProps {
package/react.js CHANGED
@@ -1236,10 +1236,16 @@ var StyleSheet = {
1236
1236
  create: (styles) => styles,
1237
1237
  flatten: (style) => flattenStyles(style)
1238
1238
  };
1239
+ function useLatestRef(value) {
1240
+ const ref = React3__namespace.useRef(value);
1241
+ ref.current = value;
1242
+ return ref;
1243
+ }
1244
+
1245
+ // src/utils/useRafCoalescer.ts
1239
1246
  function useRafCoalescer(callback) {
1240
- const callbackRef = React3.useRef(callback);
1247
+ const callbackRef = useLatestRef(callback);
1241
1248
  const rafIdRef = React3.useRef(void 0);
1242
- callbackRef.current = callback;
1243
1249
  const coalescer = React3.useMemo(
1244
1250
  () => ({
1245
1251
  cancel() {
@@ -1801,6 +1807,19 @@ function WebAnchoredEndSpace({ horizontal }) {
1801
1807
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1802
1808
  return /* @__PURE__ */ React3__namespace.createElement("div", { style }, null);
1803
1809
  }
1810
+ function useStableRenderComponent(renderComponent, mapProps) {
1811
+ const renderComponentRef = useLatestRef(renderComponent);
1812
+ const mapPropsRef = useLatestRef(mapProps);
1813
+ return React3__namespace.useMemo(
1814
+ () => React3__namespace.forwardRef(
1815
+ (props, ref) => {
1816
+ var _a3, _b;
1817
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1818
+ }
1819
+ ),
1820
+ [mapPropsRef, renderComponentRef]
1821
+ );
1822
+ }
1804
1823
  var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1805
1824
  const localRef = React3.useRef(null);
1806
1825
  const ref = refView != null ? refView : localRef;
@@ -1845,14 +1864,11 @@ var ListComponent = typedMemo(function ListComponent2({
1845
1864
  needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1846
1865
  otherAxisSize
1847
1866
  });
1848
- const ScrollComponent = React3.useMemo(() => {
1849
- if (!renderScrollComponent) {
1850
- return ListComponentScrollView;
1851
- }
1852
- return React3__namespace.forwardRef(
1853
- (props, ref) => renderScrollComponent({ ...props, ref })
1854
- );
1855
- }, [renderScrollComponent]);
1867
+ const CustomScrollComponent = useStableRenderComponent(
1868
+ renderScrollComponent,
1869
+ (props, ref) => ({ ...props, ref })
1870
+ );
1871
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1856
1872
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1857
1873
  React3.useLayoutEffect(() => {
1858
1874
  if (!ListHeaderComponent) {
@@ -3212,11 +3228,51 @@ function getObservedBootstrapInitialScrollOffset(state) {
3212
3228
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3213
3229
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3214
3230
  }
3231
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3232
+ var _a3;
3233
+ const state = ctx.state;
3234
+ const initialScroll = state.initialScroll;
3235
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3236
+ return;
3237
+ }
3238
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3239
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3240
+ }
3241
+ function schedulePreservedEndAnchorCorrection(ctx) {
3242
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3243
+ return false;
3244
+ }
3245
+ const correction = {};
3246
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3247
+ return true;
3248
+ }
3249
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3250
+ const state = ctx.state;
3251
+ state.preservedEndAnchorCorrection = correction;
3252
+ requestAnimationFrame(() => {
3253
+ var _a3;
3254
+ const activeCorrection = state.preservedEndAnchorCorrection;
3255
+ if (activeCorrection !== correction) {
3256
+ return;
3257
+ }
3258
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3259
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3260
+ state.preservedEndAnchorCorrection = void 0;
3261
+ return;
3262
+ }
3263
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3264
+ if (hasObservedNativeScrollAfterRequest) {
3265
+ activeCorrection.lastRequestTime = Date.now();
3266
+ requestAdjust(ctx, offsetDiff);
3267
+ }
3268
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3269
+ });
3270
+ }
3215
3271
  function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3216
3272
  var _a3, _b;
3217
3273
  const state = ctx.state;
3218
3274
  const initialScroll = state.initialScroll;
3219
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
3275
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3220
3276
  return;
3221
3277
  }
3222
3278
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -3373,7 +3429,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3373
3429
  }
3374
3430
  }
3375
3431
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3376
- var _a3, _b, _c, _d;
3432
+ var _a3, _b, _c;
3377
3433
  const state = ctx.state;
3378
3434
  const initialScroll = state.initialScroll;
3379
3435
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -3384,7 +3440,9 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3384
3440
  const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3385
3441
  const offsetDiff = resolvedOffset - currentOffset;
3386
3442
  if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3387
- if (scrollingTo) {
3443
+ if (state.didFinishInitialScroll) {
3444
+ schedulePreservedEndAnchorCorrection(ctx);
3445
+ } else if (scrollingTo) {
3388
3446
  const existingWatchdog = initialScrollWatchdog.get(state);
3389
3447
  scrollingTo.offset = resolvedOffset;
3390
3448
  scrollingTo.targetOffset = resolvedOffset;
@@ -3397,14 +3455,8 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3397
3455
  startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3398
3456
  targetOffset: resolvedOffset
3399
3457
  });
3458
+ requestAdjust(ctx, offsetDiff);
3400
3459
  }
3401
- requestAdjust(ctx, offsetDiff);
3402
- if (state.didFinishInitialScroll) {
3403
- (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3404
- }
3405
- }
3406
- if (state.didFinishInitialScroll) {
3407
- clearFinishedViewportRetargetableInitialScroll(state);
3408
3460
  }
3409
3461
  } else {
3410
3462
  rearmBootstrapInitialScroll(ctx, {
@@ -3655,7 +3707,10 @@ function retargetActiveInitialScrollAtEnd(ctx) {
3655
3707
  var _a3;
3656
3708
  const state = ctx.state;
3657
3709
  const initialScroll = state.initialScroll;
3658
- if (!initialScroll || state.didFinishInitialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3710
+ if (state.didFinishInitialScroll) {
3711
+ return schedulePreservedEndAnchorCorrection(ctx);
3712
+ }
3713
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3659
3714
  return false;
3660
3715
  }
3661
3716
  return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
@@ -5658,7 +5713,7 @@ function cloneScrollEvent(event) {
5658
5713
  };
5659
5714
  }
5660
5715
  function onScroll(ctx, event) {
5661
- var _a3, _b, _c, _d, _e;
5716
+ var _a3, _b, _c, _d, _e, _f;
5662
5717
  const state = ctx.state;
5663
5718
  const { scrollProcessingEnabled } = state;
5664
5719
  if (scrollProcessingEnabled === false) {
@@ -5680,6 +5735,9 @@ function onScroll(ctx, event) {
5680
5735
  if (state.props.horizontal) {
5681
5736
  newScroll = toLogicalHorizontalOffset(state, newScroll, (_e = event.nativeEvent.contentSize) == null ? void 0 : _e.width);
5682
5737
  }
5738
+ state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
5739
+ state.lastNativeScroll = newScroll;
5740
+ state.lastNativeScrollTime = Date.now();
5683
5741
  if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
5684
5742
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
5685
5743
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
@@ -6305,6 +6363,8 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
6305
6363
  indices.sort(sortAsc);
6306
6364
  return indices;
6307
6365
  }
6366
+
6367
+ // src/utils/getRenderedItem.ts
6308
6368
  function getRenderedItem(ctx, key) {
6309
6369
  var _a3;
6310
6370
  const state = ctx.state;
@@ -6330,7 +6390,7 @@ function getRenderedItem(ctx, key) {
6330
6390
  item,
6331
6391
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
6332
6392
  };
6333
- renderedItem = React3__namespace.default.createElement(renderItem, itemProps);
6393
+ renderedItem = renderItem(itemProps);
6334
6394
  }
6335
6395
  return { index, item: data[index], renderedItem };
6336
6396
  }
@@ -7056,6 +7116,8 @@ var internal = {
7056
7116
  typedMemo,
7057
7117
  useArr$,
7058
7118
  useCombinedRef,
7119
+ useLatestRef,
7120
+ useStableRenderComponent,
7059
7121
  useStateContext
7060
7122
  };
7061
7123