react-native-header-motion 1.0.0-alpha.0 → 1.0.0

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 (137) hide show
  1. package/README.md +65 -528
  2. package/lib/module/components/Bridge.js +16 -0
  3. package/lib/module/components/Bridge.js.map +1 -0
  4. package/lib/module/components/FlatList.js +5 -54
  5. package/lib/module/components/FlatList.js.map +1 -1
  6. package/lib/module/components/Header.js +71 -13
  7. package/lib/module/components/Header.js.map +1 -1
  8. package/lib/module/components/HeaderDynamic.js +34 -0
  9. package/lib/module/components/HeaderDynamic.js.map +1 -0
  10. package/lib/module/components/HeaderMotion.js +14 -20
  11. package/lib/module/components/HeaderMotion.js.map +1 -1
  12. package/lib/module/components/HeaderPanBoundary.js +54 -0
  13. package/lib/module/components/HeaderPanBoundary.js.map +1 -0
  14. package/lib/module/components/NavigationBridge.js +20 -0
  15. package/lib/module/components/NavigationBridge.js.map +1 -0
  16. package/lib/module/components/ScrollManager.js +19 -7
  17. package/lib/module/components/ScrollManager.js.map +1 -1
  18. package/lib/module/components/ScrollView.js +6 -39
  19. package/lib/module/components/ScrollView.js.map +1 -1
  20. package/lib/module/components/createHeaderMotionScrollable.js +136 -0
  21. package/lib/module/components/createHeaderMotionScrollable.js.map +1 -0
  22. package/lib/module/components/index.js +3 -1
  23. package/lib/module/components/index.js.map +1 -1
  24. package/lib/module/context.js +8 -1
  25. package/lib/module/context.js.map +1 -1
  26. package/lib/module/hooks/index.js +1 -0
  27. package/lib/module/hooks/index.js.map +1 -1
  28. package/lib/module/hooks/useActiveScrollId.js +7 -6
  29. package/lib/module/hooks/useActiveScrollId.js.map +1 -1
  30. package/lib/module/hooks/useConsumerScrollHandlers.js +86 -0
  31. package/lib/module/hooks/useConsumerScrollHandlers.js.map +1 -0
  32. package/lib/module/hooks/useHeaderMotionBridge.js +14 -0
  33. package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
  34. package/lib/module/hooks/useMotionProgress.js +12 -42
  35. package/lib/module/hooks/useMotionProgress.js.map +1 -1
  36. package/lib/module/hooks/useMotionProgress.test.js +56 -0
  37. package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
  38. package/lib/module/hooks/useScrollManager.js +168 -87
  39. package/lib/module/hooks/useScrollManager.js.map +1 -1
  40. package/lib/module/index.js +21 -18
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/utils/defaults.js +2 -1
  43. package/lib/module/utils/defaults.js.map +1 -1
  44. package/lib/module/utils/header.js +24 -0
  45. package/lib/module/utils/header.js.map +1 -0
  46. package/lib/module/utils/headerOffsetStyle.js +31 -0
  47. package/lib/module/utils/headerOffsetStyle.js.map +1 -0
  48. package/lib/module/utils/index.js +2 -0
  49. package/lib/module/utils/index.js.map +1 -1
  50. package/lib/typescript/docs/docusaurus.config.d.ts +4 -0
  51. package/lib/typescript/docs/docusaurus.config.d.ts.map +1 -0
  52. package/lib/typescript/docs/sidebars.d.ts +4 -0
  53. package/lib/typescript/docs/sidebars.d.ts.map +1 -0
  54. package/lib/typescript/docs/src/pages/index.d.ts +2 -0
  55. package/lib/typescript/docs/src/pages/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/Bridge.d.ts +19 -0
  57. package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
  58. package/lib/typescript/src/components/FlatList.d.ts +7 -15
  59. package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
  60. package/lib/typescript/src/components/Header.d.ts +73 -12
  61. package/lib/typescript/src/components/Header.d.ts.map +1 -1
  62. package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
  63. package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
  64. package/lib/typescript/src/components/HeaderMotion.d.ts +38 -23
  65. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
  66. package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
  67. package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
  68. package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
  69. package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
  70. package/lib/typescript/src/components/ScrollManager.d.ts +13 -9
  71. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
  72. package/lib/typescript/src/components/ScrollView.d.ts +7 -14
  73. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
  74. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
  75. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
  76. package/lib/typescript/src/components/index.d.ts +3 -1
  77. package/lib/typescript/src/components/index.d.ts.map +1 -1
  78. package/lib/typescript/src/context.d.ts +3 -17
  79. package/lib/typescript/src/context.d.ts.map +1 -1
  80. package/lib/typescript/src/hooks/index.d.ts +1 -0
  81. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  82. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
  83. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
  84. package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts +64 -0
  85. package/lib/typescript/src/hooks/useConsumerScrollHandlers.d.ts.map +1 -0
  86. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
  87. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
  88. package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
  89. package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
  90. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
  91. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
  92. package/lib/typescript/src/hooks/useScrollManager.d.ts +61 -29
  93. package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
  94. package/lib/typescript/src/index.d.ts +56 -26
  95. package/lib/typescript/src/index.d.ts.map +1 -1
  96. package/lib/typescript/src/types.d.ts +54 -17
  97. package/lib/typescript/src/types.d.ts.map +1 -1
  98. package/lib/typescript/src/utils/defaults.d.ts +3 -2
  99. package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/header.d.ts +10 -0
  101. package/lib/typescript/src/utils/header.d.ts.map +1 -0
  102. package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
  103. package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
  104. package/lib/typescript/src/utils/index.d.ts +2 -0
  105. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  106. package/lib/typescript/src/utils/refreshControl.d.ts +12 -12
  107. package/package.json +12 -5
  108. package/src/components/Bridge.tsx +29 -0
  109. package/src/components/FlatList.tsx +18 -76
  110. package/src/components/Header.tsx +159 -23
  111. package/src/components/HeaderDynamic.tsx +45 -0
  112. package/src/components/HeaderMotion.tsx +47 -50
  113. package/src/components/HeaderPanBoundary.tsx +92 -0
  114. package/src/components/NavigationBridge.tsx +30 -0
  115. package/src/components/ScrollManager.tsx +23 -11
  116. package/src/components/ScrollView.tsx +16 -60
  117. package/src/components/createHeaderMotionScrollable.tsx +438 -0
  118. package/src/components/index.ts +3 -1
  119. package/src/context.ts +11 -24
  120. package/src/hooks/index.ts +1 -0
  121. package/src/hooks/useActiveScrollId.ts +7 -6
  122. package/src/hooks/useConsumerScrollHandlers.ts +148 -0
  123. package/src/hooks/useHeaderMotionBridge.ts +15 -0
  124. package/src/hooks/useMotionProgress.test.ts +67 -0
  125. package/src/hooks/useMotionProgress.ts +12 -45
  126. package/src/hooks/useScrollManager.ts +251 -114
  127. package/src/index.ts +82 -36
  128. package/src/types.ts +81 -29
  129. package/src/utils/defaults.ts +7 -1
  130. package/src/utils/header.tsx +52 -0
  131. package/src/utils/headerOffsetStyle.ts +40 -0
  132. package/src/utils/index.ts +2 -0
  133. package/lib/module/components/HeaderBase.js +0 -107
  134. package/lib/module/components/HeaderBase.js.map +0 -1
  135. package/lib/typescript/src/components/HeaderBase.d.ts +0 -41
  136. package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
  137. package/src/components/HeaderBase.tsx +0 -140
