react-native-screen-transitions 2.0.2 → 2.0.3

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 (57) hide show
  1. package/package.json +5 -3
  2. package/src/__tests__ /geometry.test.ts +127 -0
  3. package/src/components/bounds-activator.tsx +29 -0
  4. package/src/components/controllers/screen-lifecycle.tsx +72 -0
  5. package/src/components/create-transition-aware-component.tsx +99 -0
  6. package/src/components/root-transition-aware.tsx +56 -0
  7. package/src/configs/index.ts +2 -0
  8. package/src/configs/presets.ts +227 -0
  9. package/src/configs/specs.ts +9 -0
  10. package/src/hooks/animation/use-associated-style.tsx +28 -0
  11. package/src/hooks/animation/use-screen-animation.tsx +142 -0
  12. package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
  13. package/src/hooks/gestures/use-build-gestures.tsx +369 -0
  14. package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
  15. package/src/hooks/use-stable-callback.tsx +15 -0
  16. package/src/index.ts +32 -0
  17. package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
  18. package/src/integrations/native-stack/utils/debounce.tsx +14 -0
  19. package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
  20. package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
  21. package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
  22. package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
  23. package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
  24. package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
  25. package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
  26. package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
  27. package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
  28. package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
  29. package/src/providers/gestures.tsx +89 -0
  30. package/src/providers/keys.tsx +38 -0
  31. package/src/stores/animations.ts +45 -0
  32. package/src/stores/bounds.ts +71 -0
  33. package/src/stores/gestures.ts +55 -0
  34. package/src/stores/navigator-dismiss-state.ts +17 -0
  35. package/src/stores/utils/reset-stores-for-screen.ts +14 -0
  36. package/src/types/animation.ts +76 -0
  37. package/src/types/bounds.ts +82 -0
  38. package/src/types/core.ts +50 -0
  39. package/src/types/gesture.ts +33 -0
  40. package/src/types/navigator.ts +744 -0
  41. package/src/types/utils.ts +3 -0
  42. package/src/utils/animation/animate.ts +28 -0
  43. package/src/utils/animation/run-transition.ts +49 -0
  44. package/src/utils/bounds/_types/builder.ts +35 -0
  45. package/src/utils/bounds/_types/geometry.ts +17 -0
  46. package/src/utils/bounds/_types/get-bounds.ts +10 -0
  47. package/src/utils/bounds/build-bound-styles.ts +184 -0
  48. package/src/utils/bounds/constants.ts +28 -0
  49. package/src/utils/bounds/flatten-styles.ts +21 -0
  50. package/src/utils/bounds/geometry.ts +113 -0
  51. package/src/utils/bounds/get-bounds.ts +56 -0
  52. package/src/utils/bounds/index.ts +46 -0
  53. package/src/utils/bounds/style-composers.ts +172 -0
  54. package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
  55. package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
  56. package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
  57. package/src/utils/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-screen-transitions",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Easy screen transitions for React Native and Expo",
5
5
  "author": "Ed",
6
6
  "license": "MIT",
@@ -12,6 +12,7 @@
12
12
  "main": "lib/commonjs/index.js",
13
13
  "module": "lib/module/index.js",
14
14
  "types": "lib/typescript/index.d.ts",
15
+ "react-native": "src/index",
15
16
  "scripts": {
16
17
  "build": "bob build",
17
18
  "lint": "biome check ./src",
@@ -27,6 +28,7 @@
27
28
  ],
28
29
  "files": [
29
30
  "lib",
31
+ "src",
30
32
  "README.md",
31
33
  "LICENSE"
32
34
  ],
@@ -48,7 +50,7 @@
48
50
  "@types/react": "~19.0.10",
49
51
  "react-native-builder-bob": "0.39.0",
50
52
  "tsup": "^8.5.0",
51
- "typescript": "~5.8.3"
53
+ "typescript": ""
52
54
  },
53
55
  "react-native-builder-bob": {
54
56
  "source": "src",
@@ -64,5 +66,5 @@
64
66
  ]
65
67
  ]
66
68
  },
67
- "gitHead": "3f7cb6a77b24a99123fd4cb1277f152aa365e792"
69
+ "gitHead": "abdad91d29dffbb246dc478311659548c18979b1"
68
70
  }
