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.
- package/README.md +400 -335
- package/lib/module/components/Bridge.js +16 -0
- package/lib/module/components/Bridge.js.map +1 -0
- package/lib/module/components/FlatList.js +5 -62
- package/lib/module/components/FlatList.js.map +1 -1
- package/lib/module/components/Header.js +71 -13
- package/lib/module/components/Header.js.map +1 -1
- package/lib/module/components/HeaderDynamic.js +34 -0
- package/lib/module/components/HeaderDynamic.js.map +1 -0
- package/lib/module/components/HeaderMotion.js +59 -23
- package/lib/module/components/HeaderMotion.js.map +1 -1
- package/lib/module/components/HeaderPanBoundary.js +54 -0
- package/lib/module/components/HeaderPanBoundary.js.map +1 -0
- package/lib/module/components/NavigationBridge.js +20 -0
- package/lib/module/components/NavigationBridge.js.map +1 -0
- package/lib/module/components/ScrollManager.js +7 -5
- package/lib/module/components/ScrollManager.js.map +1 -1
- package/lib/module/components/ScrollView.js +6 -47
- package/lib/module/components/ScrollView.js.map +1 -1
- package/lib/module/components/createHeaderMotionScrollable.js +136 -0
- package/lib/module/components/createHeaderMotionScrollable.js.map +1 -0
- package/lib/module/components/index.js +3 -1
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/context.js +8 -1
- package/lib/module/context.js.map +1 -1
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useActiveScrollId.js +7 -6
- package/lib/module/hooks/useActiveScrollId.js.map +1 -1
- package/lib/module/hooks/useHeaderMotionBridge.js +14 -0
- package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
- package/lib/module/hooks/useMotionProgress.js +10 -36
- package/lib/module/hooks/useMotionProgress.js.map +1 -1
- package/lib/module/hooks/useMotionProgress.test.js +56 -0
- package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
- package/lib/module/hooks/useScrollManager.js +219 -109
- package/lib/module/hooks/useScrollManager.js.map +1 -1
- package/lib/module/index.js +21 -18
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/defaults.js +2 -1
- package/lib/module/utils/defaults.js.map +1 -1
- package/lib/module/utils/header.js +24 -0
- package/lib/module/utils/header.js.map +1 -0
- package/lib/module/utils/headerOffsetStyle.js +31 -0
- package/lib/module/utils/headerOffsetStyle.js.map +1 -0
- package/lib/module/utils/index.js +3 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/refreshControl.js +93 -0
- package/lib/module/utils/refreshControl.js.map +1 -0
- package/lib/module/utils/values.js +36 -0
- package/lib/module/utils/values.js.map +1 -1
- package/lib/typescript/src/components/Bridge.d.ts +19 -0
- package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
- package/lib/typescript/src/components/FlatList.d.ts +7 -15
- package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/Header.d.ts +73 -12
- package/lib/typescript/src/components/Header.d.ts.map +1 -1
- package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
- package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
- package/lib/typescript/src/components/HeaderMotion.d.ts +37 -18
- package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
- package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
- package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
- package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
- package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/ScrollManager.d.ts +18 -25
- package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
- package/lib/typescript/src/components/ScrollView.d.ts +7 -14
- package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
- package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +3 -1
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/context.d.ts +3 -13
- package/lib/typescript/src/context.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
- package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
- package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
- package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
- package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useScrollManager.d.ts +63 -31
- package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +56 -26
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +63 -15
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/defaults.d.ts +3 -2
- package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
- package/lib/typescript/src/utils/header.d.ts +10 -0
- package/lib/typescript/src/utils/header.d.ts.map +1 -0
- package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
- package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +3 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/refreshControl.d.ts +150 -0
- package/lib/typescript/src/utils/refreshControl.d.ts.map +1 -0
- package/lib/typescript/src/utils/values.d.ts +4 -1
- package/lib/typescript/src/utils/values.d.ts.map +1 -1
- package/package.json +13 -5
- package/src/components/Bridge.tsx +29 -0
- package/src/components/FlatList.tsx +18 -84
- package/src/components/Header.tsx +159 -23
- package/src/components/HeaderDynamic.tsx +45 -0
- package/src/components/HeaderMotion.tsx +114 -41
- package/src/components/HeaderPanBoundary.tsx +92 -0
- package/src/components/NavigationBridge.tsx +30 -0
- package/src/components/ScrollManager.tsx +38 -43
- package/src/components/ScrollView.tsx +16 -68
- package/src/components/createHeaderMotionScrollable.tsx +438 -0
- package/src/components/index.ts +3 -1
- package/src/context.ts +12 -18
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useActiveScrollId.ts +7 -6
- package/src/hooks/useHeaderMotionBridge.ts +15 -0
- package/src/hooks/useMotionProgress.test.ts +67 -0
- package/src/hooks/useMotionProgress.ts +12 -37
- package/src/hooks/useScrollManager.ts +310 -129
- package/src/index.ts +82 -36
- package/src/types.ts +85 -25
- package/src/utils/defaults.ts +7 -1
- package/src/utils/header.tsx +52 -0
- package/src/utils/headerOffsetStyle.ts +40 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/refreshControl.tsx +118 -0
- package/src/utils/values.ts +57 -1
- package/lib/module/components/HeaderBase.js +0 -59
- package/lib/module/components/HeaderBase.js.map +0 -1
- package/lib/module/hooks/refreshControl.js +0 -31
- package/lib/module/hooks/refreshControl.js.map +0 -1
- package/lib/typescript/src/components/HeaderBase.d.ts +0 -34
- package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
- package/lib/typescript/src/hooks/refreshControl.d.ts +0 -13
- package/lib/typescript/src/hooks/refreshControl.d.ts.map +0 -1
- package/src/components/HeaderBase.tsx +0 -51
- 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 {
|
|
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
|
|
15
|
-
|
|
11
|
+
export interface HeaderMotionScrollManagerProps<
|
|
12
|
+
TRef extends InstanceOrElement = any
|
|
13
|
+
> extends UseScrollManagerOptions<TRef> {
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
40
|
-
*
|
|
29
|
+
* Render-prop wrapper around `useScrollManager()`.
|
|
30
|
+
*
|
|
31
|
+
* **Most code should prefer `createHeaderMotionScrollable()` instead.**
|
|
41
32
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
|
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(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 {
|
|
5
|
+
import {
|
|
6
|
+
createHeaderMotionScrollable,
|
|
7
|
+
type HeaderMotionScrollableOwnProps,
|
|
8
|
+
} from './createHeaderMotionScrollable';
|
|
6
9
|
|
|
7
|
-
export type HeaderMotionScrollViewProps = AnimatedScrollViewProps &
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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;
|
package/src/components/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export * from './Bridge';
|
|
1
2
|
export * from './FlatList';
|
|
2
3
|
export * from './Header';
|
|
3
|
-
export * from './
|
|
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 {
|
|
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<
|
|
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
|
+
}
|
package/src/hooks/index.ts
CHANGED