@@ -1,4 +1,4 @@
1
- import { useCallback, useRef, useEffect, useMemo } from 'react';
1
+ import { useCallback, useRef, useEffect, useMemo, useState } from 'react';
2
2
  import {
3
3
  Extrapolation,
4
4
  interpolate,
@@ -52,53 +52,68 @@ const resolveScrollIdForProgress = (
52
52
 
53
53
  export interface HeaderMotionProps<T extends string> {
54
54
  /**
55
- * The threshold at which the header animation completes (reaches progress = 1).
56
- * Can be a fixed number or a function that calculates based on the result of {@link measureDynamic}.
55
+ * Distance that maps the active scrollable from `progress = 0`
56
+ * to `progress = 1`.
57
57
  *
58
- * Defaults to a function that returns the return value of `measureDynamic` unchanged.
58
+ * Use a number when the collapse distance is fixed. Use a function when the
59
+ * distance should depend on what `measureDynamic` reads from
60
+ * `HeaderMotion.Header.Dynamic`.
61
+ *
62
+ * A common pattern is to measure the height of the part of the header that
63
+ * should disappear and use that as the threshold.
59
64
  */
60
65
  progressThreshold?: ProgressThreshold;
61
66
  /**
62
- * Function to measure a dimension of choice of the animated element of the header.
63
- *
64
- * Receives the layout change event from React Native.
67
+ * Reads the value that should define the "collapsible" part of the header.
65
68
  *
66
- * This function can be further accessed when rendering headers from `HeaderMotion.Header` or `useMotionProgress` - should be passed to the `onLayout` prop of such. If used, can be used for dynamic calculation of the {@link progressThreshold}.
69
+ * This is called from `HeaderMotion.Header.Dynamic` on layout. The returned
70
+ * number feeds `progressThreshold` when you provide that prop as a function.
67
71
  *
68
- * Defaults to measuring the height from the event.
72
+ * By default, the library measures the dynamic section's height. Override
73
+ * this when the collapse distance should be based on something else, for
74
+ * example width or a derived value from the layout event.
69
75
  */
70
76
  measureDynamic?: MeasureAnimatedHeader;
71
77
  /**
72
- * Mode for measuring dynamic header height.
78
+ * Controls when `measureDynamic` is allowed to update.
79
+ *
73
80
  * - 'mount': Only measure once on mount
74
- * - 'update': Update measurement on every layout recalculation of the component that {@link measureDynamic} was provided to as the `onLayout` property
81
+ * - 'update': Re-measure whenever `HeaderMotion.Header.Dynamic` lays out again
82
+ *
83
+ * Use `'mount'` for stable headers. Use `'update'` when the dynamic section
84
+ * can change size after mount, for example after async data loads or content
85
+ * expansion.
86
+ *
75
87
  * @default 'mount'
76
88
  */
77
89
  measureDynamicMode?: 'update' | 'mount';
78
90
  /**
79
- * Shared value for tracking the active scroll ID in multi-scroll scenarios (e.g. tabs).
80
- * When provided, the header animation will sync across multiple scroll views.
91
+ * Shared value that tells HeaderMotion which scrollable currently owns the
92
+ * header progress in multi-scroll setups.
93
+ *
94
+ * Pass this when one header is shared across multiple scrollables, such as
95
+ * tabs or pager pages. Each scrollable should also get its own `scrollId`.
81
96
  */
82
97
  activeScrollId?: SharedValue<T>;
83
98
  /**
84
- * Extrapolation type for the progress animation.
85
- * Controls how the progress value behaves outside the threshold range.
99
+ * Controls how `progress` behaves outside the `[0, threshold]` range.
100
+ *
101
+ * The default clamps the value between `0` and `1`. Relax this if you want
102
+ * to animate overscroll or other out-of-range states.
86
103
  *
87
- * You may want to modify it to achieve some animations for the overscroll scenarios.
88
104
  * @default Extrapolation.CLAMP
89
105
  */
90
106
  progressExtrapolation?: ExtrapolationType;
91
- /** Enables panning directly on the header surface.
92
- * @default false
93
- */
94
- enableHeaderPan?: boolean;
95
- /** Child components that will have access to the header motion context */
107
+ /** Descendants that should participate in the shared header-motion state. */
96
108
  children: ReactNode;
97
109
  }
98
110
 
99
111
  /**
100
- * Context provider component for HeaderMotion.
101
- * Manages header animation state and provides it to child components via context.
112
+ * Root provider for a header-motion setup.
113
+ *
114
+ * It tracks the measured header layout, the active scroll position, and the
115
+ * derived `progress` shared value consumed by your animated header UI.
116
+ *
102
117
  * @template T - The type of scroll ID string
103
118
  */
104
119
  function HeaderMotionContextProvider<T extends string>({
@@ -107,11 +122,10 @@ function HeaderMotionContextProvider<T extends string>({
107
122
  measureDynamicMode = 'mount',
108
123
  activeScrollId,
109
124
  progressExtrapolation = Extrapolation.CLAMP,
110
- enableHeaderPan = false,
111
125
  children,
112
126
  }: HeaderMotionProps<T>) {
113
127
  const dynamicMeasurement = useSharedValue<number | undefined>(undefined);
114
- const originalHeaderHeight = useSharedValue(0);
128
+ const [originalHeaderHeight, setOriginalHeaderHeight] = useState(0);
115
129
  const progressThresholdValue = useSharedValue(
116
130
  typeof progressThreshold === 'number' ? progressThreshold : Infinity
117
131
  );
@@ -131,11 +145,11 @@ function HeaderMotionContextProvider<T extends string>({
131
145
  }
132
146
 
133
147
  dynamicMeasurement.set(measured);
134
- progressThresholdValue.set(
148
+ const nextThreshold =
135
149
  typeof progressThreshold === 'number'
136
150
  ? progressThreshold
137
- : progressThreshold(measured)
138
- );
151
+ : progressThreshold(measured);
152
+ progressThresholdValue.set(nextThreshold);
139
153
  },
140
154
  [
141
155
  measureDynamicMode,
@@ -153,21 +167,17 @@ function HeaderMotionContextProvider<T extends string>({
153
167
  }
154
168
 
155
169
  const measured = dynamicMeasurement.get();
156
- progressThresholdValue.set(
157
- measured === undefined ? Infinity : progressThreshold(measured)
158
- );
170
+ const nextThreshold =
171
+ measured === undefined ? Infinity : progressThreshold(measured);
172
+ progressThresholdValue.set(nextThreshold);
159
173
  }, [progressThreshold, dynamicMeasurement, progressThresholdValue]);
160
174
 
161
175
  const measureTotalHeight = useCallback<MeasureAnimatedHeaderAndSet>(
162
176
  (e) => {
163
177
  const measuredValue = e.nativeEvent.layout.height;
164
- if (originalHeaderHeight.get() === measuredValue) {
165
- return;
166
- }
167
-
168
- originalHeaderHeight.set(measuredValue);
178
+ setOriginalHeaderHeight(measuredValue);
169
179
  },
170
- [originalHeaderHeight]
180
+ [setOriginalHeaderHeight]
171
181
  );
172
182
 
173
183
  const scrollValues = useSharedValue<ScrollValues>({
@@ -212,24 +222,13 @@ function HeaderMotionContextProvider<T extends string>({
212
222
  // were not propagating reliably, while it works for refs. Revisit later.
213
223
  // We need to be updating the scrollTo on active scroll ID changes and doing it via state would cause re-renders.
214
224
  // It's a bit of an anti-pattern to use refs for this as well, but I am yet to figure out a better way to pass those if SV won't work.
215
- const animatedHeaderBaseProps = useMemo(
216
- () => ({
217
- enableHeaderPan,
218
- scrollToRef,
219
- headerPanMomentumOffset,
220
- }),
221
- [enableHeaderPan, headerPanMomentumOffset]
222
- );
223
-
224
225
  const ctxValue = useMemo(
225
226
  () => ({
226
227
  progress,
227
228
  originalHeaderHeight,
228
229
  measureDynamic: setOrUpdateDynamicMeasurement,
229
230
  measureTotalHeight,
230
- enableHeaderPan,
231
231
  headerPanMomentumOffset,
232
- animatedHeaderBaseProps,
233
232
  progressThreshold: progressThresholdValue,
234
233
  scrollValues,
235
234
  scrollToRef,
@@ -239,9 +238,7 @@ function HeaderMotionContextProvider<T extends string>({
239
238
  originalHeaderHeight,
240
239
  progress,
241
240
  measureTotalHeight,
242
- enableHeaderPan,
243
241
  headerPanMomentumOffset,
244
- animatedHeaderBaseProps,
245
242
  setOrUpdateDynamicMeasurement,
246
243
  scrollValues,
247
244
  activeScrollId,
@@ -0,0 +1,92 @@
1
+ import { useMemo, type ReactElement } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import {
4
+ Gesture,
5
+ GestureDetector,
6
+ GestureHandlerRootView,
7
+ } from 'react-native-gesture-handler';
8
+ import { useAnimatedReaction, withDecay } from 'react-native-reanimated';
9
+ import type {
10
+ HeaderPanDecayConfig,
11
+ HeaderPanDecayEvent,
12
+ HeaderMotionBridgeValue,
13
+ } from '../types';
14
+
15
+ const PLATFORM_PANNING_ENABLED = Platform.select({
16
+ default: true,
17
+ android: false,
18
+ });
19
+
20
+ type HeaderPanBoundaryProps = Pick<
21
+ HeaderMotionBridgeValue,
22
+ 'scrollToRef' | 'headerPanMomentumOffset'
23
+ > & {
24
+ children: ReactElement;
25
+ pannable?: boolean;
26
+ panDecayConfig?: HeaderPanDecayConfig;
27
+ withGestureHandlerRootView?: boolean;
28
+ };
29
+
30
+ export function HeaderPanBoundary({
31
+ children,
32
+ pannable = false,
33
+ panDecayConfig,
34
+ scrollToRef,
35
+ headerPanMomentumOffset,
36
+ withGestureHandlerRootView = false,
37
+ }: HeaderPanBoundaryProps) {
38
+ useAnimatedReaction(
39
+ () => headerPanMomentumOffset.get(),
40
+ (offset, prevOffset) => {
41
+ if (offset !== null) {
42
+ const dy = offset - (prevOffset ?? 0);
43
+ scrollToRef.current?.(dy);
44
+ }
45
+ }
46
+ );
47
+
48
+ const isPanEnabled = PLATFORM_PANNING_ENABLED && pannable;
49
+
50
+ const pan = useMemo(
51
+ () =>
52
+ Gesture.Pan()
53
+ .enabled(isPanEnabled)
54
+ .onChange((e) => {
55
+ const dy = e.changeY;
56
+ scrollToRef.current?.(dy);
57
+ })
58
+ .onEnd((e) => {
59
+ const resolvedConfig = resolvePanDecayConfig(panDecayConfig, e);
60
+ headerPanMomentumOffset.set(
61
+ withDecay(resolvedConfig, () => headerPanMomentumOffset.set(null))
62
+ );
63
+ })
64
+ .shouldCancelWhenOutside(false),
65
+ [headerPanMomentumOffset, isPanEnabled, panDecayConfig, scrollToRef]
66
+ );
67
+
68
+ const content = <GestureDetector gesture={pan}>{children}</GestureDetector>;
69
+
70
+ if (!withGestureHandlerRootView) {
71
+ return content;
72
+ }
73
+
74
+ return <GestureHandlerRootView>{content}</GestureHandlerRootView>;
75
+ }
76
+
77
+ function resolvePanDecayConfig(
78
+ panDecayConfig: HeaderPanDecayConfig | undefined,
79
+ event: HeaderPanDecayEvent
80
+ ) {
81
+ 'worklet';
82
+
83
+ const resolvedConfig =
84
+ typeof panDecayConfig === 'function'
85
+ ? panDecayConfig(event)
86
+ : panDecayConfig;
87
+
88
+ return {
89
+ ...resolvedConfig,
90
+ velocity: resolvedConfig?.velocity ?? event.velocityY,
91
+ };
92
+ }
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react';
2
+ import { HeaderMotionContext } from '../context';
3
+ import type { HeaderMotionBridgeValue } from '../types';
4
+
5
+ export interface HeaderMotionNavigationBridgeProps {
6
+ /**
7
+ * Previously captured HeaderMotion context value to re-provide in another
8
+ * subtree.
9
+ */
10
+ value: HeaderMotionBridgeValue;
11
+ /** Subtree that should regain access to HeaderMotion context. */
12
+ children: ReactNode;
13
+ }
14
+
15
+ /**
16
+ * Re-provides HeaderMotion context in a different part of the React tree.
17
+ *
18
+ * This is primarily useful for navigation libraries that render headers outside
19
+ * the screen subtree where `HeaderMotion` itself lives.
20
+ */
21
+ export function NavigationBridge({
22
+ value,
23
+ children,
24
+ }: HeaderMotionNavigationBridgeProps) {
25
+ return (
26
+ <HeaderMotionContext.Provider value={value}>
27
+ {children}
28
+ </HeaderMotionContext.Provider>
29
+ );
30
+ }
@@ -12,23 +12,27 @@ export interface HeaderMotionScrollManagerProps<
12
12
  TRef extends InstanceOrElement = any
13
13
  > extends UseScrollManagerOptions<TRef> {
14
14
  /**
15
- * Optional unique identifier for this scroll view.
16
- * Use this when you have multiple scroll views (e.g., in tabs) to track them separately.
15
+ * Unique identifier for this scrollable in multi-scroll setups.
16
+ *
17
+ * Omit it for single-scroll screens.
17
18
  */
18
19
  scrollId?: string;
19
20
  /**
20
- * Render function that receives scroll props and header context.
21
- * Use this to create custom scroll implementations that integrate with HeaderMotion.
21
+ * Render function that receives:
22
+ * - the props to spread onto your scrollable
23
+ * - the layout values needed to offset content below the header
22
24
  */
23
25
  children: ScrollManagerRenderChildren<TRef>;
24
26
  }
25
27
 
26
28
  /**
27
- * ScrollManager component that provides scroll tracking functionality for custom scroll implementations. Uses {@link useScrollManager} under the hood.
28
- * Must be used within a HeaderMotion component.
29
+ * Render-prop wrapper around `useScrollManager()`.
29
30
  *
30
- * This is useful when you need to use a scroll component that isn't directly supported
31
- * (like a custom scroll view or third-party list components).
31
+ * **Most code should prefer `createHeaderMotionScrollable()` instead.**
32
+ *
33
+ * Use `ScrollManager` only when the factory approach is not enough and you
34
+ * still need HeaderMotion to manage a custom scrollable through render-prop
35
+ * composition.
32
36
  *
33
37
  * @example
34
38
  * ```tsx
@@ -45,9 +49,7 @@ export interface HeaderMotionScrollManagerProps<
45
49
  * </HeaderMotion>
46
50
  * ```
47
51
  */
48
- export function HeaderMotionScrollManager<
49
- TRef extends InstanceOrElement = any
50
- >({
52
+ export function ScrollManager<TRef extends InstanceOrElement = any>({
51
53
  children,
52
54
  scrollId,
53
55
  animatedRef,
@@ -55,6 +57,11 @@ export function HeaderMotionScrollManager<
55
57
  refreshing,
56
58
  onRefresh,
57
59
  progressViewOffset,
60
+ onScroll,
61
+ onScrollBeginDrag,
62
+ onScrollEndDrag,
63
+ onMomentumScrollBegin,
64
+ onMomentumScrollEnd,
58
65
  }: HeaderMotionScrollManagerProps<TRef>) {
59
66
  if (typeof children !== 'function') {
60
67
  throw new Error(
@@ -70,6 +77,11 @@ export function HeaderMotionScrollManager<
70
77
  refreshing,
71
78
  onRefresh,
72
79
  progressViewOffset,
80
+ onScroll,
81
+ onScrollBeginDrag,
82
+ onScrollEndDrag,
83
+ onMomentumScrollBegin,
84
+ onMomentumScrollEnd,
73
85
  }
74
86
  );
75
87
 
@@ -1,21 +1,18 @@
1
+ import type { ReactElement } from 'react';
1
2
  import Animated, {
2
- type AnimatedRef,
3
3
  type AnimatedScrollViewProps,
4
4
  } from 'react-native-reanimated';
5
- import { HeaderMotionScrollManager } from './ScrollManager';
5
+ import {
6
+ createHeaderMotionScrollable,
7
+ type HeaderMotionScrollableOwnProps,
8
+ } from './createHeaderMotionScrollable';
6
9
 
7
- export type HeaderMotionScrollViewProps = AnimatedScrollViewProps & {
8
- /**
9
- * Optional unique identifier for this scroll view.
10
- * Use this when you have multiple scroll views (e.g. in tabs) to track them separately.
11
- */
12
- scrollId?: string;
13
- /**
14
- * Optional animated ref to use for the scroll view.
15
- * When provided, the scroll manager will use this ref instead of creating its own.
16
- */
17
- animatedRef?: AnimatedRef<Animated.ScrollView> | AnimatedRef;
18
- };
10
+ export type HeaderMotionScrollViewProps = AnimatedScrollViewProps &
11
+ HeaderMotionScrollableOwnProps<Animated.ScrollView>;
12
+
13
+ type HeaderMotionScrollViewComponent = (
14
+ props: HeaderMotionScrollViewProps
15
+ ) => ReactElement | null;
19
16
 
20
17
  /**
21
18
  * Animated ScrollView component that integrates with HeaderMotion.
@@ -31,49 +28,8 @@ export type HeaderMotionScrollViewProps = AnimatedScrollViewProps & {
31
28
  * </HeaderMotion>
32
29
  * ```
33
30
  */
34
- export function HeaderMotionScrollView({
35
- scrollId,
36
- animatedRef,
37
- children,
38
- contentContainerStyle,
39
- refreshControl,
40
- ...props
41
- }: HeaderMotionScrollViewProps) {
42
- return (
43
- <HeaderMotionScrollManager
44
- scrollId={scrollId}
45
- animatedRef={animatedRef as AnimatedRef<Animated.ScrollView>}
46
- refreshControl={refreshControl}
47
- >
48
- {(
49
- {
50
- onScroll,
51
- ref,
52
- refreshControl: managedRefreshControl,
53
- ...scrollViewProps
54
- },
55
- { originalHeaderHeight, minHeightContentContainerStyle }
56
- ) => (
57
- <Animated.ScrollView
58
- {...scrollViewProps}
59
- {...props}
60
- ref={ref}
61
- onScroll={onScroll}
62
- {...(managedRefreshControl && {
63
- refreshControl: managedRefreshControl,
64
- })}
65
- >
66
- <Animated.View
67
- style={[
68
- minHeightContentContainerStyle,
69
- { paddingTop: originalHeaderHeight },
70
- contentContainerStyle,
71
- ]}
72
- >
73
- {children}
74
- </Animated.View>
75
- </Animated.ScrollView>
76
- )}
77
- </HeaderMotionScrollManager>
78
- );
79
- }
31
+ export const ScrollView = createHeaderMotionScrollable(Animated.ScrollView, {
32
+ displayName: 'HeaderMotion.ScrollView',
33
+ contentContainerMode: 'children',
34
+ isComponentAnimated: true,
35
+ }) as HeaderMotionScrollViewComponent;