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
@@ -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,27 +1,14 @@
1
- import { createContext } from 'react';
2
- import { type SharedValue } from 'react-native-reanimated';
3
- import type {
4
- AnimatedHeaderBaseMotionProps,
5
- MeasureAnimatedHeaderAndSet,
6
- Progress,
7
- ScrollTo,
8
- ScrollValues,
9
- } from './types';
1
+ import { createContext, useContext } from 'react';
2
+ import type { HeaderMotionBridgeValue } from './types';
10
3
 
11
- interface HeaderMotionContextType {
12
- progress: Progress;
13
- measureTotalHeight: MeasureAnimatedHeaderAndSet;
14
- measureDynamic: MeasureAnimatedHeaderAndSet;
15
- enableHeaderPan: boolean;
16
- headerPanMomentumOffset: SharedValue<number | null>;
17
- animatedHeaderBaseProps: AnimatedHeaderBaseMotionProps;
18
- scrollValues: SharedValue<ScrollValues>;
19
- activeScrollId: SharedValue<string> | undefined;
20
- progressThreshold: SharedValue<number>;
21
- originalHeaderHeight: SharedValue<number>;
4
+ export const HeaderMotionContext =
5
+ createContext<HeaderMotionBridgeValue | null>(null);
22
6
 
23
- scrollToRef: React.RefObject<ScrollTo | null>;
24
- }
7
+ export function useHeaderMotionContextOrThrow(errorMessage: string) {
8
+ const ctxValue = useContext(HeaderMotionContext);
9
+ if (!ctxValue) {
10
+ throw new Error(errorMessage);
11
+ }
25
12
 
26
- export const HeaderMotionContext =
27
- createContext<HeaderMotionContextType | null>(null);
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';
@@ -3,17 +3,18 @@ import { useSharedValue } from 'react-native-reanimated';
3
3
  import type { ActiveScrollIdValues, SetActiveScrollId } from '../types';
4
4
 
5
5
  /**
6
- * Hook to manage active scroll ID for multi-scroll scenarios (e.g. tabs with different scroll views).
7
- * Returns both a state value and a shared value, along with a setter function.
6
+ * Keeps a React state value and a shared value in sync for the currently active
7
+ * scrollable.
8
8
  *
9
- * Use this when you have multiple scroll views (like in a tabbed interface) and need to
10
- * track which one is currently active. Pass the shared value to `HeaderMotion`'s `activeScrollId` prop.
9
+ * Use this when one header is shared across multiple scroll views, for example
10
+ * pager pages or tabs. Pass `values.sv` to `HeaderMotion` and use the setter
11
+ * whenever the active page changes.
11
12
  *
12
13
  * @template T - The type of the scroll ID string
13
14
  * @param initialActiveScrollId - The initial active scroll ID
14
15
  * @returns A tuple containing:
15
- * - `[0]`: Object with `state` (React state) and `sv` (shared value) for the active scroll ID
16
- * - `[1]`: Function to set the active scroll ID
16
+ * - an object with both the React `state` and shared-value `sv`
17
+ * - a setter that updates both in lockstep
17
18
  *
18
19
  * @example
19
20
  * ```tsx
@@ -0,0 +1,148 @@
1
+ import { useCallback } from 'react';
2
+ import {
3
+ useComposedEventHandler,
4
+ type AnimatedScrollViewProps,
5
+ type ScrollHandler,
6
+ type ScrollHandlerProcessed,
7
+ } from 'react-native-reanimated';
8
+ import { scheduleOnRN } from 'react-native-worklets';
9
+ import type { ScrollHandlerContext } from '../types';
10
+
11
+ type ConsumerScrollHandler = (event: unknown) => void;
12
+ type AnimatedScrollViewOnScroll = AnimatedScrollViewProps['onScroll'];
13
+ type ScrollEvent = Parameters<ScrollHandler<Record<string, unknown>>>[0];
14
+
15
+ export type ConsumerScrollEventHandlers = Pick<
16
+ AnimatedScrollViewProps,
17
+ | 'onScroll'
18
+ | 'onScrollBeginDrag'
19
+ | 'onScrollEndDrag'
20
+ | 'onMomentumScrollBegin'
21
+ | 'onMomentumScrollEnd'
22
+ >;
23
+
24
+ export interface ConsumerScrollBridges {
25
+ onScroll?: (event: ScrollEvent) => void;
26
+ onBeginDrag?: (event: ScrollEvent) => void;
27
+ onEndDrag?: (event: ScrollEvent) => void;
28
+ onMomentumBegin?: (event: ScrollEvent) => void;
29
+ onMomentumEnd?: (event: ScrollEvent) => void;
30
+ }
31
+
32
+ export function useConsumerScrollHandlers({
33
+ onScroll,
34
+ onScrollBeginDrag,
35
+ onScrollEndDrag,
36
+ onMomentumScrollBegin,
37
+ onMomentumScrollEnd,
38
+ }: ConsumerScrollEventHandlers): ConsumerScrollBridges {
39
+ const consumerOnScroll =
40
+ typeof onScroll === 'function'
41
+ ? (onScroll as ConsumerScrollHandler)
42
+ : undefined;
43
+ const consumerOnScrollBeginDrag =
44
+ typeof onScrollBeginDrag === 'function'
45
+ ? (onScrollBeginDrag as ConsumerScrollHandler)
46
+ : undefined;
47
+ const consumerOnScrollEndDrag =
48
+ typeof onScrollEndDrag === 'function'
49
+ ? (onScrollEndDrag as ConsumerScrollHandler)
50
+ : undefined;
51
+ const consumerOnMomentumScrollBegin =
52
+ typeof onMomentumScrollBegin === 'function'
53
+ ? (onMomentumScrollBegin as ConsumerScrollHandler)
54
+ : undefined;
55
+ const consumerOnMomentumScrollEnd =
56
+ typeof onMomentumScrollEnd === 'function'
57
+ ? (onMomentumScrollEnd as ConsumerScrollHandler)
58
+ : undefined;
59
+
60
+ const onScrollBridge = useCallback(
61
+ (event: ScrollEvent) => {
62
+ 'worklet';
63
+ if (!consumerOnScroll) {
64
+ return;
65
+ }
66
+ scheduleOnRN(consumerOnScroll, { nativeEvent: event } as unknown);
67
+ },
68
+ [consumerOnScroll]
69
+ );
70
+
71
+ const onBeginDragBridge = useCallback(
72
+ (event: ScrollEvent) => {
73
+ 'worklet';
74
+ if (!consumerOnScrollBeginDrag) {
75
+ return;
76
+ }
77
+ scheduleOnRN(consumerOnScrollBeginDrag, {
78
+ nativeEvent: event,
79
+ } as unknown);
80
+ },
81
+ [consumerOnScrollBeginDrag]
82
+ );
83
+
84
+ const onEndDragBridge = useCallback(
85
+ (event: ScrollEvent) => {
86
+ 'worklet';
87
+ if (!consumerOnScrollEndDrag) {
88
+ return;
89
+ }
90
+ scheduleOnRN(consumerOnScrollEndDrag, { nativeEvent: event } as unknown);
91
+ },
92
+ [consumerOnScrollEndDrag]
93
+ );
94
+
95
+ const onMomentumBeginBridge = useCallback(
96
+ (event: ScrollEvent) => {
97
+ 'worklet';
98
+ if (!consumerOnMomentumScrollBegin) {
99
+ return;
100
+ }
101
+ scheduleOnRN(consumerOnMomentumScrollBegin, {
102
+ nativeEvent: event,
103
+ } as unknown);
104
+ },
105
+ [consumerOnMomentumScrollBegin]
106
+ );
107
+
108
+ const onMomentumEndBridge = useCallback(
109
+ (event: ScrollEvent) => {
110
+ 'worklet';
111
+ if (!consumerOnMomentumScrollEnd) {
112
+ return;
113
+ }
114
+ scheduleOnRN(consumerOnMomentumScrollEnd, {
115
+ nativeEvent: event,
116
+ } as unknown);
117
+ },
118
+ [consumerOnMomentumScrollEnd]
119
+ );
120
+
121
+ return {
122
+ onScroll: onScrollBridge,
123
+ onBeginDrag: onBeginDragBridge,
124
+ onEndDrag: onEndDragBridge,
125
+ onMomentumBegin: onMomentumBeginBridge,
126
+ onMomentumEnd: onMomentumEndBridge,
127
+ };
128
+ }
129
+
130
+ export function useScrollHandlerComposition(
131
+ ownScrollHandler: ScrollHandlerProcessed<ScrollHandlerContext>,
132
+ consumerScrollHandler: AnimatedScrollViewOnScroll | undefined
133
+ ) {
134
+ // TODO: I guess the typing here could be more precise
135
+ const consumerWorkletHandler = isAnimatedScrollHandler(consumerScrollHandler)
136
+ ? (consumerScrollHandler as ScrollHandlerProcessed<ScrollHandlerContext>)
137
+ : null;
138
+
139
+ return useComposedEventHandler([ownScrollHandler, consumerWorkletHandler]);
140
+ }
141
+
142
+ function isAnimatedScrollHandler(
143
+ handler: AnimatedScrollViewOnScroll | undefined
144
+ ): handler is AnimatedScrollViewOnScroll {
145
+ // FUTURE: we could be checking just by typeof handler === 'object'?
146
+ // This seems safer for now, unless Reanimated changes this shape. Revisit
147
+ return !!handler && 'workletEventHandler' in handler;
148
+ }
@@ -0,0 +1,15 @@
1
+ import { useHeaderMotionContextOrThrow } from '../context';
2
+ import type { HeaderMotionBridgeValue } from '../types';
3
+
4
+ /**
5
+ * Returns the full internal HeaderMotion context value.
6
+ *
7
+ * Most app code should use `useMotionProgress()` instead. Reach for this hook
8
+ * only when you need to carry HeaderMotion context across a tree boundary and
9
+ * re-provide it somewhere else.
10
+ */
11
+ export function useHeaderMotionBridge(): HeaderMotionBridgeValue {
12
+ return useHeaderMotionContextOrThrow(
13
+ 'useHeaderMotionBridge must be used within <HeaderMotion />. Use it only when bridging context into a separate subtree with <HeaderMotion.Bridge /> and <HeaderMotion.NavigationBridge />.'
14
+ );
15
+ }