react-native-screen-transitions 2.0.1 → 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.
- package/package.json +4 -3
- package/src/__tests__ /geometry.test.ts +127 -0
- package/src/components/bounds-activator.tsx +29 -0
- package/src/components/controllers/screen-lifecycle.tsx +72 -0
- package/src/components/create-transition-aware-component.tsx +99 -0
- package/src/components/root-transition-aware.tsx +56 -0
- package/src/configs/index.ts +2 -0
- package/src/configs/presets.ts +227 -0
- package/src/configs/specs.ts +9 -0
- package/src/hooks/animation/use-associated-style.tsx +28 -0
- package/src/hooks/animation/use-screen-animation.tsx +142 -0
- package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
- package/src/hooks/gestures/use-build-gestures.tsx +369 -0
- package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
- package/src/hooks/use-stable-callback.tsx +15 -0
- package/src/index.ts +32 -0
- package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
- package/src/integrations/native-stack/utils/debounce.tsx +14 -0
- package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
- package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
- package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
- package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
- package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
- package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
- package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
- package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
- package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
- package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
- package/src/providers/gestures.tsx +89 -0
- package/src/providers/keys.tsx +38 -0
- package/src/stores/animations.ts +45 -0
- package/src/stores/bounds.ts +71 -0
- package/src/stores/gestures.ts +55 -0
- package/src/stores/navigator-dismiss-state.ts +17 -0
- package/src/stores/utils/reset-stores-for-screen.ts +14 -0
- package/src/types/animation.ts +76 -0
- package/src/types/bounds.ts +82 -0
- package/src/types/core.ts +50 -0
- package/src/types/gesture.ts +33 -0
- package/src/types/navigator.ts +744 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/animation/animate.ts +28 -0
- package/src/utils/animation/run-transition.ts +49 -0
- package/src/utils/bounds/_types/builder.ts +35 -0
- package/src/utils/bounds/_types/geometry.ts +17 -0
- package/src/utils/bounds/_types/get-bounds.ts +10 -0
- package/src/utils/bounds/build-bound-styles.ts +184 -0
- package/src/utils/bounds/constants.ts +28 -0
- package/src/utils/bounds/flatten-styles.ts +21 -0
- package/src/utils/bounds/geometry.ts +113 -0
- package/src/utils/bounds/get-bounds.ts +56 -0
- package/src/utils/bounds/index.ts +46 -0
- package/src/utils/bounds/style-composers.ts +172 -0
- package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
- package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
- package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
- 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.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Easy screen transitions for React Native and Expo",
|
|
5
5
|
"author": "Ed",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"files": [
|
|
30
30
|
"lib",
|
|
31
|
+
"src",
|
|
31
32
|
"README.md",
|
|
32
33
|
"LICENSE"
|
|
33
34
|
],
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"@types/react": "~19.0.10",
|
|
50
51
|
"react-native-builder-bob": "0.39.0",
|
|
51
52
|
"tsup": "^8.5.0",
|
|
52
|
-
"typescript": "
|
|
53
|
+
"typescript": ""
|
|
53
54
|
},
|
|
54
55
|
"react-native-builder-bob": {
|
|
55
56
|
"source": "src",
|
|
@@ -65,5 +66,5 @@
|
|
|
65
66
|
]
|
|
66
67
|
]
|
|
67
68
|
},
|
|
68
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "abdad91d29dffbb246dc478311659548c18979b1"
|
|
69
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,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,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
|
+
};
|