react-native-header-motion 0.4.0 → 1.0.0-beta.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 (140) hide show
  1. package/README.md +400 -335
  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 -62
  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 +59 -23
  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 +7 -5
  17. package/lib/module/components/ScrollManager.js.map +1 -1
  18. package/lib/module/components/ScrollView.js +6 -47
  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/useHeaderMotionBridge.js +14 -0
  31. package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
  32. package/lib/module/hooks/useMotionProgress.js +10 -36
  33. package/lib/module/hooks/useMotionProgress.js.map +1 -1
  34. package/lib/module/hooks/useMotionProgress.test.js +56 -0
  35. package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
  36. package/lib/module/hooks/useScrollManager.js +219 -109
  37. package/lib/module/hooks/useScrollManager.js.map +1 -1
  38. package/lib/module/index.js +21 -18
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/utils/defaults.js +2 -1
  41. package/lib/module/utils/defaults.js.map +1 -1
  42. package/lib/module/utils/header.js +24 -0
  43. package/lib/module/utils/header.js.map +1 -0
  44. package/lib/module/utils/headerOffsetStyle.js +31 -0
  45. package/lib/module/utils/headerOffsetStyle.js.map +1 -0
  46. package/lib/module/utils/index.js +3 -0
  47. package/lib/module/utils/index.js.map +1 -1
  48. package/lib/module/utils/refreshControl.js +93 -0
  49. package/lib/module/utils/refreshControl.js.map +1 -0
  50. package/lib/module/utils/values.js +36 -0
  51. package/lib/module/utils/values.js.map +1 -1
  52. package/lib/typescript/src/components/Bridge.d.ts +19 -0
  53. package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
  54. package/lib/typescript/src/components/FlatList.d.ts +7 -15
  55. package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
  56. package/lib/typescript/src/components/Header.d.ts +73 -12
  57. package/lib/typescript/src/components/Header.d.ts.map +1 -1
  58. package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
  59. package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
  60. package/lib/typescript/src/components/HeaderMotion.d.ts +37 -18
  61. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
  62. package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
  63. package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
  64. package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
  65. package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
  66. package/lib/typescript/src/components/ScrollManager.d.ts +18 -25
  67. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
  68. package/lib/typescript/src/components/ScrollView.d.ts +7 -14
  69. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
  70. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
  71. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
  72. package/lib/typescript/src/components/index.d.ts +3 -1
  73. package/lib/typescript/src/components/index.d.ts.map +1 -1
  74. package/lib/typescript/src/context.d.ts +3 -13
  75. package/lib/typescript/src/context.d.ts.map +1 -1
  76. package/lib/typescript/src/hooks/index.d.ts +1 -0
  77. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  78. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
  79. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
  80. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
  81. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
  82. package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
  83. package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
  84. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
  85. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
  86. package/lib/typescript/src/hooks/useScrollManager.d.ts +63 -31
  87. package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
  88. package/lib/typescript/src/index.d.ts +56 -26
  89. package/lib/typescript/src/index.d.ts.map +1 -1
  90. package/lib/typescript/src/types.d.ts +63 -15
  91. package/lib/typescript/src/types.d.ts.map +1 -1
  92. package/lib/typescript/src/utils/defaults.d.ts +3 -2
  93. package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
  94. package/lib/typescript/src/utils/header.d.ts +10 -0
  95. package/lib/typescript/src/utils/header.d.ts.map +1 -0
  96. package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
  97. package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
  98. package/lib/typescript/src/utils/index.d.ts +3 -0
  99. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/refreshControl.d.ts +150 -0
  101. package/lib/typescript/src/utils/refreshControl.d.ts.map +1 -0
  102. package/lib/typescript/src/utils/values.d.ts +4 -1
  103. package/lib/typescript/src/utils/values.d.ts.map +1 -1
  104. package/package.json +13 -5
  105. package/src/components/Bridge.tsx +29 -0
  106. package/src/components/FlatList.tsx +18 -84
  107. package/src/components/Header.tsx +159 -23
  108. package/src/components/HeaderDynamic.tsx +45 -0
  109. package/src/components/HeaderMotion.tsx +114 -41
  110. package/src/components/HeaderPanBoundary.tsx +92 -0
  111. package/src/components/NavigationBridge.tsx +30 -0
  112. package/src/components/ScrollManager.tsx +38 -43
  113. package/src/components/ScrollView.tsx +16 -68
  114. package/src/components/createHeaderMotionScrollable.tsx +438 -0
  115. package/src/components/index.ts +3 -1
  116. package/src/context.ts +12 -18
  117. package/src/hooks/index.ts +1 -0
  118. package/src/hooks/useActiveScrollId.ts +7 -6
  119. package/src/hooks/useHeaderMotionBridge.ts +15 -0
  120. package/src/hooks/useMotionProgress.test.ts +67 -0
  121. package/src/hooks/useMotionProgress.ts +12 -37
  122. package/src/hooks/useScrollManager.ts +310 -129
  123. package/src/index.ts +82 -36
  124. package/src/types.ts +85 -25
  125. package/src/utils/defaults.ts +7 -1
  126. package/src/utils/header.tsx +52 -0
  127. package/src/utils/headerOffsetStyle.ts +40 -0
  128. package/src/utils/index.ts +3 -0
  129. package/src/utils/refreshControl.tsx +118 -0
  130. package/src/utils/values.ts +57 -1
  131. package/lib/module/components/HeaderBase.js +0 -59
  132. package/lib/module/components/HeaderBase.js.map +0 -1
  133. package/lib/module/hooks/refreshControl.js +0 -31
  134. package/lib/module/hooks/refreshControl.js.map +0 -1
  135. package/lib/typescript/src/components/HeaderBase.d.ts +0 -34
  136. package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
  137. package/lib/typescript/src/hooks/refreshControl.d.ts +0 -13
  138. package/lib/typescript/src/hooks/refreshControl.d.ts.map +0 -1
  139. package/src/components/HeaderBase.tsx +0 -51
  140. package/src/hooks/refreshControl.ts +0 -55
