@shopify/flash-list 2.1.0 → 2.2.1

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.
Files changed (54) hide show
  1. package/dist/FlashListProps.d.ts +32 -0
  2. package/dist/FlashListProps.d.ts.map +1 -1
  3. package/dist/native/config/PlatformHelper.android.d.ts +1 -0
  4. package/dist/native/config/PlatformHelper.android.d.ts.map +1 -1
  5. package/dist/native/config/PlatformHelper.android.js +2 -0
  6. package/dist/native/config/PlatformHelper.android.js.map +1 -1
  7. package/dist/native/config/PlatformHelper.d.ts +1 -0
  8. package/dist/native/config/PlatformHelper.d.ts.map +1 -1
  9. package/dist/native/config/PlatformHelper.ios.d.ts +1 -0
  10. package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -1
  11. package/dist/native/config/PlatformHelper.ios.js +2 -0
  12. package/dist/native/config/PlatformHelper.ios.js.map +1 -1
  13. package/dist/native/config/PlatformHelper.js +1 -0
  14. package/dist/native/config/PlatformHelper.js.map +1 -1
  15. package/dist/native/config/PlatformHelper.web.d.ts +1 -0
  16. package/dist/native/config/PlatformHelper.web.d.ts.map +1 -1
  17. package/dist/native/config/PlatformHelper.web.js +1 -0
  18. package/dist/native/config/PlatformHelper.web.js.map +1 -1
  19. package/dist/native/config/versionCheck.d.ts +2 -0
  20. package/dist/native/config/versionCheck.d.ts.map +1 -0
  21. package/dist/native/config/versionCheck.js +5 -0
  22. package/dist/native/config/versionCheck.js.map +1 -0
  23. package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
  24. package/dist/recyclerview/RecyclerView.js +30 -9
  25. package/dist/recyclerview/RecyclerView.js.map +1 -1
  26. package/dist/recyclerview/ViewHolder.d.ts +2 -0
  27. package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
  28. package/dist/recyclerview/ViewHolder.js +4 -2
  29. package/dist/recyclerview/ViewHolder.js.map +1 -1
  30. package/dist/recyclerview/ViewHolderCollection.d.ts +4 -0
  31. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
  32. package/dist/recyclerview/ViewHolderCollection.js +2 -2
  33. package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
  34. package/dist/recyclerview/components/StickyHeaders.d.ts +5 -1
  35. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
  36. package/dist/recyclerview/components/StickyHeaders.js +51 -15
  37. package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
  38. package/dist/recyclerview/hooks/useSecondaryProps.d.ts +2 -0
  39. package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -1
  40. package/dist/recyclerview/hooks/useSecondaryProps.js +16 -1
  41. package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -1
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/src/FlashListProps.ts +43 -0
  45. package/src/native/config/PlatformHelper.android.ts +3 -0
  46. package/src/native/config/PlatformHelper.ios.ts +3 -0
  47. package/src/native/config/PlatformHelper.ts +1 -0
  48. package/src/native/config/PlatformHelper.web.ts +1 -0
  49. package/src/native/config/versionCheck.ts +6 -0
  50. package/src/recyclerview/RecyclerView.tsx +35 -3
  51. package/src/recyclerview/ViewHolder.tsx +6 -1
  52. package/src/recyclerview/ViewHolderCollection.tsx +10 -0
  53. package/src/recyclerview/components/StickyHeaders.tsx +59 -13
  54. package/src/recyclerview/hooks/useSecondaryProps.tsx +23 -0
@@ -365,4 +365,47 @@ export interface FlashListProps<TItem>
365
365
  * Doing set state inside the callback can lead to infinite loops. Make sure FlashList's props are memoized.
366
366
  */
367
367
  onCommitLayoutEffect?: () => void;
