react-native-screen-transitions 2.0.2 → 2.0.4

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 (77) hide show
  1. package/lib/commonjs/utils/bounds/constants.js +3 -3
  2. package/lib/commonjs/utils/bounds/constants.js.map +1 -1
  3. package/lib/commonjs/utils/bounds/get-bounds.js +3 -3
  4. package/lib/commonjs/utils/bounds/get-bounds.js.map +1 -1
  5. package/lib/module/utils/bounds/constants.js +3 -3
  6. package/lib/module/utils/bounds/constants.js.map +1 -1
  7. package/lib/module/utils/bounds/get-bounds.js +3 -3
  8. package/lib/module/utils/bounds/get-bounds.js.map +1 -1
  9. package/lib/typescript/utils/bounds/_types/get-bounds.d.ts +2 -2
  10. package/lib/typescript/utils/bounds/_types/get-bounds.d.ts.map +1 -1
  11. package/lib/typescript/utils/bounds/constants.d.ts +4 -4
  12. package/lib/typescript/utils/bounds/constants.d.ts.map +1 -1
  13. package/lib/typescript/utils/bounds/get-bounds.d.ts +2 -2
  14. package/lib/typescript/utils/bounds/get-bounds.d.ts.map +1 -1
  15. package/lib/typescript/utils/bounds/index.d.ts +1 -1
  16. package/lib/typescript/utils/bounds/index.d.ts.map +1 -1
  17. package/package.json +4 -2
  18. package/src/__tests__/geometry.test.ts +127 -0
  19. package/src/components/bounds-activator.tsx +29 -0
  20. package/src/components/controllers/screen-lifecycle.tsx +72 -0
  21. package/src/components/create-transition-aware-component.tsx +99 -0
  22. package/src/components/root-transition-aware.tsx +56 -0
  23. package/src/configs/index.ts +2 -0
  24. package/src/configs/presets.ts +227 -0
  25. package/src/configs/specs.ts +9 -0
  26. package/src/hooks/animation/use-associated-style.tsx +28 -0
  27. package/src/hooks/animation/use-screen-animation.tsx +142 -0
  28. package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
  29. package/src/hooks/gestures/use-build-gestures.tsx +369 -0
  30. package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
  31. package/src/hooks/use-stable-callback.tsx +15 -0
  32. package/src/index.ts +32 -0
  33. package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
  34. package/src/integrations/native-stack/utils/debounce.tsx +14 -0
  35. package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
  36. package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
  37. package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
  38. package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
  39. package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
  40. package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
  41. package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
  42. package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
  43. package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
  44. package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
  45. package/src/providers/gestures.tsx +89 -0
  46. package/src/providers/keys.tsx +38 -0
  47. package/src/stores/animations.ts +45 -0
  48. package/src/stores/bounds.ts +71 -0
  49. package/src/stores/gestures.ts +55 -0
  50. package/src/stores/navigator-dismiss-state.ts +17 -0
  51. package/src/stores/utils/reset-stores-for-screen.ts +14 -0
  52. package/src/types/animation.ts +76 -0
  53. package/src/types/bounds.ts +82 -0
  54. package/src/types/core.ts +50 -0
  55. package/src/types/gesture.ts +33 -0
  56. package/src/types/navigator.ts +744 -0
  57. package/src/types/utils.ts +3 -0
  58. package/src/utils/animation/animate.ts +28 -0
  59. package/src/utils/animation/run-transition.ts +49 -0
  60. package/src/utils/bounds/_types/builder.ts +35 -0
  61. package/src/utils/bounds/_types/geometry.ts +17 -0
  62. package/src/utils/bounds/_types/get-bounds.ts +10 -0
  63. package/src/utils/bounds/build-bound-styles.ts +184 -0
  64. package/src/utils/bounds/constants.ts +25 -0
  65. package/src/utils/bounds/flatten-styles.ts +21 -0
  66. package/src/utils/bounds/geometry.ts +113 -0
  67. package/src/utils/bounds/get-bounds.ts +56 -0
  68. package/src/utils/bounds/index.ts +46 -0
  69. package/src/utils/bounds/style-composers.ts +172 -0
  70. package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
  71. package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
  72. package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
  73. package/src/utils/index.ts +1 -0
  74. package/lib/commonjs/__tests__ /geometry.test.js +0 -178
  75. package/lib/commonjs/__tests__ /geometry.test.js.map +0 -1
  76. package/lib/module/__tests__ /geometry.test.js +0 -178
  77. package/lib/module/__tests__ /geometry.test.js.map +0 -1