@@ -1,46 +1,38 @@
1
- import { useScrollManager } from '../hooks';
1
+ import { useScrollManager, type UseScrollManagerOptions } from '../hooks';
2
2
  import type { ScrollManagerConfig } from '../types';
3
- import type { ResolveRefreshControlOptions } from '../hooks/refreshControl';
4
3
  import type { ReactNode } from 'react';
5
- import type { AnimatedRef } from 'react-native-reanimated';
6
- import type { ConsumerScrollEventHandlers } from '../hooks/useConsumerScrollHandlers';
4
+ import type { InstanceOrElement } from 'react-native-reanimated/lib/typescript/commonTypes';
7
5
 
8
- type ScrollManagerRenderChildren = (
9
- scrollableProps: ScrollManagerConfig['scrollableProps'],
10
- options: ScrollManagerConfig['headerMotionContext']
6
+ type ScrollManagerRenderChildren<TRef extends InstanceOrElement = any> = (
7
+ scrollableProps: ScrollManagerConfig<TRef>['scrollableProps'],
8
+ options: ScrollManagerConfig<TRef>['headerMotionContext']
11
9
  ) => ReactNode;
12
10
 
13
- export interface HeaderMotionScrollManagerProps
14
- extends Omit<ResolveRefreshControlOptions, 'progressViewOffset'>,
15
- ConsumerScrollEventHandlers {
11
+ export interface HeaderMotionScrollManagerProps<
12
+ TRef extends InstanceOrElement = any
13
+ > extends UseScrollManagerOptions<TRef> {
16
14
  /**
17
- * Optional unique identifier for this scroll view.
18
- * 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.
19
18
  */
20
19
  scrollId?: string;
21
20
  /**
22
- * Optional animated ref to use for the scroll view.
23
- * When provided, the scroll manager will use this ref instead of creating its own.
21
+ * Render function that receives:
22
+ * - the props to spread onto your scrollable
23
+ * - the layout values needed to offset content below the header
24
24
  */
25
- animatedRef?: AnimatedRef<any>;
26
- /**
27
- * Optional refresh progress offset override.
28
- * When provided, it takes precedence over the automatic offset based on header height.
29
- */
30
- progressViewOffset?: ResolveRefreshControlOptions['progressViewOffset'];
31
- /**
32
- * Render function that receives scroll props and header context.
33
- * Use this to create custom scroll implementations that integrate with HeaderMotion.
34
- */
35
- children: ScrollManagerRenderChildren;
25
+ children: ScrollManagerRenderChildren<TRef>;
36
26
  }
37
27
 
38
28
  /**
39
- * ScrollManager component that provides scroll tracking functionality for custom scroll implementations. Uses {@link useScrollManager} under the hood.
40
- * Must be used within a HeaderMotion component.
29
+ * Render-prop wrapper around `useScrollManager()`.
30
+ *
31
+ * **Most code should prefer `createHeaderMotionScrollable()` instead.**
41
32
  *
42
- * This is useful when you need to use a scroll component that isn't directly supported
43
- * (like a custom scroll view or third-party list components).
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.
44
36
  *
45
37
  * @example
46
38
  * ```tsx
@@ -57,7 +49,7 @@ export interface HeaderMotionScrollManagerProps
57
49
  * </HeaderMotion>
58
50
  * ```
59
51
  */
60
- export function HeaderMotionScrollManager({
52
+ export function ScrollManager<TRef extends InstanceOrElement = any>({
61
53
  children,
62
54
  scrollId,
63
55
  animatedRef,
@@ -70,25 +62,28 @@ export function HeaderMotionScrollManager({
70
62
  onScrollEndDrag,
71
63
  onMomentumScrollBegin,
72
64
  onMomentumScrollEnd,
73
- }: HeaderMotionScrollManagerProps) {
65
+ }: HeaderMotionScrollManagerProps<TRef>) {
74
66
  if (typeof children !== 'function') {
75
67
  throw new Error(
76
68
  'HeaderMotion.ScrollManager only accepts render function as the only child.'
77
69
  );
78
70
  }
79
71
 
80
- const { scrollableProps, headerMotionContext } = useScrollManager(scrollId, {
81
- animatedRef,
82
- refreshControl,
83
- refreshing,
84
- onRefresh,
85
- progressViewOffset,
86
- onScroll,
87
- onScrollBeginDrag,
88
- onScrollEndDrag,
89
- onMomentumScrollBegin,
90
- onMomentumScrollEnd,
91
- });
72
+ const { scrollableProps, headerMotionContext } = useScrollManager<TRef>(
73
+ scrollId,
74
+ {
75
+ animatedRef,
76
+ refreshControl,
77
+ refreshing,
78
+ onRefresh,
79
+ progressViewOffset,
80
+ onScroll,
81
+ onScrollBeginDrag,
82
+ onScrollEndDrag,
83
+ onMomentumScrollBegin,
84
+ onMomentumScrollEnd,
85
+ }
86
+ );
92
87
 
93
88
  return children(scrollableProps, headerMotionContext);
94
89
  }
@@ -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<any>;
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,57 +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
- onScroll,
41
- onScrollBeginDrag,
42
- onScrollEndDrag,
43
- onMomentumScrollBegin,
44
- onMomentumScrollEnd,
45
- ...props
46
- }: HeaderMotionScrollViewProps) {
47
- return (
48
- <HeaderMotionScrollManager
49
- scrollId={scrollId}
50
- animatedRef={animatedRef}
51
- refreshControl={refreshControl}
52
- onScroll={onScroll}
53
- onScrollBeginDrag={onScrollBeginDrag}
54
- onScrollEndDrag={onScrollEndDrag}
55
- onMomentumScrollBegin={onMomentumScrollBegin}
56
- onMomentumScrollEnd={onMomentumScrollEnd}
57
- >
58
- {(
59
- {
60
- onScroll: managedOnScroll,
61
- refreshControl: managedRefreshControl,
62
- ...scrollViewProps
63
- },
64
- { originalHeaderHeight, minHeightContentContainerStyle }
65
- ) => (
66
- <Animated.ScrollView
67
- {...scrollViewProps}
68
- {...props}
69
- onScroll={managedOnScroll}
70
- {...(managedRefreshControl && {
71
- refreshControl: managedRefreshControl,
72
- })}
73
- >
74
- <Animated.View
75
- style={[
76
- minHeightContentContainerStyle,
77
- { paddingTop: originalHeaderHeight },
78
- contentContainerStyle,
79
- ]}
80
- >
81
- {children}
82
- </Animated.View>
83
- </Animated.ScrollView>
84
- )}
85
- </HeaderMotionScrollManager>
86
- );
87
- }
31
+ export const ScrollView = createHeaderMotionScrollable(Animated.ScrollView, {
32
+ displayName: 'HeaderMotion.ScrollView',
33
+ contentContainerMode: 'children',
34
+ isComponentAnimated: true,
35
+ }) as HeaderMotionScrollViewComponent;
@@ -0,0 +1,438 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ useRef,
7
+ type ComponentRef,
8
+ type ReactElement,
9
+ type ReactNode,
10
+ type Ref,
11
+ } from 'react';
12
+ import type { LayoutChangeEvent, ScrollViewProps } from 'react-native';
13
+ import Animated, {
14
+ type AnimatedProps,
15
+ type AnimatedRef,
16
+ } from 'react-native-reanimated';
17
+ import type { InstanceOrElement } from 'react-native-reanimated/lib/typescript/commonTypes';
18
+ import { useScrollManager, type UseScrollManagerOptions } from '../hooks';
19
+ import type { HeaderMotionOffsetProps } from '../types';
20
+ import { resolveHeaderOffsetStyle } from '../utils';
21
+
22
+ export type HeaderMotionScrollableOwnProps<
23
+ TRef extends InstanceOrElement = any
24
+ > = HeaderMotionOffsetProps & {
25
+ /**
26
+ * Unique identifier for this scrollable when one header is shared across
27
+ * multiple scrollables.
28
+ */
29
+ scrollId?: string;
30
+ /**
31
+ * Animated ref to reuse instead of letting HeaderMotion create one.
32
+ */
33
+ animatedRef?: AnimatedRef<TRef> | AnimatedRef;
34
+ };
35
+
36
+ export interface CreateHeaderMotionScrollableOptions<
37
+ TIsComponentAnimated extends boolean = boolean
38
+ > {
39
+ displayName?: string;
40
+ /**
41
+ * If true, this function will NOT call Animated.createAnimatedComponent internally.
42
+ * Useful when you are creating a HeaderMotionScrollable from lists that already export their
43
+ * own (Re)animated components (e.g. LegendList).
44
+ *
45
+ * @default false
46
+ */
47
+ isComponentAnimated?: TIsComponentAnimated;
48
+ /**
49
+ * Controls how HeaderMotion injects content-container spacing.
50
+ *
51
+ * - `children`: wraps `children` in an inner `Animated.View`
52
+ * - `renderScrollComponent`: injects a custom scroll component that wraps the content
53
+ *
54
+ * Use `children` for ScrollView-like components. Use
55
+ * `renderScrollComponent` for FlatList-like components that own their
56
+ * internal scroll container.
57
+ *
58
+ * @default 'renderScrollComponent'
59
+ */
60
+ contentContainerMode?: ContentContainerMode;
61
+ }
62
+
63
+ export function createHeaderMotionScrollable<
64
+ TScrollableComponent extends ScrollableComponent,
65
+ TIsComponentAnimated extends boolean = false
66
+ >(
67
+ ScrollableComponent: TScrollableComponent,
68
+ options?: CreateHeaderMotionScrollableOptions<TIsComponentAnimated>
69
+ ): HeaderMotionScrollableComponent<TScrollableComponent, TIsComponentAnimated> {
70
+ const {
71
+ isComponentAnimated = false,
72
+ contentContainerMode = 'renderScrollComponent',
73
+ displayName = `HeaderMotion(${getDisplayName(
74
+ ScrollableComponent as {
75
+ displayName?: string;
76
+ name?: string;
77
+ }
78
+ )})`,
79
+ } = options || {};
80
+
81
+ const AnimatedScrollable = (
82
+ isComponentAnimated
83
+ ? ScrollableComponent
84
+ : Animated.createAnimatedComponent(ScrollableComponent)
85
+ ) as ScrollableImplementationComponent;
86
+
87
+ function HeaderMotionScrollable(props: ScrollableRuntimeProps) {
88
+ const {
89
+ scrollId,
90
+ animatedRef,
91
+ headerOffsetStrategy,
92
+ ensureScrollableContentMinHeight = false,
93
+ contentContainerStyle,
94
+ refreshControl,
95
+ refreshing,
96
+ onRefresh,
97
+ progressViewOffset,
98
+ onScroll,
99
+ onScrollBeginDrag,
100
+ onScrollEndDrag,
101
+ onMomentumScrollBegin,
102
+ onMomentumScrollEnd,
103
+ ...rest
104
+ } = props;
105
+
106
+ const { scrollableProps, headerMotionContext } = useScrollManager(
107
+ scrollId,
108
+ {
109
+ refreshControl:
110
+ (refreshControl as UseScrollManagerOptions['refreshControl']) ??
111
+ undefined,
112
+ refreshing: refreshing ?? undefined,
113
+ onRefresh: onRefresh ?? undefined,
114
+ progressViewOffset,
115
+ onScroll,
116
+ onScrollBeginDrag,
117
+ onScrollEndDrag,
118
+ onMomentumScrollBegin,
119
+ onMomentumScrollEnd,
120
+ animatedRef,
121
+ ensureScrollableContentMinHeight,
122
+ }
123
+ );
124
+
125
+ const {
126
+ onScroll: managedOnScroll,
127
+ onLayout: managedOnLayout,
128
+ refreshControl: managedRefreshControl,
129
+ ref,
130
+ ...scrollViewProps
131
+ } = scrollableProps;
132
+ const { originalHeaderHeight, contentContainerMinHeight } =
133
+ headerMotionContext;
134
+
135
+ const userOnLayoutRef = useRef<UserOnLayout>(rest.onLayout as UserOnLayout);
136
+ useLayoutEffect(() => {
137
+ userOnLayoutRef.current = rest.onLayout as UserOnLayout;
138
+ });
139
+
140
+ const managedContentContainerStyle = useMemo(
141
+ () => [
142
+ ensureScrollableContentMinHeight &&
143
+ contentContainerMinHeight !== undefined
144
+ ? { minHeight: contentContainerMinHeight }
145
+ : undefined,
146
+ resolveHeaderOffsetStyle(originalHeaderHeight, headerOffsetStrategy),
147
+ contentContainerStyle,
148
+ ],
149
+ [
150
+ contentContainerStyle,
151
+ contentContainerMinHeight,
152
+ ensureScrollableContentMinHeight,
153
+ headerOffsetStrategy,
154
+ originalHeaderHeight,
155
+ ]
156
+ );
157
+
158
+ const refreshControlProps = managedRefreshControl && {
159
+ refreshControl: managedRefreshControl,
160
+ };
161
+
162
+ const handleLayout = useCallback(
163
+ (e: LayoutChangeEvent) => {
164
+ managedOnLayout?.(e);
165
+ userOnLayoutRef.current?.(e);
166
+ },
167
+ [managedOnLayout]
168
+ );
169
+
170
+ const contentContainerProps = useContentContainerProps({
171
+ children: rest.children,
172
+ mode: contentContainerMode,
173
+ style: managedContentContainerStyle,
174
+ });
175
+
176
+ return (
177
+ <AnimatedScrollable
178
+ {...scrollViewProps}
179
+ {...rest}
180
+ {...refreshControlProps}
181
+ {...contentContainerProps}
182
+ ref={ref}
183
+ onLayout={handleLayout}
184
+ onScroll={managedOnScroll}
185
+ />
186
+ );
187
+ }
188
+
189
+ const TypedHeaderMotionScrollable =
190
+ HeaderMotionScrollable as HeaderMotionScrollableComponent<
191
+ TScrollableComponent,
192
+ TIsComponentAnimated
193
+ >;
194
+
195
+ TypedHeaderMotionScrollable.displayName = displayName;
196
+ return TypedHeaderMotionScrollable;
197
+ }
198
+
199
+ function useContentContainerProps({
200
+ children: rawChildren,
201
+ mode,
202
+ style,
203
+ }: {
204
+ children?: ReactNode;
205
+ mode: ContentContainerMode;
206
+ style?: any;
207
+ }) {
208
+ const renderScrollComponent = useCallback(
209
+ (props: ScrollViewProps) => (
210
+ <AnimatedScrollContainer {...props} contentContainerStyle={style} />
211
+ ),
212
+ [style]
213
+ );
214
+
215
+ const children = <Animated.View style={style}>{rawChildren}</Animated.View>;
216
+
217
+ if (mode === 'children') {
218
+ return { children };
219
+ }
220
+
221
+ return {
222
+ renderScrollComponent,
223
+ };
224
+ }
225
+
226
+ const AnimatedScrollContainer = forwardRef<
227
+ ComponentRef<typeof Animated.ScrollView>,
228
+ ScrollViewProps
229
+ >(({ children, contentContainerStyle, ...rest }, ref) => {
230
+ return (
231
+ <Animated.ScrollView {...rest} ref={ref}>
232
+ <Animated.View style={contentContainerStyle}>{children}</Animated.View>
233
+ </Animated.ScrollView>
234
+ );
235
+ });
236
+
237
+ function getDisplayName(ScrollableComponent: {
238
+ displayName?: string;
239
+ name?: string;
240
+ }) {
241
+ return (
242
+ ScrollableComponent.displayName ?? ScrollableComponent.name ?? 'Scrollable'
243
+ );
244
+ }
245
+
246
+ type UserOnLayout = ScrollViewProps['onLayout'] | undefined;
247
+
248
+ // TODO: From here below Codex did some absolute TypeScript magic but it seems to work
249
+ // Having limited time, I can't spend more on adjusting this to make it less convoluted
250
+ // But what matters is that it seems that for the user the types work very well
251
+
252
+ type ContentContainerMode = 'children' | 'renderScrollComponent';
253
+
254
+ type ScrollableComponent =
255
+ | ((props: any) => ReactElement | null)
256
+ | (new (...args: any[]) => any);
257
+
258
+ declare const noListItemSymbol: unique symbol;
259
+ type NoListItem = { readonly [noListItemSymbol]: true };
260
+
261
+ type ScrollableComponentProps<TScrollableComponent> =
262
+ TScrollableComponent extends new (props: infer TProps, ...args: any[]) => any
263
+ ? TProps
264
+ : TScrollableComponent extends (props: infer TProps, ...args: any[]) => any
265
+ ? TProps
266
+ : never;
267
+
268
+ type IsUnknown<TValue> = unknown extends TValue
269
+ ? [keyof TValue] extends [never]
270
+ ? true
271
+ : false
272
+ : false;
273
+
274
+ type ReplaceUnknownDeep<TValue, TReplacement> = IsUnknown<TValue> extends true
275
+ ? TReplacement
276
+ : TValue extends (...args: infer TArgs) => infer TResult
277
+ ? (
278
+ ...args: {
279
+ [TIndex in keyof TArgs]: ReplaceUnknownDeep<
280
+ TArgs[TIndex],
281
+ TReplacement
282
+ >;
283
+ }
284
+ ) => TResult
285
+ : TValue extends readonly (infer TItem)[]
286
+ ? readonly ReplaceUnknownDeep<TItem, TReplacement>[]
287
+ : TValue extends object
288
+ ? {
289
+ [TKey in keyof TValue]: ReplaceUnknownDeep<TValue[TKey], TReplacement>;
290
+ }
291
+ : TValue;
292
+
293
+ type MaybeAnimatedProps<
294
+ TProps extends object,
295
+ TIsComponentAnimated
296
+ > = TIsComponentAnimated extends true ? TProps : AnimatedProps<TProps>;
297
+
298
+ type ResolveListItemProps<TProps extends object, TListItem> = [
299
+ TListItem
300
+ ] extends [NoListItem]
301
+ ? TProps
302
+ : ReplaceUnknownDeep<TProps, TListItem>;
303
+
304
+ type ExtractDataProp<TProps> = TProps extends { data?: infer TData }
305
+ ? TData
306
+ : TProps extends { data: infer TData }
307
+ ? TData
308
+ : never;
309
+
310
+ type ExtractListItemFromData<TData> = TData extends
311
+ | ReadonlyArray<infer TItem>
312
+ | null
313
+ | undefined
314
+ ? TItem
315
+ : TData extends ArrayLike<infer TItem> | null | undefined
316
+ ? TItem
317
+ : never;
318
+
319
+ type HasGenericDataProp<TProps> = IsUnknown<
320
+ ExtractListItemFromData<ExtractDataProp<TProps>>
321
+ >;
322
+
323
+ type ExtractRefTargetFromRef<TRef> = TRef extends Ref<infer TInstance>
324
+ ? TInstance
325
+ : TRef extends AnimatedRef<infer TInstance>
326
+ ? TInstance
327
+ : never;
328
+
329
+ type ExtractRefTargetFromProps<TProps> = TProps extends { ref?: infer TRef }
330
+ ? ExtractRefTargetFromRef<TRef>
331
+ : TProps extends { ref: infer TRef }
332
+ ? ExtractRefTargetFromRef<TRef>
333
+ : never;
334
+
335
+ type ResolveScrollableRefTarget<TScrollableComponent, TProps> = [
336
+ ExtractRefTargetFromProps<TProps>
337
+ ] extends [never]
338
+ ? TScrollableComponent extends new (...args: any[]) => infer TInstance
339
+ ? TInstance extends InstanceOrElement
340
+ ? TInstance
341
+ : any
342
+ : any
343
+ : ExtractRefTargetFromProps<TProps> extends InstanceOrElement
344
+ ? ExtractRefTargetFromProps<TProps>
345
+ : any;
346
+
347
+ type HeaderMotionScrollableBaseProps<
348
+ TScrollableComponent extends ScrollableComponent,
349
+ TIsComponentAnimated extends boolean,
350
+ TListItem = NoListItem
351
+ > = ResolveListItemProps<
352
+ MaybeAnimatedProps<
353
+ ScrollableComponentProps<TScrollableComponent>,
354
+ TIsComponentAnimated
355
+ >,
356
+ TListItem
357
+ >;
358
+
359
+ type HeaderMotionScrollablePublicProps<
360
+ TScrollableComponent extends ScrollableComponent,
361
+ TIsComponentAnimated extends boolean,
362
+ TListItem = NoListItem
363
+ > = HeaderMotionScrollableBaseProps<
364
+ TScrollableComponent,
365
+ TIsComponentAnimated,
366
+ TListItem
367
+ > &
368
+ HeaderMotionScrollableOwnProps<
369
+ ResolveScrollableRefTarget<
370
+ TScrollableComponent,
371
+ HeaderMotionScrollableBaseProps<
372
+ TScrollableComponent,
373
+ TIsComponentAnimated,
374
+ TListItem
375
+ >
376
+ >
377
+ >;
378
+
379
+ type HeaderMotionGenericScrollableComponent<
380
+ TScrollableComponent extends ScrollableComponent,
381
+ TIsComponentAnimated extends boolean
382
+ > = {
383
+ <TListItem = any>(
384
+ props: HeaderMotionScrollablePublicProps<
385
+ TScrollableComponent,
386
+ TIsComponentAnimated,
387
+ TListItem
388
+ >
389
+ ): ReactElement | null;
390
+ displayName?: string;
391
+ };
392
+
393
+ type HeaderMotionStaticScrollableComponent<
394
+ TScrollableComponent extends ScrollableComponent,
395
+ TIsComponentAnimated extends boolean
396
+ > = {
397
+ (
398
+ props: HeaderMotionScrollablePublicProps<
399
+ TScrollableComponent,
400
+ TIsComponentAnimated
401
+ >
402
+ ): ReactElement | null;
403
+ displayName?: string;
404
+ };
405
+
406
+ type HeaderMotionScrollableComponent<
407
+ TScrollableComponent extends ScrollableComponent,
408
+ TIsComponentAnimated extends boolean
409
+ > = HasGenericDataProp<
410
+ ScrollableComponentProps<TScrollableComponent>
411
+ > extends true
412
+ ? HeaderMotionGenericScrollableComponent<
413
+ TScrollableComponent,
414
+ TIsComponentAnimated
415
+ >
416
+ : HeaderMotionStaticScrollableComponent<
417
+ TScrollableComponent,
418
+ TIsComponentAnimated
419
+ >;
420
+
421
+ type ScrollableRuntimeProps = HeaderMotionScrollableOwnProps & {
422
+ children?: ReactNode;
423
+ contentContainerStyle?: ScrollViewProps['contentContainerStyle'];
424
+ onScroll?: ScrollViewProps['onScroll'];
425
+ onScrollBeginDrag?: ScrollViewProps['onScrollBeginDrag'];
426
+ onScrollEndDrag?: ScrollViewProps['onScrollEndDrag'];
427
+ onMomentumScrollBegin?: ScrollViewProps['onMomentumScrollBegin'];
428
+ onMomentumScrollEnd?: ScrollViewProps['onMomentumScrollEnd'];
429
+ refreshControl?: ReactElement | null;
430
+ refreshing?: UseScrollManagerOptions['refreshing'] | null;
431
+ onRefresh?: UseScrollManagerOptions['onRefresh'] | null;
432
+ progressViewOffset?: UseScrollManagerOptions['progressViewOffset'];
433
+ [key: string]: unknown;
434
+ };
435
+
436
+ type ScrollableImplementationComponent = (
437
+ props: ScrollableRuntimeProps
438
+ ) => ReactElement | null;
@@ -1,6 +1,8 @@
1
+ export * from './Bridge';
1
2
  export * from './FlatList';
2
3
  export * from './Header';
3
- export * from './HeaderBase';
4
+ export * from './NavigationBridge';
4
5
  export * from './HeaderMotion';
5
6
  export * from './ScrollManager';
6
7
  export * from './ScrollView';
8
+ export * from './createHeaderMotionScrollable';
package/src/context.ts CHANGED
@@ -1,20 +1,14 @@
1
- import { createContext } from 'react';
2
- import { type SharedValue } from 'react-native-reanimated';
3
- import type {
4
- MeasureAnimatedHeaderAndSet,
5
- Progress,
6
- ScrollValues,
7
- } from './types';
8
-
9
- interface HeaderMotionContextType {
10
- progress: Progress;
11
- measureTotalHeight: MeasureAnimatedHeaderAndSet;
12
- measureDynamic: MeasureAnimatedHeaderAndSet;
13
- scrollValues: SharedValue<ScrollValues>;
14
- activeScrollId: SharedValue<string> | undefined;
15
- progressThreshold: number;
16
- originalHeaderHeight: number;
17
- }
1
+ import { createContext, useContext } from 'react';
2
+ import type { HeaderMotionBridgeValue } from './types';
18
3
 
19
4
  export const HeaderMotionContext =
20
- createContext<HeaderMotionContextType | null>(null);
5
+ createContext<HeaderMotionBridgeValue | null>(null);
6
+
7
+ export function useHeaderMotionContextOrThrow(errorMessage: string) {
8
+ const ctxValue = useContext(HeaderMotionContext);
9
+ if (!ctxValue) {
10
+ throw new Error(errorMessage);
11
+ }
12
+
13
+ return ctxValue;
14
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './useActiveScrollId';
2
+ export * from './useHeaderMotionBridge';
2
3
  export * from './useMotionProgress';
3
4
  export * from './useScrollManager';