react-native-screen-transitions 3.0.0-rc.4 → 3.0.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 +228 -96
- package/lib/commonjs/blank-stack/components/overlay.js +1 -1
- package/lib/commonjs/blank-stack/components/overlay.js.map +1 -1
- package/lib/commonjs/blank-stack/components/screens.js +6 -2
- package/lib/commonjs/blank-stack/components/screens.js.map +1 -1
- package/lib/commonjs/blank-stack/components/stack-view.js +2 -5
- package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js +11 -10
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
- package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js +2 -0
- package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js.map +1 -1
- package/lib/commonjs/shared/components/create-transition-aware-component.js +2 -0
- package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +8 -4
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +29 -5
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture.js +26 -0
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture.js.map +1 -0
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +32 -60
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/index.js +7 -0
- package/lib/commonjs/shared/index.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.provider.js +21 -42
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/helpers/interpolate-style.js +30 -0
- package/lib/commonjs/shared/utils/bounds/helpers/interpolate-style.js.map +1 -0
- package/lib/commonjs/shared/utils/bounds/index.js +29 -1
- package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
- package/lib/commonjs/shared/utils/create-provider.js +16 -0
- package/lib/commonjs/shared/utils/create-provider.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +4 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/blank-stack/components/overlay.js +1 -1
- package/lib/module/blank-stack/components/overlay.js.map +1 -1
- package/lib/module/blank-stack/components/screens.js +6 -2
- package/lib/module/blank-stack/components/screens.js.map +1 -1
- package/lib/module/blank-stack/components/stack-view.js +2 -5
- package/lib/module/blank-stack/components/stack-view.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js +11 -10
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
- package/lib/module/shared/components/controllers/native-stack-lifecycle.js +1 -0
- package/lib/module/shared/components/controllers/native-stack-lifecycle.js.map +1 -1
- package/lib/module/shared/components/create-transition-aware-component.js +2 -0
- package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js +8 -4
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +30 -6
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture.js +22 -0
- package/lib/module/shared/hooks/gestures/use-screen-gesture.js.map +1 -0
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +32 -60
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/index.js +1 -0
- package/lib/module/shared/index.js.map +1 -1
- package/lib/module/shared/providers/gestures.provider.js +19 -41
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/utils/bounds/helpers/interpolate-style.js +26 -0
- package/lib/module/shared/utils/bounds/helpers/interpolate-style.js.map +1 -0
- package/lib/module/shared/utils/bounds/index.js +29 -1
- package/lib/module/shared/utils/bounds/index.js.map +1 -1
- package/lib/module/shared/utils/create-provider.js +17 -1
- package/lib/module/shared/utils/create-provider.js.map +1 -1
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +4 -4
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/typescript/blank-stack/components/screens.d.ts +1 -3
- package/lib/typescript/blank-stack/components/screens.d.ts.map +1 -1
- package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -1
- package/lib/typescript/blank-stack/types.d.ts +2 -14
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/index.d.ts.map +1 -1
- package/lib/typescript/shared/components/controllers/native-stack-lifecycle.d.ts.map +1 -1
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts +1 -0
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +1 -0
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture.d.ts +15 -0
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +1 -0
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
- package/lib/typescript/shared/index.d.ts +4 -2
- package/lib/typescript/shared/index.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.provider.d.ts +6 -13
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +44 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts +6 -0
- package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/core.types.d.ts +7 -0
- package/lib/typescript/shared/types/core.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/bounds/helpers/interpolate-style.d.ts +17 -0
- package/lib/typescript/shared/utils/bounds/helpers/interpolate-style.d.ts.map +1 -0
- package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
- package/lib/typescript/shared/utils/create-provider.d.ts +5 -1
- package/lib/typescript/shared/utils/create-provider.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +49 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/blank-stack/components/overlay.tsx +1 -1
- package/src/blank-stack/components/screens.tsx +4 -4
- package/src/blank-stack/components/stack-view.tsx +4 -11
- package/src/blank-stack/types.ts +2 -15
- package/src/blank-stack/utils/with-stack-navigation/index.tsx +17 -3
- package/src/shared/__tests__/derivations.test.ts +155 -0
- package/src/shared/__tests__/gesture-activation.test.ts +251 -0
- package/src/shared/components/controllers/native-stack-lifecycle.tsx +4 -2
- package/src/shared/components/create-transition-aware-component.tsx +2 -1
- package/src/shared/hooks/animation/use-screen-animation.tsx +8 -2
- package/src/shared/hooks/gestures/use-build-gestures.tsx +35 -8
- package/src/shared/hooks/gestures/use-screen-gesture.ts +19 -0
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +39 -59
- package/src/shared/index.ts +2 -0
- package/src/shared/providers/gestures.provider.tsx +35 -75
- package/src/shared/types/animation.types.ts +49 -0
- package/src/shared/types/bounds.types.ts +11 -0
- package/src/shared/types/core.types.ts +8 -0
- package/src/shared/utils/bounds/helpers/interpolate-style.ts +38 -0
- package/src/shared/utils/bounds/index.ts +31 -1
- package/src/shared/utils/create-provider.tsx +31 -1
- package/src/shared/utils/gesture/check-gesture-activation.ts +4 -4
|
@@ -8,16 +8,15 @@ import Animated, {
|
|
|
8
8
|
} from "react-native-reanimated";
|
|
9
9
|
import { Screen as RNSScreen } from "react-native-screens";
|
|
10
10
|
import { AnimationStore } from "../../shared/stores/animation.store";
|
|
11
|
+
import { useStackNavigationContext } from "../utils/with-stack-navigation";
|
|
11
12
|
|
|
12
13
|
interface ScreenProps {
|
|
13
14
|
routeKey: string;
|
|
14
15
|
index: number;
|
|
15
|
-
routesLength: number;
|
|
16
16
|
isPreloaded: boolean;
|
|
17
17
|
children: React.ReactNode;
|
|
18
18
|
freezeOnBlur?: boolean;
|
|
19
19
|
shouldFreeze?: boolean;
|
|
20
|
-
activeScreensLimit: number;
|
|
21
20
|
}
|
|
22
21
|
enum ScreenActivity {
|
|
23
22
|
INACTIVE = 0,
|
|
@@ -32,13 +31,14 @@ const AnimatedScreen = Animated.createAnimatedComponent(RNSScreen);
|
|
|
32
31
|
export const Screen = ({
|
|
33
32
|
routeKey,
|
|
34
33
|
index,
|
|
35
|
-
routesLength,
|
|
36
34
|
isPreloaded,
|
|
37
|
-
activeScreensLimit,
|
|
38
35
|
children,
|
|
39
36
|
freezeOnBlur,
|
|
40
37
|
shouldFreeze,
|
|
41
38
|
}: ScreenProps) => {
|
|
39
|
+
const { activeScreensLimit, routes } = useStackNavigationContext();
|
|
40
|
+
const routesLength = routes.length;
|
|
41
|
+
|
|
42
42
|
const sceneProgress = AnimationStore.getAnimation(routeKey, "progress");
|
|
43
43
|
const sceneClosing = AnimationStore.getAnimation(routeKey, "closing");
|
|
44
44
|
const screenActivity = useSharedValue<ScreenActivity>(
|
|
@@ -24,7 +24,9 @@ type SceneViewProps = {
|
|
|
24
24
|
descriptor: BlankStackDescriptor;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const SceneView = React.memo(({
|
|
27
|
+
const SceneView = React.memo(function SceneView({
|
|
28
|
+
descriptor,
|
|
29
|
+
}: SceneViewProps) {
|
|
28
30
|
const { route, navigation, render } = descriptor;
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -38,14 +40,7 @@ const SceneView = React.memo(({ descriptor }: SceneViewProps) => {
|
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
export const StackView = withStackNavigationProvider(
|
|
41
|
-
({
|
|
42
|
-
activeScreensLimit,
|
|
43
|
-
descriptors,
|
|
44
|
-
focusedIndex,
|
|
45
|
-
routes,
|
|
46
|
-
scenes,
|
|
47
|
-
shouldShowFloatOverlay,
|
|
48
|
-
}) => {
|
|
43
|
+
({ descriptors, focusedIndex, routes, scenes, shouldShowFloatOverlay }) => {
|
|
49
44
|
// Memoize route keys array for ScenesProvider
|
|
50
45
|
const routeKeys = React.useMemo(
|
|
51
46
|
() => routes.map((route) => route.key),
|
|
@@ -82,9 +77,7 @@ export const StackView = withStackNavigationProvider(
|
|
|
82
77
|
key={route.key}
|
|
83
78
|
isPreloaded={isPreloaded}
|
|
84
79
|
index={sceneIndex}
|
|
85
|
-
activeScreensLimit={activeScreensLimit}
|
|
86
80
|
routeKey={route.key}
|
|
87
|
-
routesLength={routes.length}
|
|
88
81
|
shouldFreeze={shouldFreeze}
|
|
89
82
|
freezeOnBlur={descriptor.options.freezeOnBlur}
|
|
90
83
|
>
|
package/src/blank-stack/types.ts
CHANGED
|
@@ -81,13 +81,9 @@ export type BlankStackOverlayProps = {
|
|
|
81
81
|
routes: Route<string>[];
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* Custom metadata from the focused screen's options.
|
|
85
85
|
*/
|
|
86
|
-
|
|
87
|
-
title?: string;
|
|
88
|
-
subtitle?: string;
|
|
89
|
-
[key: string]: unknown;
|
|
90
|
-
};
|
|
86
|
+
meta?: Record<string, unknown>;
|
|
91
87
|
|
|
92
88
|
/**
|
|
93
89
|
* Navigation prop for the overlay.
|
|
@@ -134,15 +130,6 @@ export type BlankStackNavigationOptions = BlankStackScreenTransitionConfig & {
|
|
|
134
130
|
*/
|
|
135
131
|
overlayShown?: boolean;
|
|
136
132
|
|
|
137
|
-
/**
|
|
138
|
-
* Options passed to the overlay component.
|
|
139
|
-
*/
|
|
140
|
-
overlayOptions?: {
|
|
141
|
-
title?: string;
|
|
142
|
-
subtitle?: string;
|
|
143
|
-
[key: string]: unknown;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
133
|
/**
|
|
147
134
|
* Whether inactive screens should be suspended from re-rendering. Defaults to `false`.
|
|
148
135
|
* Defaults to `true` when `enableFreeze()` is run at the top of the application.
|
|
@@ -43,8 +43,8 @@ const { withStackNavigationProvider, useStackNavigationContext } =
|
|
|
43
43
|
};
|
|
44
44
|
}, [state.routes, state.descriptors]);
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const value = useMemo(
|
|
47
|
+
() => ({
|
|
48
48
|
routes: state.routes,
|
|
49
49
|
focusedIndex: props.state.index,
|
|
50
50
|
descriptors: state.descriptors,
|
|
@@ -53,7 +53,21 @@ const { withStackNavigationProvider, useStackNavigationContext } =
|
|
|
53
53
|
handleCloseRoute,
|
|
54
54
|
scenes,
|
|
55
55
|
shouldShowFloatOverlay,
|
|
56
|
-
},
|
|
56
|
+
}),
|
|
57
|
+
[
|
|
58
|
+
state.routes,
|
|
59
|
+
state.descriptors,
|
|
60
|
+
props.state.index,
|
|
61
|
+
closingRouteKeys.shared,
|
|
62
|
+
activeScreensLimit,
|
|
63
|
+
handleCloseRoute,
|
|
64
|
+
scenes,
|
|
65
|
+
shouldShowFloatOverlay,
|
|
66
|
+
],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
value,
|
|
57
71
|
};
|
|
58
72
|
});
|
|
59
73
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { ScreenTransitionState } from "../types/animation.types";
|
|
3
|
+
import { derivations } from "../utils/animation/derivations";
|
|
4
|
+
|
|
5
|
+
const createMockState = (
|
|
6
|
+
overrides: Partial<ScreenTransitionState> = {},
|
|
7
|
+
): ScreenTransitionState => ({
|
|
8
|
+
progress: 1,
|
|
9
|
+
closing: 0,
|
|
10
|
+
animating: 0,
|
|
11
|
+
gesture: {
|
|
12
|
+
isDragging: 0,
|
|
13
|
+
x: 0,
|
|
14
|
+
y: 0,
|
|
15
|
+
normalizedX: 0,
|
|
16
|
+
normalizedY: 0,
|
|
17
|
+
isDismissing: 0,
|
|
18
|
+
direction: null,
|
|
19
|
+
},
|
|
20
|
+
route: { key: "test-route", name: "TestScreen" } as any,
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("derivations", () => {
|
|
25
|
+
describe("progress", () => {
|
|
26
|
+
it("returns current progress when no next screen", () => {
|
|
27
|
+
const result = derivations({
|
|
28
|
+
current: createMockState({ progress: 0.5 }),
|
|
29
|
+
});
|
|
30
|
+
expect(result.progress).toBe(0.5);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("combines current + next progress (0-2 range)", () => {
|
|
34
|
+
const result = derivations({
|
|
35
|
+
current: createMockState({ progress: 1 }),
|
|
36
|
+
next: createMockState({ progress: 0.5 }),
|
|
37
|
+
});
|
|
38
|
+
expect(result.progress).toBe(1.5);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns 2 when both screens fully transitioned", () => {
|
|
42
|
+
const result = derivations({
|
|
43
|
+
current: createMockState({ progress: 1 }),
|
|
44
|
+
next: createMockState({ progress: 1 }),
|
|
45
|
+
});
|
|
46
|
+
expect(result.progress).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("focused", () => {
|
|
51
|
+
it("returns true when no next screen", () => {
|
|
52
|
+
const result = derivations({
|
|
53
|
+
current: createMockState(),
|
|
54
|
+
});
|
|
55
|
+
expect(result.focused).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns false when next screen exists", () => {
|
|
59
|
+
const result = derivations({
|
|
60
|
+
current: createMockState(),
|
|
61
|
+
next: createMockState(),
|
|
62
|
+
});
|
|
63
|
+
expect(result.focused).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("active", () => {
|
|
68
|
+
it("returns current when focused (no next)", () => {
|
|
69
|
+
const current = createMockState({ progress: 0.3 });
|
|
70
|
+
const result = derivations({ current });
|
|
71
|
+
expect(result.active).toBe(current);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns next when not focused", () => {
|
|
75
|
+
const current = createMockState({ progress: 1 });
|
|
76
|
+
const next = createMockState({ progress: 0.5 });
|
|
77
|
+
const result = derivations({ current, next });
|
|
78
|
+
expect(result.active).toBe(next);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("isActiveTransitioning", () => {
|
|
83
|
+
it("returns true when active screen is dragging", () => {
|
|
84
|
+
const result = derivations({
|
|
85
|
+
current: createMockState({
|
|
86
|
+
gesture: {
|
|
87
|
+
isDragging: 1,
|
|
88
|
+
x: 0,
|
|
89
|
+
y: 0,
|
|
90
|
+
normalizedX: 0,
|
|
91
|
+
normalizedY: 0,
|
|
92
|
+
isDismissing: 0,
|
|
93
|
+
direction: null,
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
expect(result.isActiveTransitioning).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns true when active screen is animating", () => {
|
|
101
|
+
const result = derivations({
|
|
102
|
+
current: createMockState({ animating: 1 }),
|
|
103
|
+
});
|
|
104
|
+
expect(result.isActiveTransitioning).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns false when not dragging or animating", () => {
|
|
108
|
+
const result = derivations({
|
|
109
|
+
current: createMockState({ animating: 0 }),
|
|
110
|
+
});
|
|
111
|
+
expect(result.isActiveTransitioning).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("isDismissing", () => {
|
|
116
|
+
it("returns true when gesture isDismissing", () => {
|
|
117
|
+
const result = derivations({
|
|
118
|
+
current: createMockState({
|
|
119
|
+
gesture: {
|
|
120
|
+
isDragging: 0,
|
|
121
|
+
x: 0,
|
|
122
|
+
y: 0,
|
|
123
|
+
normalizedX: 0,
|
|
124
|
+
normalizedY: 0,
|
|
125
|
+
isDismissing: 1,
|
|
126
|
+
direction: null,
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
expect(result.isDismissing).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns true when closing flag is set", () => {
|
|
134
|
+
const result = derivations({
|
|
135
|
+
current: createMockState({ closing: 1 }),
|
|
136
|
+
});
|
|
137
|
+
expect(result.isDismissing).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns false when not dismissing or closing", () => {
|
|
141
|
+
const result = derivations({
|
|
142
|
+
current: createMockState(),
|
|
143
|
+
});
|
|
144
|
+
expect(result.isDismissing).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("checks active screen (next) for dismissing state", () => {
|
|
148
|
+
const result = derivations({
|
|
149
|
+
current: createMockState(),
|
|
150
|
+
next: createMockState({ closing: 1 }),
|
|
151
|
+
});
|
|
152
|
+
expect(result.isDismissing).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
normalizeSides,
|
|
4
|
+
computeEdgeConstraints,
|
|
5
|
+
calculateSwipeDirs,
|
|
6
|
+
shouldActivateOrFail,
|
|
7
|
+
} from "../utils/gesture/check-gesture-activation";
|
|
8
|
+
|
|
9
|
+
describe("normalizeSides", () => {
|
|
10
|
+
it("returns all sides as 'screen' when no area provided", () => {
|
|
11
|
+
const result = normalizeSides();
|
|
12
|
+
expect(result).toEqual({
|
|
13
|
+
left: "screen",
|
|
14
|
+
right: "screen",
|
|
15
|
+
top: "screen",
|
|
16
|
+
bottom: "screen",
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("normalizes string input to all sides", () => {
|
|
21
|
+
const result = normalizeSides("edge");
|
|
22
|
+
expect(result).toEqual({
|
|
23
|
+
left: "edge",
|
|
24
|
+
right: "edge",
|
|
25
|
+
top: "edge",
|
|
26
|
+
bottom: "edge",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("handles per-side object input", () => {
|
|
31
|
+
const result = normalizeSides({
|
|
32
|
+
left: "edge",
|
|
33
|
+
right: "screen",
|
|
34
|
+
top: "edge",
|
|
35
|
+
});
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
left: "edge",
|
|
38
|
+
right: "screen",
|
|
39
|
+
top: "edge",
|
|
40
|
+
bottom: "screen", // defaults to screen
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("defaults missing sides to screen", () => {
|
|
45
|
+
const result = normalizeSides({ left: "edge" });
|
|
46
|
+
expect(result.left).toBe("edge");
|
|
47
|
+
expect(result.right).toBe("screen");
|
|
48
|
+
expect(result.top).toBe("screen");
|
|
49
|
+
expect(result.bottom).toBe("screen");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("computeEdgeConstraints", () => {
|
|
54
|
+
const dimensions = { width: 375, height: 812 };
|
|
55
|
+
const allScreen = { left: "screen", right: "screen", top: "screen", bottom: "screen" } as const;
|
|
56
|
+
const allEdge = { left: "edge", right: "edge", top: "edge", bottom: "edge" } as const;
|
|
57
|
+
|
|
58
|
+
it("allows all directions when all sides are 'screen'", () => {
|
|
59
|
+
const result = computeEdgeConstraints({ x: 200, y: 400 }, dimensions, allScreen);
|
|
60
|
+
expect(result.horizontalRight).toBe(true);
|
|
61
|
+
expect(result.horizontalLeft).toBe(true);
|
|
62
|
+
expect(result.verticalDown).toBe(true);
|
|
63
|
+
expect(result.verticalUp).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("restricts to left edge for horizontal-right when edge mode", () => {
|
|
67
|
+
// Touch at x=200 (center) should NOT allow right swipe with edge activation
|
|
68
|
+
const center = computeEdgeConstraints({ x: 200, y: 400 }, dimensions, allEdge);
|
|
69
|
+
expect(center.horizontalRight).toBe(false);
|
|
70
|
+
|
|
71
|
+
// Touch at x=30 (within 50px edge) should allow right swipe
|
|
72
|
+
const edge = computeEdgeConstraints({ x: 30, y: 400 }, dimensions, allEdge);
|
|
73
|
+
expect(edge.horizontalRight).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("restricts to right edge for horizontal-left when edge mode", () => {
|
|
77
|
+
// Touch at x=200 (center) should NOT allow left swipe
|
|
78
|
+
const center = computeEdgeConstraints({ x: 200, y: 400 }, dimensions, allEdge);
|
|
79
|
+
expect(center.horizontalLeft).toBe(false);
|
|
80
|
+
|
|
81
|
+
// Touch at x=350 (within 50px of right edge) should allow left swipe
|
|
82
|
+
const edge = computeEdgeConstraints({ x: 350, y: 400 }, dimensions, allEdge);
|
|
83
|
+
expect(edge.horizontalLeft).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("restricts to top edge for vertical-down when edge mode", () => {
|
|
87
|
+
// Touch at y=400 (center) should NOT allow down swipe
|
|
88
|
+
const center = computeEdgeConstraints({ x: 200, y: 400 }, dimensions, allEdge);
|
|
89
|
+
expect(center.verticalDown).toBe(false);
|
|
90
|
+
|
|
91
|
+
// Touch at y=100 (within 135px edge) should allow down swipe
|
|
92
|
+
const edge = computeEdgeConstraints({ x: 200, y: 100 }, dimensions, allEdge);
|
|
93
|
+
expect(edge.verticalDown).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("uses custom responseDistance", () => {
|
|
97
|
+
// With custom distance of 100px
|
|
98
|
+
const result = computeEdgeConstraints({ x: 80, y: 400 }, dimensions, allEdge, 100);
|
|
99
|
+
expect(result.horizontalRight).toBe(true); // 80 < 100
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("calculateSwipeDirs", () => {
|
|
104
|
+
it("detects horizontal swipe right", () => {
|
|
105
|
+
const result = calculateSwipeDirs(50, 10);
|
|
106
|
+
expect(result.isHorizontalSwipe).toBe(true);
|
|
107
|
+
expect(result.isVerticalSwipe).toBe(false);
|
|
108
|
+
expect(result.isSwipingRight).toBe(true);
|
|
109
|
+
expect(result.isSwipingLeft).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("detects horizontal swipe left", () => {
|
|
113
|
+
const result = calculateSwipeDirs(-50, 10);
|
|
114
|
+
expect(result.isHorizontalSwipe).toBe(true);
|
|
115
|
+
expect(result.isSwipingLeft).toBe(true);
|
|
116
|
+
expect(result.isSwipingRight).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("detects vertical swipe down", () => {
|
|
120
|
+
const result = calculateSwipeDirs(10, 50);
|
|
121
|
+
expect(result.isVerticalSwipe).toBe(true);
|
|
122
|
+
expect(result.isHorizontalSwipe).toBe(false);
|
|
123
|
+
expect(result.isSwipingDown).toBe(true);
|
|
124
|
+
expect(result.isSwipingUp).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("detects vertical swipe up", () => {
|
|
128
|
+
const result = calculateSwipeDirs(10, -50);
|
|
129
|
+
expect(result.isVerticalSwipe).toBe(true);
|
|
130
|
+
expect(result.isSwipingUp).toBe(true);
|
|
131
|
+
expect(result.isSwipingDown).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("horizontal wins when equal deltas", () => {
|
|
135
|
+
// When deltaX === deltaY, neither wins (both false)
|
|
136
|
+
const result = calculateSwipeDirs(50, 50);
|
|
137
|
+
expect(result.isHorizontalSwipe).toBe(false);
|
|
138
|
+
expect(result.isVerticalSwipe).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("shouldActivateOrFail", () => {
|
|
143
|
+
const baseParams = {
|
|
144
|
+
deltaX: 0,
|
|
145
|
+
deltaY: 0,
|
|
146
|
+
hasHorizontal: true,
|
|
147
|
+
hasVertical: true,
|
|
148
|
+
isHorizontalSwipe: false,
|
|
149
|
+
isVerticalSwipe: false,
|
|
150
|
+
allowedRight: true,
|
|
151
|
+
allowedLeft: true,
|
|
152
|
+
allowedUp: true,
|
|
153
|
+
allowedDown: true,
|
|
154
|
+
horizontalGateRight: true,
|
|
155
|
+
horizontalGateLeft: true,
|
|
156
|
+
verticalGateUp: true,
|
|
157
|
+
verticalGateDown: true,
|
|
158
|
+
isSwipingRight: false,
|
|
159
|
+
isSwipingLeft: false,
|
|
160
|
+
isSwipingUp: false,
|
|
161
|
+
isSwipingDown: false,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
it("activates on valid horizontal right swipe", () => {
|
|
165
|
+
const result = shouldActivateOrFail({
|
|
166
|
+
...baseParams,
|
|
167
|
+
deltaX: 15, // above threshold (10)
|
|
168
|
+
deltaY: 5, // within tolerance (15)
|
|
169
|
+
isHorizontalSwipe: true,
|
|
170
|
+
isSwipingRight: true,
|
|
171
|
+
});
|
|
172
|
+
expect(result.shouldActivate).toBe(true);
|
|
173
|
+
expect(result.shouldFail).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("activates on valid vertical down swipe", () => {
|
|
177
|
+
const result = shouldActivateOrFail({
|
|
178
|
+
...baseParams,
|
|
179
|
+
deltaX: 5, // within tolerance (20)
|
|
180
|
+
deltaY: 15, // above threshold (10)
|
|
181
|
+
isVerticalSwipe: true,
|
|
182
|
+
isSwipingDown: true,
|
|
183
|
+
});
|
|
184
|
+
expect(result.shouldActivate).toBe(true);
|
|
185
|
+
expect(result.shouldFail).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("fails when swiping in disallowed direction", () => {
|
|
189
|
+
const result = shouldActivateOrFail({
|
|
190
|
+
...baseParams,
|
|
191
|
+
deltaX: 15,
|
|
192
|
+
deltaY: 5,
|
|
193
|
+
isHorizontalSwipe: true,
|
|
194
|
+
isSwipingRight: true,
|
|
195
|
+
allowedRight: false, // direction not allowed
|
|
196
|
+
});
|
|
197
|
+
expect(result.shouldActivate).toBe(false);
|
|
198
|
+
expect(result.shouldFail).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("fails when edge gate blocks the swipe", () => {
|
|
202
|
+
const result = shouldActivateOrFail({
|
|
203
|
+
...baseParams,
|
|
204
|
+
deltaX: 15,
|
|
205
|
+
deltaY: 5,
|
|
206
|
+
isHorizontalSwipe: true,
|
|
207
|
+
isSwipingRight: true,
|
|
208
|
+
horizontalGateRight: false, // edge gate blocks
|
|
209
|
+
});
|
|
210
|
+
expect(result.shouldActivate).toBe(false);
|
|
211
|
+
expect(result.shouldFail).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("fails when vertical deviation exceeds tolerance during horizontal swipe", () => {
|
|
215
|
+
const result = shouldActivateOrFail({
|
|
216
|
+
...baseParams,
|
|
217
|
+
deltaX: 15,
|
|
218
|
+
deltaY: 20, // exceeds GESTURE_FAIL_TOLERANCE_X (15)
|
|
219
|
+
isHorizontalSwipe: true,
|
|
220
|
+
isSwipingRight: true,
|
|
221
|
+
});
|
|
222
|
+
expect(result.shouldActivate).toBe(false);
|
|
223
|
+
expect(result.shouldFail).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("does not activate when movement is below threshold", () => {
|
|
227
|
+
const result = shouldActivateOrFail({
|
|
228
|
+
...baseParams,
|
|
229
|
+
deltaX: 5, // below threshold (10)
|
|
230
|
+
deltaY: 2,
|
|
231
|
+
isHorizontalSwipe: true,
|
|
232
|
+
isSwipingRight: true,
|
|
233
|
+
});
|
|
234
|
+
expect(result.shouldActivate).toBe(false);
|
|
235
|
+
expect(result.shouldFail).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("handles bidirectional gesture activation", () => {
|
|
239
|
+
// Both horizontal and vertical allowed, vertical swipe detected
|
|
240
|
+
const result = shouldActivateOrFail({
|
|
241
|
+
...baseParams,
|
|
242
|
+
hasHorizontal: true,
|
|
243
|
+
hasVertical: true,
|
|
244
|
+
deltaX: 5,
|
|
245
|
+
deltaY: 15,
|
|
246
|
+
isVerticalSwipe: true,
|
|
247
|
+
isSwipingDown: true,
|
|
248
|
+
});
|
|
249
|
+
expect(result.shouldActivate).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noNonNullAssertion: <Lifecycles are rendered right under the gesture provider> */
|
|
1
2
|
import { useEffect, useLayoutEffect } from "react";
|
|
2
3
|
import { useDerivedValue } from "react-native-reanimated";
|
|
3
4
|
import type { NativeStackDescriptor } from "../../../native-stack/types";
|
|
@@ -7,6 +8,7 @@ import { useGestureContext } from "../../providers/gestures.provider";
|
|
|
7
8
|
import { useKeys } from "../../providers/keys.provider";
|
|
8
9
|
import { AnimationStore } from "../../stores/animation.store";
|
|
9
10
|
import { TRUE } from "../../types/state.types";
|
|
11
|
+
import type { Any } from "../../types/utils.types";
|
|
10
12
|
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
11
13
|
import { resetStoresForScreen } from "../../utils/reset-stores-for-screen";
|
|
12
14
|
|
|
@@ -19,7 +21,7 @@ export interface Props {
|
|
|
19
21
|
*/
|
|
20
22
|
export const NativeStackScreenLifecycleController = ({ children }: Props) => {
|
|
21
23
|
const { current } = useKeys<NativeStackDescriptor>();
|
|
22
|
-
const { ancestorContext } = useGestureContext()
|
|
24
|
+
const { ancestorContext } = useGestureContext()!;
|
|
23
25
|
|
|
24
26
|
const isAncestorDismissingViaGesture = useSharedValueState(
|
|
25
27
|
useDerivedValue(() => {
|
|
@@ -32,7 +34,7 @@ export const NativeStackScreenLifecycleController = ({ children }: Props) => {
|
|
|
32
34
|
|
|
33
35
|
const animations = AnimationStore.getAll(current.route.key);
|
|
34
36
|
|
|
35
|
-
const handleBeforeRemove = useStableCallback((e:
|
|
37
|
+
const handleBeforeRemove = useStableCallback((e: Any) => {
|
|
36
38
|
const isEnabled = current.options.enableTransitions;
|
|
37
39
|
|
|
38
40
|
const isFirstScreen = current.navigation.getState().index === 0;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noNonNullAssertion: <This helper is usually being used inside a transitionable stack> */
|
|
1
2
|
import type React from "react";
|
|
2
3
|
import { type ComponentType, forwardRef, memo } from "react";
|
|
3
4
|
import type { View } from "react-native";
|
|
@@ -26,7 +27,7 @@ export function createTransitionAwareComponent<P extends object>(
|
|
|
26
27
|
React.ComponentRef<typeof Wrapped>,
|
|
27
28
|
TransitionAwareProps<P>
|
|
28
29
|
>((props: Any, ref) => {
|
|
29
|
-
const { nativeGesture } = useGestureContext()
|
|
30
|
+
const { nativeGesture } = useGestureContext()!;
|
|
30
31
|
const { scrollHandler, onContentSizeChange, onLayout } = useScrollRegistry({
|
|
31
32
|
onScroll: props.onScroll,
|
|
32
33
|
onContentSizeChange: props.onContentSizeChange,
|
|
@@ -28,11 +28,13 @@ type BuiltState = {
|
|
|
28
28
|
animating: SharedValue<number>;
|
|
29
29
|
gesture: GestureStoreMap;
|
|
30
30
|
route: RouteProp<ParamListBase>;
|
|
31
|
+
meta?: Record<string, unknown>;
|
|
31
32
|
unwrapped: ScreenTransitionState;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const createScreenTransitionState = (
|
|
35
36
|
route: RouteProp<ParamListBase>,
|
|
37
|
+
meta?: Record<string, unknown>,
|
|
36
38
|
): ScreenTransitionState => ({
|
|
37
39
|
progress: 0,
|
|
38
40
|
closing: 0,
|
|
@@ -47,6 +49,7 @@ const createScreenTransitionState = (
|
|
|
47
49
|
direction: null,
|
|
48
50
|
},
|
|
49
51
|
route,
|
|
52
|
+
meta,
|
|
50
53
|
});
|
|
51
54
|
|
|
52
55
|
const unwrapInto = (s: BuiltState): ScreenTransitionState => {
|
|
@@ -62,6 +65,7 @@ const unwrapInto = (s: BuiltState): ScreenTransitionState => {
|
|
|
62
65
|
out.gesture.isDismissing = s.gesture.isDismissing.value;
|
|
63
66
|
out.gesture.isDragging = s.gesture.isDragging.value;
|
|
64
67
|
out.gesture.direction = s.gesture.direction.value;
|
|
68
|
+
out.meta = s.meta;
|
|
65
69
|
|
|
66
70
|
return out;
|
|
67
71
|
};
|
|
@@ -70,6 +74,7 @@ const useBuildScreenTransitionState = (
|
|
|
70
74
|
descriptor: TransitionDescriptor | undefined,
|
|
71
75
|
): BuiltState | undefined => {
|
|
72
76
|
const key = descriptor?.route.key;
|
|
77
|
+
const meta = descriptor?.options?.meta;
|
|
73
78
|
|
|
74
79
|
return useMemo(() => {
|
|
75
80
|
if (!key) return undefined;
|
|
@@ -80,9 +85,10 @@ const useBuildScreenTransitionState = (
|
|
|
80
85
|
animating: AnimationStore.getAnimation(key, "animating"),
|
|
81
86
|
gesture: GestureStore.getRouteGestures(key),
|
|
82
87
|
route: descriptor.route,
|
|
83
|
-
|
|
88
|
+
meta,
|
|
89
|
+
unwrapped: createScreenTransitionState(descriptor.route, meta),
|
|
84
90
|
};
|
|
85
|
-
}, [key, descriptor?.route]);
|
|
91
|
+
}, [key, descriptor?.route, meta]);
|
|
86
92
|
};
|
|
87
93
|
|
|
88
94
|
const hasTransitionsEnabled = (
|