@legendapp/list 3.0.0-beta.43 → 3.0.0-beta.45

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/reanimated.d.ts CHANGED
@@ -1,18 +1,30 @@
1
1
  import * as React from 'react';
2
2
  import { Key, ComponentProps } from 'react';
3
- import Reanimated from 'react-native-reanimated';
3
+ import Reanimated, { SharedValue } from 'react-native-reanimated';
4
4
  import { ScrollViewProps, NativeSyntheticEvent as NativeSyntheticEvent$1, NativeScrollEvent as NativeScrollEvent$1, ScrollView, StyleProp as StyleProp$1, ViewStyle as ViewStyle$1, ScrollViewComponent, ScrollResponderMixin, Insets as Insets$1 } from 'react-native';
5
5
 
6
- type ListenerType = "activeStickyIndex" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
7
- type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
6
+ interface MaintainVisibleContentPositionNormalized<ItemT = any> {
7
+ data: boolean;
8
+ size: boolean;
9
+ shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
10
+ }
11
+
12
+ type ListenerType = "activeStickyIndex" | "anchoredEndSpaceSize" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
13
+ type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
8
14
  type ListenerTypeValueMap = {
9
15
  activeStickyIndex: number;
16
+ anchoredEndSpaceSize: number;
10
17
  animatedScrollY: any;
11
18
  debugComputedScroll: number;
12
19
  debugRawScroll: number;
13
20
  extraData: any;
14
21
  footerSize: number;
15
22
  headerSize: number;
23
+ isAtEnd: boolean;
24
+ isAtStart: boolean;
25
+ isNearEnd: boolean;
26
+ isNearStart: boolean;
27
+ isWithinMaintainScrollAtEndThreshold: boolean;
16
28
  lastItemKeys: string[];
17
29
  lastPositionUpdate: number;
18
30
  maintainVisibleContentPosition: MaintainVisibleContentPositionNormalized;
@@ -114,6 +126,11 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
114
126
  * Style applied to each column's wrapper view.
115
127
  */
116
128
  columnWrapperStyle?: ColumnWrapperStyle;
129
+ /**
130
+ * Version token that forces the list to treat data as updated even when the array reference is stable.
131
+ * Increment or change this when mutating the data array in place.
132
+ */
133
+ dataVersion?: Key;
117
134
  /**
118
135
  * Distance in pixels to pre-render items ahead of the visible area.
119
136
  * @default 250
@@ -137,33 +154,35 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
137
154
  * Extra data to trigger re-rendering when changed.
138
155
  */
139
156
  extraData?: any;
140
- /**
141
- * Version token that forces the list to treat data as updated even when the array reference is stable.
142
- * Increment or change this when mutating the data array in place.
143
- */
144
- dataVersion?: Key;
145
157
  /**
146
158
  * In case you have distinct item sizes, you can provide a function to get the size of an item.
147
- * Use instead of FlatList's getItemLayout or FlashList overrideItemLayout if you want to have accurate initialScrollOffset, you should provide this function
148
159
  */
149
160
  getEstimatedItemSize?: (item: ItemT, index: number, type: TItemType) => number;
150
161
  /**
151
- * Customize layout for multi-column lists, such as allowing items to span multiple columns.
152
- * Similar to FlashList's overrideItemLayout.
162
+ * In case items always have a fixed size, you can provide a function to return it.
153
163
  */
154
- overrideItemLayout?: (layout: {
155
- span?: number;
156
- }, item: ItemT, index: number, maxColumns: number, extraData?: any) => void;
164
+ getFixedItemSize?: (item: ItemT, index: number, type: TItemType) => number | undefined;
165
+ /**
166
+ * Returns a stable item type used for pooling and size estimation.
167
+ */
168
+ getItemType?: (item: ItemT, index: number) => TItemType;
169
+ /**
170
+ * Component to render between items, receiving the leading item as prop.
171
+ */
172
+ ItemSeparatorComponent?: React.ComponentType<{
173
+ leadingItem: ItemT;
174
+ }>;
157
175
  /**
158
176
  * Ratio of initial container pool size to data length (e.g., 0.5 for half).
159
177
  * @default 2
160
178
  */
161
179
  initialContainerPoolRatio?: number | undefined;
162
180
  /**
163
- * Initial scroll position in pixels.
164
- * @default 0
181
+ * When true, the list initializes scrolled to the last item.
182
+ * Overrides `initialScrollIndex` and `initialScrollOffset` when data is available.
183
+ * @default false
165
184
  */
166
- initialScrollOffset?: number;
185
+ initialScrollAtEnd?: boolean;
167
186
  /**
168
187
  * Index to scroll to initially.
169
188
  * @default 0
@@ -174,17 +193,14 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
174
193
  viewPosition?: number | undefined;
175
194
  };
176
195
  /**
177
- * When true, the list initializes scrolled to the last item.
178
- * Overrides `initialScrollIndex` and `initialScrollOffset` when data is available.
179
- * @default false
196
+ * Initial scroll position in pixels.
197
+ * @default 0
180
198
  */
181
- initialScrollAtEnd?: boolean;
199
+ initialScrollOffset?: number;
182
200
  /**
183
- * Component to render between items, receiving the leading item as prop.
201
+ * Custom equality function to detect semantically unchanged items.
184
202
  */
185
- ItemSeparatorComponent?: React.ComponentType<{
186
- leadingItem: ItemT;
187
- }>;
203
+ itemsAreEqual?: (itemPrevious: ItemT, item: ItemT, index: number, data: readonly ItemT[]) => boolean;
188
204
  /**
189
205
  * Function to extract a unique key for each item.
190
206
  */
@@ -230,10 +246,9 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
230
246
  */
231
247
  maintainVisibleContentPosition?: boolean | MaintainVisibleContentPositionConfig<ItemT>;
232
248
  /**
233
- * Web only: when true, listens to window/body scrolling instead of rendering a scrollable list container.
234
- * @default false
249
+ * Keeps an item visually anchored to the start by adding trailing space when the content below it underflows.
235
250
  */
236
- useWindowScroll?: boolean;
251
+ anchoredEndSpace?: AnchoredEndSpaceConfig;
237
252
  /**
238
253
  * Number of columns to render items in.
239
254
  * @default 1
@@ -260,6 +275,12 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
260
275
  itemKey: string;
261
276
  itemData: ItemT;
262
277
  }) => void;
278
+ /**
279
+ * Called after the initial render work completes.
280
+ */
281
+ onLoad?: (info: {
282
+ elapsedTimeInMs: number;
283
+ }) => void;
263
284
  /**
264
285
  * Called when list layout metrics change.
265
286
  */
@@ -268,6 +289,9 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
268
289
  * Function to call when the user pulls to refresh.
269
290
  */
270
291
  onRefresh?: () => void;
292
+ /**
293
+ * Called when the list scrolls.
294
+ */
271
295
  onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
272
296
  /**
273
297
  * Called when scrolling reaches the start within onStartReachedThreshold.
@@ -291,6 +315,12 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
291
315
  * Called when the viewability of items changes.
292
316
  */
293
317
  onViewableItemsChanged?: OnViewableItemsChanged<ItemT> | undefined;
318
+ /**
319
+ * Customize layout for multi-column lists, such as allowing items to span multiple columns.
320
+ */
321
+ overrideItemLayout?: (layout: {
322
+ span?: number;
323
+ }, item: ItemT, index: number, maxColumns: number, extraData?: any) => void;
294
324
  /**
295
325
  * Offset in pixels for the refresh indicator.
296
326
  * @default 0
@@ -316,6 +346,10 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
316
346
  * @default (props) => <ScrollView {...props} />
317
347
  */
318
348
  renderScrollComponent?: (props: any) => React.ReactElement | null;
349
+ /**
350
+ * Array of item indices to use as snap points.
351
+ */
352
+ snapToIndices?: number[];
319
353
  /**
320
354
  * This will log a suggested estimatedItemSize.
321
355
  * @required
@@ -330,15 +364,6 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
330
364
  * Pairs of viewability configs and their callbacks for tracking visibility.
331
365
  */
332
366
  viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs<ItemT> | undefined;
333
- /**
334
- * If true, delays rendering until initial layout is complete.
335
- * @default false
336
- */
337
- waitForInitialLayout?: boolean;
338
- onLoad?: (info: {
339
- elapsedTimeInMs: number;
340
- }) => void;
341
- snapToIndices?: number[];
342
367
  /**
343
368
  * Array of child indices determining which children get docked to the top of the screen when scrolling.
344
369
  * For example, passing stickyHeaderIndices={[0]} will cause the first child to be fixed to the top of the scroll view.
@@ -355,9 +380,11 @@ interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
355
380
  * @default undefined
356
381
  */
357
382
  stickyHeaderConfig?: StickyHeaderConfig;
358
- getItemType?: (item: ItemT, index: number) => TItemType;
359
- getFixedItemSize?: (item: ItemT, index: number, type: TItemType) => number | undefined;
360
- itemsAreEqual?: (itemPrevious: ItemT, item: ItemT, index: number, data: readonly ItemT[]) => boolean;
383
+ /**
384
+ * Web only: when true, listens to window/body scrolling instead of rendering a scrollable list container.
385
+ * @default false
386
+ */
387
+ useWindowScroll?: boolean;
361
388
  }