368
+
369
+ /**
370
+ * Callback invoked when the currently displayed sticky header changes.
371
+ * Receives the current sticky header index and the previous sticky header index.
372
+ * This is useful for tracking which header is currently stuck at the top while scrolling.
373
+ * The index refers to the position of the item in your data array that's being used as a sticky header.
374
+ */
375
+ onChangeStickyIndex?: (current: number, previous: number) => void;
376
+
377
+ stickyHeaderConfig?:
378
+ | {
379
+ /**
380
+ * If true, the sticky headers will use native driver for animations.
381
+ * @default true
382
+ */
383
+ useNativeDriver?: boolean;
384
+
385
+ /**
386
+ * Offset from the top of the list where sticky headers should stick.
387
+ * This is useful when you have a fixed header or navigation bar at the top of your screen
388
+ * and want sticky headers to appear below it instead of at the very top.
389
+ * @default 0
390
+ */
391
+ offset?: number;
392
+
393
+ /**
394
+ * Component to render behind sticky headers (e.g., a backdrop or blur effect).
395
+ * Renders in front of the scroll view content but behind the sticky header itself.
396
+ * Useful for creating visual separation or effects like backgrounds with blur.
397
+ */
398
+ backdropComponent?:
399
+ | React.ComponentType<any>
400
+ | React.ReactElement
401
+ | null
402
+ | undefined;
403
+
404
+ /**
405
+ * When a sticky header is displayed, the cell associated with it is hidden.
406
+ * @default false
407
+ */
408
+ hideRelatedCell?: boolean;
409
+ }
410
+ | undefined;
368
411
  }
@@ -1,7 +1,10 @@
1
+ import { isRN083OrAbove } from "./versionCheck";
2
+
1
3
  const PlatformConfig = {
2
4
  defaultDrawDistance: 250,
3
5
  supportsOffsetCorrection: true,
4
6
  trackAverageRenderTimeForOffsetProjection: true,
7
+ isRN083OrAbove: isRN083OrAbove(),
5
8
  };
6
9
 
7
10
  export { PlatformConfig };
@@ -1,7 +1,10 @@
1
+ import { isRN083OrAbove } from "./versionCheck";
2
+
1
3
  const PlatformConfig = {
2
4
  defaultDrawDistance: 250,
3
5
  supportsOffsetCorrection: true,
4
6
  trackAverageRenderTimeForOffsetProjection: false,
7
+ isRN083OrAbove: isRN083OrAbove(),
5
8
  };
6
9
 
7
10
  export { PlatformConfig };
@@ -2,6 +2,7 @@ const PlatformConfig = {
2
2
  defaultDrawDistance: 250,
3
3
  supportsOffsetCorrection: false,
4
4
  trackAverageRenderTimeForOffsetProjection: false,
5
+ isRN083OrAbove: true,
5
6
  };
6
7
 
7
8
  export { PlatformConfig };
@@ -2,6 +2,7 @@ const PlatformConfig = {
2
2
  defaultDrawDistance: 500,
3
3
  supportsOffsetCorrection: false,
4
4
  trackAverageRenderTimeForOffsetProjection: false,
5
+ isRN083OrAbove: true,
5
6
  };
6
7
 
7
8
  export { PlatformConfig };
@@ -0,0 +1,6 @@
1
+ import { Platform } from "react-native";
2
+
3
+ const rnVersion = Platform.constants?.reactNativeVersion;
4
+
5
+ export const isRN083OrAbove = () =>
6
+ rnVersion && (rnVersion.major > 0 || rnVersion.minor >= 83);
@@ -84,6 +84,8 @@ const RecyclerViewComponent = <T,>(
84
84
  stickyHeaderIndices,
85
85
  maintainVisibleContentPosition,
86
86
  onCommitLayoutEffect,
87
+ onChangeStickyIndex,
88
+ stickyHeaderConfig,
87
89
  ...rest
88
90
  } = props;
89
91
 
