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
package/src/index.ts
CHANGED
|
@@ -1,76 +1,122 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
createHeaderMotionScrollable,
|
|
3
|
+
Bridge,
|
|
4
4
|
HeaderMotionContextProvider,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
FlatList,
|
|
6
|
+
Header,
|
|
7
|
+
NavigationBridge,
|
|
8
|
+
ScrollManager,
|
|
9
|
+
ScrollView,
|
|
10
|
+
type CreateHeaderMotionScrollableOptions,
|
|
11
|
+
type HeaderProps,
|
|
12
|
+
type HeaderMotionBridgeProps,
|
|
9
13
|
type HeaderMotionFlatListProps,
|
|
10
|
-
type
|
|
14
|
+
type HeaderMotionNavigationBridgeProps,
|
|
11
15
|
type HeaderMotionProps,
|
|
12
16
|
type HeaderMotionScrollManagerProps,
|
|
17
|
+
type HeaderMotionScrollableOwnProps,
|
|
13
18
|
type HeaderMotionScrollViewProps,
|
|
14
19
|
} from './components';
|
|
15
|
-
import type {
|
|
20
|
+
import type { HeaderDynamicProps } from './types';
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
type HeaderMotionCompound = typeof HeaderMotionContextProvider & {
|
|
23
|
+
/**
|
|
24
|
+
* Header container that measures the total header height and can optionally
|
|
25
|
+
* make the header surface pannable.
|
|
26
|
+
*
|
|
27
|
+
* Use `HeaderMotion.Header.Dynamic` inside it to mark the part of the header
|
|
28
|
+
* that should define the collapse distance.
|
|
29
|
+
*/
|
|
30
|
+
Header: typeof Header;
|
|
31
|
+
/**
|
|
32
|
+
* Captures the current HeaderMotion context and exposes it through a render
|
|
33
|
+
* function so it can be forwarded across a React tree boundary.
|
|
34
|
+
*
|
|
35
|
+
* This is primarily useful for navigation-rendered headers.
|
|
36
|
+
*/
|
|
37
|
+
Bridge: typeof Bridge;
|
|
38
|
+
/**
|
|
39
|
+
* Re-provides a previously captured HeaderMotion context value in another
|
|
40
|
+
* subtree.
|
|
41
|
+
*
|
|
42
|
+
* This is primarily useful for navigation libraries that render headers
|
|
43
|
+
* outside the screen subtree where `HeaderMotion` lives.
|
|
44
|
+
*/
|
|
45
|
+
NavigationBridge: typeof NavigationBridge;
|
|
46
|
+
/**
|
|
47
|
+
* Render-prop wrapper for managing a custom scrollable.
|
|
48
|
+
*
|
|
49
|
+
* Prefer `createHeaderMotionScrollable()` for most custom integrations. Use
|
|
50
|
+
* `ScrollManager` only when the factory approach is not flexible enough.
|
|
26
51
|
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
ScrollManager: typeof ScrollManager;
|
|
53
|
+
/**
|
|
54
|
+
* Pre-wired `Animated.ScrollView` that participates in HeaderMotion's scroll
|
|
55
|
+
* tracking and header offsetting.
|
|
56
|
+
*/
|
|
57
|
+
ScrollView: typeof ScrollView;
|
|
58
|
+
/**
|
|
59
|
+
* Pre-wired `Animated.FlatList` that participates in HeaderMotion's scroll
|
|
60
|
+
* tracking and header offsetting.
|
|
61
|
+
*/
|
|
62
|
+
FlatList: typeof FlatList;
|
|
34
63
|
};
|
|
35
64
|
|
|
36
65
|
/**
|
|
37
66
|
* Main HeaderMotion component.
|
|
38
|
-
*
|
|
67
|
+
* Root provider and compound entrypoint for the library.
|
|
68
|
+
*
|
|
69
|
+
* It tracks header measurements, derives the shared `progress` value, and
|
|
70
|
+
* exposes the pre-wired subcomponents used to connect headers and scrollables.
|
|
39
71
|
*
|
|
40
72
|
* @example
|
|
41
73
|
* ```tsx
|
|
42
74
|
* <HeaderMotion>
|
|
43
|
-
* <HeaderMotion.
|
|
44
|
-
* {(
|
|
75
|
+
* <HeaderMotion.Bridge>
|
|
76
|
+
* {(value) => (
|
|
45
77
|
* <Stack.Screen
|
|
46
78
|
* options={{
|
|
47
79
|
* header: () => (
|
|
48
|
-
* <
|
|
80
|
+
* <HeaderMotion.NavigationBridge value={value}>
|
|
81
|
+
* <MyAnimatedHeader />
|
|
82
|
+
* </HeaderMotion.NavigationBridge>
|
|
49
83
|
* ),
|
|
50
84
|
* }}
|
|
51
85
|
* />
|
|
52
86
|
* )}
|
|
53
|
-
* </HeaderMotion.
|
|
87
|
+
* </HeaderMotion.Bridge>
|
|
54
88
|
* <HeaderMotion.ScrollView>
|
|
55
89
|
* <MyScrollableContent />
|
|
56
90
|
* </HeaderMotion.ScrollView>
|
|
57
91
|
* </HeaderMotion>
|
|
58
92
|
* ```
|
|
59
93
|
*/
|
|
60
|
-
const HeaderMotion =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
const HeaderMotion: HeaderMotionCompound = Object.assign(
|
|
95
|
+
HeaderMotionContextProvider,
|
|
96
|
+
{
|
|
97
|
+
Header,
|
|
98
|
+
Bridge,
|
|
99
|
+
NavigationBridge,
|
|
100
|
+
ScrollManager,
|
|
101
|
+
ScrollView,
|
|
102
|
+
FlatList,
|
|
103
|
+
}
|
|
104
|
+
);
|
|
65
105
|
|
|
66
106
|
export default HeaderMotion;
|
|
67
107
|
export * from './hooks';
|
|
68
108
|
export type * from './types';
|
|
69
|
-
export {
|
|
109
|
+
export { createHeaderMotionScrollable };
|
|
110
|
+
export { Bridge, Header, NavigationBridge };
|
|
70
111
|
export type {
|
|
112
|
+
CreateHeaderMotionScrollableOptions,
|
|
113
|
+
HeaderDynamicProps,
|
|
71
114
|
HeaderMotionFlatListProps,
|
|
72
|
-
|
|
115
|
+
HeaderMotionBridgeProps,
|
|
116
|
+
HeaderMotionNavigationBridgeProps,
|
|
73
117
|
HeaderMotionProps,
|
|
74
118
|
HeaderMotionScrollManagerProps,
|
|
119
|
+
HeaderMotionScrollableOwnProps,
|
|
75
120
|
HeaderMotionScrollViewProps,
|
|
121
|
+
HeaderProps,
|
|
76
122
|
};
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,58 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type {
|
|
3
3
|
LayoutChangeEvent,
|
|
4
|
-
RefreshControlProps,
|
|
5
4
|
ScrollViewProps,
|
|
5
|
+
ViewProps,
|
|
6
6
|
} from 'react-native';
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AnimatedProps,
|
|
9
|
+
AnimatedRef,
|
|
10
|
+
SharedValue,
|
|
11
|
+
} from 'react-native-reanimated';
|
|
8
12
|
import { DEFAULT_SCROLL_ID } from './utils/defaults';
|
|
13
|
+
import type { InstanceOrElement } from 'react-native-reanimated/lib/typescript/commonTypes';
|
|
14
|
+
import type {
|
|
15
|
+
GestureStateChangeEvent,
|
|
16
|
+
PanGestureHandlerEventPayload,
|
|
17
|
+
} from 'react-native-gesture-handler';
|
|
18
|
+
import type { WithDecayConfig } from 'react-native-reanimated';
|
|
9
19
|
|
|
10
20
|
export type Progress = SharedValue<number>;
|
|
21
|
+
export type HeaderMotionOffsetStrategy =
|
|
22
|
+
| 'padding'
|
|
23
|
+
| 'margin'
|
|
24
|
+
| 'top'
|
|
25
|
+
| 'translate'
|
|
26
|
+
| 'none';
|
|
27
|
+
|
|
28
|
+
export interface HeaderMotionOffsetProps {
|
|
29
|
+
/**
|
|
30
|
+
* How the scrollable content should be pushed below the measured header.
|
|
31
|
+
*
|
|
32
|
+
* `padding` is the safest default for most screens. `margin`, `top`, and
|
|
33
|
+
* `translate` can be useful when the scrollable or its children need a
|
|
34
|
+
* different layout behavior.
|
|
35
|
+
*
|
|
36
|
+
* `top` and `translate` add bottom compensation so the end of the content
|
|
37
|
+
* remains reachable.
|
|
38
|
+
*
|
|
39
|
+
* @default 'padding'
|
|
40
|
+
*/
|
|
41
|
+
headerOffsetStrategy?: HeaderMotionOffsetStrategy;
|
|
42
|
+
/**
|
|
43
|
+
* Adds a minimum content height so scrollables with short content can still collapse the
|
|
44
|
+
* header completely.
|
|
45
|
+
*
|
|
46
|
+
* **Experimental: this relies on extra layout measurement and may still be
|
|
47
|
+
* refined.**
|
|
48
|
+
*
|
|
49
|
+
* Enable this when some screens do not have enough content to naturally
|
|
50
|
+
* scroll through the full collapse distance.
|
|
51
|
+
*
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
ensureScrollableContentMinHeight?: boolean;
|
|
55
|
+
}
|
|
11
56
|
|
|
12
57
|
export type ProgressThreshold =
|
|
13
58
|
| number
|
|
@@ -29,44 +74,59 @@ export type ScrollValues = Record<string, ScrollValue> & {
|
|
|
29
74
|
[key in typeof DEFAULT_SCROLL_ID]?: ScrollValue;
|
|
30
75
|
};
|
|
31
76
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
77
|
+
export interface MotionProgress {
|
|
78
|
+
progress: Progress;
|
|
79
|
+
progressThreshold: SharedValue<number>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type HeaderPanDecayEvent =
|
|
83
|
+
GestureStateChangeEvent<PanGestureHandlerEventPayload>;
|
|
84
|
+
|
|
85
|
+
export type HeaderPanDecayConfig =
|
|
86
|
+
| WithDecayConfig
|
|
87
|
+
| ((event: HeaderPanDecayEvent) => WithDecayConfig);
|
|
35
88
|
|
|
36
|
-
export type
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
> = WithCollapsibleHeaderProps<T> & {
|
|
40
|
-
onTabChange: (newTab: Tab) => void;
|
|
41
|
-
activeTab: Tab;
|
|
89
|
+
export type HeaderAsChildProps = {
|
|
90
|
+
asChild: true;
|
|
91
|
+
children: ReactElement;
|
|
42
92
|
};
|
|
43
93
|
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
|
|
94
|
+
export type HeaderDefaultProps = AnimatedProps<ViewProps> & {
|
|
95
|
+
asChild?: false;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type HeaderDynamicProps = HeaderDefaultProps | HeaderAsChildProps;
|
|
99
|
+
|
|
100
|
+
export interface HeaderMotionBridgeValue extends MotionProgress {
|
|
47
101
|
measureTotalHeight: MeasureAnimatedHeaderAndSet;
|
|
48
102
|
measureDynamic: MeasureAnimatedHeaderAndSet;
|
|
103
|
+
headerPanMomentumOffset: SharedValue<number | null>;
|
|
104
|
+
scrollValues: SharedValue<ScrollValues>;
|
|
105
|
+
activeScrollId: SharedValue<string> | undefined;
|
|
106
|
+
scrollToRef: React.RefObject<ScrollTo | null>;
|
|
107
|
+
originalHeaderHeight: number;
|
|
49
108
|
}
|
|
50
109
|
|
|
51
110
|
export interface ScrollManagerHeaderMotionContext {
|
|
52
111
|
originalHeaderHeight: number;
|
|
53
|
-
|
|
54
|
-
| {}
|
|
55
|
-
| {
|
|
56
|
-
minHeight: number;
|
|
57
|
-
};
|
|
112
|
+
contentContainerMinHeight?: number;
|
|
58
113
|
}
|
|
59
114
|
|
|
60
|
-
export interface ScrollManagerConfig {
|
|
61
|
-
scrollableProps:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
refreshControl?: ReactElement<RefreshControlProps>;
|
|
65
|
-
ref: AnimatedRef<any>; // TODO: better typing
|
|
115
|
+
export interface ScrollManagerConfig<TRef extends InstanceOrElement = any> {
|
|
116
|
+
scrollableProps: Pick<ScrollViewProps, 'onScroll' | 'onLayout'> & {
|
|
117
|
+
refreshControl?: ReactElement;
|
|
118
|
+
ref: AnimatedRef<TRef>;
|
|
66
119
|
};
|
|
67
120
|
headerMotionContext: ScrollManagerHeaderMotionContext;
|
|
68
121
|
}
|
|
69
122
|
|
|
123
|
+
export type ScrollTo = (y: number, options?: ScrollToOptions) => void;
|
|
124
|
+
|
|
70
125
|
export type ScrollHandlerContext = {
|
|
71
126
|
lastOffset: number | undefined;
|
|
72
127
|
};
|
|
128
|
+
|
|
129
|
+
interface ScrollToOptions {
|
|
130
|
+
isValueDelta?: boolean;
|
|
131
|
+
animated?: boolean;
|
|
132
|
+
}
|
package/src/utils/defaults.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
HeaderMotionOffsetStrategy,
|
|
3
|
+
MeasureAnimatedHeader,
|
|
4
|
+
ProgressThreshold,
|
|
5
|
+
} from '../types';
|
|
2
6
|
|
|
3
7
|
const DEFAULT_PROGRESS_THRESHOLD: ProgressThreshold = (measuredHeaderValue) =>
|
|
4
8
|
measuredHeaderValue;
|
|
5
9
|
const DEFAULT_MEASURE_DYNAMIC: MeasureAnimatedHeader = (e) =>
|
|
6
10
|
e.nativeEvent.layout.height;
|
|
11
|
+
const DEFAULT_HEADER_OFFSET_STRATEGY: HeaderMotionOffsetStrategy = 'padding';
|
|
7
12
|
|
|
8
13
|
// Symbol doesn't work?
|
|
9
14
|
// const DEFAULT_SCROLL_ID = Symbol("HEADER_MOTION_DEFAULT_SCROLL_ID");
|
|
10
15
|
const DEFAULT_SCROLL_ID = '__HEADER_MOTION_DEFAULT_SCROLL_ID__';
|
|
11
16
|
|
|
12
17
|
export {
|
|
18
|
+
DEFAULT_HEADER_OFFSET_STRATEGY,
|
|
13
19
|
DEFAULT_MEASURE_DYNAMIC,
|
|
14
20
|
DEFAULT_PROGRESS_THRESHOLD,
|
|
15
21
|
DEFAULT_SCROLL_ID,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Fragment,
|
|
3
|
+
cloneElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import type { ViewProps } from 'react-native';
|
|
8
|
+
|
|
9
|
+
export type SlottableElementProps = {
|
|
10
|
+
onLayout?: ViewProps['onLayout'];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type SlottableElement = ReactElement<SlottableElementProps>;
|
|
14
|
+
|
|
15
|
+
export function composeOnLayoutHandlers(
|
|
16
|
+
userHandler: ViewProps['onLayout'],
|
|
17
|
+
internalHandler: ViewProps['onLayout']
|
|
18
|
+
) {
|
|
19
|
+
return (e: Parameters<NonNullable<ViewProps['onLayout']>>[0]) => {
|
|
20
|
+
internalHandler?.(e);
|
|
21
|
+
userHandler?.(e);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveSlottableChild(
|
|
26
|
+
componentName: string,
|
|
27
|
+
child: ReactElement
|
|
28
|
+
) {
|
|
29
|
+
if (!isValidElement(child) || child.type === Fragment) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`${componentName} with \`asChild\` expects a single valid React element child that accepts \`onLayout\`.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return child as SlottableElement;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function cloneWithOnLayout(
|
|
39
|
+
child: SlottableElement,
|
|
40
|
+
onLayout: ViewProps['onLayout'],
|
|
41
|
+
componentName: string
|
|
42
|
+
) {
|
|
43
|
+
if (!isValidElement(child)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`${componentName} with \`asChild\` expects a valid React element child.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return cloneElement(child, {
|
|
50
|
+
onLayout: composeOnLayoutHandlers(child.props.onLayout, onLayout),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
2
|
+
import type { HeaderMotionOffsetStrategy } from '../types';
|
|
3
|
+
import { DEFAULT_HEADER_OFFSET_STRATEGY } from './defaults';
|
|
4
|
+
|
|
5
|
+
type HeaderOffsetValue = number | SharedValue<number>;
|
|
6
|
+
|
|
7
|
+
type HeaderOffsetStyle =
|
|
8
|
+
| undefined
|
|
9
|
+
| { paddingTop: HeaderOffsetValue }
|
|
10
|
+
| { marginTop: HeaderOffsetValue }
|
|
11
|
+
| { top: HeaderOffsetValue; paddingBottom: HeaderOffsetValue }
|
|
12
|
+
| {
|
|
13
|
+
transform: [{ translateY: HeaderOffsetValue }];
|
|
14
|
+
paddingBottom: HeaderOffsetValue;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function resolveHeaderOffsetStyle(
|
|
18
|
+
originalHeaderHeight: HeaderOffsetValue,
|
|
19
|
+
headerOffsetStrategy: HeaderMotionOffsetStrategy = DEFAULT_HEADER_OFFSET_STRATEGY
|
|
20
|
+
): HeaderOffsetStyle {
|
|
21
|
+
switch (headerOffsetStrategy) {
|
|
22
|
+
case 'none':
|
|
23
|
+
return undefined;
|
|
24
|
+
case 'margin':
|
|
25
|
+
return { marginTop: originalHeaderHeight };
|
|
26
|
+
case 'top':
|
|
27
|
+
return {
|
|
28
|
+
top: originalHeaderHeight,
|
|
29
|
+
paddingBottom: originalHeaderHeight,
|
|
30
|
+
};
|
|
31
|
+
case 'translate':
|
|
32
|
+
return {
|
|
33
|
+
transform: [{ translateY: originalHeaderHeight }],
|
|
34
|
+
paddingBottom: originalHeaderHeight,
|
|
35
|
+
};
|
|
36
|
+
case 'padding':
|
|
37
|
+
default:
|
|
38
|
+
return { paddingTop: originalHeaderHeight };
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cloneElement,
|
|
3
|
+
createElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import { RefreshControl, type RefreshControlProps } from 'react-native';
|
|
8
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
9
|
+
import Animated, { useAnimatedProps } from 'react-native-reanimated';
|
|
10
|
+
|
|
11
|
+
// FIXME: Types are a mess here
|
|
12
|
+
|
|
13
|
+
const AnimatedRefreshControl = Animated.createAnimatedComponent(RefreshControl);
|
|
14
|
+
|
|
15
|
+
type MaybeShared<T> = T | SharedValue<T | undefined>;
|
|
16
|
+
|
|
17
|
+
export interface ResolveRefreshControlOptions {
|
|
18
|
+
refreshControl?: MaybeShared<ReactElement<RefreshControlProps>>;
|
|
19
|
+
refreshing?: MaybeShared<boolean>;
|
|
20
|
+
onRefresh?: MaybeShared<() => void>;
|
|
21
|
+
progressViewOffset: MaybeShared<number> | SharedValue<number>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveRefreshControl({
|
|
25
|
+
refreshControl,
|
|
26
|
+
refreshing,
|
|
27
|
+
onRefresh,
|
|
28
|
+
progressViewOffset,
|
|
29
|
+
}: ResolveRefreshControlOptions):
|
|
30
|
+
| ReactElement<
|
|
31
|
+
RefreshControlProps | React.ComponentProps<typeof AnimatedRefreshControl>
|
|
32
|
+
>
|
|
33
|
+
| undefined {
|
|
34
|
+
if (!refreshControl) {
|
|
35
|
+
return createRefreshControlWithOffset({
|
|
36
|
+
refreshing,
|
|
37
|
+
onRefresh,
|
|
38
|
+
progressViewOffset,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return isValidElement<RefreshControlProps>(refreshControl)
|
|
43
|
+
? injectProgressViewOffset(refreshControl, progressViewOffset)
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createRefreshControlWithOffset({
|
|
48
|
+
refreshing,
|
|
49
|
+
onRefresh,
|
|
50
|
+
progressViewOffset,
|
|
51
|
+
}: Omit<ResolveRefreshControlOptions, 'refreshControl'>) {
|
|
52
|
+
if (!onRefresh) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return createElement(ResolvedRefreshControl, {
|
|
57
|
+
refreshing: (refreshing as boolean) ?? false,
|
|
58
|
+
onRefresh: onRefresh as () => void,
|
|
59
|
+
progressViewOffset: progressViewOffset as number,
|
|
60
|
+
}) as ReactElement<RefreshControlProps>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function injectProgressViewOffset(
|
|
64
|
+
refreshControl: ReactElement<RefreshControlProps>,
|
|
65
|
+
progressViewOffset: MaybeShared<number> | SharedValue<number>
|
|
66
|
+
) {
|
|
67
|
+
const offset = refreshControl.props.progressViewOffset ?? progressViewOffset;
|
|
68
|
+
|
|
69
|
+
if (!isSharedValue<number>(offset)) {
|
|
70
|
+
return cloneElement(refreshControl, {
|
|
71
|
+
progressViewOffset: offset as number,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return createElement(ResolvedRefreshControl, {
|
|
76
|
+
...refreshControl.props,
|
|
77
|
+
progressViewOffset: progressViewOffset as number,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ResolvedRefreshControl({
|
|
82
|
+
refreshing,
|
|
83
|
+
onRefresh,
|
|
84
|
+
progressViewOffset,
|
|
85
|
+
...props
|
|
86
|
+
}: React.ComponentProps<typeof AnimatedRefreshControl>) {
|
|
87
|
+
const animatedProps = useAnimatedProps<RefreshControlProps>(() => {
|
|
88
|
+
return {
|
|
89
|
+
...(isSharedValue(refreshing) ? { refreshing: refreshing.value } : {}),
|
|
90
|
+
...(isSharedValue(onRefresh) ? { onRefresh: onRefresh.value } : {}),
|
|
91
|
+
...(isSharedValue(progressViewOffset)
|
|
92
|
+
? { progressViewOffset: progressViewOffset.value }
|
|
93
|
+
: {}),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const nonAnimatedProps = {
|
|
98
|
+
...(isSharedValue(refreshing) ? {} : { refreshing }),
|
|
99
|
+
...(isSharedValue(onRefresh) ? {} : { onRefresh }),
|
|
100
|
+
...(isSharedValue(progressViewOffset) ? {} : { progressViewOffset }),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<AnimatedRefreshControl
|
|
105
|
+
{...props}
|
|
106
|
+
{...nonAnimatedProps}
|
|
107
|
+
refreshing={nonAnimatedProps.refreshing as boolean}
|
|
108
|
+
animatedProps={animatedProps}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isSharedValue<T>(
|
|
114
|
+
value: MaybeShared<T> | SharedValue<T>
|
|
115
|
+
): value is SharedValue<T> {
|
|
116
|
+
'worklet';
|
|
117
|
+
return typeof value === 'object' && value !== null && 'value' in value;
|
|
118
|
+
}
|
package/src/utils/values.ts
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
2
|
+
import type { ScrollValue, ScrollValues } from '../types';
|
|
3
|
+
import { DEFAULT_SCROLL_ID } from './defaults';
|
|
2
4
|
|
|
3
5
|
export function getInitialScrollValue(): ScrollValue {
|
|
4
6
|
'worklet';
|
|
5
7
|
return { min: 0, current: 0 };
|
|
6
8
|
}
|
|
9
|
+
|
|
10
|
+
export function ensureScrollValueRegistered(
|
|
11
|
+
scrollValues: SharedValue<ScrollValues>,
|
|
12
|
+
id: string
|
|
13
|
+
): ScrollValues {
|
|
14
|
+
'worklet';
|
|
15
|
+
|
|
16
|
+
const values = scrollValues.get();
|
|
17
|
+
if (values[id]) {
|
|
18
|
+
return values;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
scrollValues.modify((value) => {
|
|
22
|
+
if (!value[id]) {
|
|
23
|
+
(value as ScrollValues)[id] = getInitialScrollValue();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return value;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return scrollValues.get();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function warnIfMissingActiveScrollId(
|
|
33
|
+
scrollValues: ScrollValues,
|
|
34
|
+
id: string,
|
|
35
|
+
activeScrollId: string | undefined
|
|
36
|
+
): void {
|
|
37
|
+
'worklet';
|
|
38
|
+
|
|
39
|
+
if (!__DEV__ || activeScrollId || id === DEFAULT_SCROLL_ID) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let nonDefaultCount = 0;
|
|
44
|
+
let nonDefaultIds = '';
|
|
45
|
+
|
|
46
|
+
for (const key in scrollValues) {
|
|
47
|
+
if (key === DEFAULT_SCROLL_ID) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
nonDefaultCount += 1;
|
|
52
|
+
nonDefaultIds = nonDefaultIds ? `${nonDefaultIds}, ${key}` : key;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (nonDefaultCount < 1) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.warn(
|
|
60
|
+
`[react-native-header-motion] Explicit scrollIds (${nonDefaultIds}) are registered but no activeScrollId was provided. Pass useActiveScrollId(...).sv to <HeaderMotion activeScrollId={...}> to keep header motion deterministic.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { StyleSheet, View } from 'react-native';
|
|
4
|
-
import Animated from 'react-native-reanimated';
|
|
5
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
-
/**
|
|
7
|
-
* Base header component with absolute positioning.
|
|
8
|
-
* Provides a foundation for building headers that need to be positioned absolutely.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```tsx
|
|
12
|
-
* <HeaderBase
|
|
13
|
-
* onLayout={measureTotalHeight}
|
|
14
|
-
* >
|
|
15
|
-
* ...
|
|
16
|
-
* </HeaderBase>
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
export function HeaderBase({
|
|
20
|
-
style,
|
|
21
|
-
...rest
|
|
22
|
-
}) {
|
|
23
|
-
return /*#__PURE__*/_jsx(View, {
|
|
24
|
-
style: [style, styles.container],
|
|
25
|
-
...rest
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Animated version of HeaderBase using Reanimated's Animated.View.
|
|
31
|
-
* Use this when you need to animate the header based on scroll progress.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```tsx
|
|
35
|
-
* <AnimatedHeaderBase
|
|
36
|
-
* onLayout={measureTotalHeight}
|
|
37
|
-
* style={[{ paddingTop: insets.top }, animatedStyle]}
|
|
38
|
-
* >
|
|
39
|
-
* ...
|
|
40
|
-
* </AnimatedHeaderBase>
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export function AnimatedHeaderBase({
|
|
44
|
-
style,
|
|
45
|
-
...rest
|
|
46
|
-
}) {
|
|
47
|
-
return /*#__PURE__*/_jsx(Animated.View, {
|
|
48
|
-
style: [style, styles.container],
|
|
49
|
-
...rest
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
const styles = StyleSheet.create({
|
|
53
|
-
container: {
|
|
54
|
-
position: 'absolute',
|
|
55
|
-
left: 0,
|
|
56
|
-
right: 0
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
//# sourceMappingURL=HeaderBase.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["StyleSheet","View","Animated","jsx","_jsx","HeaderBase","style","rest","styles","container","AnimatedHeaderBase","create","position","left","right"],"sourceRoot":"../../../src","sources":["components/HeaderBase.tsx"],"mappings":";;AAAA,SAASA,UAAU,EAAEC,IAAI,QAAwB,cAAc;AAC/D,OAAOC,QAAQ,MAA8B,yBAAyB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAKvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAC;EAAEC,KAAK;EAAE,GAAGC;AAAsB,CAAC,EAAE;EAC9D,oBAAOH,IAAA,CAACH,IAAI;IAACK,KAAK,EAAE,CAACA,KAAK,EAAEE,MAAM,CAACC,SAAS,CAAE;IAAA,GAAKF;EAAI,CAAG,CAAC;AAC7D;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,kBAAkBA,CAAC;EACjCJ,KAAK;EACL,GAAGC;AACoB,CAAC,EAAE;EAC1B,oBAAOH,IAAA,CAACF,QAAQ,CAACD,IAAI;IAACK,KAAK,EAAE,CAACA,KAAK,EAAEE,MAAM,CAACC,SAAS,CAAE;IAAA,GAAKF;EAAI,CAAG,CAAC;AACtE;AAEA,MAAMC,MAAM,GAAGR,UAAU,CAACW,MAAM,CAAC;EAC/BF,SAAS,EAAE;IACTG,QAAQ,EAAE,UAAU;IACpBC,IAAI,EAAE,CAAC;IACPC,KAAK,EAAE;EACT;AACF,CAAC,CAAC","ignoreList":[]}
|