362
389
  type LegendListPropsBase<ItemT, TScrollViewProps = Record<string, any>, TItemType extends string | undefined = string | undefined> = BaseScrollViewProps<TScrollViewProps> & LegendListSpecificProps<ItemT, TItemType> & (DataModeProps<ItemT, TItemType> | ChildrenModeProps);
363
390
  interface MaintainVisibleContentPositionConfig<ItemT = any> {
@@ -365,10 +392,12 @@ interface MaintainVisibleContentPositionConfig<ItemT = any> {
365
392
  size?: boolean;
366
393
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
367
394
  }
368
- interface MaintainVisibleContentPositionNormalized<ItemT = any> {
369
- data: boolean;
370
- size: boolean;
371
- shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
395
+ interface AnchoredEndSpaceConfig {
396
+ anchorIndex: number;
397
+ anchorOffset?: number;
398
+ anchorMaxSize?: number;
399
+ includeInEndInset?: boolean;
400
+ onSizeChanged?: (size: number) => void;
372
401
  }
373
402
  interface StickyHeaderConfig {
374
403
  /**
@@ -431,8 +460,11 @@ type LegendListState = {
431
460
  endBuffered: number;
432
461
  isAtEnd: boolean;
433
462
  isAtStart: boolean;
463
+ isNearEnd: boolean;
464
+ isNearStart: boolean;
434
465
  isEndReached: boolean;
435
466
  isStartReached: boolean;
467
+ isWithinMaintainScrollAtEndThreshold: boolean;
436
468
  listen: <T extends LegendListListenerType>(listenerType: T, callback: (value: ListenerTypeValueMap[T]) => void) => () => void;
437
469
  listenToPosition: (key: string, callback: (value: number) => void) => () => void;
438
470
  positionAtIndex: (index: number) => number;
@@ -571,10 +603,15 @@ interface ViewabilityConfigCallbackPair<ItemT = any> {
571
603
  viewabilityConfig: ViewabilityConfig;
572
604
  }
573
605
  type ViewabilityConfigCallbackPairs<ItemT> = ViewabilityConfigCallbackPair<ItemT>[];
574
- type OnViewableItemsChanged<ItemT> = ((info: {
575
- viewableItems: Array<ViewToken<ItemT>>;
606
+ interface OnViewableItemsChangedInfo<ItemT> {
576
607
  changed: Array<ViewToken<ItemT>>;
577
- }) => void) | null;
608
+ end: number;
609
+ endBuffered: number;
610
+ start: number;
611
+ startBuffered: number;
612
+ viewableItems: Array<ViewToken<ItemT>>;
613
+ }
614
+ type OnViewableItemsChanged<ItemT> = ((info: OnViewableItemsChangedInfo<ItemT>) => void) | null;
578
615
  interface ViewabilityConfig {
579
616
  /**
580
617
  * A unique ID to identify this viewability config
@@ -605,7 +642,7 @@ interface ViewabilityConfig {
605
642
  waitForInteraction?: boolean | undefined;
606
643
  }
607
644
 
608
- type LegendListPropsOverrides<ItemT, TItemType extends string | undefined> = Omit<LegendListPropsBase<ItemT, ScrollViewProps, TItemType>, "onScroll" | "refScrollView" | "renderScrollComponent" | "ListHeaderComponentStyle" | "ListFooterComponentStyle"> & {
645
+ type LegendListPropsOverrides<ItemT, TItemType extends string | undefined> = Omit<LegendListPropsBase<ItemT, ScrollViewProps, TItemType>, "anchoredEndSpace" | "onScroll" | "refScrollView" | "renderScrollComponent" | "ListHeaderComponentStyle" | "ListFooterComponentStyle"> & {
609
646
  onScroll?: (event: NativeSyntheticEvent$1<NativeScrollEvent$1>) => void;
610
647
  refScrollView?: React.Ref<ScrollView>;
611
648
  renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement<ScrollViewProps>;
@@ -623,9 +660,19 @@ type KeysToOmit = "getEstimatedItemSize" | "getFixedItemSize" | "getItemType" |
623
660
  type PropsBase<ItemT> = LegendListProps<ItemT>;
624
661
  type AnimatedScrollView = React.ElementRef<typeof Reanimated.ScrollView>;
625
662
  type ReanimatedLayoutAnimation = ComponentProps<typeof Reanimated.View>["layout"];
663
+ interface AnimatedLegendListSharedValues {
664
+ activeStickyIndex?: SharedValue<number>;
665
+ isAtEnd?: SharedValue<boolean>;
666
+ isAtStart?: SharedValue<boolean>;
667
+ isNearEnd?: SharedValue<boolean>;
668
+ isNearStart?: SharedValue<boolean>;
669
+ isWithinMaintainScrollAtEndThreshold?: SharedValue<boolean>;
670
+ scrollOffset?: SharedValue<number>;
671
+ }
626
672
  interface AnimatedLegendListPropsBase<ItemT> extends Omit<PropsBase<ItemT>, KeysToOmit | "refScrollView"> {
627
673
  animatedProps?: ComponentProps<typeof Reanimated.ScrollView>["animatedProps"];
628
674
  refScrollView?: React.Ref<AnimatedScrollView>;
675
+ sharedValues?: AnimatedLegendListSharedValues;
629
676
  /**
630
677
  * Reanimated layout transition applied to each item container position view.
631
678
  * Example: `LinearTransition.duration(280)`.
@@ -639,4 +686,4 @@ type AnimatedLegendListDefinition = <ItemT>(props: AnimatedLegendListProps<ItemT
639
686
  }) => React.ReactElement | null;
640
687
  declare const AnimatedLegendList: AnimatedLegendListDefinition;
641
688
 
642
- export { AnimatedLegendList, type AnimatedLegendListProps, type AnimatedLegendListPropsBase };
689
+ export { AnimatedLegendList, type AnimatedLegendListProps, type AnimatedLegendListPropsBase, type AnimatedLegendListSharedValues };
package/reanimated.js CHANGED
@@ -29,9 +29,16 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
29
29
  var Reanimated__default = /*#__PURE__*/_interopDefault(Reanimated);
30
30
 
31
31
  // src/integrations/reanimated.tsx
32
- var { POSITION_OUT_OF_VIEW, IsNewArchitecture, useArr$, useCombinedRef, getComponent } = reactNative.internal;
32
+ var {
33
+ POSITION_OUT_OF_VIEW,
34
+ IsNewArchitecture,
35
+ getStickyPushLimit,
36
+ typedMemo,
37
+ useArr$,
38
+ useCombinedRef,
39
+ getComponent
40
+ } = reactNative.internal;
33
41
  var { peek$, useStateContext } = reactNative.internal;
34
- var typedMemo = React.memo;
35
42
  var ReanimatedScrollBridge = typedMemo(function ReanimatedScrollBridgeComponent({
36
43
  forwardedRef,
37
44
  scrollOffset,
@@ -67,18 +74,27 @@ var StickyOverlay = typedMemo(function StickyOverlayComponent({ stickyHeaderConf
67
74
  });
68
75
  var ReanimatedPositionViewSticky = typedMemo(function ReanimatedPositionViewStickyComponent(props) {
69
76
  var _a;
77
+ const ctx = useStateContext();
70
78
  const { id, horizontal, style, refView, stickyScrollOffset, stickyHeaderConfig, index, children, ...rest } = props;
71
- const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0] = useArr$([
79
+ const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0, itemKey, _totalSize = 0] = useArr$([
72
80
  `containerPosition${id}`,
73
81
  "headerSize",
74
- "stylePaddingTop"
82
+ "stylePaddingTop",
83
+ `containerItemKey${id}`,
84
+ "totalSize"
75
85
  ]);
86
+ const pushLimit = React__namespace.useMemo(
87
+ () => getStickyPushLimit(ctx.state, index, itemKey),
88
+ [ctx.state, index, itemKey, _totalSize]
89
+ );
76
90
  const stickyOffset = (_a = stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset) != null ? _a : 0;
77
91
  const stickyStart = position + headerSize + stylePaddingTop - stickyOffset;
78
92
  const transformStyle = Reanimated.useAnimatedStyle(() => {
79
93
  const delta = Math.max(0, stickyScrollOffset.value - stickyStart);
80
- return horizontal ? { transform: [{ translateX: position + delta }] } : { transform: [{ translateY: position + delta }] };
81
- }, [horizontal, position, stickyStart]);
94
+ const stickyPosition = position + delta;
95
+ const resolvedPosition = pushLimit !== void 0 ? Math.min(stickyPosition, pushLimit) : stickyPosition;
96
+ return horizontal ? { transform: [{ translateX: resolvedPosition }] } : { transform: [{ translateY: resolvedPosition }] };
97
+ }, [horizontal, position, pushLimit, stickyStart]);
82
98
  const viewStyle = React__namespace.useMemo(
83
99
  () => [style, { zIndex: index + 1e3 }, transformStyle],
84
100
  [index, style, transformStyle]
@@ -116,18 +132,71 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(
116
132
  children
117
133
  );
118
134
  });
135
+ function setSharedValueValue(sharedValue, value) {
136
+ if (!sharedValue) {
137
+ return;
138
+ }
139
+ const sharedValueWithMethods = sharedValue;
140
+ const currentValue = typeof sharedValueWithMethods.get === "function" ? sharedValueWithMethods.get() : sharedValueWithMethods.value;
141
+ if (currentValue === value) {
142
+ return;
143
+ }
144
+ if (typeof sharedValueWithMethods.set === "function") {
145
+ sharedValueWithMethods.set(value);
146
+ } else {
147
+ sharedValueWithMethods.value = value;
148
+ }
149
+ }
150
+ function useAnimatedLegendListSharedValuesSync(legendList, sharedValues) {
151
+ React__namespace.useEffect(() => {
152
+ if (!legendList || !sharedValues) {
153
+ return;
154
+ }
155
+ const state = legendList.getState();
156
+ setSharedValueValue(sharedValues.activeStickyIndex, state.activeStickyIndex);
157
+ setSharedValueValue(sharedValues.isAtEnd, state.isAtEnd);
158
+ setSharedValueValue(sharedValues.isAtStart, state.isAtStart);
159
+ setSharedValueValue(sharedValues.isNearEnd, state.isNearEnd);
160
+ setSharedValueValue(sharedValues.isNearStart, state.isNearStart);
161
+ setSharedValueValue(
162
+ sharedValues.isWithinMaintainScrollAtEndThreshold,
163
+ state.isWithinMaintainScrollAtEndThreshold
164
+ );
165
+ setSharedValueValue(sharedValues.scrollOffset, state.scroll);
166
+ const unsubscribers = [
167
+ sharedValues.activeStickyIndex ? state.listen(
168
+ "activeStickyIndex",
169
+ (value) => setSharedValueValue(sharedValues.activeStickyIndex, value)
170
+ ) : void 0,
171
+ sharedValues.isAtEnd ? state.listen("isAtEnd", (value) => setSharedValueValue(sharedValues.isAtEnd, value)) : void 0,
172
+ sharedValues.isAtStart ? state.listen("isAtStart", (value) => setSharedValueValue(sharedValues.isAtStart, value)) : void 0,
173
+ sharedValues.isNearEnd ? state.listen("isNearEnd", (value) => setSharedValueValue(sharedValues.isNearEnd, value)) : void 0,
174
+ sharedValues.isNearStart ? state.listen("isNearStart", (value) => setSharedValueValue(sharedValues.isNearStart, value)) : void 0,
175
+ sharedValues.isWithinMaintainScrollAtEndThreshold ? state.listen(
176
+ "isWithinMaintainScrollAtEndThreshold",
177
+ (value) => setSharedValueValue(sharedValues.isWithinMaintainScrollAtEndThreshold, value)
178
+ ) : void 0
179
+ ];
180
+ return () => {
181
+ for (const unsubscribe of unsubscribers) {
182
+ unsubscribe == null ? void 0 : unsubscribe();
183
+ }
184
+ };
185
+ }, [legendList, sharedValues]);
186
+ }
119
187
  var LegendListForwardedRef = typedMemo(
120
188
  // biome-ignore lint/nursery/noShadow: const function name shadowing is intentional
121
189
  React__namespace.forwardRef(function LegendListForwardedRef2(props, ref) {
122
- const { itemLayoutAnimation, recycleItems, refLegendList, renderScrollComponent, ...rest } = props;
190
+ var _a;
191
+ const { itemLayoutAnimation, recycleItems, refLegendList, renderScrollComponent, sharedValues, ...rest } = props;
123
192
  const refFn = React.useCallback(
124
193
  (r) => {
125
194
  refLegendList(r);
126
195
  },
127
196
  [refLegendList]
128
197
  );
129
- const stickyScrollOffset = Reanimated.useSharedValue(0);
130
- const shouldUseReanimatedScrollView = IsNewArchitecture;
198
+ const internalScrollOffset = Reanimated.useSharedValue(0);
199
+ const scrollOffset = (_a = sharedValues == null ? void 0 : sharedValues.scrollOffset) != null ? _a : internalScrollOffset;
131
200
  const renderScrollComponentForBridge = React__namespace.useMemo(
132
201
  () => renderScrollComponent ? (scrollViewProps) => renderScrollComponent(scrollViewProps) : void 0,
133
202
  [renderScrollComponent]
@@ -141,17 +210,17 @@ var LegendListForwardedRef = typedMemo(
141
210
  ...restScrollViewProps,
142
211
  forwardedRef,
143
212
  renderScrollComponent: renderScrollComponentForBridge,
144
- scrollOffset: stickyScrollOffset
213
+ scrollOffset
145
214
  }
146
215
  );
147
216
  },
148
- [renderScrollComponentForBridge, stickyScrollOffset]
217
+ [renderScrollComponentForBridge, scrollOffset]
149
218
  );
150
219
  const stickyPositionComponentInternal = React__namespace.useMemo(
151
220
  () => function StickyPositionComponent(stickyProps) {
152
- return /* @__PURE__ */ React__namespace.createElement(ReanimatedPositionViewSticky, { ...stickyProps, stickyScrollOffset });
221
+ return /* @__PURE__ */ React__namespace.createElement(ReanimatedPositionViewSticky, { ...stickyProps, stickyScrollOffset: scrollOffset });
153
222
  },
154
- [stickyScrollOffset]
223
+ [scrollOffset]
155
224
  );
156
225
  const itemLayoutAnimationRef = React__namespace.useRef(itemLayoutAnimation);
157
226
  itemLayoutAnimationRef.current = itemLayoutAnimation;
@@ -175,10 +244,10 @@ var LegendListForwardedRef = typedMemo(
175
244
  ...rest,
176
245
  positionComponentInternal,
177
246
  recycleItems,
178
- ...shouldUseReanimatedScrollView ? {
247
+ ...{
179
248
  renderScrollComponent: renderReanimatedScrollComponent,
180
- stickyPositionComponentInternal
181
- } : {}
249
+ ...IsNewArchitecture ? { stickyPositionComponentInternal } : {}
250
+ }
182
251
  };
183
252
  return /* @__PURE__ */ React__namespace.createElement(reactNative.LegendList, { ref: refFn, refScrollView: ref, ...legendListProps });
184
253
  })
@@ -189,9 +258,15 @@ var AnimatedLegendList = typedMemo(
189
258
  // biome-ignore lint/nursery/noShadow: const function name shadowing is intentional
190
259
  React__namespace.forwardRef(function AnimatedLegendList2(props, ref) {
191
260
  const { refScrollView, ...rest } = props;
192
- const { animatedProps } = props;
193
- const refLegendList = React__namespace.useRef(null);
194
- const combinedRef = useCombinedRef(refLegendList, ref);
261
+ const { animatedProps, sharedValues } = props;
262
+ const [legendList, setLegendList] = React__namespace.useState(null);
263
+ const combinedRef = useCombinedRef(
264
+ React__namespace.useCallback((instance) => {
265
+ setLegendList((prev) => prev === instance ? prev : instance);
266
+ }, []),
267
+ ref
268
+ );
269
+ useAnimatedLegendListSharedValuesSync(legendList, sharedValues);
195
270
  const forwardedProps = {
196
271
  ...rest,
197
272
  animatedPropsInternal: animatedProps,
package/reanimated.mjs CHANGED
@@ -1,13 +1,20 @@
1
1
  import * as React from 'react';
2
- import { useCallback, memo } from 'react';
2
+ import { useCallback } from 'react';
3
3
  import { View } from 'react-native';
4
4
  import Reanimated, { useAnimatedRef, useScrollViewOffset, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
5
5
  import { internal, LegendList } from '@legendapp/list/react-native';
6
6
 
7
7
  // src/integrations/reanimated.tsx
8
- var { POSITION_OUT_OF_VIEW, IsNewArchitecture, useArr$, useCombinedRef, getComponent } = internal;
8
+ var {
9
+ POSITION_OUT_OF_VIEW,
10
+ IsNewArchitecture,
11
+ getStickyPushLimit,
12
+ typedMemo,
13
+ useArr$,
14
+ useCombinedRef,
15
+ getComponent
16
+ } = internal;
9
17
  var { peek$, useStateContext } = internal;
10
- var typedMemo = memo;
11
18
  var ReanimatedScrollBridge = typedMemo(function ReanimatedScrollBridgeComponent({
12
19
  forwardedRef,
13
20
  scrollOffset,
@@ -43,18 +50,27 @@ var StickyOverlay = typedMemo(function StickyOverlayComponent({ stickyHeaderConf
43
50
  });
44
51
  var ReanimatedPositionViewSticky = typedMemo(function ReanimatedPositionViewStickyComponent(props) {
45
52
  var _a;
53
+ const ctx = useStateContext();
46
54
  const { id, horizontal, style, refView, stickyScrollOffset, stickyHeaderConfig, index, children, ...rest } = props;
47
- const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0] = useArr$([
55
+ const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0, itemKey, _totalSize = 0] = useArr$([
48
56
  `containerPosition${id}`,
49
57
  "headerSize",
50
- "stylePaddingTop"
58
+ "stylePaddingTop",
59
+ `containerItemKey${id}`,
60
+ "totalSize"
51
61
  ]);
62
+ const pushLimit = React.useMemo(
63
+ () => getStickyPushLimit(ctx.state, index, itemKey),
64
+ [ctx.state, index, itemKey, _totalSize]
65
+ );
52
66
  const stickyOffset = (_a = stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset) != null ? _a : 0;
53
67
  const stickyStart = position + headerSize + stylePaddingTop - stickyOffset;
54
68
  const transformStyle = useAnimatedStyle(() => {
55
69
  const delta = Math.max(0, stickyScrollOffset.value - stickyStart);
56
- return horizontal ? { transform: [{ translateX: position + delta }] } : { transform: [{ translateY: position + delta }] };
57
- }, [horizontal, position, stickyStart]);
70
+ const stickyPosition = position + delta;
71
+ const resolvedPosition = pushLimit !== void 0 ? Math.min(stickyPosition, pushLimit) : stickyPosition;
72
+ return horizontal ? { transform: [{ translateX: resolvedPosition }] } : { transform: [{ translateY: resolvedPosition }] };
73
+ }, [horizontal, position, pushLimit, stickyStart]);
58
74
  const viewStyle = React.useMemo(
59
75
  () => [style, { zIndex: index + 1e3 }, transformStyle],
60
76
  [index, style, transformStyle]
@@ -92,18 +108,71 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent(
92
108
  children
93
109
  );
94
110
  });
111
+ function setSharedValueValue(sharedValue, value) {
112
+ if (!sharedValue) {
113
+ return;
114
+ }
115
+ const sharedValueWithMethods = sharedValue;
116
+ const currentValue = typeof sharedValueWithMethods.get === "function" ? sharedValueWithMethods.get() : sharedValueWithMethods.value;
117
+ if (currentValue === value) {
118
+ return;
119
+ }
120
+ if (typeof sharedValueWithMethods.set === "function") {
121
+ sharedValueWithMethods.set(value);
122
+ } else {
123
+ sharedValueWithMethods.value = value;
124
+ }
125
+ }
126
+ function useAnimatedLegendListSharedValuesSync(legendList, sharedValues) {
127
+ React.useEffect(() => {
128
+ if (!legendList || !sharedValues) {
129
+ return;
130
+ }
131
+ const state = legendList.getState();
132
+ setSharedValueValue(sharedValues.activeStickyIndex, state.activeStickyIndex);
133
+ setSharedValueValue(sharedValues.isAtEnd, state.isAtEnd);
134
+ setSharedValueValue(sharedValues.isAtStart, state.isAtStart);
135
+ setSharedValueValue(sharedValues.isNearEnd, state.isNearEnd);
136
+ setSharedValueValue(sharedValues.isNearStart, state.isNearStart);
137
+ setSharedValueValue(
138
+ sharedValues.isWithinMaintainScrollAtEndThreshold,
139
+ state.isWithinMaintainScrollAtEndThreshold
140
+ );
141
+ setSharedValueValue(sharedValues.scrollOffset, state.scroll);
142
+ const unsubscribers = [
143
+ sharedValues.activeStickyIndex ? state.listen(
144
+ "activeStickyIndex",
145
+ (value) => setSharedValueValue(sharedValues.activeStickyIndex, value)
146
+ ) : void 0,
147
+ sharedValues.isAtEnd ? state.listen("isAtEnd", (value) => setSharedValueValue(sharedValues.isAtEnd, value)) : void 0,
148
+ sharedValues.isAtStart ? state.listen("isAtStart", (value) => setSharedValueValue(sharedValues.isAtStart, value)) : void 0,
149
+ sharedValues.isNearEnd ? state.listen("isNearEnd", (value) => setSharedValueValue(sharedValues.isNearEnd, value)) : void 0,
150
+ sharedValues.isNearStart ? state.listen("isNearStart", (value) => setSharedValueValue(sharedValues.isNearStart, value)) : void 0,
151
+ sharedValues.isWithinMaintainScrollAtEndThreshold ? state.listen(
152
+ "isWithinMaintainScrollAtEndThreshold",
153
+ (value) => setSharedValueValue(sharedValues.isWithinMaintainScrollAtEndThreshold, value)
154
+ ) : void 0
155
+ ];
156
+ return () => {
157
+ for (const unsubscribe of unsubscribers) {
158
+ unsubscribe == null ? void 0 : unsubscribe();
159
+ }
160
+ };
161
+ }, [legendList, sharedValues]);
162
+ }
95
163
  var LegendListForwardedRef = typedMemo(
96
164
  // biome-ignore lint/nursery/noShadow: const function name shadowing is intentional
97
165
  React.forwardRef(function LegendListForwardedRef2(props, ref) {
98
- const { itemLayoutAnimation, recycleItems, refLegendList, renderScrollComponent, ...rest } = props;
166
+ var _a;
167
+ const { itemLayoutAnimation, recycleItems, refLegendList, renderScrollComponent, sharedValues, ...rest } = props;
99
168
  const refFn = useCallback(
100
169
  (r) => {
101
170
  refLegendList(r);
102
171
  },
103
172
  [refLegendList]
104
173
  );
105
- const stickyScrollOffset = useSharedValue(0);
106
- const shouldUseReanimatedScrollView = IsNewArchitecture;
174
+ const internalScrollOffset = useSharedValue(0);
175
+ const scrollOffset = (_a = sharedValues == null ? void 0 : sharedValues.scrollOffset) != null ? _a : internalScrollOffset;
107
176
  const renderScrollComponentForBridge = React.useMemo(
108
177
  () => renderScrollComponent ? (scrollViewProps) => renderScrollComponent(scrollViewProps) : void 0,
109
178
  [renderScrollComponent]
@@ -117,17 +186,17 @@ var LegendListForwardedRef = typedMemo(
117
186
  ...restScrollViewProps,
118
187
  forwardedRef,
119
188
  renderScrollComponent: renderScrollComponentForBridge,
120
- scrollOffset: stickyScrollOffset
189
+ scrollOffset
121
190
  }
122
191
  );
123
192
  },
124
- [renderScrollComponentForBridge, stickyScrollOffset]
193
+ [renderScrollComponentForBridge, scrollOffset]
125
194
  );
126
195
  const stickyPositionComponentInternal = React.useMemo(
127
196
  () => function StickyPositionComponent(stickyProps) {
128
- return /* @__PURE__ */ React.createElement(ReanimatedPositionViewSticky, { ...stickyProps, stickyScrollOffset });
197
+ return /* @__PURE__ */ React.createElement(ReanimatedPositionViewSticky, { ...stickyProps, stickyScrollOffset: scrollOffset });
129
198
  },
130
- [stickyScrollOffset]
199
+ [scrollOffset]
131
200
  );
132
201
  const itemLayoutAnimationRef = React.useRef(itemLayoutAnimation);
133
202
  itemLayoutAnimationRef.current = itemLayoutAnimation;
@@ -151,10 +220,10 @@ var LegendListForwardedRef = typedMemo(
151
220
  ...rest,
152
221
  positionComponentInternal,
153
222
  recycleItems,
154
- ...shouldUseReanimatedScrollView ? {
223
+ ...{
155
224
  renderScrollComponent: renderReanimatedScrollComponent,
156
- stickyPositionComponentInternal
157
- } : {}
225
+ ...IsNewArchitecture ? { stickyPositionComponentInternal } : {}
226
+ }
158
227
  };
159
228
  return /* @__PURE__ */ React.createElement(LegendList, { ref: refFn, refScrollView: ref, ...legendListProps });
160
229
  })
@@ -165,9 +234,15 @@ var AnimatedLegendList = typedMemo(
165
234
  // biome-ignore lint/nursery/noShadow: const function name shadowing is intentional
166
235
  React.forwardRef(function AnimatedLegendList2(props, ref) {
167
236
  const { refScrollView, ...rest } = props;
168
- const { animatedProps } = props;
169
- const refLegendList = React.useRef(null);
170
- const combinedRef = useCombinedRef(refLegendList, ref);
237
+ const { animatedProps, sharedValues } = props;
238
+ const [legendList, setLegendList] = React.useState(null);
239
+ const combinedRef = useCombinedRef(
240
+ React.useCallback((instance) => {
241
+ setLegendList((prev) => prev === instance ? prev : instance);
242
+ }, []),
243
+ ref
244
+ );
245
+ useAnimatedLegendListSharedValuesSync(legendList, sharedValues);
171
246
  const forwardedProps = {
172
247
  ...rest,
173
248
  animatedPropsInternal: animatedProps,