@@ -0,0 +1,227 @@
1
+ import {
2
+ Extrapolation,
3
+ interpolate,
4
+ interpolateColor,
5
+ } from "react-native-reanimated";
6
+ import type { ScreenTransitionConfig } from "../types/navigator";
7
+ import { DefaultSpec } from "./specs";
8
+
9
+ export const SlideFromTop = (
10
+ config: Partial<ScreenTransitionConfig> = {},
11
+ ): ScreenTransitionConfig => {
12
+ return {
13
+ enableTransitions: true,
14
+ gestureEnabled: true,
15
+ gestureDirection: "vertical-inverted",
16
+ screenStyleInterpolator: ({
17
+ current,
18
+ next,
19
+ layouts: {
20
+ screen: { height },
21
+ },
22
+ }) => {
23
+ "worklet";
24
+
25
+ const progress = current.progress + (next?.progress ?? 0);
26
+
27
+ const y = interpolate(progress, [0, 1, 2], [-height, 0, height]);
28
+
29
+ return {
30
+ contentStyle: {
31
+ transform: [{ translateY: y }],
32
+ },
33
+ };
34
+ },
35
+ transitionSpec: {
36
+ open: DefaultSpec,
37
+ close: DefaultSpec,
38
+ },
39
+
40
+ ...config,
41
+ };
42
+ };
43
+
44
+ export const ZoomIn = (
45
+ config: Partial<ScreenTransitionConfig> = {},
46
+ ): ScreenTransitionConfig => {
47
+ return {
48
+ enableTransitions: true,
49
+ gestureEnabled: false,
50
+ screenStyleInterpolator: ({ current, next }) => {
51
+ "worklet";
52
+
53
+ const progress = current.progress + (next?.progress ?? 0);
54
+
55
+ const scale = interpolate(
56
+ progress,
57
+ [0, 1, 2],
58
+ [0.5, 1, 0.5],
59
+ Extrapolation.CLAMP,
60
+ );
61
+
62
+ const opacity = interpolate(
63
+ progress,
64
+ [0, 1, 2],
65
+ [0, 1, 0],
66
+ Extrapolation.CLAMP,
67
+ );
68
+
69
+ return {
70
+ contentStyle: {
71
+ transform: [{ scale }],
72
+ opacity,
73
+ },
74
+ };
75
+ },
76
+ transitionSpec: {
77
+ open: DefaultSpec,
78
+ close: DefaultSpec,
79
+ },
80
+ ...config,
81
+ };
82
+ };
83
+
84
+ export const SlideFromBottom = (
85
+ config: Partial<ScreenTransitionConfig> = {},
86
+ ): ScreenTransitionConfig => {
87
+ return {
88
+ enableTransitions: true,
89
+ gestureEnabled: true,
90
+ gestureDirection: "vertical",
91
+ screenStyleInterpolator: ({
92
+ current,
93
+ next,
94
+ layouts: {
95
+ screen: { height },
96
+ },
97
+ }) => {
98
+ "worklet";
99
+
100
+ const progress = current.progress + (next?.progress ?? 0);
101
+
102
+ const y = interpolate(progress, [0, 1, 2], [height, 0, -height]);
103
+
104
+ return {
105
+ contentStyle: {
106
+ transform: [{ translateY: y }],
107
+ },
108
+ };
109
+ },
110
+ transitionSpec: {
111
+ open: DefaultSpec,
112
+ close: DefaultSpec,
113
+ },
114
+ ...config,
115
+ };
116
+ };
117
+
118
+ export const DraggableCard = (
119
+ config: Partial<ScreenTransitionConfig> = {},
120
+ ): ScreenTransitionConfig => {
121
+ return {
122
+ enableTransitions: true,
123
+ gestureEnabled: true,
124
+ gestureDirection: ["horizontal", "vertical"],
125
+ screenStyleInterpolator: ({ current, progress, layouts: { screen } }) => {
126
+ "worklet";
127
+
128
+ /** Combined */
129
+ const scale = interpolate(progress, [0, 1, 2], [0, 1, 0.75]);
130
+
131
+ /** Vertical */
132
+ const translateY = interpolate(
133
+ current.gesture.normalizedY,
134
+ [-1, 1],
135
+ [-screen.height * 0.5, screen.height * 0.5],
136
+ "clamp",
137
+ );
138
+
139
+ /** Horizontal */
140
+ const translateX = interpolate(
141
+ current.gesture.normalizedX,
142
+ [-1, 1],
143
+ [-screen.width * 0.5, screen.width * 0.5],
144
+ "clamp",
145
+ );
146
+
147
+ return {
148
+ contentStyle: {
149
+ transform: [{ scale }, { translateY: translateY }, { translateX }],
150
+ },
151
+ };
152
+ },
153
+ transitionSpec: {
154
+ open: DefaultSpec,
155
+ close: DefaultSpec,
156
+ },
157
+ ...config,
158
+ };
159
+ };
160
+
161
+ export const ElasticCard = (
162
+ config: Partial<ScreenTransitionConfig> & {
163
+ elasticFactor?: number;
164
+ } = { elasticFactor: 0.5 },
165
+ ): ScreenTransitionConfig => {
166
+ return {
167
+ enableTransitions: true,
168
+ gestureEnabled: true,
169
+ gestureDirection: "bidirectional",
170
+ screenStyleInterpolator: ({
171
+ current,
172
+ next,
173
+ layouts: { screen },
174
+ progress,
175
+ }) => {
176
+ "worklet";
177
+
178
+ /**
179
+ * Applies to both screens ( previous and incoming)
180
+ */
181
+
182
+ const scale = interpolate(progress, [0, 1, 2], [0, 1, 0.8]);
183
+
184
+ /**
185
+ * Applies to current screen
186
+ */
187
+ const maxElasticityX = screen.width * (config.elasticFactor ?? 0.5);
188
+ const maxElasticityY = screen.height * (config.elasticFactor ?? 0.5);
189
+ const translateX = interpolate(
190
+ current.gesture.normalizedX,
191
+ [-1, 0, 1],
192
+ [-maxElasticityX, 0, maxElasticityX],
193
+ "clamp",
194
+ );
195
+
196
+ const translateY = interpolate(
197
+ current.gesture.normalizedY,
198
+ [-1, 0, 1],
199
+ [-maxElasticityY, 0, maxElasticityY],
200
+ "clamp",
201
+ );
202
+
203
+ /**
204
+ * Applies to unfocused screen ( previous screen )
205
+ */
206
+ const overlayColor = interpolateColor(
207
+ progress,
208
+ [0, 1],
209
+ ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"],
210
+ );
211
+
212
+ return {
213
+ contentStyle: {
214
+ transform: [{ scale }, { translateX }, { translateY }],
215
+ },
216
+ overlayStyle: {
217
+ backgroundColor: !next ? overlayColor : "rgba(0,0,0,0)",
218
+ },
219
+ };
220
+ },
221
+ transitionSpec: {
222
+ open: DefaultSpec,
223
+ close: DefaultSpec,
224
+ },
225
+ ...config,
226
+ };
227
+ };
@@ -0,0 +1,9 @@
1
+ import type { WithSpringConfig } from "react-native-reanimated";
2
+
3
+ export const DefaultSpec: WithSpringConfig = {
4
+ stiffness: 1000,
5
+ damping: 500,
6
+ mass: 3,
7
+ overshootClamping: true,
8
+ restSpeedThreshold: 0.01,
9
+ };
@@ -0,0 +1,28 @@
1
+ import { useAnimatedStyle } from "react-native-reanimated";
2
+ import { _useScreenAnimation } from "./use-screen-animation";
3
+
4
+ /**
5
+ * This hook is used to get the associated styles for a given styleId.
6
+ * It is used to get the associated styles for a given styleId.
7
+ * It is used to get the associated styles for a given styleId.
8
+ */
9
+ export const useAssociatedStyles = ({ id }: { id?: string } = {}) => {
10
+ const { screenStyleInterpolator, screenInterpolatorProps } =
11
+ _useScreenAnimation();
12
+
13
+ const associatedStyles = useAnimatedStyle(() => {
14
+ "worklet";
15
+
16
+ if (!id || !screenStyleInterpolator) {
17
+ return {};
18
+ }
19
+
20
+ return (
21
+ screenStyleInterpolator(screenInterpolatorProps.value)[id] || {
22
+ opacity: 1, // <-- This fixes flickering?? We'll have to deep dive this?? wtf
23
+ }
24
+ );
25
+ });
26
+
27
+ return { associatedStyles };
28
+ };
@@ -0,0 +1,142 @@
1
+ import type { ParamListBase, RouteProp } from "@react-navigation/native";
2
+ import { useWindowDimensions } from "react-native";
3
+ import { type SharedValue, useDerivedValue } from "react-native-reanimated";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import { useKeys } from "../../providers/keys";
6
+ import { Animations } from "../../stores/animations";
7
+ import { Bounds } from "../../stores/bounds";
8
+ import { type GestureMap, Gestures } from "../../stores/gestures";
9
+ import type {
10
+ ScreenInterpolationProps,
11
+ ScreenTransitionState,
12
+ } from "../../types/animation";
13
+ import type { BoundEntry } from "../../types/bounds";
14
+ import type { NativeStackDescriptor } from "../../types/navigator";
15
+ import { buildBoundsAccessor } from "../../utils/bounds";
16
+
17
+ type BuiltState = {
18
+ progress: SharedValue<number>;
19
+ closing: SharedValue<number>;
20
+ animating: SharedValue<number>;
21
+ gesture: GestureMap;
22
+ route: RouteProp<ParamListBase>;
23
+ };
24
+
25
+ const FALLBACK = Object.freeze({
26
+ progress: 0,
27
+ closing: 0,
28
+ animating: 0,
29
+ gesture: {
30
+ x: 0,
31
+ y: 0,
32
+ normalizedX: 0,
33
+ normalizedY: 0,
34
+ isDismissing: 0,
35
+ isDragging: 0,
36
+ },
37
+ bounds: {} as Record<string, BoundEntry>,
38
+ route: {} as RouteProp<ParamListBase>,
39
+ });
40
+
41
+ const useBuildScreenTransitionState = (
42
+ descriptor: NativeStackDescriptor | undefined,
43
+ ): BuiltState | undefined => {
44
+ const key = descriptor?.route.key;
45
+ if (!key) return undefined;
46
+ const progress = Animations.getAnimation(key, "progress");
47
+ const closing = Animations.getAnimation(key, "closing");
48
+ const animating = Animations.getAnimation(key, "animating");
49
+ const gesture = Gestures.getRouteGestures(key);
50
+ const route = descriptor?.route;
51
+
52
+ return { progress, closing, animating, gesture, route };
53
+ };
54
+
55
+ const unwrap = (
56
+ s: BuiltState | undefined,
57
+ key: string | undefined,
58
+ ): ScreenTransitionState | undefined => {
59
+ "worklet";
60
+ return s && key
61
+ ? {
62
+ progress: s.progress.value ?? 0,
63
+ closing: s.closing.value ?? 0,
64
+ animating: s.animating.value ?? 0,
65
+ gesture: {
66
+ x: s.gesture.x.value ?? 0,
67
+ y: s.gesture.y.value ?? 0,
68
+ normalizedX: s.gesture.normalizedX.value ?? 0,
69
+ normalizedY: s.gesture.normalizedY.value ?? 0,
70
+ isDismissing: s.gesture.isDismissing.value ?? 0,
71
+ isDragging: s.gesture.isDragging.value ?? 0,
72
+ },
73
+ bounds: Bounds.getBounds(key) ?? {},
74
+ route: s.route,
75
+ }
76
+ : undefined;
77
+ };
78
+
79
+ export function _useScreenAnimation() {
80
+ const {
81
+ current: currentDescriptor,
82
+ next: nextDescriptor,
83
+ previous: previousDescriptor,
84
+ } = useKeys();
85
+
86
+ const dimensions = useWindowDimensions();
87
+
88
+ const currentAnimation = useBuildScreenTransitionState(currentDescriptor);
89
+
90
+ const nextAnimation = useBuildScreenTransitionState(nextDescriptor);
91
+ const prevAnimation = useBuildScreenTransitionState(previousDescriptor);
92
+
93
+ const insets = useSafeAreaInsets();
94
+
95
+ const screenInterpolatorProps = useDerivedValue<ScreenInterpolationProps>(
96
+ () => {
97
+ "worklet";
98
+
99
+ const previous = unwrap(prevAnimation, previousDescriptor?.route.key);
100
+ const next = unwrap(nextAnimation, nextDescriptor?.route.key);
101
+ const current =
102
+ unwrap(currentAnimation, currentDescriptor?.route.key) ?? FALLBACK;
103
+
104
+ const progress = current.progress + (next?.progress ?? 0);
105
+
106
+ const focused = !next;
107
+ const activeBoundId = Bounds.getActiveBoundId() || "";
108
+
109
+ const bounds = buildBoundsAccessor({
110
+ activeBoundId,
111
+ current,
112
+ previous,
113
+ next,
114
+ progress,
115
+ dimensions,
116
+ });
117
+
118
+ return {
119
+ layouts: { screen: dimensions },
120
+ insets,
121
+ previous,
122
+ current,
123
+ next,
124
+ focused,
125
+ activeBoundId,
126
+ progress,
127
+ bounds,
128
+ } satisfies ScreenInterpolationProps;
129
+ },
130
+ );
131
+
132
+ const screenStyleInterpolator =
133
+ nextDescriptor?.options.screenStyleInterpolator ||
134
+ currentDescriptor?.options.screenStyleInterpolator;
135
+
136
+ return { screenInterpolatorProps, screenStyleInterpolator };
137
+ }
138
+ export function useScreenAnimation() {
139
+ const { screenInterpolatorProps } = _useScreenAnimation();
140
+
141
+ return useDerivedValue(() => screenInterpolatorProps.value);
142
+ }
@@ -0,0 +1,71 @@
1
+ import { useCallback } from "react";
2
+ import type { View } from "react-native";
3
+ import {
4
+ type AnimatedRef,
5
+ measure,
6
+ type StyleProps,
7
+ useSharedValue,
8
+ } from "react-native-reanimated";
9
+ import { useKeys } from "../../providers/keys";
10
+ import { Bounds } from "../../stores/bounds";
11
+ import { flattenStyle } from "../../utils/bounds/flatten-styles";
12
+ import { useScreenAnimation } from "../animation/use-screen-animation";
13
+
14
+ interface BoundMeasurerHookProps {
15
+ sharedBoundTag: string;
16
+ animatedRef: AnimatedRef<View>;
17
+ current: { route: { key: string } };
18
+ style: StyleProps;
19
+ }
20
+
21
+ export const useBoundMeasurer = ({
22
+ sharedBoundTag,
23
+ animatedRef,
24
+ current,
25
+ style,
26
+ }: BoundMeasurerHookProps) => {
27
+ const { previous } = useKeys();
28
+ const interpolatorProps = useScreenAnimation();
29
+ const hasAlreadyMeasured = useSharedValue(false);
30
+
31
+ const measureAndSet = useCallback(() => {
32
+ "worklet";
33
+ if (!sharedBoundTag) return;
34
+ const measured = measure(animatedRef);
35
+ if (measured) {
36
+ Bounds.setBounds(
37
+ current.route.key,
38
+ sharedBoundTag,
39
+ measured,
40
+ flattenStyle(style),
41
+ );
42
+ }
43
+ }, [sharedBoundTag, animatedRef, current.route.key, style]);
44
+
45
+ const measureOnLayout = useCallback(() => {
46
+ "worklet";
47
+ if (!sharedBoundTag || hasAlreadyMeasured.value) return;
48
+
49
+ const previousRouteKey = previous?.route.key;
50
+ if (!previousRouteKey) return;
51
+
52
+ const previousBounds = Bounds.getBounds(previousRouteKey);
53
+ const hasPreviousBoundForTag = previousBounds[sharedBoundTag];
54
+
55
+ if (interpolatorProps.value.current.animating && hasPreviousBoundForTag) {
56
+ measureAndSet();
57
+ hasAlreadyMeasured.value = true;
58
+ }
59
+ }, [
60
+ measureAndSet,
61
+ interpolatorProps,
62
+ sharedBoundTag,
63
+ previous?.route.key,
64
+ hasAlreadyMeasured,
65
+ ]);
66
+
67
+ return {
68
+ measureAndSet,
69
+ measureOnLayout,
70
+ };
71
+ };