react-native-screen-transitions 3.3.0-beta.0 → 3.3.0-beta.2
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 +104 -9
- package/lib/commonjs/blank-stack/components/adjusted-screen.js +2 -2
- package/lib/commonjs/blank-stack/components/adjusted-screen.js.map +1 -1
- package/lib/commonjs/shared/animation/snap-to.js +62 -0
- package/lib/commonjs/shared/animation/snap-to.js.map +1 -0
- package/lib/commonjs/shared/constants.js +36 -10
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +25 -18
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +0 -25
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +74 -64
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +2 -61
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.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/stores/animation.store.js +2 -1
- package/lib/commonjs/shared/stores/animation.store.js.map +1 -1
- package/lib/commonjs/shared/utils/animation/animate-to-progress.js +8 -2
- package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +90 -23
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/commonjs/shared/utils/logger.js +22 -0
- package/lib/commonjs/shared/utils/logger.js.map +1 -0
- package/lib/module/blank-stack/components/adjusted-screen.js +1 -1
- package/lib/module/blank-stack/components/adjusted-screen.js.map +1 -1
- package/lib/module/shared/animation/snap-to.js +59 -0
- package/lib/module/shared/animation/snap-to.js.map +1 -0
- package/lib/module/shared/constants.js +34 -9
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js +25 -18
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +0 -25
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +69 -59
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/module/shared/hooks/navigation/use-screen-state.js +4 -63
- package/lib/module/shared/hooks/navigation/use-screen-state.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/stores/animation.store.js +2 -1
- package/lib/module/shared/stores/animation.store.js.map +1 -1
- package/lib/module/shared/utils/animation/animate-to-progress.js +8 -2
- package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -1
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +90 -23
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/shared/utils/logger.js +17 -0
- package/lib/module/shared/utils/logger.js.map +1 -0
- package/lib/typescript/blank-stack/components/adjusted-screen.d.ts.map +1 -1
- package/lib/typescript/shared/animation/snap-to.d.ts +18 -0
- package/lib/typescript/shared/animation/snap-to.d.ts.map +1 -0
- package/lib/typescript/shared/constants.d.ts +9 -0
- package/lib/typescript/shared/constants.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.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +1 -16
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +0 -14
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
- package/lib/typescript/shared/index.d.ts +1 -0
- package/lib/typescript/shared/index.d.ts.map +1 -1
- package/lib/typescript/shared/stores/animation.store.d.ts +1 -0
- package/lib/typescript/shared/stores/animation.store.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +9 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +4 -5
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/lib/typescript/shared/utils/logger.d.ts +6 -0
- package/lib/typescript/shared/utils/logger.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/blank-stack/components/adjusted-screen.tsx +1 -1
- package/src/shared/__tests__/derivations.test.ts +1 -0
- package/src/shared/__tests__/gesture-activation.test.ts +29 -56
- package/src/shared/animation/snap-to.ts +62 -0
- package/src/shared/constants.ts +36 -9
- package/src/shared/hooks/animation/use-screen-animation.tsx +32 -21
- package/src/shared/hooks/gestures/use-build-gestures.tsx +2 -34
- package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +94 -92
- package/src/shared/hooks/navigation/use-screen-state.tsx +2 -106
- package/src/shared/index.ts +1 -0
- package/src/shared/stores/animation.store.ts +2 -0
- package/src/shared/types/animation.types.ts +10 -0
- package/src/shared/utils/animation/animate-to-progress.ts +7 -2
- package/src/shared/utils/gesture/check-gesture-activation.ts +74 -23
- package/src/shared/utils/logger.ts +15 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { runOnUI } from "react-native-reanimated";
|
|
2
|
+
import { DefaultSnapSpec } from "../configs/specs";
|
|
3
|
+
import { AnimationStore } from "../stores/animation.store";
|
|
4
|
+
import { HistoryStore } from "../stores/history.store";
|
|
5
|
+
import { animateToProgress } from "../utils/animation/animate-to-progress";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Programmatically snap the currently focused screen to a specific snap point.
|
|
9
|
+
*
|
|
10
|
+
* @param index - The index of the snap point to snap to (0-based, sorted ascending)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { snapTo } from 'react-native-screen-transitions';
|
|
15
|
+
*
|
|
16
|
+
* // Snap to the first (smallest) snap point
|
|
17
|
+
* snapTo(0);
|
|
18
|
+
*
|
|
19
|
+
* // Snap to the last (largest) snap point
|
|
20
|
+
* snapTo(2); // if there are 3 snap points
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function snapTo(index: number): void {
|
|
24
|
+
const recent = HistoryStore.getMostRecent();
|
|
25
|
+
|
|
26
|
+
if (!recent) {
|
|
27
|
+
console.warn("snapTo: No screen in history");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { descriptor } = recent;
|
|
32
|
+
const snapPoints = descriptor.options?.snapPoints;
|
|
33
|
+
|
|
34
|
+
if (!snapPoints || snapPoints.length === 0) {
|
|
35
|
+
console.warn("snapTo: No snapPoints defined on current screen");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sorted = [...snapPoints].sort((a, b) => a - b);
|
|
40
|
+
|
|
41
|
+
if (index < 0 || index >= sorted.length) {
|
|
42
|
+
console.warn(
|
|
43
|
+
`snapTo: index ${index} out of bounds (0-${sorted.length - 1})`,
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const targetProgress = sorted[index];
|
|
49
|
+
const animations = AnimationStore.getAll(descriptor.route.key);
|
|
50
|
+
|
|
51
|
+
runOnUI(() => {
|
|
52
|
+
"worklet";
|
|
53
|
+
animateToProgress({
|
|
54
|
+
target: targetProgress,
|
|
55
|
+
animations,
|
|
56
|
+
spec: {
|
|
57
|
+
open: descriptor.options.transitionSpec?.expand ?? DefaultSnapSpec,
|
|
58
|
+
close: descriptor.options.transitionSpec?.collapse ?? DefaultSnapSpec,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
})();
|
|
62
|
+
}
|
package/src/shared/constants.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { MeasuredDimensions } from "react-native-reanimated";
|
|
|
4
4
|
import type { ScreenTransitionState } from "./types/animation.types";
|
|
5
5
|
import type { ActivationArea } from "./types/gesture.types";
|
|
6
6
|
import type { Layout } from "./types/screen.types";
|
|
7
|
+
import type { BaseStackRoute } from "./types/stack.types";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Masked view integration
|
|
@@ -16,6 +17,35 @@ export const CONTAINER_STYLE_ID = "_ROOT_CONTAINER";
|
|
|
16
17
|
*/
|
|
17
18
|
export const NO_STYLES = Object.freeze({});
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Default gesture values
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_GESTURE_VALUES = {
|
|
24
|
+
x: 0,
|
|
25
|
+
y: 0,
|
|
26
|
+
normalizedX: 0,
|
|
27
|
+
normalizedY: 0,
|
|
28
|
+
isDismissing: 0,
|
|
29
|
+
isDragging: 0,
|
|
30
|
+
direction: null,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new screen transition state object
|
|
35
|
+
*/
|
|
36
|
+
export const createScreenTransitionState = (
|
|
37
|
+
route: BaseStackRoute,
|
|
38
|
+
meta?: Record<string, unknown>,
|
|
39
|
+
): ScreenTransitionState => ({
|
|
40
|
+
progress: 0,
|
|
41
|
+
closing: 0,
|
|
42
|
+
animating: 0,
|
|
43
|
+
entering: 1,
|
|
44
|
+
gesture: { ...DEFAULT_GESTURE_VALUES },
|
|
45
|
+
route,
|
|
46
|
+
meta,
|
|
47
|
+
});
|
|
48
|
+
|
|
19
49
|
/**
|
|
20
50
|
* Default screen transition state
|
|
21
51
|
*/
|
|
@@ -25,15 +55,7 @@ export const DEFAULT_SCREEN_TRANSITION_STATE: ScreenTransitionState =
|
|
|
25
55
|
closing: 0,
|
|
26
56
|
animating: 0,
|
|
27
57
|
entering: 1,
|
|
28
|
-
gesture:
|
|
29
|
-
x: 0,
|
|
30
|
-
y: 0,
|
|
31
|
-
normalizedX: 0,
|
|
32
|
-
normalizedY: 0,
|
|
33
|
-
isDismissing: 0,
|
|
34
|
-
isDragging: 0,
|
|
35
|
-
direction: null,
|
|
36
|
-
},
|
|
58
|
+
gesture: DEFAULT_GESTURE_VALUES,
|
|
37
59
|
route: {} as RouteProp<ParamListBase>,
|
|
38
60
|
});
|
|
39
61
|
|
|
@@ -79,3 +101,8 @@ export const IS_WEB = Platform.OS === "web";
|
|
|
79
101
|
|
|
80
102
|
export const TRUE = 1;
|
|
81
103
|
export const FALSE = 0;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Small value for floating-point comparisons to handle animation/interpolation imprecision
|
|
107
|
+
*/
|
|
108
|
+
export const EPSILON = 1e-5;
|
|
@@ -3,7 +3,10 @@ import { useWindowDimensions } from "react-native";
|
|
|
3
3
|
import { type SharedValue, useDerivedValue } from "react-native-reanimated";
|
|
4
4
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
5
|
import type { NativeStackScreenTransitionConfig } from "../../../native-stack/types";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createScreenTransitionState,
|
|
8
|
+
DEFAULT_SCREEN_TRANSITION_STATE,
|
|
9
|
+
} from "../../constants";
|
|
7
10
|
import {
|
|
8
11
|
type BaseDescriptor,
|
|
9
12
|
useKeys,
|
|
@@ -31,26 +34,26 @@ type BuiltState = {
|
|
|
31
34
|
unwrapped: ScreenTransitionState;
|
|
32
35
|
};
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
37
|
+
/**
|
|
38
|
+
* Computes the animated snap index based on progress and snap points.
|
|
39
|
+
* Returns -1 if no snap points, otherwise interpolates between indices.
|
|
40
|
+
*/
|
|
41
|
+
const computeSnapIndex = (progress: number, snapPoints: number[]): number => {
|
|
42
|
+
"worklet";
|
|
43
|
+
if (snapPoints.length === 0) return -1;
|
|
44
|
+
if (progress <= snapPoints[0]) return 0;
|
|
45
|
+
if (progress >= snapPoints[snapPoints.length - 1])
|
|
46
|
+
return snapPoints.length - 1;
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < snapPoints.length - 1; i++) {
|
|
49
|
+
if (progress <= snapPoints[i + 1]) {
|
|
50
|
+
const t =
|
|
51
|
+
(progress - snapPoints[i]) / (snapPoints[i + 1] - snapPoints[i]);
|
|
52
|
+
return i + t;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return snapPoints.length - 1;
|
|
56
|
+
};
|
|
54
57
|
|
|
55
58
|
const unwrapInto = (s: BuiltState): ScreenTransitionState => {
|
|
56
59
|
"worklet";
|
|
@@ -123,6 +126,11 @@ export function _useScreenAnimation() {
|
|
|
123
126
|
const currentRouteKey = currentDescriptor?.route?.key;
|
|
124
127
|
const currentIndex = routeKeys.indexOf(currentRouteKey);
|
|
125
128
|
|
|
129
|
+
const sortedSnapPoints = useMemo(() => {
|
|
130
|
+
const points = currentDescriptor?.options?.snapPoints;
|
|
131
|
+
return points ? [...points].sort((a, b) => a - b) : [];
|
|
132
|
+
}, [currentDescriptor?.options?.snapPoints]);
|
|
133
|
+
|
|
126
134
|
const screenInterpolatorProps = useDerivedValue<
|
|
127
135
|
Omit<ScreenInterpolationProps, "bounds">
|
|
128
136
|
>(() => {
|
|
@@ -152,6 +160,8 @@ export function _useScreenAnimation() {
|
|
|
152
160
|
const stackProgress =
|
|
153
161
|
currentIndex >= 0 ? rootStackProgress.value - currentIndex : progress;
|
|
154
162
|
|
|
163
|
+
const snapIndex = computeSnapIndex(current.progress, sortedSnapPoints);
|
|
164
|
+
|
|
155
165
|
return {
|
|
156
166
|
layouts: { screen: dimensions },
|
|
157
167
|
insets,
|
|
@@ -160,6 +170,7 @@ export function _useScreenAnimation() {
|
|
|
160
170
|
next,
|
|
161
171
|
progress,
|
|
162
172
|
stackProgress,
|
|
173
|
+
snapIndex,
|
|
163
174
|
...helpers,
|
|
164
175
|
};
|
|
165
176
|
});
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import { StackActions } from "@react-navigation/native";
|
|
2
2
|
import { useCallback, useMemo, useRef } from "react";
|
|
3
|
-
import { useWindowDimensions } from "react-native";
|
|
4
3
|
import { Gesture, type GestureType } from "react-native-gesture-handler";
|
|
5
4
|
import type { SharedValue } from "react-native-reanimated";
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
8
|
-
DEFAULT_GESTURE_DIRECTION,
|
|
9
|
-
DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
10
|
-
GESTURE_VELOCITY_IMPACT,
|
|
11
|
-
} from "../../constants";
|
|
12
5
|
import type {
|
|
13
6
|
GestureContextType,
|
|
14
7
|
ScrollConfig,
|
|
15
8
|
} from "../../providers/gestures.provider";
|
|
16
9
|
import { useKeys } from "../../providers/screen/keys.provider";
|
|
17
|
-
import { AnimationStore } from "../../stores/animation.store";
|
|
18
10
|
import { GestureStore, type GestureStoreMap } from "../../stores/gesture.store";
|
|
19
11
|
import { useScreenGestureHandlers } from "./use-screen-gesture-handlers";
|
|
20
12
|
|
|
@@ -32,8 +24,6 @@ export const useBuildGestures = ({
|
|
|
32
24
|
nativeGesture: GestureType;
|
|
33
25
|
gestureAnimationValues: GestureStoreMap;
|
|
34
26
|
} => {
|
|
35
|
-
const dimensions = useWindowDimensions();
|
|
36
|
-
|
|
37
27
|
const { current } = useKeys();
|
|
38
28
|
|
|
39
29
|
const navState = current.navigation.getState();
|
|
@@ -48,17 +38,8 @@ export const useBuildGestures = ({
|
|
|
48
38
|
const gestureAnimationValues = GestureStore.getRouteGestures(
|
|
49
39
|
current.route.key,
|
|
50
40
|
);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const {
|
|
54
|
-
gestureDirection = DEFAULT_GESTURE_DIRECTION,
|
|
55
|
-
gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
|
|
56
|
-
gestureDrivesProgress = DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
57
|
-
gestureActivationArea = DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
58
|
-
gestureResponseDistance,
|
|
59
|
-
transitionSpec,
|
|
60
|
-
snapPoints,
|
|
61
|
-
} = current.options;
|
|
41
|
+
|
|
42
|
+
const { snapPoints } = current.options;
|
|
62
43
|
|
|
63
44
|
// Dismiss gesture is controlled by gestureEnabled (disabled for first screen)
|
|
64
45
|
const canDismiss = Boolean(
|
|
@@ -97,26 +78,13 @@ export const useBuildGestures = ({
|
|
|
97
78
|
|
|
98
79
|
const { onTouchesDown, onTouchesMove, onStart, onUpdate, onEnd } =
|
|
99
80
|
useScreenGestureHandlers({
|
|
100
|
-
dimensions,
|
|
101
|
-
animations,
|
|
102
|
-
gestureAnimationValues,
|
|
103
|
-
gestureDirection,
|
|
104
|
-
gestureDrivesProgress,
|
|
105
|
-
gestureVelocityImpact,
|
|
106
81
|
scrollConfig,
|
|
107
|
-
gestureActivationArea,
|
|
108
|
-
gestureResponseDistance,
|
|
109
|
-
snapPoints,
|
|
110
82
|
canDismiss,
|
|
111
|
-
transitionSpec,
|
|
112
83
|
handleDismiss,
|
|
113
84
|
ancestorIsDismissing:
|
|
114
85
|
ancestorContext?.gestureAnimationValues.isDismissing,
|
|
115
86
|
});
|
|
116
87
|
|
|
117
|
-
// Memoize gestures to keep stable references - critical for RNGH
|
|
118
|
-
// Child gestures reference ancestor's pan via requireExternalGestureToFail,
|
|
119
|
-
// so the pan gesture MUST be stable or children will reference stale objects
|
|
120
88
|
return useMemo(() => {
|
|
121
89
|
const panGesture = Gesture.Pan()
|
|
122
90
|
.withRef(panGestureRef)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
+
import { useWindowDimensions } from "react-native";
|
|
2
3
|
import type {
|
|
3
4
|
GestureStateChangeEvent,
|
|
4
5
|
GestureTouchEvent,
|
|
@@ -8,17 +9,20 @@ import type {
|
|
|
8
9
|
import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
|
|
9
10
|
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
|
10
11
|
import { DefaultSnapSpec } from "../../configs/specs";
|
|
11
|
-
import { FALSE, TRUE } from "../../constants";
|
|
12
|
-
import type { ScrollConfig } from "../../providers/gestures.provider";
|
|
13
|
-
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
14
|
-
import type { GestureStoreMap } from "../../stores/gesture.store";
|
|
15
|
-
import type { TransitionSpec } from "../../types/animation.types";
|
|
16
12
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
14
|
+
DEFAULT_GESTURE_DIRECTION,
|
|
15
|
+
DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
16
|
+
EPSILON,
|
|
17
|
+
FALSE,
|
|
18
|
+
GESTURE_VELOCITY_IMPACT,
|
|
19
|
+
TRUE,
|
|
20
|
+
} from "../../constants";
|
|
21
|
+
import type { ScrollConfig } from "../../providers/gestures.provider";
|
|
22
|
+
import { useKeys } from "../../providers/screen/keys.provider";
|
|
23
|
+
import { AnimationStore } from "../../stores/animation.store";
|
|
24
|
+
import { GestureStore } from "../../stores/gesture.store";
|
|
25
|
+
import { GestureOffsetState } from "../../types/gesture.types";
|
|
22
26
|
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
23
27
|
import {
|
|
24
28
|
applyOffsetRules,
|
|
@@ -30,50 +34,71 @@ import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progres
|
|
|
30
34
|
import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
|
|
31
35
|
import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
|
|
32
36
|
import { velocity } from "../../utils/gesture/velocity";
|
|
37
|
+
import { logger } from "../../utils/logger";
|
|
33
38
|
import useStableCallbackValue from "../use-stable-callback-value";
|
|
34
39
|
|
|
35
40
|
interface UseScreenGestureHandlersProps {
|
|
36
|
-
dimensions: Layout;
|
|
37
|
-
animations: AnimationStoreMap;
|
|
38
|
-
gestureAnimationValues: GestureStoreMap;
|
|
39
|
-
gestureDirection: GestureDirection | GestureDirection[];
|
|
40
|
-
gestureDrivesProgress: boolean;
|
|
41
|
-
gestureVelocityImpact: number;
|
|
42
41
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
43
|
-
gestureActivationArea: GestureActivationArea;
|
|
44
|
-
gestureResponseDistance?: number;
|
|
45
42
|
ancestorIsDismissing?: SharedValue<number> | null;
|
|
46
|
-
snapPoints?: number[];
|
|
47
43
|
canDismiss: boolean;
|
|
48
|
-
transitionSpec?: TransitionSpec;
|
|
49
44
|
handleDismiss: () => void;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
export const useScreenGestureHandlers = ({
|
|
53
|
-
dimensions,
|
|
54
|
-
animations,
|
|
55
|
-
gestureAnimationValues,
|
|
56
|
-
gestureDirection,
|
|
57
|
-
gestureDrivesProgress,
|
|
58
|
-
gestureVelocityImpact,
|
|
59
48
|
scrollConfig,
|
|
60
|
-
gestureActivationArea,
|
|
61
|
-
gestureResponseDistance,
|
|
62
49
|
ancestorIsDismissing,
|
|
63
|
-
snapPoints: rawSnapPoints,
|
|
64
50
|
canDismiss,
|
|
65
|
-
transitionSpec,
|
|
66
51
|
handleDismiss,
|
|
67
52
|
}: UseScreenGestureHandlersProps) => {
|
|
53
|
+
const dimensions = useWindowDimensions();
|
|
54
|
+
const { current } = useKeys();
|
|
55
|
+
|
|
56
|
+
const animations = AnimationStore.getAll(current.route.key);
|
|
57
|
+
const gestureAnimationValues = GestureStore.getRouteGestures(
|
|
58
|
+
current.route.key,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
gestureDirection = DEFAULT_GESTURE_DIRECTION,
|
|
63
|
+
gestureDrivesProgress = DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
64
|
+
gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
|
|
65
|
+
gestureActivationArea = DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
66
|
+
gestureResponseDistance,
|
|
67
|
+
transitionSpec,
|
|
68
|
+
snapPoints: rawSnapPoints,
|
|
69
|
+
} = current.options;
|
|
70
|
+
|
|
68
71
|
const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
|
|
69
72
|
() => validateSnapPoints({ snapPoints: rawSnapPoints, canDismiss }),
|
|
70
73
|
[rawSnapPoints, canDismiss],
|
|
71
74
|
);
|
|
72
75
|
|
|
73
76
|
const directions = useMemo(() => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
if (hasSnapPoints && Array.isArray(gestureDirection)) {
|
|
78
|
+
/**
|
|
79
|
+
* Unsure if this behavior will change in the future, as I cannot find a use case as to why
|
|
80
|
+
* you would want multiple gesture dismisals for a sheet.
|
|
81
|
+
*
|
|
82
|
+
* e.g. When defining a snap point with a gesture of vertical ( default ), the system
|
|
83
|
+
* assumes that the inverse ( vertical-inverted ), will grow the sheet.
|
|
84
|
+
*/
|
|
85
|
+
logger.warn(
|
|
86
|
+
`gestureDirection array is not supported with snapPoints. ` +
|
|
87
|
+
`Only the first direction "${gestureDirection[0]}" will be used. ` +
|
|
88
|
+
`Snap points define a single axis of movement, so only one gesture direction is needed.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// When snap points are defined, use only the first direction from the array
|
|
93
|
+
const effectiveDirection = hasSnapPoints
|
|
94
|
+
? Array.isArray(gestureDirection)
|
|
95
|
+
? gestureDirection[0]
|
|
96
|
+
: gestureDirection
|
|
97
|
+
: gestureDirection;
|
|
98
|
+
|
|
99
|
+
const directionsArray = Array.isArray(effectiveDirection)
|
|
100
|
+
? effectiveDirection
|
|
101
|
+
: [effectiveDirection];
|
|
77
102
|
|
|
78
103
|
const isBidirectional = directionsArray.includes("bidirectional");
|
|
79
104
|
|
|
@@ -128,7 +153,7 @@ export const useScreenGestureHandlers = ({
|
|
|
128
153
|
|
|
129
154
|
// If an ancestor navigator is already dismissing via gesture, block new gestures here.
|
|
130
155
|
if (ancestorIsDismissing?.value) {
|
|
131
|
-
gestureOffsetState.
|
|
156
|
+
gestureOffsetState.value = GestureOffsetState.FAILED;
|
|
132
157
|
manager.fail();
|
|
133
158
|
return;
|
|
134
159
|
}
|
|
@@ -176,13 +201,13 @@ export const useScreenGestureHandlers = ({
|
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
if (isSwipingDown) {
|
|
179
|
-
gestureAnimationValues.direction.
|
|
204
|
+
gestureAnimationValues.direction.value = "vertical";
|
|
180
205
|
} else if (isSwipingUp) {
|
|
181
|
-
gestureAnimationValues.direction.
|
|
206
|
+
gestureAnimationValues.direction.value = "vertical-inverted";
|
|
182
207
|
} else if (isSwipingRight) {
|
|
183
|
-
gestureAnimationValues.direction.
|
|
208
|
+
gestureAnimationValues.direction.value = "horizontal";
|
|
184
209
|
} else if (isSwipingLeft) {
|
|
185
|
-
gestureAnimationValues.direction.
|
|
210
|
+
gestureAnimationValues.direction.value = "horizontal-inverted";
|
|
186
211
|
}
|
|
187
212
|
|
|
188
213
|
manager.activate();
|
|
@@ -190,18 +215,12 @@ export const useScreenGestureHandlers = ({
|
|
|
190
215
|
}
|
|
191
216
|
|
|
192
217
|
// Touch IS on ScrollView - apply scroll-aware rules
|
|
193
|
-
const scrollX = scrollCfg?.x ?? 0;
|
|
194
|
-
const scrollY = scrollCfg?.y ?? 0;
|
|
195
|
-
const maxScrollX = scrollCfg?.contentWidth
|
|
196
|
-
? scrollCfg.contentWidth - scrollCfg.layoutWidth
|
|
197
|
-
: 0;
|
|
198
|
-
const maxScrollY = scrollCfg?.contentHeight
|
|
199
|
-
? scrollCfg.contentHeight - scrollCfg.layoutHeight
|
|
200
|
-
: 0;
|
|
201
|
-
|
|
202
218
|
// Snap mode: determine if sheet can still expand
|
|
219
|
+
// Also check targetProgress - if we're already animating toward max, scroll should win
|
|
203
220
|
const canExpandMore =
|
|
204
|
-
hasSnapPoints &&
|
|
221
|
+
hasSnapPoints &&
|
|
222
|
+
animations.progress.value < maxSnapPoint - EPSILON &&
|
|
223
|
+
animations.targetProgress.value < maxSnapPoint - EPSILON;
|
|
205
224
|
|
|
206
225
|
const { shouldActivate, direction: activatedDirection } =
|
|
207
226
|
checkScrollAwareActivation({
|
|
@@ -212,10 +231,7 @@ export const useScreenGestureHandlers = ({
|
|
|
212
231
|
isSwipingLeft,
|
|
213
232
|
},
|
|
214
233
|
directions,
|
|
215
|
-
|
|
216
|
-
scrollY,
|
|
217
|
-
maxScrollX,
|
|
218
|
-
maxScrollY,
|
|
234
|
+
scrollConfig: scrollCfg,
|
|
219
235
|
hasSnapPoints,
|
|
220
236
|
canExpandMore,
|
|
221
237
|
});
|
|
@@ -242,6 +258,7 @@ export const useScreenGestureHandlers = ({
|
|
|
242
258
|
gestureAnimationValues.isDragging.value = TRUE;
|
|
243
259
|
gestureAnimationValues.isDismissing.value = FALSE;
|
|
244
260
|
gestureStartProgress.value = animations.progress.value;
|
|
261
|
+
animations.animating.value = TRUE;
|
|
245
262
|
});
|
|
246
263
|
|
|
247
264
|
const onUpdate = useStableCallbackValue(
|
|
@@ -251,7 +268,6 @@ export const useScreenGestureHandlers = ({
|
|
|
251
268
|
const { translationX, translationY } = event;
|
|
252
269
|
const { width, height } = dimensions;
|
|
253
270
|
|
|
254
|
-
// Update gesture values (shared across all modes)
|
|
255
271
|
gestureAnimationValues.x.value = translationX;
|
|
256
272
|
gestureAnimationValues.y.value = translationY;
|
|
257
273
|
gestureAnimationValues.normalizedX.value = velocity.normalizeTranslation(
|
|
@@ -264,7 +280,6 @@ export const useScreenGestureHandlers = ({
|
|
|
264
280
|
);
|
|
265
281
|
|
|
266
282
|
if (hasSnapPoints && gestureDrivesProgress) {
|
|
267
|
-
// Snap mode: bidirectional tracking on snap axis
|
|
268
283
|
const isHorizontal = snapAxis === "horizontal";
|
|
269
284
|
const translation = isHorizontal ? translationX : translationY;
|
|
270
285
|
const dimension = isHorizontal ? width : height;
|
|
@@ -283,43 +298,30 @@ export const useScreenGestureHandlers = ({
|
|
|
283
298
|
Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
|
|
284
299
|
);
|
|
285
300
|
} else if (gestureDrivesProgress) {
|
|
286
|
-
// Standard mode: find max progress across allowed directions
|
|
287
|
-
const axes = [
|
|
288
|
-
{
|
|
289
|
-
enabled: directions.horizontal,
|
|
290
|
-
translation: translationX,
|
|
291
|
-
dimension: width,
|
|
292
|
-
sign: 1,
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
enabled: directions.horizontalInverted,
|
|
296
|
-
translation: translationX,
|
|
297
|
-
dimension: width,
|
|
298
|
-
sign: -1,
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
enabled: directions.vertical,
|
|
302
|
-
translation: translationY,
|
|
303
|
-
dimension: height,
|
|
304
|
-
sign: 1,
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
enabled: directions.verticalInverted,
|
|
308
|
-
translation: translationY,
|
|
309
|
-
dimension: height,
|
|
310
|
-
sign: -1,
|
|
311
|
-
},
|
|
312
|
-
];
|
|
313
|
-
|
|
314
301
|
let maxProgress = 0;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
302
|
+
|
|
303
|
+
// Horizontal swipe right (positive X)
|
|
304
|
+
if (directions.horizontal && translationX > 0) {
|
|
305
|
+
const progress = mapGestureToProgress(translationX, width);
|
|
306
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Horizontal inverted swipe left (negative X)
|
|
310
|
+
if (directions.horizontalInverted && translationX < 0) {
|
|
311
|
+
const progress = mapGestureToProgress(-translationX, width);
|
|
312
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Vertical swipe down (positive Y)
|
|
316
|
+
if (directions.vertical && translationY > 0) {
|
|
317
|
+
const progress = mapGestureToProgress(translationY, height);
|
|
318
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Vertical inverted swipe up (negative Y)
|
|
322
|
+
if (directions.verticalInverted && translationY < 0) {
|
|
323
|
+
const progress = mapGestureToProgress(-translationY, height);
|
|
324
|
+
maxProgress = Math.max(maxProgress, progress);
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
animations.progress.value = Math.max(
|
|
@@ -394,7 +396,6 @@ export const useScreenGestureHandlers = ({
|
|
|
394
396
|
initialVelocity,
|
|
395
397
|
});
|
|
396
398
|
} else {
|
|
397
|
-
// Standard mode: use determineDismissal
|
|
398
399
|
const result = determineDismissal({
|
|
399
400
|
event,
|
|
400
401
|
directions,
|
|
@@ -403,7 +404,8 @@ export const useScreenGestureHandlers = ({
|
|
|
403
404
|
});
|
|
404
405
|
|
|
405
406
|
const shouldDismiss = result.shouldDismiss;
|
|
406
|
-
|
|
407
|
+
// Without snap points, always animate to fully visible (1) when not dismissing
|
|
408
|
+
const targetProgress = shouldDismiss ? 0 : 1;
|
|
407
409
|
|
|
408
410
|
resetGestureValues({
|
|
409
411
|
spec: shouldDismiss ? transitionSpec?.close : transitionSpec?.open,
|