@@ -91,6 +93,13 @@ const RecyclerViewComponent = <T,>(
91
93
 
92
94
  renderTimeTracker.startTracking();
93
95
 
96
+ // Sticky header config
97
+ const stickyHeaderOffset = stickyHeaderConfig?.offset ?? 0;
98
+ const stickyHeaderUseNativeDriver =
99
+ stickyHeaderConfig?.useNativeDriver ?? true;
100
+ const stickyHeaderHideRelatedCell =
101
+ stickyHeaderConfig?.hideRelatedCell ?? false;
102
+
94
103
  // Core refs for managing scroll view, internal view, and child container
95
104
  const scrollViewRef = useRef<CompatScroller>(null);
96
105
  const internalViewRef = useRef<CompatView>(null);
@@ -108,6 +117,7 @@ const RecyclerViewComponent = <T,>(
108
117
  // State for managing layout and render updates
109
118
  const [_, setLayoutTreeId] = useLayoutState(0);
110
119
  const [__, setRenderId] = useState(0);
120
+ const [currentStickyIndex, setCurrentStickyIndex] = useState(-1);
111
121
 
112
122
  // Map to store refs for each item in the list
113
123
  const refHolder = useMemo(
@@ -388,6 +398,7 @@ const RecyclerViewComponent = <T,>(
388
398
  renderFooter,
389
399
  renderEmpty,
390
400
  CompatScrollView,
401
+ renderStickyHeaderBackdrop,
391
402
  } = useSecondaryProps(props);
392
403
 
393
404
  if (
@@ -408,15 +419,23 @@ const RecyclerViewComponent = <T,>(
408
419
  if (horizontal) {
409
420
  throw new Error(ErrorMessages.stickyHeadersNotSupportedForHorizontal);
410
421
  }
422
+
411
423
  return (
412
424
  <StickyHeaders
413
425
  stickyHeaderIndices={stickyHeaderIndices}
426
+ stickyHeaderOffset={stickyHeaderOffset}
414
427
  data={data}
415
428
  renderItem={renderItem}
416
429
  scrollY={scrollY}
417
430
  stickyHeaderRef={stickyHeaderRef}
418
431
  recyclerViewManager={recyclerViewManager}
419
432
  extraData={extraData}
433
+ onChangeStickyIndex={(newStickyHeaderIndex) => {
434
+ if (stickyHeaderHideRelatedCell) {
435
+ setCurrentStickyIndex(newStickyHeaderIndex);
436
+ }
437
+ onChangeStickyIndex?.(newStickyHeaderIndex, currentStickyIndex);
438
+ }}
420
439
  />
421
440
  );
422
441
  }
@@ -424,11 +443,15 @@ const RecyclerViewComponent = <T,>(
424
443
  }, [
425
444
  data,
426
445
  stickyHeaderIndices,
446
+ stickyHeaderOffset,
427
447
  renderItem,
428
448
  scrollY,
429
449
  horizontal,
430
450
  recyclerViewManager,
431
451
  extraData,
452
+ currentStickyIndex,
453
+ onChangeStickyIndex,
454
+ stickyHeaderHideRelatedCell,
432
455
  ]);
433
456
 
434
457
  // Set up scroll event handling with animation support for sticky headers
@@ -436,11 +459,14 @@ const RecyclerViewComponent = <T,>(
436
459
  if (stickyHeaders) {
437
460
  return Animated.event(
438
461
  [{ nativeEvent: { contentOffset: { y: scrollY } } }],
439
- { useNativeDriver: true, listener: onScrollHandler }
462
+ {
463
+ useNativeDriver: stickyHeaderUseNativeDriver,
464
+ listener: onScrollHandler,
465
+ }
440
466
  );
441
467
  }
442
468
  return onScrollHandler;
443
- }, [onScrollHandler, scrollY, stickyHeaders]);
469
+ }, [onScrollHandler, scrollY, stickyHeaders, stickyHeaderUseNativeDriver]);
444
470
 
445
471
  const shouldMaintainVisibleContentPosition =
446
472
  recyclerViewManager.shouldMaintainVisibleContentPosition();
@@ -464,13 +490,14 @@ const RecyclerViewComponent = <T,>(
464
490
  return (
465
491
  <CompatView
466
492
  style={{
493
+ marginTop: horizontal ? undefined : stickyHeaderOffset,
467
494
  height: horizontal ? undefined : 0,
468
495
  width: horizontal ? 0 : undefined,
469
496
  }}
470
497
  ref={firstChildViewRef}
471
498
  />
472
499
  );
473
- }, [horizontal]);
500
+ }, [horizontal, stickyHeaderOffset]);
474
501
 