@@ -0,0 +1,127 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ computeContentTransformGeometry,
4
+ computeRelativeGeometry,
5
+ } from "../utils/bounds/geometry";
6
+
7
+ describe("computeRelativeGeometry", () => {
8
+ it("calculates correct relative geometry when entering", () => {
9
+ const start = {
10
+ x: 10,
11
+ y: 20,
12
+ pageX: 10,
13
+ pageY: 20,
14
+ width: 100,
15
+ height: 200,
16
+ };
17
+ const end = {
18
+ x: 50,
19
+ y: 100,
20
+ pageX: 50,
21
+ pageY: 100,
22
+ width: 200,
23
+ height: 400,
24
+ };
25
+
26
+ const result = computeRelativeGeometry({ start, end, entering: true });
27
+
28
+ expect(result.dx).toBe(-90); // center diff X
29
+ expect(result.dy).toBe(-180); // center diff Y
30
+ expect(result.scaleX).toBe(0.5); // width ratio
31
+ expect(result.scaleY).toBe(0.5); // height ratio
32
+ expect(result.ranges).toEqual([0, 1]);
33
+ expect(result.entering).toBe(true);
34
+ });
35
+
36
+ it("calculates correct relative geometry when exiting", () => {
37
+ const start = { x: 0, y: 0, pageX: 10, pageY: 20, width: 100, height: 200 };
38
+ const end = { x: 0, y: 0, pageX: 50, pageY: 100, width: 200, height: 400 };
39
+
40
+ const result = computeRelativeGeometry({ start, end, entering: false });
41
+
42
+ expect(result.ranges).toEqual([1, 2]);
43
+ expect(result.entering).toBe(false);
44
+ });
45
+ });
46
+
47
+ describe("computeContentTransformGeometry", () => {
48
+ const dimensions = { width: 375, height: 812, scale: 1, fontScale: 1 };
49
+
50
+ it("calculates transform with aspectFit mode", () => {
51
+ const start = {
52
+ x: 0,
53
+ y: 0,
54
+ pageX: 50,
55
+ pageY: 100,
56
+ width: 100,
57
+ height: 100,
58
+ };
59
+ const end = { x: 0, y: 0, pageX: 100, pageY: 200, width: 200, height: 50 };
60
+
61
+ const result = computeContentTransformGeometry({
62
+ start,
63
+ end,
64
+ entering: true,
65
+ dimensions,
66
+ contentScaleMode: "aspectFit",
67
+ });
68
+
69
+ expect(result.s).toBe(0.5);
70
+ expect(result.entering).toBe(true);
71
+ expect(result.ranges).toEqual([0, 1]);
72
+ });
73
+
74
+ it("calculates transform with aspectFill mode", () => {
75
+ const start = {
76
+ x: 0,
77
+ y: 0,
78
+ pageX: 50,
79
+ pageY: 100,
80
+ width: 100,
81
+ height: 100,
82
+ };
83
+ const end = { x: 0, y: 0, pageX: 100, pageY: 200, width: 200, height: 50 };
84
+
85
+ const result = computeContentTransformGeometry({
86
+ start,
87
+ end,
88
+ entering: true,
89
+ dimensions,
90
+ contentScaleMode: "aspectFill",
91
+ });
92
+
93
+ expect(result.s).toBe(2);
94
+ });
95
+
96
+ it("handles auto mode based on aspect ratio difference", () => {
97
+ const start = { x: 0, y: 0, pageX: 0, pageY: 0, width: 100, height: 100 };
98
+ const end = { x: 0, y: 0, pageX: 0, pageY: 0, width: 200, height: 195 };
99
+
100
+ const result = computeContentTransformGeometry({
101
+ start,
102
+ end,
103
+ entering: true,
104
+ dimensions,
105
+ contentScaleMode: "auto",
106
+ });
107
+
108
+ expect(result.s).toBeCloseTo(0.512, 2);
109
+ });
110
+
111
+ it("handles zero dimensions safely", () => {
112
+ const start = { x: 0, y: 0, pageX: 0, pageY: 0, width: 0, height: 100 };
113
+ const end = { x: 0, y: 0, pageX: 0, pageY: 0, width: 200, height: 200 };
114
+
115
+ // Should not throw and use safe fallback
116
+ const result = computeContentTransformGeometry({
117
+ start,
118
+ end,
119
+ entering: true,
120
+ dimensions,
121
+ contentScaleMode: "aspectFit",
122
+ });
123
+
124
+ expect(result.s).toBeDefined();
125
+ expect(Number.isFinite(result.s)).toBe(true);
126
+ });
127
+ });
@@ -0,0 +1,29 @@
1
+ import { useMemo } from "react";
2
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
3
+ import { Bounds } from "../stores/bounds";
4
+
5
+ interface BoundActivatorProps {
6
+ sharedBoundTag?: string;
7
+ children: React.ReactNode;
8
+ measure: () => void;
9
+ }
10
+
11
+ export const BoundActivator = ({
12
+ sharedBoundTag,
13
+ children,
14
+ measure,
15
+ }: BoundActivatorProps) => {
16
+ const tapGesture = useMemo(() => {
17
+ return Gesture.Tap().onStart(() => {
18
+ "worklet";
19
+ if (sharedBoundTag) {
20
+ Bounds.setActiveBoundId(sharedBoundTag);
21
+ measure();
22
+ }
23
+ });
24
+ }, [sharedBoundTag, measure]);
25
+
26
+ if (!sharedBoundTag) return children;
27
+
28
+ return <GestureDetector gesture={tapGesture}>{children}</GestureDetector>;
29
+ };
@@ -0,0 +1,72 @@
1
+ import { useEffect, useLayoutEffect } from "react";
2
+ import useStableCallback from "../../hooks/use-stable-callback";
3
+ import { useKeys } from "../../providers/keys";
4
+ import { Animations } from "../../stores/animations";
5
+ import { NavigatorDismissState } from "../../stores/navigator-dismiss-state";
6
+ import { resetStoresForScreen } from "../../stores/utils/reset-stores-for-screen";
7
+ import { runTransition } from "../../utils/animation/run-transition";
8
+
9
+ interface ScreenLifecycleProps {
10
+ children: React.ReactNode;
11
+ }
12
+
13
+ export const ScreenLifecycleController = ({
14
+ children,
15
+ }: ScreenLifecycleProps) => {
16
+ const { current } = useKeys();
17
+
18
+ const animations = Animations.getAll(current.route.key);
19
+
20
+ const handleBeforeRemove = useStableCallback((e: any) => {
21
+ const key = current.navigation.getParent()?.getState().key;
22
+ const requestedDismissOnNavigator = NavigatorDismissState.get(key);
23
+
24
+ // Don't run e.preventDefault when the dismissal was on the local root
25
+ if (requestedDismissOnNavigator) {
26
+ resetStoresForScreen(current);
27
+ return;
28
+ }
29
+
30
+ // Don't run e.preventDefault when this is the first screen of the stack
31
+ if (current.navigation.getState().index === 0) {
32
+ resetStoresForScreen(current);
33
+ return;
34
+ }
35
+
36
+ e.preventDefault();
37
+ const onFinish = (finished: boolean) => {
38
+ if (finished) {
39
+ resetStoresForScreen(current);
40
+ current.navigation.dispatch(e.data.action);
41
+ }
42
+ };
43
+
44
+ runTransition({
45
+ target: "close",
46
+ spec: current.options.transitionSpec,
47
+ onFinish,
48
+ animations,
49
+ });
50
+ });
51
+
52
+ const handleInitialize = useStableCallback(() => {
53
+ runTransition({
54
+ target: "open",
55
+ spec: current.options.transitionSpec,
56
+ animations,
57
+ });
58
+ });
59
+
60
+ useLayoutEffect(handleInitialize, []);
61
+
62
+ useEffect(() => {
63
+ const unsubscribe = current.navigation.addListener(
64
+ "beforeRemove",
65
+ handleBeforeRemove,
66
+ );
67
+
68
+ return unsubscribe;
69
+ }, [current.navigation, handleBeforeRemove]);
70
+
71
+ return children;
72
+ };
@@ -0,0 +1,99 @@
1
+ import type React from "react";
2
+ import { type ComponentType, forwardRef, memo } from "react";
3
+ import type { View } from "react-native";
4
+ import { GestureDetector } from "react-native-gesture-handler";
5
+ import Animated, { runOnUI, useAnimatedRef } from "react-native-reanimated";
6
+ import { useAssociatedStyles } from "../hooks/animation/use-associated-style";
7
+ import { useBoundMeasurer } from "../hooks/bounds/use-bound-measurer";
8
+ import { useScrollProgress } from "../hooks/gestures/use-scroll-progress";
9
+ import { useGestureContext } from "../providers/gestures";
10
+ import { useKeys } from "../providers/keys";
11
+ import type { TransitionAwareProps } from "../types/core";
12
+ import type { Any } from "../types/utils";
13
+ import { BoundActivator } from "./bounds-activator";
14
+
15
+ interface CreateTransitionAwareComponentOptions {
16
+ isScrollable?: boolean;
17
+ }
18
+
19
+ export function createTransitionAwareComponent<P extends object>(
20
+ Wrapped: ComponentType<P>,
21
+ options: CreateTransitionAwareComponentOptions = {},
22
+ ) {
23
+ const { isScrollable = false } = options;
24
+
25
+ const AnimatedComponent = Animated.createAnimatedComponent(Wrapped);
26
+
27
+ const ScrollableInner = forwardRef<
28
+ React.ComponentRef<typeof Wrapped>,
29
+ TransitionAwareProps<P>
30
+ >((props: Any, ref) => {
31
+ const { nativeGesture } = useGestureContext();
32
+
33
+ const { scrollHandler, onContentSizeChange, onLayout } = useScrollProgress({
34
+ onScroll: props.onScroll,
35
+ onContentSizeChange: props.onContentSizeChange,
36
+ onLayout: props.onLayout, // Add this line to pass through onLayout
37
+ });
38
+
39
+ return (
40
+ <GestureDetector gesture={nativeGesture}>
41
+ <AnimatedComponent
42
+ {...(props as Any)}
43
+ ref={ref}
44
+ onScroll={scrollHandler}
45
+ onContentSizeChange={onContentSizeChange}
46
+ onLayout={onLayout}
47
+ scrollEventThrottle={props.scrollEventThrottle || 16}
48
+ />
49
+ </GestureDetector>
50
+ );
51
+ });
52
+
53
+ const Inner = forwardRef<
54
+ React.ComponentRef<typeof AnimatedComponent>,
55
+ TransitionAwareProps<P>
56
+ >((props, ref) => {
57
+ const { children, style, sharedBoundTag, styleId, onPress, ...rest } =
58
+ props as Any;
59
+
60
+ const animatedRef = useAnimatedRef<View>();
61
+ const { current } = useKeys();
62
+
63
+ const { associatedStyles } = useAssociatedStyles({
64
+ id: sharedBoundTag || styleId,
65
+ });
66
+
67
+ const { measureAndSet, measureOnLayout } = useBoundMeasurer({
68
+ sharedBoundTag,
69
+ animatedRef,
70
+ current,
71
+ style,
72
+ });
73
+
74
+ if (isScrollable) {
75
+ return <ScrollableInner {...(props as Any)} ref={ref} />;
76
+ }
77
+
78
+ return (
79
+ <BoundActivator sharedBoundTag={sharedBoundTag} measure={measureAndSet}>
80
+ <AnimatedComponent
81
+ {...(rest as Any)}
82
+ ref={animatedRef}
83
+ style={[style, associatedStyles]}
84
+ onPress={onPress}
85
+ onLayout={runOnUI(measureOnLayout)}
86
+ >
87
+ {children}
88
+ </AnimatedComponent>
89
+ </BoundActivator>
90
+ );
91
+ });
92
+
93
+ return memo(Inner) as React.MemoExoticComponent<
94
+ React.ForwardRefExoticComponent<
95
+ TransitionAwareProps<P> &
96
+ React.RefAttributes<React.ComponentRef<typeof Wrapped>>
97
+ >
98
+ >;
99
+ }
@@ -0,0 +1,56 @@
1
+ import { memo } from "react";
2
+ import { StyleSheet } from "react-native";
3
+ import Animated, { useAnimatedStyle } from "react-native-reanimated";
4
+ import { _useScreenAnimation } from "../hooks/animation/use-screen-animation";
5
+
6
+ interface RootTransitionAwareProps {
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ export const RootTransitionAware = memo(
11
+ ({ children }: RootTransitionAwareProps) => {
12
+ const { screenInterpolatorProps, screenStyleInterpolator } =
13
+ _useScreenAnimation();
14
+
15
+ const animatedContentStyle = useAnimatedStyle(() => {
16
+ "worklet";
17
+ if (!screenStyleInterpolator) {
18
+ return {};
19
+ }
20
+ const props = screenInterpolatorProps.value;
21
+ return screenStyleInterpolator(props).contentStyle || {};
22
+ });
23
+
24
+ const animatedOverlayStyle = useAnimatedStyle(() => {
25
+ "worklet";
26
+ if (!screenStyleInterpolator) {
27
+ return {};
28
+ }
29
+ return (
30
+ screenStyleInterpolator(screenInterpolatorProps.value).overlayStyle ||
31
+ {}
32
+ );
33
+ });
34
+
35
+ return (
36
+ <Animated.View style={styles.container}>
37
+ <Animated.View
38
+ style={[StyleSheet.absoluteFillObject, animatedOverlayStyle]}
39
+ pointerEvents="none"
40
+ />
41
+ <Animated.View style={[styles.content, animatedContentStyle]}>
42
+ {children}
43
+ </Animated.View>
44
+ </Animated.View>
45
+ );
46
+ },
47
+ );
48
+
49
+ const styles = StyleSheet.create({
50
+ container: {
51
+ flex: 1,
52
+ },
53
+ content: {
54
+ flex: 1,
55
+ },
56
+ });
@@ -0,0 +1,2 @@
1
+ export * as presets from "./presets";
2
+ export * as specs from "./specs";
@@ -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
+ };