@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.
package/animated.d.ts CHANGED
@@ -91,13 +91,11 @@ interface DataModeProps<ItemT, TItemType extends string | undefined> {
91
91
  */
92
92
  data: ReadonlyArray<ItemT>;
93
93
  /**
94
- * Function or React component to render each item in the list.
95
- * Can be either:
96
- * - A function: (props: LegendListRenderItemProps<ItemT>) => ReactNode
97
- * - A React component: React.ComponentType<LegendListRenderItemProps<ItemT>>
94
+ * Callback to render each item in the list.
95
+ * To use hooks in an item component, return that component from this callback.
98
96
  * @required when using data mode
99
97
  */
100
- renderItem: ((props: LegendListRenderItemProps<ItemT, TItemType>) => React.ReactNode) | React.ComponentType<LegendListRenderItemProps<ItemT, TItemType>>;
98
+ renderItem: (props: LegendListRenderItemProps<ItemT, TItemType>) => React.ReactNode;
101
99
  children?: never;
102
100
  }
103
101
  interface ChildrenModeProps {
package/index.d.ts CHANGED
@@ -116,7 +116,7 @@ type BootstrapOwnedInitialScrollSession = InternalInitialScrollSessionBase & {
116
116
  type InternalInitialScrollSession = OffsetInitialScrollSession | BootstrapOwnedInitialScrollSession;
117
117
  type LegendListPropsInternal = LegendListPropsBase$1<any, Record<string, any>, string | undefined> & {
118
118
  data: readonly any[];
119
- renderItem: ((props: LegendListRenderItemProps$1<any, string | undefined>) => React.ReactNode) | React.ComponentType<LegendListRenderItemProps$1<any, string | undefined>>;
119
+ renderItem: (props: LegendListRenderItemProps$1<any, string | undefined>) => React.ReactNode;
120
120
  };
121
121
  interface PendingDataComparison {
122
122
  byIndex: Array<0 | 1 | 2 | undefined>;
@@ -147,6 +147,9 @@ interface InternalState$1 {
147
147
  endNoBuffer: number;
148
148
  endReachedSnapshot: ThresholdSnapshot$1 | undefined;
149
149
  firstFullyOnScreenIndex: number;
150
+ preservedEndAnchorCorrection?: {
151
+ lastRequestTime?: number;
152
+ };
150
153
  hasScrolled?: boolean;
151
154
  idCache: string[];
152
155
  idsInView: string[];
@@ -166,6 +169,8 @@ interface InternalState$1 {
166
169
  isStartReached: boolean | null;
167
170
  lastBatchingAction: number;
168
171
  lastLayout: LayoutRectangle$1 | undefined;
172
+ lastNativeScroll?: number;
173
+ lastNativeScrollTime?: number;
169
174
  lastScrollAdjustForHistory?: number;
170
175
  lastScrollDelta: number;
171
176
  loadStartTime: number;
@@ -419,13 +424,11 @@ interface DataModeProps<ItemT, TItemType extends string | undefined> {
419
424
  */
420
425
  data: ReadonlyArray<ItemT>;
421
426
  /**
422
- * Function or React component to render each item in the list.
423
- * Can be either:
424
- * - A function: (props: LegendListRenderItemProps<ItemT>) => ReactNode
425
- * - A React component: React.ComponentType<LegendListRenderItemProps<ItemT>>
427
+ * Callback to render each item in the list.
428
+ * To use hooks in an item component, return that component from this callback.
426
429
  * @required when using data mode
427
430
  */
428
- renderItem: ((props: LegendListRenderItemProps$1<ItemT, TItemType>) => React.ReactNode) | React.ComponentType<LegendListRenderItemProps$1<ItemT, TItemType>>;
431
+ renderItem: (props: LegendListRenderItemProps$1<ItemT, TItemType>) => React.ReactNode;
429
432
  children?: never;
430
433
  }
431
434
  interface ChildrenModeProps {
package/index.js CHANGED
@@ -1233,10 +1233,16 @@ var StyleSheet = {
1233
1233
  create: (styles) => styles,
1234
1234
  flatten: (style) => flattenStyles(style)
1235
1235
  };
1236
+ function useLatestRef(value) {
1237
+ const ref = React3__namespace.useRef(value);
1238
+ ref.current = value;
1239
+ return ref;
1240
+ }
1241
+
1242
+ // src/utils/useRafCoalescer.ts
1236
1243
  function useRafCoalescer(callback) {
1237
- const callbackRef = React3.useRef(callback);
1244
+ const callbackRef = useLatestRef(callback);
1238
1245
  const rafIdRef = React3.useRef(void 0);
1239
- callbackRef.current = callback;
1240
1246
  const coalescer = React3.useMemo(
1241
1247
  () => ({
1242
1248
  cancel() {
@@ -1798,6 +1804,19 @@ function WebAnchoredEndSpace({ horizontal }) {
1798
1804
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1799
1805
  return /* @__PURE__ */ React3__namespace.createElement("div", { style }, null);
1800
1806
  }
1807
+ function useStableRenderComponent(renderComponent, mapProps) {
1808
+ const renderComponentRef = useLatestRef(renderComponent);
1809
+ const mapPropsRef = useLatestRef(mapProps);
1810
+ return React3__namespace.useMemo(
1811
+ () => React3__namespace.forwardRef(
1812
+ (props, ref) => {
1813
+ var _a3, _b;
1814
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1815
+ }
1816
+ ),
1817
+ [mapPropsRef, renderComponentRef]
1818
+ );
1819
+ }
1801
1820
  var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1802
1821
  const localRef = React3.useRef(null);
1803
1822
  const ref = refView != null ? refView : localRef;
@@ -1842,14 +1861,11 @@ var ListComponent = typedMemo(function ListComponent2({
1842
1861
  needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1843
1862
  otherAxisSize
1844
1863
  });
1845
- const ScrollComponent = React3.useMemo(() => {
1846
- if (!renderScrollComponent) {
1847
- return ListComponentScrollView;
1848
- }
1849
- return React3__namespace.forwardRef(
1850
- (props, ref) => renderScrollComponent({ ...props, ref })
1851
- );
1852
- }, [renderScrollComponent]);
1864
+ const CustomScrollComponent = useStableRenderComponent(
1865
+ renderScrollComponent,
1866
+ (props, ref) => ({ ...props, ref })
1867
+ );
1868
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1853
1869
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1854
1870
  React3.useLayoutEffect(() => {
1855
1871
  if (!ListHeaderComponent) {
@@ -3209,11 +3225,51 @@ function getObservedBootstrapInitialScrollOffset(state) {
3209
3225
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3210
3226
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3211
3227
  }
3228
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3229
+ var _a3;
3230
+ const state = ctx.state;
3231
+ const initialScroll = state.initialScroll;
3232
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3233
+ return;
3234
+ }
3235
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3236
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3237
+ }
3238
+ function schedulePreservedEndAnchorCorrection(ctx) {
3239
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3240
+ return false;
3241
+ }
3242
+ const correction = {};
3243
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3244
+ return true;
3245
+ }
3246
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3247
+ const state = ctx.state;
3248
+ state.preservedEndAnchorCorrection = correction;
3249
+ requestAnimationFrame(() => {
3250
+ var _a3;
3251
+ const activeCorrection = state.preservedEndAnchorCorrection;
3252
+ if (activeCorrection !== correction) {
3253
+ return;
3254
+ }
3255
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3256
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3257
+ state.preservedEndAnchorCorrection = void 0;
3258
+ return;
3259
+ }
3260
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3261
+ if (hasObservedNativeScrollAfterRequest) {
3262
+ activeCorrection.lastRequestTime = Date.now();
3263
+ requestAdjust(ctx, offsetDiff);
3264
+ }
3265
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3266
+ });
3267
+ }
3212
3268
  function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3213
3269
  var _a3, _b;
3214
3270
  const state = ctx.state;
3215
3271
  const initialScroll = state.initialScroll;
3216
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
3272
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3217
3273
  return;
3218
3274
  }
3219
3275
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -3370,7 +3426,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3370
3426
  }
3371
3427
  }
3372
3428
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3373
- var _a3, _b, _c, _d;
3429
+ var _a3, _b, _c;
3374
3430
  const state = ctx.state;
3375
3431
  const initialScroll = state.initialScroll;
3376
3432
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -3381,7 +3437,9 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3381
3437
  const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3382
3438
  const offsetDiff = resolvedOffset - currentOffset;
3383
3439
  if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3384
- if (scrollingTo) {
3440
+ if (state.didFinishInitialScroll) {
3441
+ schedulePreservedEndAnchorCorrection(ctx);
3442
+ } else if (scrollingTo) {
3385
3443
  const existingWatchdog = initialScrollWatchdog.get(state);
3386
3444
  scrollingTo.offset = resolvedOffset;
3387
3445
  scrollingTo.targetOffset = resolvedOffset;
@@ -3394,14 +3452,8 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3394
3452
  startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3395
3453
  targetOffset: resolvedOffset
3396
3454
  });
3455
+ requestAdjust(ctx, offsetDiff);
3397
3456
  }
3398
- requestAdjust(ctx, offsetDiff);
3399
- if (state.didFinishInitialScroll) {
3400
- (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3401
- }
3402
- }
3403
- if (state.didFinishInitialScroll) {
3404
- clearFinishedViewportRetargetableInitialScroll(state);
3405
3457
  }
3406
3458
  } else {
3407
3459
  rearmBootstrapInitialScroll(ctx, {
@@ -3652,7 +3704,10 @@ function retargetActiveInitialScrollAtEnd(ctx) {
3652
3704
  var _a3;
3653
3705
  const state = ctx.state;
3654
3706
  const initialScroll = state.initialScroll;
3655
- if (!initialScroll || state.didFinishInitialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3707
+ if (state.didFinishInitialScroll) {
3708
+ return schedulePreservedEndAnchorCorrection(ctx);
3709
+ }
3710
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3656
3711
  return false;
3657
3712
  }
3658
3713
  return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
@@ -5655,7 +5710,7 @@ function cloneScrollEvent(event) {
5655
5710
  };
5656
5711
  }
5657
5712
  function onScroll(ctx, event) {
5658
- var _a3, _b, _c, _d, _e;
5713
+ var _a3, _b, _c, _d, _e, _f;
5659
5714
  const state = ctx.state;
5660
5715
  const { scrollProcessingEnabled } = state;
5661
5716
  if (scrollProcessingEnabled === false) {
@@ -5677,6 +5732,9 @@ function onScroll(ctx, event) {
5677
5732
  if (state.props.horizontal) {
5678
5733
  newScroll = toLogicalHorizontalOffset(state, newScroll, (_e = event.nativeEvent.contentSize) == null ? void 0 : _e.width);
5679
5734
  }
5735
+ state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
5736
+ state.lastNativeScroll = newScroll;
5737
+ state.lastNativeScrollTime = Date.now();
5680
5738
  if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
5681
5739
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
5682
5740
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
@@ -6302,6 +6360,8 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
6302
6360
  indices.sort(sortAsc);
6303
6361
  return indices;
6304
6362
  }
6363
+
6364
+ // src/utils/getRenderedItem.ts
6305
6365
  function getRenderedItem(ctx, key) {
6306
6366
  var _a3;
6307
6367
  const state = ctx.state;
@@ -6327,7 +6387,7 @@ function getRenderedItem(ctx, key) {
6327
6387
  item,
6328
6388
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
6329
6389
  };
6330
- renderedItem = React3__namespace.default.createElement(renderItem, itemProps);
6390
+ renderedItem = renderItem(itemProps);
6331
6391
  }
6332
6392
  return { index, item: data[index], renderedItem };
6333
6393
  }
package/index.mjs CHANGED
@@ -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';
@@ -1212,10 +1212,16 @@ var StyleSheet = {
1212
1212
  create: (styles) => styles,
1213
1213
  flatten: (style) => flattenStyles(style)
1214
1214
  };
1215
+ function useLatestRef(value) {
1216
+ const ref = React3.useRef(value);
1217
+ ref.current = value;
1218
+ return ref;
1219
+ }
1220
+
1221
+ // src/utils/useRafCoalescer.ts
1215
1222
  function useRafCoalescer(callback) {
1216
- const callbackRef = useRef(callback);
1223
+ const callbackRef = useLatestRef(callback);
1217
1224
  const rafIdRef = useRef(void 0);
1218
- callbackRef.current = callback;
1219
1225
  const coalescer = useMemo(
1220
1226
  () => ({
1221
1227
  cancel() {
@@ -1777,6 +1783,19 @@ function WebAnchoredEndSpace({ horizontal }) {
1777
1783
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1778
1784
  return /* @__PURE__ */ React3.createElement("div", { style }, null);
1779
1785
  }
1786
+ function useStableRenderComponent(renderComponent, mapProps) {
1787
+ const renderComponentRef = useLatestRef(renderComponent);
1788
+ const mapPropsRef = useLatestRef(mapProps);
1789
+ return React3.useMemo(
1790
+ () => React3.forwardRef(
1791
+ (props, ref) => {
1792
+ var _a3, _b;
1793
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1794
+ }
1795
+ ),
1796
+ [mapPropsRef, renderComponentRef]
1797
+ );
1798
+ }
1780
1799
  var LayoutView = ({ onLayoutChange, refView, children, ...rest }) => {
1781
1800
  const localRef = useRef(null);
1782
1801
  const ref = refView != null ? refView : localRef;
@@ -1821,14 +1840,11 @@ var ListComponent = typedMemo(function ListComponent2({
1821
1840
  needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1822
1841
  otherAxisSize
1823
1842
  });
1824
- const ScrollComponent = useMemo(() => {
1825
- if (!renderScrollComponent) {
1826
- return ListComponentScrollView;
1827
- }
1828
- return React3.forwardRef(
1829
- (props, ref) => renderScrollComponent({ ...props, ref })
1830
- );
1831
- }, [renderScrollComponent]);
1843
+ const CustomScrollComponent = useStableRenderComponent(
1844
+ renderScrollComponent,
1845
+ (props, ref) => ({ ...props, ref })
1846
+ );
1847
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1832
1848
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1833
1849
  useLayoutEffect(() => {
1834
1850
  if (!ListHeaderComponent) {
@@ -3188,11 +3204,51 @@ function getObservedBootstrapInitialScrollOffset(state) {
3188
3204
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
3189
3205
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
3190
3206
  }
3207
+ function getPreservedEndAnchorOffsetDiff(ctx) {
3208
+ var _a3;
3209
+ const state = ctx.state;
3210
+ const initialScroll = state.initialScroll;
3211
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
3212
+ return;
3213
+ }
3214
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
3215
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
3216
+ }
3217
+ function schedulePreservedEndAnchorCorrection(ctx) {
3218
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
3219
+ return false;
3220
+ }
3221
+ const correction = {};
3222
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3223
+ return true;
3224
+ }
3225
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
3226
+ const state = ctx.state;
3227
+ state.preservedEndAnchorCorrection = correction;
3228
+ requestAnimationFrame(() => {
3229
+ var _a3;
3230
+ const activeCorrection = state.preservedEndAnchorCorrection;
3231
+ if (activeCorrection !== correction) {
3232
+ return;
3233
+ }
3234
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
3235
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3236
+ state.preservedEndAnchorCorrection = void 0;
3237
+ return;
3238
+ }
3239
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
3240
+ if (hasObservedNativeScrollAfterRequest) {
3241
+ activeCorrection.lastRequestTime = Date.now();
3242
+ requestAdjust(ctx, offsetDiff);
3243
+ }
3244
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
3245
+ });
3246
+ }
3191
3247
  function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
3192
3248
  var _a3, _b;
3193
3249
  const state = ctx.state;
3194
3250
  const initialScroll = state.initialScroll;
3195
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
3251
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
3196
3252
  return;
3197
3253
  }
3198
3254
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -3349,7 +3405,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
3349
3405
  }
3350
3406
  }
3351
3407
  function handleBootstrapInitialScrollLayoutChange(ctx) {
3352
- var _a3, _b, _c, _d;
3408
+ var _a3, _b, _c;
3353
3409
  const state = ctx.state;
3354
3410
  const initialScroll = state.initialScroll;
3355
3411
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -3360,7 +3416,9 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3360
3416
  const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
3361
3417
  const offsetDiff = resolvedOffset - currentOffset;
3362
3418
  if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
3363
- if (scrollingTo) {
3419
+ if (state.didFinishInitialScroll) {
3420
+ schedulePreservedEndAnchorCorrection(ctx);
3421
+ } else if (scrollingTo) {
3364
3422
  const existingWatchdog = initialScrollWatchdog.get(state);
3365
3423
  scrollingTo.offset = resolvedOffset;
3366
3424
  scrollingTo.targetOffset = resolvedOffset;
@@ -3373,14 +3431,8 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
3373
3431
  startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
3374
3432
  targetOffset: resolvedOffset
3375
3433
  });
3434
+ requestAdjust(ctx, offsetDiff);
3376
3435
  }
3377
- requestAdjust(ctx, offsetDiff);
3378
- if (state.didFinishInitialScroll) {
3379
- (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
3380
- }
3381
- }
3382
- if (state.didFinishInitialScroll) {
3383
- clearFinishedViewportRetargetableInitialScroll(state);
3384
3436
  }
3385
3437
  } else {
3386
3438
  rearmBootstrapInitialScroll(ctx, {
@@ -3631,7 +3683,10 @@ function retargetActiveInitialScrollAtEnd(ctx) {
3631
3683
  var _a3;
3632
3684
  const state = ctx.state;
3633
3685
  const initialScroll = state.initialScroll;
3634
- if (!initialScroll || state.didFinishInitialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3686
+ if (state.didFinishInitialScroll) {
3687
+ return schedulePreservedEndAnchorCorrection(ctx);
3688
+ }
3689
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3635
3690
  return false;
3636
3691
  }
3637
3692
  return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
@@ -5634,7 +5689,7 @@ function cloneScrollEvent(event) {
5634
5689
  };
5635
5690
  }
5636
5691
  function onScroll(ctx, event) {
5637
- var _a3, _b, _c, _d, _e;
5692
+ var _a3, _b, _c, _d, _e, _f;
5638
5693
  const state = ctx.state;
5639
5694
  const { scrollProcessingEnabled } = state;
5640
5695
  if (scrollProcessingEnabled === false) {
@@ -5656,6 +5711,9 @@ function onScroll(ctx, event) {
5656
5711
  if (state.props.horizontal) {
5657
5712
  newScroll = toLogicalHorizontalOffset(state, newScroll, (_e = event.nativeEvent.contentSize) == null ? void 0 : _e.width);
5658
5713
  }
5714
+ state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
5715
+ state.lastNativeScroll = newScroll;
5716
+ state.lastNativeScrollTime = Date.now();
5659
5717
  if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
5660
5718
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
5661
5719
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
@@ -6281,6 +6339,8 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
6281
6339
  indices.sort(sortAsc);
6282
6340
  return indices;
6283
6341
  }
6342
+
6343
+ // src/utils/getRenderedItem.ts
6284
6344
  function getRenderedItem(ctx, key) {
6285
6345
  var _a3;
6286
6346
  const state = ctx.state;
@@ -6306,7 +6366,7 @@ function getRenderedItem(ctx, key) {
6306
6366
  item,
6307
6367
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
6308
6368
  };
6309
- renderedItem = React3__default.createElement(renderItem, itemProps);
6369
+ renderedItem = renderItem(itemProps);
6310
6370
  }
6311
6371
  return { index, item: data[index], renderedItem };
6312
6372
  }
package/index.native.js CHANGED
@@ -1141,6 +1141,26 @@ function WebAnchoredEndSpace({ horizontal }) {
1141
1141
  const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 };
1142
1142
  return /* @__PURE__ */ React2__namespace.createElement("div", { style }, null);
1143
1143
  }
1144
+ function useLatestRef(value) {
1145
+ const ref = React2__namespace.useRef(value);
1146
+ ref.current = value;
1147
+ return ref;
1148
+ }
1149
+
1150
+ // src/hooks/useStableRenderComponent.tsx
1151
+ function useStableRenderComponent(renderComponent, mapProps) {
1152
+ const renderComponentRef = useLatestRef(renderComponent);
1153
+ const mapPropsRef = useLatestRef(mapProps);
1154
+ return React2__namespace.useMemo(
1155
+ () => React2__namespace.forwardRef(
1156
+ (props, ref) => {
1157
+ var _a3, _b;
1158
+ return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null;
1159
+ }
1160
+ ),
1161
+ [mapPropsRef, renderComponentRef]
1162
+ );
1163
+ }
1144
1164
  var LayoutView = ({ onLayoutChange, refView, ...rest }) => {
1145
1165
  const localRef = React2.useRef(null);
1146
1166
  const ref = refView != null ? refView : localRef;
@@ -1185,14 +1205,11 @@ var ListComponent = typedMemo(function ListComponent2({
1185
1205
  needsOtherAxisSize: ctx.state.needsOtherAxisSize,
1186
1206
  otherAxisSize
1187
1207
  });
1188
- const ScrollComponent = React2.useMemo(() => {
1189
- if (!renderScrollComponent) {
1190
- return ListComponentScrollView;
1191
- }
1192
- return React2__namespace.forwardRef(
1193
- (props, ref) => renderScrollComponent({ ...props, ref })
1194
- );
1195
- }, [renderScrollComponent]);
1208
+ const CustomScrollComponent = useStableRenderComponent(
1209
+ renderScrollComponent,
1210
+ (props, ref) => ({ ...props, ref })
1211
+ );
1212
+ const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView;
1196
1213
  const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent;
1197
1214
  React2.useLayoutEffect(() => {
1198
1215
  if (!ListHeaderComponent) {
@@ -2673,11 +2690,51 @@ function getObservedBootstrapInitialScrollOffset(state) {
2673
2690
  const observedOffset = (_b = (_a3 = state.refScroller.current) == null ? void 0 : _a3.getCurrentScrollOffset) == null ? void 0 : _b.call(_a3);
2674
2691
  return typeof observedOffset === "number" && Number.isFinite(observedOffset) ? observedOffset : (_d = (_c = state.scrollPending) != null ? _c : state.scroll) != null ? _d : 0;
2675
2692
  }
2693
+ function getPreservedEndAnchorOffsetDiff(ctx) {
2694
+ var _a3;
2695
+ const state = ctx.state;
2696
+ const initialScroll = state.initialScroll;
2697
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || !initialScroll || initialScroll.viewPosition !== 1 || state.props.data.length === 0 || isOffsetInitialScrollSession(state)) {
2698
+ return;
2699
+ }
2700
+ const currentOffset = typeof state.lastNativeScroll === "number" && Number.isFinite(state.lastNativeScroll) ? state.lastNativeScroll : getObservedBootstrapInitialScrollOffset(state);
2701
+ return resolveInitialScrollOffset(ctx, initialScroll) - currentOffset;
2702
+ }
2703
+ function schedulePreservedEndAnchorCorrection(ctx) {
2704
+ if (getPreservedEndAnchorOffsetDiff(ctx) === void 0) {
2705
+ return false;
2706
+ }
2707
+ const correction = {};
2708
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
2709
+ return true;
2710
+ }
2711
+ function schedulePreservedEndAnchorCorrectionFrame(ctx, correction) {
2712
+ const state = ctx.state;
2713
+ state.preservedEndAnchorCorrection = correction;
2714
+ requestAnimationFrame(() => {
2715
+ var _a3;
2716
+ const activeCorrection = state.preservedEndAnchorCorrection;
2717
+ if (activeCorrection !== correction) {
2718
+ return;
2719
+ }
2720
+ const offsetDiff = getPreservedEndAnchorOffsetDiff(ctx);
2721
+ if (offsetDiff === void 0 || Math.abs(offsetDiff) <= DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2722
+ state.preservedEndAnchorCorrection = void 0;
2723
+ return;
2724
+ }
2725
+ const hasObservedNativeScrollAfterRequest = !activeCorrection.lastRequestTime || ((_a3 = state.lastNativeScrollTime) != null ? _a3 : 0) > activeCorrection.lastRequestTime;
2726
+ if (hasObservedNativeScrollAfterRequest) {
2727
+ activeCorrection.lastRequestTime = Date.now();
2728
+ requestAdjust(ctx, offsetDiff);
2729
+ }
2730
+ schedulePreservedEndAnchorCorrectionFrame(ctx, correction);
2731
+ });
2732
+ }
2676
2733
  function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) {
2677
2734
  var _a3, _b;
2678
2735
  const state = ctx.state;
2679
2736
  const initialScroll = state.initialScroll;
2680
- if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1) {
2737
+ if (!state.didFinishInitialScroll || ((_a3 = state.scrollingTo) == null ? void 0 : _a3.isInitialScroll) || (initialScroll == null ? void 0 : initialScroll.viewPosition) !== 1 || state.preservedEndAnchorCorrection) {
2681
2738
  return;
2682
2739
  }
2683
2740
  if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) {
@@ -2834,7 +2891,7 @@ function handleBootstrapInitialScrollFooterLayout(ctx, options) {
2834
2891
  }
2835
2892
  }
2836
2893
  function handleBootstrapInitialScrollLayoutChange(ctx) {
2837
- var _a3, _b, _c, _d;
2894
+ var _a3, _b, _c;
2838
2895
  const state = ctx.state;
2839
2896
  const initialScroll = state.initialScroll;
2840
2897
  const bootstrapInitialScroll = getBootstrapInitialScrollSession(state);
@@ -2845,7 +2902,9 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2845
2902
  const currentOffset = scrollingTo ? (_b = scrollingTo.targetOffset) != null ? _b : scrollingTo.offset : getObservedBootstrapInitialScrollOffset(state);
2846
2903
  const offsetDiff = resolvedOffset - currentOffset;
2847
2904
  if (Math.abs(offsetDiff) > DEFAULT_BOOTSTRAP_REVEAL_EPSILON) {
2848
- if (scrollingTo) {
2905
+ if (state.didFinishInitialScroll) {
2906
+ schedulePreservedEndAnchorCorrection(ctx);
2907
+ } else if (scrollingTo) {
2849
2908
  const existingWatchdog = initialScrollWatchdog.get(state);
2850
2909
  scrollingTo.offset = resolvedOffset;
2851
2910
  scrollingTo.targetOffset = resolvedOffset;
@@ -2858,14 +2917,8 @@ function handleBootstrapInitialScrollLayoutChange(ctx) {
2858
2917
  startScroll: (_c = existingWatchdog == null ? void 0 : existingWatchdog.startScroll) != null ? _c : state.scroll,
2859
2918
  targetOffset: resolvedOffset
2860
2919
  });
2920
+ requestAdjust(ctx, offsetDiff);
2861
2921
  }
2862
- requestAdjust(ctx, offsetDiff);
2863
- if (state.didFinishInitialScroll) {
2864
- (_d = state.triggerCalculateItemsInView) == null ? void 0 : _d.call(state, { forceFullItemPositions: true });
2865
- }
2866
- }
2867
- if (state.didFinishInitialScroll) {
2868
- clearFinishedViewportRetargetableInitialScroll(state);
2869
2922
  }
2870
2923
  } else {
2871
2924
  rearmBootstrapInitialScroll(ctx, {
@@ -2982,7 +3035,10 @@ function retargetActiveInitialScrollAtEnd(ctx) {
2982
3035
  var _a3;
2983
3036
  const state = ctx.state;
2984
3037
  const initialScroll = state.initialScroll;
2985
- if (!initialScroll || state.didFinishInitialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
3038
+ if (state.didFinishInitialScroll) {
3039
+ return schedulePreservedEndAnchorCorrection(ctx);
3040
+ }
3041
+ if (!initialScroll || ((_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind) === "offset" || initialScroll.viewPosition !== 1 || state.props.data.length === 0) {
2986
3042
  return false;
2987
3043
  }
2988
3044
  return advanceCurrentInitialScrollSession(ctx, { forceScroll: true });
@@ -4996,7 +5052,7 @@ function cloneScrollEvent(event) {
4996
5052
  };
4997
5053
  }
4998
5054
  function onScroll(ctx, event) {
4999
- var _a3, _b, _c, _d, _e;
5055
+ var _a3, _b, _c, _d, _e, _f;
5000
5056
  const state = ctx.state;
5001
5057
  const { scrollProcessingEnabled } = state;
5002
5058
  if (scrollProcessingEnabled === false) {
@@ -5018,6 +5074,13 @@ function onScroll(ctx, event) {
5018
5074
  if (state.props.horizontal) {
5019
5075
  newScroll = toLogicalHorizontalOffset(state, newScroll, (_e = event.nativeEvent.contentSize) == null ? void 0 : _e.width);
5020
5076
  }
5077
+ const isFinishedEndInitialScroll = state.didFinishInitialScroll && ((_f = state.initialScroll) == null ? void 0 : _f.viewPosition) === 1 && state.scroll > state.scrollLength;
5078
+ const shouldIgnoreNegativeInsetChange = Platform.OS !== "web" && insetChanged && newScroll < 0 && isFinishedEndInitialScroll;
5079
+ if (shouldIgnoreNegativeInsetChange) {
5080
+ return;
5081
+ }
5082
+ state.lastNativeScroll = newScroll;
5083
+ state.lastNativeScrollTime = Date.now();
5021
5084
  if (state.scrollingTo && state.scrollingTo.offset >= newScroll) {
5022
5085
  const maxOffset = clampScrollOffset(ctx, newScroll, state.scrollingTo);
5023
5086
  if (newScroll !== maxOffset && Math.abs(newScroll - maxOffset) > 1) {
@@ -5658,6 +5721,8 @@ function getAlwaysRenderIndices(config, data, keyExtractor, anchoredEndSpaceAnch
5658
5721
  indices.sort(sortAsc);
5659
5722
  return indices;
5660
5723
  }
5724
+
5725
+ // src/utils/getRenderedItem.ts
5661
5726
  function getRenderedItem(ctx, key) {
5662
5727
  var _a3;
5663
5728
  const state = ctx.state;
@@ -5683,7 +5748,7 @@ function getRenderedItem(ctx, key) {
5683
5748
  item,
5684
5749
  type: getItemType ? (_a3 = getItemType(item, index)) != null ? _a3 : "" : ""
5685
5750
  };
5686
- renderedItem = React2__namespace.default.createElement(renderItem, itemProps);
5751
+ renderedItem = renderItem(itemProps);
5687
5752
  }
5688
5753
  return { index, item: data[index], renderedItem };
5689
5754
  }