475
502
  const scrollAnchor = useMemo(() => {
476
503
  if (shouldMaintainVisibleContentPosition) {
@@ -590,10 +617,15 @@ const RecyclerViewComponent = <T,>(
590
617
  ? recyclerViewManager.getChildContainerDimensions()
591
618
  : undefined
592
619
  }
620
+ currentStickyIndex={currentStickyIndex}
621
+ hideStickyHeaderRelatedCell={stickyHeaderHideRelatedCell}
593
622
  />
594
623
  {renderEmpty}
595
624
  {renderFooter}
596
625
  </CompatScrollView>
626
+ {stickyHeaderIndices && stickyHeaderIndices.length > 0
627
+ ? renderStickyHeaderBackdrop
628
+ : null}
597
629
  {stickyHeaders}
598
630
  </CompatView>
599
631
  </RecyclerViewContextProvider>
@@ -47,6 +47,8 @@ export interface ViewHolderProps<TItem> {
47
47
  horizontal?: FlashListProps<TItem>["horizontal"];
48
48
  /** Callback when the item's size changes */
49
49
  onSizeChanged?: (index: number, size: RVDimension) => void;
50
+ /** Whether this item should be hidden (likely because it is associated with the active sticky header) */
51
+ hidden: boolean;
50
52
  }
51
53
 
52
54
  /**
@@ -69,6 +71,7 @@ const ViewHolderInternal = <TItem,>(props: ViewHolderProps<TItem>) => {
69
71
  ItemSeparatorComponent,
70
72
  trailingItem,
71
73
  horizontal,
74
+ hidden,
72
75
  } = props;
73
76
 
74
77
  useLayoutEffect(() => {
@@ -113,6 +116,7 @@ const ViewHolderInternal = <TItem,>(props: ViewHolderProps<TItem>) => {
113
116
  maxWidth: layout.maxWidth,
114
117
  left: layout.x,
115
118
  top: layout.y,
119
+ opacity: hidden ? 0 : 1,
116
120
  } as const;
117
121
 
118
122
  // TODO: Fix this type issue
@@ -152,7 +156,8 @@ export const ViewHolder = React.memo(
152
156
  prevProps.CellRendererComponent === nextProps.CellRendererComponent &&
153
157
  prevProps.ItemSeparatorComponent === nextProps.ItemSeparatorComponent &&
154
158
  prevProps.trailingItem === nextProps.trailingItem &&
155
- prevProps.horizontal === nextProps.horizontal
159
+ prevProps.horizontal === nextProps.horizontal &&
160
+ prevProps.hidden === nextProps.hidden
156
161
  );
157
162
  }
158
163
  );
@@ -50,6 +50,10 @@ export interface ViewHolderCollectionProps<TItem> {
50
50
  * For startRenderingFromBottom, we need to adjust the height of the container
51
51
  */
52
52
  getAdjustmentMargin: () => number;
53
+ /** Current sticky index */
54
+ currentStickyIndex: number;
55
+ /** Whether the cell associated with an active sticky header is hidden */
56
+ hideStickyHeaderRelatedCell: boolean;
53
57
  }
54
58
 
55
59
  /**
@@ -84,6 +88,8 @@ export const ViewHolderCollection = <TItem,>(
84
88
  onCommitEffect,
85
89
  horizontal,
86
90
  getAdjustmentMargin,
91
+ currentStickyIndex,
92
+ hideStickyHeaderRelatedCell,
87
93
  } = props;
88
94
 
89
95
  const [renderId, setRenderId] = React.useState(0);
@@ -168,6 +174,7 @@ export const ViewHolderCollection = <TItem,>(
168
174
  const trailingItem = ItemSeparatorComponent
169
175
  ? data[index + 1]
170
176
  : undefined;
177
+
171
178
  return (
172
179
  <ViewHolder
173
180
  key={reactKey}
@@ -185,6 +192,9 @@ export const ViewHolderCollection = <TItem,>(
185
192
  CellRendererComponent={CellRendererComponent}
186
193
  ItemSeparatorComponent={ItemSeparatorComponent}
187
194
  horizontal={horizontal}
195
+ hidden={
196
+ hideStickyHeaderRelatedCell && currentStickyIndex === index
197
+ }
188
198
  />
189
199
  );
190
200
  })}
@@ -15,6 +15,7 @@ import React, {
15
15
  import { Animated, NativeScrollEvent } from "react-native";
16
16
 
17
17
  import { FlashListProps } from "../..";
18
+ import { PlatformConfig } from "../../native/config/PlatformHelper";
18
19
  import { RecyclerViewManager } from "../RecyclerViewManager";
19
20
  import { ViewHolder } from "../ViewHolder";
20
21
 
@@ -27,6 +28,10 @@ import { CompatAnimatedView } from "./CompatView";
27
28
  export interface StickyHeaderProps<TItem> {
28
29
  /** Array of indices that should have sticky headers */
29
30
  stickyHeaderIndices: number[];
31
+ /** Offset from the top where sticky headers should stick (in pixels) */
32
+ stickyHeaderOffset: number;
33
+ /** Sticky header change handler */
34
+ onChangeStickyIndex: (index: number) => void;
30
35
  /** The data array being rendered */
31
36
  data: ReadonlyArray<TItem>;
32
37
  /** Animated value tracking scroll position */
@@ -56,12 +61,14 @@ interface StickyHeaderState {
56
61
 
57
62
  export const StickyHeaders = <TItem,>({
58
63
  stickyHeaderIndices,
64
+ stickyHeaderOffset,
59
65
  renderItem,
60
66
  stickyHeaderRef,
61
67
  recyclerViewManager,
62
68
  scrollY,
63
69
  data,
64
70
  extraData,
71
+ onChangeStickyIndex,
65
72
  }: StickyHeaderProps<TItem>) => {
66
73
  const [stickyHeaderState, setStickyHeaderState] = useState<StickyHeaderState>(
67
74
  {
@@ -91,7 +98,7 @@ export const StickyHeaders = <TItem,>({
91
98
  // Binary search for current sticky index
92
99
  const currentIndexInArray = findCurrentStickyIndex(
93
100
  sortedIndices,
94
- adjustedScrollOffset,
101
+ adjustedScrollOffset + stickyHeaderOffset,
95
102
  (index) => recyclerViewManager.getLayout(index).y
96
103
  );
97
104
 
@@ -102,7 +109,8 @@ export const StickyHeaders = <TItem,>({
102
109
  newNextStickyIndex = -1;
103
110
  }
104
111
 
105
- // To make sure header offset is 0 in the interpolate compute
112
+ // Calculate when the next sticky header should start pushing the current one
113
+ // The next header starts pushing when it reaches the bottom of the current sticky header
106
114
  const newNextStickyY =
107
115
  newNextStickyIndex === -1
108
116
  ? Number.MAX_SAFE_INTEGER
@@ -111,6 +119,7 @@ export const StickyHeaders = <TItem,>({
111
119
  const newCurrentStickyHeight =
112
120
  recyclerViewManager.tryGetLayout(newStickyIndex)?.height ?? 0;
113
121
 
122
+ // Push should start when the next header reaches the bottom of the current sticky header
114
123
  const newPushStartsAt = newNextStickyY - newCurrentStickyHeight;
115
124
 
116
125
  if (
@@ -119,15 +128,21 @@ export const StickyHeaders = <TItem,>({
119
128
  ) {
120
129
  setStickyHeaderState({
121
130
  currentStickyIndex: newStickyIndex,
122
- pushStartsAt: newPushStartsAt,
131
+ pushStartsAt: newPushStartsAt - stickyHeaderOffset,
123
132
  });
124
133
  }
134
+
135
+ if (newStickyIndex !== currentStickyIndex) {
136
+ onChangeStickyIndex?.(newStickyIndex);
137
+ }
125
138
  }, [
126
139
  legthInvalid,
127
140
  recyclerViewManager,
128
141
  sortedIndices,
129
142
  currentStickyIndex,
130
143
  pushStartsAt,
144
+ onChangeStickyIndex,
145
+ stickyHeaderOffset,
131
146
  ]);
132
147
 
133
148
  useEffect(() => {
@@ -147,16 +162,32 @@ export const StickyHeaders = <TItem,>({
147
162
 
148
163
  const refHolder = useRef(new Map()).current;
149
164
 
150
- const translateY = useMemo(() => {
165
+ const { translateY, opacity } = useMemo(() => {
151
166
  const currentStickyHeight =
152
167
  recyclerViewManager.tryGetLayout(currentStickyIndex)?.height ?? 0;
153
168
 
154
- return scrollY.interpolate({
155
- inputRange: [pushStartsAt, pushStartsAt + currentStickyHeight],
156
- outputRange: [0, -currentStickyHeight],
157
- extrapolate: "clamp",
158
- });
159
- }, [recyclerViewManager, currentStickyIndex, scrollY, pushStartsAt]);
169
+ return {
170
+ translateY: scrollY.interpolate({
171
+ inputRange: [pushStartsAt, pushStartsAt + currentStickyHeight],
172
+ outputRange: [0, -currentStickyHeight],
173
+ extrapolate: "clamp",
174
+ }),
175
+ opacity:
176
+ stickyHeaderOffset > 0
177
+ ? scrollY.interpolate({
178
+ inputRange: [pushStartsAt, pushStartsAt + currentStickyHeight],
179
+ outputRange: [1, 0],
180
+ extrapolate: "clamp",
181
+ })
182
+ : undefined,
183
+ };
184
+ }, [
185
+ recyclerViewManager,
186
+ currentStickyIndex,
187
+ scrollY,
188
+ pushStartsAt,
189
+ stickyHeaderOffset,
190
+ ]);
160
191
 
161
192
  // Memoize header content
162
193
  const headerContent = useMemo(() => {
@@ -164,11 +195,12 @@ export const StickyHeaders = <TItem,>({
164
195
  <CompatAnimatedView
165
196
  style={{
166
197
  position: "absolute",
167
- top: 0,
198
+ top: stickyHeaderOffset,
168
199
  left: 0,
169
200
  right: 0,
170
- zIndex: 1,
201
+ zIndex: 2,
171
202
  transform: [{ translateY }],
203
+ opacity,
172
204
  }}
173
205
  >
174
206
  {currentStickyIndex !== -1 && currentStickyIndex < data.length ? (
@@ -181,11 +213,25 @@ export const StickyHeaders = <TItem,>({
181
213
  extraData={extraData}
182
214
  trailingItem={null}
183
215
  target="StickyHeader"
216
+ hidden={false}
184
217
  />
185
218
  ) : null}
186
219
  </CompatAnimatedView>
187
220
  );
188
- }, [translateY, currentStickyIndex, data, renderItem, refHolder, extraData]);
221
+ }, [
222
+ translateY,
223
+ opacity,
224
+ currentStickyIndex,
225
+ data,
226
+ renderItem,
227
+ refHolder,
228
+ extraData,
229
+ stickyHeaderOffset,
230
+ ]);
231
+
232
+ if (PlatformConfig.isRN083OrAbove && currentStickyIndex === -1) {
233
+ return null;
234
+ }
189
235
 
190
236
  return headerContent;
191
237
  };
@@ -20,6 +20,7 @@ import { CompatAnimatedScroller } from "../components/CompatScroller";
20
20
  * - renderHeader: The header component renderer
21
21
  * - renderFooter: The footer component renderer
22
22
  * - renderEmpty: The empty state component renderer
23
+ * - renderStickyHeaderBackdrop: The sticky header backdrop component renderer
23
24
  * - CompatScrollView: The animated scroll component
24
25
  */
25
26
  export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
@@ -35,6 +36,7 @@ export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
35
36
  onRefresh,
36
37
  data,
37
38
  refreshControl: customRefreshControl,
39
+ stickyHeaderConfig,
38
40
  } = props;
39
41
 
40
42
  /**
@@ -94,6 +96,26 @@ export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
94
96
  return getValidComponent(ListEmptyComponent);
95
97
  }, [ListEmptyComponent, data]);
96
98
 
99
+ /**
100
+ * Creates the sticky header backdrop component.
101
+ */
102
+ const renderStickyHeaderBackdrop = useMemo(() => {
103
+ if (!stickyHeaderConfig?.backdropComponent) {
104
+ return null;
105
+ }
106
+ return (
107
+ <CompatView
108
+ style={{
109
+ position: "absolute",
110
+ inset: 0,
111
+ pointerEvents: "none",
112
+ }}
113
+ >
114
+ {getValidComponent(stickyHeaderConfig?.backdropComponent)}
115
+ </CompatView>
116
+ );
117
+ }, [stickyHeaderConfig?.backdropComponent]);
118
+
97
119
  /**
98
120
  * Creates an animated scroll component based on the provided renderScrollComponent.
99
121
  * If no custom component is provided, uses the default CompatAnimatedScroller.
@@ -120,5 +142,6 @@ export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
120
142
  renderFooter,
121
143
  renderEmpty,
122
144
  CompatScrollView,
145
+ renderStickyHeaderBackdrop,
123
146
  };
124
147
  }