react-native-screen-transitions 3.3.0-beta.3 → 3.3.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -31
- package/lib/commonjs/shared/animation/snap-to.js +2 -0
- package/lib/commonjs/shared/animation/snap-to.js.map +1 -1
- package/lib/commonjs/shared/components/create-transition-aware-component.js +20 -18
- package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container.js +59 -6
- package/lib/commonjs/shared/components/screen-container.js.map +1 -1
- package/lib/commonjs/shared/constants.js +8 -1
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +49 -39
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +110 -61
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +67 -70
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.provider.js +112 -5
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/types/ownership.types.js +71 -0
- package/lib/commonjs/shared/types/ownership.types.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +72 -128
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/compute-claimed-directions.js +81 -0
- package/lib/commonjs/shared/utils/gesture/compute-claimed-directions.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js +1 -1
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/find-collapse-target.js +48 -0
- package/lib/commonjs/shared/utils/gesture/find-collapse-target.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/resolve-ownership.js +87 -0
- package/lib/commonjs/shared/utils/gesture/resolve-ownership.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/velocity.js +16 -5
- package/lib/commonjs/shared/utils/gesture/velocity.js.map +1 -1
- package/lib/module/shared/animation/snap-to.js +1 -0
- package/lib/module/shared/animation/snap-to.js.map +1 -1
- package/lib/module/shared/components/create-transition-aware-component.js +20 -18
- package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/module/shared/components/screen-container.js +59 -7
- package/lib/module/shared/components/screen-container.js.map +1 -1
- package/lib/module/shared/constants.js +7 -0
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +49 -39
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +112 -63
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +68 -70
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/providers/gestures.provider.js +112 -5
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/types/ownership.types.js +67 -0
- package/lib/module/shared/types/ownership.types.js.map +1 -0
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +70 -126
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/shared/utils/gesture/compute-claimed-directions.js +77 -0
- package/lib/module/shared/utils/gesture/compute-claimed-directions.js.map +1 -0
- package/lib/module/shared/utils/gesture/determine-snap-target.js +1 -1
- package/lib/module/shared/utils/gesture/determine-snap-target.js.map +1 -1
- package/lib/module/shared/utils/gesture/find-collapse-target.js +44 -0
- package/lib/module/shared/utils/gesture/find-collapse-target.js.map +1 -0
- package/lib/module/shared/utils/gesture/resolve-ownership.js +83 -0
- package/lib/module/shared/utils/gesture/resolve-ownership.js.map +1 -0
- package/lib/module/shared/utils/gesture/velocity.js +16 -5
- package/lib/module/shared/utils/gesture/velocity.js.map +1 -1
- package/lib/typescript/shared/animation/snap-to.d.ts.map +1 -1
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container.d.ts.map +1 -1
- package/lib/typescript/shared/constants.d.ts +6 -0
- package/lib/typescript/shared/constants.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +15 -3
- 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 +52 -2
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +11 -6
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts +1 -1
- package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.provider.d.ts +27 -2
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/types/ownership.types.d.ts +52 -0
- package/lib/typescript/shared/types/ownership.types.d.ts.map +1 -0
- package/lib/typescript/shared/types/screen.types.d.ts +22 -1
- package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +23 -19
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/compute-claimed-directions.d.ts +23 -0
- package/lib/typescript/shared/utils/gesture/compute-claimed-directions.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts +5 -1
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts +17 -0
- package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/resolve-ownership.d.ts +36 -0
- package/lib/typescript/shared/utils/gesture/resolve-ownership.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/velocity.d.ts.map +1 -1
- package/package.json +121 -120
- package/src/shared/animation/snap-to.ts +1 -0
- package/src/shared/components/create-transition-aware-component.tsx +28 -25
- package/src/shared/components/screen-container.tsx +69 -7
- package/src/shared/constants.ts +7 -0
- package/src/shared/hooks/gestures/use-build-gestures.tsx +80 -44
- package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +147 -71
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +94 -86
- package/src/shared/hooks/use-backdrop-pointer-events.ts +1 -1
- package/src/shared/providers/gestures.provider.tsx +166 -5
- package/src/shared/types/ownership.types.ts +77 -0
- package/src/shared/types/screen.types.ts +24 -1
- package/src/shared/utils/gesture/check-gesture-activation.ts +82 -116
- package/src/shared/utils/gesture/compute-claimed-directions.ts +93 -0
- package/src/shared/utils/gesture/determine-snap-target.ts +6 -2
- package/src/shared/utils/gesture/find-collapse-target.ts +42 -0
- package/src/shared/utils/gesture/resolve-ownership.ts +110 -0
- package/src/shared/utils/gesture/velocity.ts +16 -6
- package/src/shared/__tests__/bounds.store.test.ts +0 -394
- package/src/shared/__tests__/derivations.test.ts +0 -156
- package/src/shared/__tests__/determine-dismissal.test.ts +0 -111
- package/src/shared/__tests__/determine-snap-target.test.ts +0 -268
- package/src/shared/__tests__/geometry.test.ts +0 -130
- package/src/shared/__tests__/gesture-activation.test.ts +0 -471
- package/src/shared/__tests__/gesture.velocity.test.ts +0 -131
- package/src/shared/__tests__/history.store.test.ts +0 -550
- package/src/shared/__tests__/sync-routes-with-removed.test.ts +0 -137
- package/src/shared/__tests__/validate-snap-points.test.ts +0 -125
|
@@ -3,71 +3,115 @@ import { useCallback, useMemo, useRef } from "react";
|
|
|
3
3
|
import { Gesture, type GestureType } from "react-native-gesture-handler";
|
|
4
4
|
import type { SharedValue } from "react-native-reanimated";
|
|
5
5
|
import type {
|
|
6
|
+
DirectionClaimMap,
|
|
6
7
|
GestureContextType,
|
|
7
8
|
ScrollConfig,
|
|
8
9
|
} from "../../providers/gestures.provider";
|
|
9
10
|
import { useKeys } from "../../providers/screen/keys.provider";
|
|
10
11
|
import { GestureStore, type GestureStoreMap } from "../../stores/gesture.store";
|
|
12
|
+
import type { ClaimedDirections, Direction } from "../../types/ownership.types";
|
|
13
|
+
import { claimsAnyDirection } from "../../utils/gesture/compute-claimed-directions";
|
|
14
|
+
import { resolveOwnership } from "../../utils/gesture/resolve-ownership";
|
|
11
15
|
import { useScreenGestureHandlers } from "./use-screen-gesture-handlers";
|
|
12
16
|
|
|
17
|
+
const DIRECTIONS: Direction[] = [
|
|
18
|
+
"vertical",
|
|
19
|
+
"vertical-inverted",
|
|
20
|
+
"horizontal",
|
|
21
|
+
"horizontal-inverted",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Finds ancestor pan gestures that we shadow (claim the same direction).
|
|
26
|
+
* Used to block ancestors when child claims priority.
|
|
27
|
+
*/
|
|
28
|
+
function findShadowedAncestorPanGestures(
|
|
29
|
+
selfClaims: ClaimedDirections,
|
|
30
|
+
ancestorContext: GestureContextType | null | undefined,
|
|
31
|
+
isIsolated: boolean,
|
|
32
|
+
): GestureType[] {
|
|
33
|
+
const shadowedGestures: GestureType[] = [];
|
|
34
|
+
let ancestor = ancestorContext;
|
|
35
|
+
|
|
36
|
+
while (ancestor) {
|
|
37
|
+
if (ancestor.isIsolated !== isIsolated) break;
|
|
38
|
+
|
|
39
|
+
const shadowsAncestor = DIRECTIONS.some(
|
|
40
|
+
(dir) => selfClaims[dir] && ancestor?.claimedDirections?.[dir],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (shadowsAncestor && ancestor.panGesture) {
|
|
44
|
+
shadowedGestures.push(ancestor.panGesture);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ancestor = ancestor.ancestorContext;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return shadowedGestures;
|
|
51
|
+
}
|
|
52
|
+
|
|
13
53
|
interface BuildGesturesHookProps {
|
|
14
54
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
15
55
|
ancestorContext?: GestureContextType | null;
|
|
56
|
+
claimedDirections: ClaimedDirections;
|
|
57
|
+
childDirectionClaims: SharedValue<DirectionClaimMap>;
|
|
58
|
+
isIsolated: boolean;
|
|
16
59
|
}
|
|
17
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Builds the Pan gesture for screen dismissal.
|
|
63
|
+
*
|
|
64
|
+
* Handles shadowing: when child claims same direction as ancestor,
|
|
65
|
+
* child's pan blocks ancestor's pan via `blocksExternalGesture()`.
|
|
66
|
+
*
|
|
67
|
+
* ScrollView coordination is handled separately by useScrollRegistry,
|
|
68
|
+
* which creates its own Native gesture per ScrollView.
|
|
69
|
+
*/
|
|
18
70
|
export const useBuildGestures = ({
|
|
19
71
|
scrollConfig,
|
|
20
72
|
ancestorContext,
|
|
73
|
+
claimedDirections,
|
|
74
|
+
childDirectionClaims,
|
|
75
|
+
isIsolated,
|
|
21
76
|
}: BuildGesturesHookProps): {
|
|
22
77
|
panGesture: GestureType;
|
|
23
78
|
panGestureRef: React.MutableRefObject<GestureType | undefined>;
|
|
24
|
-
nativeGesture: GestureType;
|
|
25
79
|
gestureAnimationValues: GestureStoreMap;
|
|
26
80
|
} => {
|
|
27
81
|
const { current } = useKeys();
|
|
28
|
-
|
|
29
82
|
const navState = current.navigation.getState();
|
|
30
83
|
|
|
31
84
|
const isFirstScreen = useMemo(() => {
|
|
32
85
|
return navState.routes.findIndex((r) => r.key === current.route.key) === 0;
|
|
33
86
|
}, [navState.routes, current.route.key]);
|
|
34
87
|
|
|
35
|
-
// Ref for external gesture coordination (e.g., swipeable lists)
|
|
36
88
|
const panGestureRef = useRef<GestureType | undefined>(undefined);
|
|
37
|
-
|
|
38
89
|
const gestureAnimationValues = GestureStore.getRouteGestures(
|
|
39
90
|
current.route.key,
|
|
40
91
|
);
|
|
41
92
|
|
|
42
93
|
const { snapPoints } = current.options;
|
|
43
|
-
|
|
44
|
-
// Dismiss gesture is controlled by gestureEnabled (disabled for first screen)
|
|
45
94
|
const canDismiss = Boolean(
|
|
46
95
|
isFirstScreen ? false : current.options.gestureEnabled,
|
|
47
96
|
);
|
|
48
|
-
|
|
49
|
-
// Snap navigation works independently - enabled when snap points exist
|
|
50
|
-
// This matches iOS native sheet behavior where gestureEnabled: false
|
|
51
|
-
// disables dismiss but you can still drag between detents
|
|
52
97
|
const hasSnapPoints = Array.isArray(snapPoints) && snapPoints.length > 0;
|
|
53
98
|
const gestureEnabled = canDismiss || hasSnapPoints;
|
|
54
99
|
|
|
100
|
+
const ownershipStatus = useMemo(
|
|
101
|
+
() => resolveOwnership(claimedDirections, ancestorContext ?? null),
|
|
102
|
+
[claimedDirections, ancestorContext],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const selfClaimsAny = claimsAnyDirection(claimedDirections);
|
|
106
|
+
|
|
55
107
|
const handleDismiss = useCallback(() => {
|
|
56
|
-
|
|
57
|
-
// avoid racing with the ancestor
|
|
58
|
-
if (ancestorContext?.gestureAnimationValues.isDismissing?.value) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
108
|
+
if (ancestorContext?.gestureAnimationValues.isDismissing?.value) return;
|
|
61
109
|
|
|
62
110
|
const state = current.navigation.getState();
|
|
63
|
-
|
|
64
111
|
const routeStillPresent = state.routes.some(
|
|
65
112
|
(route) => route.key === current.route.key,
|
|
66
113
|
);
|
|
67
|
-
|
|
68
|
-
if (!routeStillPresent) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
114
|
+
if (!routeStillPresent) return;
|
|
71
115
|
|
|
72
116
|
current.navigation.dispatch({
|
|
73
117
|
...StackActions.pop(),
|
|
@@ -81,8 +125,12 @@ export const useBuildGestures = ({
|
|
|
81
125
|
scrollConfig,
|
|
82
126
|
canDismiss,
|
|
83
127
|
handleDismiss,
|
|
128
|
+
ownershipStatus,
|
|
84
129
|
ancestorIsDismissing:
|
|
85
130
|
ancestorContext?.gestureAnimationValues.isDismissing,
|
|
131
|
+
claimedDirections,
|
|
132
|
+
ancestorContext,
|
|
133
|
+
childDirectionClaims,
|
|
86
134
|
});
|
|
87
135
|
|
|
88
136
|
return useMemo(() => {
|
|
@@ -96,40 +144,27 @@ export const useBuildGestures = ({
|
|
|
96
144
|
.onUpdate(onUpdate)
|
|
97
145
|
.onEnd(onEnd);
|
|
98
146
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Find nearest ancestor with gestureEnabled=true (attached pan)
|
|
109
|
-
let activePanAncestor = ancestorContext;
|
|
110
|
-
while (activePanAncestor && !activePanAncestor.gestureEnabled) {
|
|
111
|
-
activePanAncestor = activePanAncestor.ancestorContext;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (activePanAncestor?.panGesture) {
|
|
115
|
-
// Found an ancestor with enabled pan - wait for it
|
|
116
|
-
nativeGesture = Gesture.Native().requireExternalGestureToFail(
|
|
117
|
-
activePanAncestor.panGesture,
|
|
118
|
-
);
|
|
119
|
-
} else {
|
|
120
|
-
// No ancestor with enabled pan - plain native
|
|
121
|
-
nativeGesture = Gesture.Native();
|
|
147
|
+
// Block shadowed ancestor pan gestures when we claim same directions
|
|
148
|
+
if (selfClaimsAny) {
|
|
149
|
+
const shadowedAncestorGestures = findShadowedAncestorPanGestures(
|
|
150
|
+
claimedDirections,
|
|
151
|
+
ancestorContext,
|
|
152
|
+
isIsolated,
|
|
153
|
+
);
|
|
154
|
+
for (const ancestorPan of shadowedAncestorGestures) {
|
|
155
|
+
panGesture.blocksExternalGesture(ancestorPan);
|
|
122
156
|
}
|
|
123
157
|
}
|
|
124
158
|
|
|
125
159
|
return {
|
|
126
160
|
panGesture,
|
|
127
161
|
panGestureRef,
|
|
128
|
-
nativeGesture,
|
|
129
162
|
gestureAnimationValues,
|
|
130
163
|
};
|
|
131
164
|
}, [
|
|
132
165
|
gestureEnabled,
|
|
166
|
+
selfClaimsAny,
|
|
167
|
+
claimedDirections,
|
|
133
168
|
onTouchesDown,
|
|
134
169
|
onTouchesMove,
|
|
135
170
|
onStart,
|
|
@@ -137,5 +172,6 @@ export const useBuildGestures = ({
|
|
|
137
172
|
onEnd,
|
|
138
173
|
gestureAnimationValues,
|
|
139
174
|
ancestorContext,
|
|
175
|
+
isIsolated,
|
|
140
176
|
]);
|
|
141
177
|
};
|
|
@@ -16,17 +16,27 @@ import {
|
|
|
16
16
|
EPSILON,
|
|
17
17
|
FALSE,
|
|
18
18
|
GESTURE_VELOCITY_IMPACT,
|
|
19
|
+
SNAP_VELOCITY_IMPACT,
|
|
19
20
|
TRUE,
|
|
20
21
|
} from "../../constants";
|
|
21
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
DirectionClaimMap,
|
|
24
|
+
GestureContextType,
|
|
25
|
+
ScrollConfig,
|
|
26
|
+
} from "../../providers/gestures.provider";
|
|
22
27
|
import { useKeys } from "../../providers/screen/keys.provider";
|
|
23
28
|
import { AnimationStore } from "../../stores/animation.store";
|
|
24
29
|
import { GestureStore } from "../../stores/gesture.store";
|
|
25
30
|
import { GestureOffsetState } from "../../types/gesture.types";
|
|
31
|
+
import type {
|
|
32
|
+
ClaimedDirections,
|
|
33
|
+
Direction,
|
|
34
|
+
DirectionOwnership,
|
|
35
|
+
} from "../../types/ownership.types";
|
|
26
36
|
import { animateToProgress } from "../../utils/animation/animate-to-progress";
|
|
27
37
|
import {
|
|
28
38
|
applyOffsetRules,
|
|
29
|
-
|
|
39
|
+
checkScrollBoundary,
|
|
30
40
|
} from "../../utils/gesture/check-gesture-activation";
|
|
31
41
|
import { determineDismissal } from "../../utils/gesture/determine-dismissal";
|
|
32
42
|
import { determineSnapTarget } from "../../utils/gesture/determine-snap-target";
|
|
@@ -42,13 +52,66 @@ interface UseScreenGestureHandlersProps {
|
|
|
42
52
|
ancestorIsDismissing?: SharedValue<number> | null;
|
|
43
53
|
canDismiss: boolean;
|
|
44
54
|
handleDismiss: () => void;
|
|
55
|
+
ownershipStatus: DirectionOwnership;
|
|
56
|
+
claimedDirections: ClaimedDirections;
|
|
57
|
+
ancestorContext: GestureContextType | null | undefined;
|
|
58
|
+
childDirectionClaims: SharedValue<DirectionClaimMap>;
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Gesture Handlers for Screen Dismissal and Snap Navigation
|
|
63
|
+
*
|
|
64
|
+
* ## Mental Model
|
|
65
|
+
*
|
|
66
|
+
* This hook implements the touch handling logic for the gesture ownership system.
|
|
67
|
+
* Each screen has a pan gesture handler that runs through this decision flow:
|
|
68
|
+
*
|
|
69
|
+
* ```
|
|
70
|
+
* onTouchesMove (for each touch move event):
|
|
71
|
+
* 1. ANCESTOR CHECK: If ancestor is dismissing → fail (avoid racing)
|
|
72
|
+
* 2. DIRECTION DETECTION: Determine swipe direction from touch delta
|
|
73
|
+
* 3. OWNERSHIP CHECK: Do we own this direction? (ownershipStatus)
|
|
74
|
+
* - "self" → continue
|
|
75
|
+
* - "ancestor" or null → fail (let it bubble up)
|
|
76
|
+
* 4. CHILD CLAIM CHECK: Has a child pre-registered a claim for this direction?
|
|
77
|
+
* - Yes → fail immediately (child shadows us, no delay)
|
|
78
|
+
* - No → continue
|
|
79
|
+
* 5. OFFSET THRESHOLD: Wait for sufficient touch movement
|
|
80
|
+
* 6. SCROLLVIEW CHECK: If touch is on ScrollView, is it at boundary?
|
|
81
|
+
* 7. EXPAND CHECK (snap sheets): If expanding via ScrollView, is expandViaScrollView enabled?
|
|
82
|
+
* 8. ACTIVATE!
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* ## Key Concepts
|
|
86
|
+
*
|
|
87
|
+
* **Ownership**: Pre-computed at render time. "self" means this screen handles
|
|
88
|
+
* the direction, "ancestor" means bubble up, null means no handler exists.
|
|
89
|
+
*
|
|
90
|
+
* **Child Claims**: Registered at mount time via useEffect in gestures.provider.tsx.
|
|
91
|
+
* When a child shadows our direction, it pre-registers a claim so we know to defer.
|
|
92
|
+
* IMPORTANT: This check happens BEFORE offset threshold to ensure the parent fails
|
|
93
|
+
* immediately when shadowed, avoiding any perceptible delay.
|
|
94
|
+
* ALSO: Claims from dismissing children are ignored, allowing the parent to handle
|
|
95
|
+
* new gestures while the child is animating out.
|
|
96
|
+
*
|
|
97
|
+
* **ScrollView Boundaries**: Per spec, a ScrollView must be at its boundary before
|
|
98
|
+
* yielding to gestures. The boundary depends on sheet type:
|
|
99
|
+
* - Bottom sheet (vertical): scrollY = 0 (top)
|
|
100
|
+
* - Top sheet (vertical-inverted): scrollY >= maxY (bottom)
|
|
101
|
+
*
|
|
102
|
+
* **Snap Points**: Sheets with snapPoints claim BOTH directions on their axis
|
|
103
|
+
* (e.g., vertical sheet claims vertical AND vertical-inverted). This allows
|
|
104
|
+
* expand (drag up) and collapse/dismiss (drag down) gestures.
|
|
105
|
+
*/
|
|
47
106
|
export const useScreenGestureHandlers = ({
|
|
48
107
|
scrollConfig,
|
|
49
108
|
ancestorIsDismissing,
|
|
50
109
|
canDismiss,
|
|
51
110
|
handleDismiss,
|
|
111
|
+
ownershipStatus,
|
|
112
|
+
claimedDirections,
|
|
113
|
+
ancestorContext,
|
|
114
|
+
childDirectionClaims,
|
|
52
115
|
}: UseScreenGestureHandlersProps) => {
|
|
53
116
|
const dimensions = useWindowDimensions();
|
|
54
117
|
const { current } = useKeys();
|
|
@@ -62,10 +125,12 @@ export const useScreenGestureHandlers = ({
|
|
|
62
125
|
gestureDirection = DEFAULT_GESTURE_DIRECTION,
|
|
63
126
|
gestureDrivesProgress = DEFAULT_GESTURE_DRIVES_PROGRESS,
|
|
64
127
|
gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
|
|
128
|
+
snapVelocityImpact = SNAP_VELOCITY_IMPACT,
|
|
65
129
|
gestureActivationArea = DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
66
130
|
gestureResponseDistance,
|
|
67
131
|
transitionSpec,
|
|
68
132
|
snapPoints: rawSnapPoints,
|
|
133
|
+
expandViaScrollView = true,
|
|
69
134
|
} = current.options;
|
|
70
135
|
|
|
71
136
|
const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
|
|
@@ -147,11 +212,13 @@ export const useScreenGestureHandlers = ({
|
|
|
147
212
|
gestureOffsetState.value = GestureOffsetState.PENDING;
|
|
148
213
|
});
|
|
149
214
|
|
|
215
|
+
const routeKey = current.route.key;
|
|
216
|
+
|
|
150
217
|
const onTouchesMove = useStableCallbackValue(
|
|
151
218
|
(e: GestureTouchEvent, manager: GestureStateManagerType) => {
|
|
152
219
|
"worklet";
|
|
153
220
|
|
|
154
|
-
//
|
|
221
|
+
// Step 1: Ancestor dismissing check
|
|
155
222
|
if (ancestorIsDismissing?.value) {
|
|
156
223
|
gestureOffsetState.value = GestureOffsetState.FAILED;
|
|
157
224
|
manager.fail();
|
|
@@ -177,79 +244,97 @@ export const useScreenGestureHandlers = ({
|
|
|
177
244
|
return;
|
|
178
245
|
}
|
|
179
246
|
|
|
180
|
-
// Keep pending until thresholds are met; no eager activation.
|
|
181
247
|
if (gestureAnimationValues.isDragging?.value) {
|
|
182
248
|
manager.activate();
|
|
183
249
|
return;
|
|
184
250
|
}
|
|
185
251
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (!isTouchingScrollView) {
|
|
193
|
-
// Early return if gesture hasn't met activation criteria
|
|
194
|
-
const canActivate =
|
|
195
|
-
recognizedDirection &&
|
|
196
|
-
gestureOffsetState.value === GestureOffsetState.PASSED &&
|
|
197
|
-
!gestureAnimationValues.isDismissing?.value;
|
|
198
|
-
|
|
199
|
-
if (!canActivate) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (isSwipingDown) {
|
|
204
|
-
gestureAnimationValues.direction.value = "vertical";
|
|
205
|
-
} else if (isSwipingUp) {
|
|
206
|
-
gestureAnimationValues.direction.value = "vertical-inverted";
|
|
207
|
-
} else if (isSwipingRight) {
|
|
208
|
-
gestureAnimationValues.direction.value = "horizontal";
|
|
209
|
-
} else if (isSwipingLeft) {
|
|
210
|
-
gestureAnimationValues.direction.value = "horizontal-inverted";
|
|
211
|
-
}
|
|
252
|
+
// Step 2: Direction detection
|
|
253
|
+
let swipeDirection: Direction | null = null;
|
|
254
|
+
if (isSwipingDown) swipeDirection = "vertical";
|
|
255
|
+
else if (isSwipingUp) swipeDirection = "vertical-inverted";
|
|
256
|
+
else if (isSwipingRight) swipeDirection = "horizontal";
|
|
257
|
+
else if (isSwipingLeft) swipeDirection = "horizontal-inverted";
|
|
212
258
|
|
|
213
|
-
|
|
259
|
+
if (!swipeDirection) {
|
|
214
260
|
return;
|
|
215
261
|
}
|
|
216
262
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const canExpandMore =
|
|
221
|
-
hasSnapPoints &&
|
|
222
|
-
animations.progress.value < maxSnapPoint - EPSILON &&
|
|
223
|
-
animations.targetProgress.value < maxSnapPoint - EPSILON;
|
|
224
|
-
|
|
225
|
-
const { shouldActivate, direction: activatedDirection } =
|
|
226
|
-
checkScrollAwareActivation({
|
|
227
|
-
swipeInfo: {
|
|
228
|
-
isSwipingDown,
|
|
229
|
-
isSwipingUp,
|
|
230
|
-
isSwipingRight,
|
|
231
|
-
isSwipingLeft,
|
|
232
|
-
},
|
|
233
|
-
directions,
|
|
234
|
-
scrollConfig: scrollCfg,
|
|
235
|
-
hasSnapPoints,
|
|
236
|
-
canExpandMore,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
if (recognizedDirection && !shouldActivate) {
|
|
263
|
+
// Step 3: Ownership check - fail if we don't own this direction
|
|
264
|
+
const ownership = ownershipStatus[swipeDirection];
|
|
265
|
+
if (ownership !== "self") {
|
|
240
266
|
manager.fail();
|
|
241
267
|
return;
|
|
242
268
|
}
|
|
243
269
|
|
|
270
|
+
// Step 4: Child claim check - fail EARLY if a child shadows this direction
|
|
271
|
+
// This MUST happen before offset threshold to avoid delay when shadowing
|
|
272
|
+
// ALSO: Ignore claims from children that are currently dismissing
|
|
273
|
+
const childClaim = childDirectionClaims.value[swipeDirection];
|
|
244
274
|
if (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
!
|
|
275
|
+
childClaim &&
|
|
276
|
+
childClaim.routeKey !== routeKey &&
|
|
277
|
+
!childClaim.isDismissing.value
|
|
248
278
|
) {
|
|
249
|
-
|
|
250
|
-
|
|
279
|
+
manager.fail();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (gestureOffsetState.value !== GestureOffsetState.PASSED) {
|
|
251
284
|
return;
|
|
252
285
|
}
|
|
286
|
+
|
|
287
|
+
// Snap sheets can interrupt their own animation; non-snap cannot
|
|
288
|
+
if (!hasSnapPoints && gestureAnimationValues.isDismissing?.value) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Step 6: ScrollView boundary check
|
|
293
|
+
const scrollCfg = scrollConfig.value;
|
|
294
|
+
const isTouchingScrollView = scrollCfg?.isTouched ?? false;
|
|
295
|
+
|
|
296
|
+
if (isTouchingScrollView) {
|
|
297
|
+
const atBoundary = checkScrollBoundary(
|
|
298
|
+
scrollCfg,
|
|
299
|
+
swipeDirection,
|
|
300
|
+
hasSnapPoints ? directions.snapAxisInverted : undefined,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
if (!atBoundary) {
|
|
304
|
+
manager.fail();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Step 7: Expand check for snap sheets
|
|
309
|
+
if (hasSnapPoints) {
|
|
310
|
+
const isExpandGesture =
|
|
311
|
+
(directions.snapAxisInverted && swipeDirection === "vertical") ||
|
|
312
|
+
(!directions.snapAxisInverted &&
|
|
313
|
+
swipeDirection === "vertical-inverted") ||
|
|
314
|
+
(directions.snapAxisInverted && swipeDirection === "horizontal") ||
|
|
315
|
+
(!directions.snapAxisInverted &&
|
|
316
|
+
swipeDirection === "horizontal-inverted");
|
|
317
|
+
|
|
318
|
+
if (isExpandGesture) {
|
|
319
|
+
if (!expandViaScrollView) {
|
|
320
|
+
manager.fail();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const canExpandMore =
|
|
325
|
+
animations.progress.value < maxSnapPoint - EPSILON &&
|
|
326
|
+
animations.targetProgress.value < maxSnapPoint - EPSILON;
|
|
327
|
+
|
|
328
|
+
if (!canExpandMore) {
|
|
329
|
+
manager.fail();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
gestureAnimationValues.direction.value = swipeDirection;
|
|
337
|
+
manager.activate();
|
|
253
338
|
},
|
|
254
339
|
);
|
|
255
340
|
|
|
@@ -284,15 +369,11 @@ export const useScreenGestureHandlers = ({
|
|
|
284
369
|
const translation = isHorizontal ? translationX : translationY;
|
|
285
370
|
const dimension = isHorizontal ? width : height;
|
|
286
371
|
|
|
287
|
-
// Map translation to progress
|
|
288
|
-
// - Positive translation (down/right) = decrease progress (dismiss)
|
|
289
|
-
// - Negative translation (up/left) = increase progress (expand)
|
|
290
|
-
// Inverted directions flip this behavior
|
|
372
|
+
// Map translation to progress: positive = dismiss, negative = expand
|
|
291
373
|
const baseSign = -1;
|
|
292
374
|
const sign = directions.snapAxisInverted ? -baseSign : baseSign;
|
|
293
375
|
const progressDelta = (sign * translation) / dimension;
|
|
294
376
|
|
|
295
|
-
// Use pre-computed bounds (minSnapPoint already accounts for canDismiss)
|
|
296
377
|
animations.progress.value = Math.max(
|
|
297
378
|
minSnapPoint,
|
|
298
379
|
Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
|
|
@@ -343,9 +424,7 @@ export const useScreenGestureHandlers = ({
|
|
|
343
424
|
? dimensions.width
|
|
344
425
|
: dimensions.height;
|
|
345
426
|
|
|
346
|
-
//
|
|
347
|
-
// Positive velocity (down/right) = dismiss for non-inverted
|
|
348
|
-
// Inverted directions need velocity flipped
|
|
427
|
+
// Normalize velocity: positive = toward dismiss
|
|
349
428
|
const snapVelocity = directions.snapAxisInverted
|
|
350
429
|
? -axisVelocity
|
|
351
430
|
: axisVelocity;
|
|
@@ -355,6 +434,7 @@ export const useScreenGestureHandlers = ({
|
|
|
355
434
|
snapPoints,
|
|
356
435
|
velocity: snapVelocity,
|
|
357
436
|
dimension: axisDimension,
|
|
437
|
+
velocityFactor: snapVelocityImpact,
|
|
358
438
|
canDismiss: canDismiss,
|
|
359
439
|
});
|
|
360
440
|
|
|
@@ -381,9 +461,6 @@ export const useScreenGestureHandlers = ({
|
|
|
381
461
|
dimensions,
|
|
382
462
|
});
|
|
383
463
|
|
|
384
|
-
// For snap transitions, velocity should match gesture direction
|
|
385
|
-
// Positive gesture velocity (down/right) = collapsing (negative progress velocity)
|
|
386
|
-
// Inverted directions flip this
|
|
387
464
|
const velocitySign = directions.snapAxisInverted ? 1 : -1;
|
|
388
465
|
const initialVelocity =
|
|
389
466
|
velocitySign * velocity.normalize(axisVelocity, axisDimension);
|
|
@@ -404,7 +481,6 @@ export const useScreenGestureHandlers = ({
|
|
|
404
481
|
});
|
|
405
482
|
|
|
406
483
|
const shouldDismiss = result.shouldDismiss;
|
|
407
|
-
// Without snap points, always animate to fully visible (1) when not dismissing
|
|
408
484
|
const targetProgress = shouldDismiss ? 0 : 1;
|
|
409
485
|
|
|
410
486
|
resetGestureValues({
|