react-native-screen-transitions 3.3.0-beta.2 → 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 +17 -10
- 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 +68 -9
- 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 +113 -25
- 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 +16 -10
- 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 +68 -10
- 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 +113 -25
- 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 +28 -3
- 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 +17 -11
- package/src/shared/components/create-transition-aware-component.tsx +28 -25
- package/src/shared/components/screen-container.tsx +79 -12
- 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 +168 -25
- 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
|
@@ -1,41 +1,107 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Connects ScrollViews to the gesture ownership system.
|
|
3
|
+
* Finds the gesture owner for the scroll axis and coordinates with their panGesture.
|
|
4
|
+
*/
|
|
2
5
|
|
|
3
6
|
import { useMemo } from "react";
|
|
4
|
-
import type {
|
|
7
|
+
import type { LayoutChangeEvent } from "react-native";
|
|
8
|
+
import { Gesture, type GestureType } from "react-native-gesture-handler";
|
|
9
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
5
10
|
import { useAnimatedScrollHandler } from "react-native-reanimated";
|
|
6
11
|
import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescript/hook/commonTypes";
|
|
7
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type GestureContextType,
|
|
14
|
+
type ScrollConfig,
|
|
15
|
+
useGestureContext,
|
|
16
|
+
} from "../../providers/gestures.provider";
|
|
8
17
|
import useStableCallback from "../use-stable-callback";
|
|
9
18
|
|
|
19
|
+
/** Walks up context tree to find the screen that owns this scroll axis. */
|
|
20
|
+
function findGestureOwnerForAxis(
|
|
21
|
+
context: GestureContextType | null | undefined,
|
|
22
|
+
axis: "vertical" | "horizontal",
|
|
23
|
+
): {
|
|
24
|
+
scrollConfig: SharedValue<ScrollConfig | null> | null;
|
|
25
|
+
panGesture: GestureType | null;
|
|
26
|
+
} {
|
|
27
|
+
let current = context;
|
|
28
|
+
const startIsolated = context?.isIsolated;
|
|
29
|
+
|
|
30
|
+
while (current) {
|
|
31
|
+
if (startIsolated !== undefined && current.isIsolated !== startIsolated) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ownsAxis =
|
|
36
|
+
axis === "vertical"
|
|
37
|
+
? current.claimedDirections?.vertical ||
|
|
38
|
+
current.claimedDirections?.["vertical-inverted"]
|
|
39
|
+
: current.claimedDirections?.horizontal ||
|
|
40
|
+
current.claimedDirections?.["horizontal-inverted"];
|
|
41
|
+
|
|
42
|
+
if (ownsAxis) {
|
|
43
|
+
return {
|
|
44
|
+
scrollConfig: current.scrollConfig,
|
|
45
|
+
panGesture: current.panGesture,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
current = current.ancestorContext;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { scrollConfig: null, panGesture: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
10
55
|
interface ScrollProgressHookProps {
|
|
11
56
|
onScroll?: (event: ReanimatedScrollEvent) => void;
|
|
12
57
|
onContentSizeChange?: (width: number, height: number) => void;
|
|
13
58
|
onLayout?: (event: LayoutChangeEvent) => void;
|
|
14
|
-
|
|
15
|
-
onTouchEnd?: (event: GestureResponderEvent) => void;
|
|
59
|
+
direction?: "vertical" | "horizontal";
|
|
16
60
|
}
|
|
17
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Returns scroll handlers and a native gesture for ScrollView coordination.
|
|
64
|
+
* Automatically finds the gesture owner for the scroll axis.
|
|
65
|
+
*/
|
|
18
66
|
export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
19
|
-
const context = useGestureContext()
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
67
|
+
const context = useGestureContext();
|
|
68
|
+
const scrollDirection = props.direction ?? "vertical";
|
|
69
|
+
|
|
70
|
+
const { scrollConfig, panGesture } = findGestureOwnerForAxis(
|
|
71
|
+
context,
|
|
72
|
+
scrollDirection,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const nativeGesture = useMemo(() => {
|
|
76
|
+
if (!panGesture || !scrollConfig) return null;
|
|
77
|
+
|
|
78
|
+
const setIsTouched = () => {
|
|
79
|
+
"worklet";
|
|
80
|
+
if (scrollConfig.value) {
|
|
81
|
+
scrollConfig.value = { ...scrollConfig.value, isTouched: true };
|
|
28
82
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const clearIsTouched = () => {
|
|
86
|
+
"worklet";
|
|
87
|
+
if (scrollConfig.value) {
|
|
88
|
+
scrollConfig.value = { ...scrollConfig.value, isTouched: false };
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return Gesture.Native()
|
|
93
|
+
.onTouchesDown(setIsTouched)
|
|
94
|
+
.onTouchesUp(clearIsTouched)
|
|
95
|
+
.onTouchesCancelled(clearIsTouched)
|
|
96
|
+
.requireExternalGestureToFail(panGesture);
|
|
97
|
+
}, [panGesture, scrollConfig]);
|
|
33
98
|
|
|
34
99
|
const scrollHandler = useAnimatedScrollHandler({
|
|
35
100
|
onScroll: (event) => {
|
|
36
101
|
props.onScroll?.(event);
|
|
102
|
+
if (!scrollConfig) return;
|
|
37
103
|
|
|
38
|
-
const
|
|
104
|
+
const update = (v: any) => {
|
|
39
105
|
"worklet";
|
|
40
106
|
if (v === null) {
|
|
41
107
|
return {
|
|
@@ -52,21 +118,16 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
52
118
|
v.y = event.contentOffset.y;
|
|
53
119
|
return v;
|
|
54
120
|
};
|
|
55
|
-
|
|
56
|
-
scrollConfig.modify(updateScrollPosition);
|
|
57
|
-
|
|
58
|
-
// Sync to ALL ancestors, not just immediate parent
|
|
59
|
-
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
60
|
-
ancestorConfig.modify(updateScrollPosition);
|
|
61
|
-
}
|
|
121
|
+
scrollConfig.modify(update);
|
|
62
122
|
},
|
|
63
123
|
});
|
|
64
124
|
|
|
65
125
|
const onContentSizeChange = useStableCallback(
|
|
66
126
|
(width: number, height: number) => {
|
|
67
127
|
props.onContentSizeChange?.(width, height);
|
|
128
|
+
if (!scrollConfig) return;
|
|
68
129
|
|
|
69
|
-
const
|
|
130
|
+
const update = (v: any) => {
|
|
70
131
|
"worklet";
|
|
71
132
|
if (v === null) {
|
|
72
133
|
return {
|
|
@@ -83,20 +144,17 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
83
144
|
v.contentHeight = height;
|
|
84
145
|
return v;
|
|
85
146
|
};
|
|
86
|
-
|
|
87
|
-
scrollConfig.modify(updateContentSize);
|
|
88
|
-
|
|
89
|
-
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
90
|
-
ancestorConfig.modify(updateContentSize);
|
|
91
|
-
}
|
|
147
|
+
scrollConfig.modify(update);
|
|
92
148
|
},
|
|
93
149
|
);
|
|
94
150
|
|
|
95
151
|
const onLayout = useStableCallback((event: LayoutChangeEvent) => {
|
|
96
152
|
props.onLayout?.(event);
|
|
153
|
+
if (!scrollConfig) return;
|
|
154
|
+
|
|
97
155
|
const { width, height } = event.nativeEvent.layout;
|
|
98
156
|
|
|
99
|
-
const
|
|
157
|
+
const update = (v: any) => {
|
|
100
158
|
"worklet";
|
|
101
159
|
if (v === null) {
|
|
102
160
|
return {
|
|
@@ -113,63 +171,13 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
113
171
|
v.layoutWidth = width;
|
|
114
172
|
return v;
|
|
115
173
|
};
|
|
116
|
-
|
|
117
|
-
scrollConfig.modify(updateLayout);
|
|
118
|
-
|
|
119
|
-
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
120
|
-
ancestorConfig.modify(updateLayout);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const onTouchStart = useStableCallback((event: GestureResponderEvent) => {
|
|
125
|
-
props.onTouchStart?.(event);
|
|
126
|
-
|
|
127
|
-
const setTouched = (v: any) => {
|
|
128
|
-
"worklet";
|
|
129
|
-
if (v === null) {
|
|
130
|
-
return {
|
|
131
|
-
x: 0,
|
|
132
|
-
y: 0,
|
|
133
|
-
contentHeight: 0,
|
|
134
|
-
contentWidth: 0,
|
|
135
|
-
layoutHeight: 0,
|
|
136
|
-
layoutWidth: 0,
|
|
137
|
-
isTouched: true,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
v.isTouched = true;
|
|
141
|
-
return v;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
scrollConfig.modify(setTouched);
|
|
145
|
-
|
|
146
|
-
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
147
|
-
ancestorConfig.modify(setTouched);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const onTouchEnd = useStableCallback((event: GestureResponderEvent) => {
|
|
152
|
-
props.onTouchEnd?.(event);
|
|
153
|
-
|
|
154
|
-
const clearTouched = (v: any) => {
|
|
155
|
-
"worklet";
|
|
156
|
-
if (v === null) return v;
|
|
157
|
-
v.isTouched = false;
|
|
158
|
-
return v;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
scrollConfig.modify(clearTouched);
|
|
162
|
-
|
|
163
|
-
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
164
|
-
ancestorConfig.modify(clearTouched);
|
|
165
|
-
}
|
|
174
|
+
scrollConfig.modify(update);
|
|
166
175
|
});
|
|
167
176
|
|
|
168
177
|
return {
|
|
169
178
|
scrollHandler,
|
|
170
179
|
onContentSizeChange,
|
|
171
180
|
onLayout,
|
|
172
|
-
|
|
173
|
-
onTouchEnd,
|
|
181
|
+
nativeGesture,
|
|
174
182
|
};
|
|
175
183
|
};
|
|
@@ -2,7 +2,7 @@ import { useKeys } from "../providers/screen/keys.provider";
|
|
|
2
2
|
import { useStackCoreContext } from "../providers/stack/core.provider";
|
|
3
3
|
import { StackType } from "../types/stack.types";
|
|
4
4
|
|
|
5
|
-
type BackdropBehavior = "block" | "passthrough" | "dismiss";
|
|
5
|
+
type BackdropBehavior = "block" | "passthrough" | "dismiss" | "collapse";
|
|
6
6
|
|
|
7
7
|
interface BackdropPointerEventsResult {
|
|
8
8
|
pointerEvents: "box-none" | undefined;
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Gesture System - Core Provider
|
|
3
|
+
*
|
|
4
|
+
* Each screen gets a GestureContext containing:
|
|
5
|
+
* - panGesture: Pan gesture handler for dismiss/snap
|
|
6
|
+
* - scrollConfig: Scroll state for boundary detection
|
|
7
|
+
* - claimedDirections: Which directions this screen handles
|
|
8
|
+
* - childDirectionClaims: Claims registered by descendant screens
|
|
9
|
+
*
|
|
10
|
+
* ScrollView coordination is handled by useScrollRegistry, which finds the
|
|
11
|
+
* gesture owner for the scroll axis and creates appropriate Native gestures.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useNavigationState } from "@react-navigation/native";
|
|
15
|
+
import { useEffect, useMemo } from "react";
|
|
16
|
+
import type { GestureType } from "react-native-gesture-handler";
|
|
6
17
|
import type { SharedValue } from "react-native-reanimated";
|
|
7
18
|
import { useSharedValue } from "react-native-reanimated";
|
|
8
19
|
import { useBuildGestures } from "../hooks/gestures/use-build-gestures";
|
|
9
|
-
import {
|
|
10
|
-
import type {
|
|
20
|
+
import { GestureStore, type GestureStoreMap } from "../stores/gesture.store";
|
|
21
|
+
import type { ClaimedDirections, Direction } from "../types/ownership.types";
|
|
22
|
+
import { StackType } from "../types/stack.types";
|
|
11
23
|
import createProvider from "../utils/create-provider";
|
|
24
|
+
import { computeClaimedDirections } from "../utils/gesture/compute-claimed-directions";
|
|
12
25
|
import { useKeys } from "./screen/keys.provider";
|
|
26
|
+
import { useStackCoreContext } from "./stack/core.provider";
|
|
13
27
|
|
|
14
28
|
export type ScrollConfig = {
|
|
15
29
|
x: number;
|
|
@@ -21,14 +35,118 @@ export type ScrollConfig = {
|
|
|
21
35
|
isTouched: boolean;
|
|
22
36
|
};
|
|
23
37
|
|
|
38
|
+
export type DirectionClaim = {
|
|
39
|
+
routeKey: string;
|
|
40
|
+
isDismissing: SharedValue<number>;
|
|
41
|
+
} | null;
|
|
42
|
+
|
|
43
|
+
export type DirectionClaimMap = {
|
|
44
|
+
vertical: DirectionClaim;
|
|
45
|
+
"vertical-inverted": DirectionClaim;
|
|
46
|
+
horizontal: DirectionClaim;
|
|
47
|
+
"horizontal-inverted": DirectionClaim;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const NO_CLAIMS: DirectionClaimMap = {
|
|
51
|
+
vertical: null,
|
|
52
|
+
"vertical-inverted": null,
|
|
53
|
+
horizontal: null,
|
|
54
|
+
"horizontal-inverted": null,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const DIRECTIONS: Direction[] = [
|
|
58
|
+
"vertical",
|
|
59
|
+
"vertical-inverted",
|
|
60
|
+
"horizontal",
|
|
61
|
+
"horizontal-inverted",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Registers direction claims on ancestors that this screen shadows.
|
|
66
|
+
* Only registers claims when this screen is the current (topmost) route
|
|
67
|
+
* in its navigator, preventing unfocused screens from blocking gestures.
|
|
68
|
+
*/
|
|
69
|
+
function useRegisterDirectionClaims(
|
|
70
|
+
ancestorContext: GestureContextType | null | undefined,
|
|
71
|
+
claimedDirections: ClaimedDirections,
|
|
72
|
+
routeKey: string,
|
|
73
|
+
isIsolated: boolean,
|
|
74
|
+
isCurrentRoute: boolean,
|
|
75
|
+
) {
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
// Only register claims when this screen is the current route
|
|
78
|
+
if (!isCurrentRoute || !ancestorContext) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const gestureValues = GestureStore.getRouteGestures(routeKey);
|
|
83
|
+
const isDismissing = gestureValues.isDismissing;
|
|
84
|
+
|
|
85
|
+
const claimedAncestors: Array<{
|
|
86
|
+
ancestor: GestureContextType;
|
|
87
|
+
directions: Direction[];
|
|
88
|
+
}> = [];
|
|
89
|
+
|
|
90
|
+
let ancestor: GestureContextType | null = ancestorContext;
|
|
91
|
+
while (ancestor) {
|
|
92
|
+
if (ancestor.isIsolated !== isIsolated) break;
|
|
93
|
+
|
|
94
|
+
const shadowedDirections: Direction[] = [];
|
|
95
|
+
for (const dir of DIRECTIONS) {
|
|
96
|
+
if (claimedDirections[dir] && ancestor.claimedDirections?.[dir]) {
|
|
97
|
+
shadowedDirections.push(dir);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (shadowedDirections.length > 0) {
|
|
102
|
+
claimedAncestors.push({ ancestor, directions: shadowedDirections });
|
|
103
|
+
const newClaims = { ...ancestor.childDirectionClaims.value };
|
|
104
|
+
for (const dir of shadowedDirections) {
|
|
105
|
+
newClaims[dir] = { routeKey, isDismissing };
|
|
106
|
+
}
|
|
107
|
+
ancestor.childDirectionClaims.value = newClaims;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ancestor = ancestor.ancestorContext;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return () => {
|
|
114
|
+
for (const { ancestor, directions } of claimedAncestors) {
|
|
115
|
+
const currentClaims = ancestor.childDirectionClaims.value;
|
|
116
|
+
const newClaims = { ...currentClaims };
|
|
117
|
+
let needsUpdate = false;
|
|
118
|
+
|
|
119
|
+
for (const dir of directions) {
|
|
120
|
+
if (currentClaims[dir]?.routeKey === routeKey) {
|
|
121
|
+
newClaims[dir] = null;
|
|
122
|
+
needsUpdate = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (needsUpdate) {
|
|
127
|
+
ancestor.childDirectionClaims.value = newClaims;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}, [
|
|
132
|
+
ancestorContext,
|
|
133
|
+
claimedDirections,
|
|
134
|
+
routeKey,
|
|
135
|
+
isIsolated,
|
|
136
|
+
isCurrentRoute,
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
|
|
24
140
|
export interface GestureContextType {
|
|
25
141
|
panGesture: GestureType;
|
|
26
142
|
panGestureRef: React.MutableRefObject<GestureType | undefined>;
|
|
27
|
-
nativeGesture: GestureType;
|
|
28
143
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
29
144
|
gestureAnimationValues: GestureStoreMap;
|
|
30
145
|
ancestorContext: GestureContextType | null;
|
|
31
146
|
gestureEnabled: boolean;
|
|
147
|
+
isIsolated: boolean;
|
|
148
|
+
claimedDirections: ClaimedDirections;
|
|
149
|
+
childDirectionClaims: SharedValue<DirectionClaimMap>;
|
|
32
150
|
}
|
|
33
151
|
|
|
34
152
|
interface ScreenGestureProviderProps {
|
|
@@ -43,42 +161,67 @@ export const {
|
|
|
43
161
|
GestureContextType
|
|
44
162
|
>(({ children }) => {
|
|
45
163
|
const { current } = useKeys();
|
|
164
|
+
const { flags } = useStackCoreContext();
|
|
46
165
|
const ancestorContext = useGestureContext();
|
|
47
|
-
const scrollConfig = useSharedValue<ScrollConfig | null>(null);
|
|
48
|
-
const { pointerEvents } = useBackdropPointerEvents();
|
|
49
166
|
|
|
50
167
|
const hasGestures = current.options.gestureEnabled === true;
|
|
168
|
+
const isIsolated = flags.STACK_TYPE === StackType.COMPONENT;
|
|
169
|
+
|
|
170
|
+
const hasSnapPoints =
|
|
171
|
+
Array.isArray(current.options.snapPoints) &&
|
|
172
|
+
current.options.snapPoints.length > 0;
|
|
173
|
+
|
|
174
|
+
const claimedDirections = useMemo(
|
|
175
|
+
() =>
|
|
176
|
+
computeClaimedDirections(
|
|
177
|
+
hasGestures,
|
|
178
|
+
current.options.gestureDirection,
|
|
179
|
+
hasSnapPoints,
|
|
180
|
+
),
|
|
181
|
+
[hasGestures, current.options.gestureDirection, hasSnapPoints],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const routeKey = current.route.key;
|
|
51
185
|
|
|
52
|
-
|
|
186
|
+
// Check if this screen is the current (topmost) route in its navigator
|
|
187
|
+
const isCurrentRoute = useNavigationState(
|
|
188
|
+
(state) => state.routes[state.index]?.key === routeKey,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const scrollConfig = useSharedValue<ScrollConfig | null>(null);
|
|
192
|
+
const childDirectionClaims = useSharedValue<DirectionClaimMap>(NO_CLAIMS);
|
|
193
|
+
|
|
194
|
+
useRegisterDirectionClaims(
|
|
195
|
+
ancestorContext,
|
|
196
|
+
claimedDirections,
|
|
197
|
+
routeKey,
|
|
198
|
+
isIsolated,
|
|
199
|
+
isCurrentRoute,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const { panGesture, panGestureRef, gestureAnimationValues } =
|
|
53
203
|
useBuildGestures({
|
|
54
204
|
scrollConfig,
|
|
55
205
|
ancestorContext,
|
|
206
|
+
claimedDirections,
|
|
207
|
+
childDirectionClaims,
|
|
208
|
+
isIsolated,
|
|
56
209
|
});
|
|
57
210
|
|
|
58
211
|
const value: GestureContextType = {
|
|
59
212
|
panGesture,
|
|
60
213
|
panGestureRef,
|
|
61
214
|
scrollConfig,
|
|
62
|
-
nativeGesture,
|
|
63
215
|
gestureAnimationValues,
|
|
64
216
|
ancestorContext,
|
|
65
217
|
gestureEnabled: hasGestures,
|
|
218
|
+
isIsolated,
|
|
219
|
+
claimedDirections,
|
|
220
|
+
childDirectionClaims,
|
|
66
221
|
};
|
|
67
222
|
|
|
68
223
|
return {
|
|
69
224
|
value,
|
|
70
|
-
children
|
|
71
|
-
<GestureDetector gesture={panGesture}>
|
|
72
|
-
<View style={styles.container} pointerEvents={pointerEvents}>
|
|
73
|
-
{children}
|
|
74
|
-
</View>
|
|
75
|
-
</GestureDetector>
|
|
76
|
-
),
|
|
225
|
+
children,
|
|
77
226
|
};
|
|
78
227
|
});
|
|
79
|
-
|
|
80
|
-
const styles = StyleSheet.create({
|
|
81
|
-
container: {
|
|
82
|
-
flex: 1,
|
|
83
|
-
},
|
|
84
|
-
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gesture Ownership System Types
|
|
3
|
+
*
|
|
4
|
+
* Core principles:
|
|
5
|
+
* 1. Gestures dismiss stacks, not screens
|
|
6
|
+
* 2. Ownership is per-direction (4 independent directions)
|
|
7
|
+
* 3. Shadowing: child claiming same direction blocks parent
|
|
8
|
+
* 4. Inheritance: no local claim walks up tree to find owner
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The four independent gesture directions.
|
|
13
|
+
* Each direction is owned independently.
|
|
14
|
+
*
|
|
15
|
+
* Uses the same format as GestureDirection from gesture.types.ts
|
|
16
|
+
* (excluding 'bidirectional' which expands to all four).
|
|
17
|
+
*/
|
|
18
|
+
export type Direction =
|
|
19
|
+
| "vertical"
|
|
20
|
+
| "vertical-inverted"
|
|
21
|
+
| "horizontal"
|
|
22
|
+
| "horizontal-inverted";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* All possible directions as an array for iteration.
|
|
26
|
+
*/
|
|
27
|
+
export const DIRECTIONS: Direction[] = [
|
|
28
|
+
"vertical",
|
|
29
|
+
"vertical-inverted",
|
|
30
|
+
"horizontal",
|
|
31
|
+
"horizontal-inverted",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Map of which directions a screen claims ownership of.
|
|
36
|
+
* A screen claims a direction when:
|
|
37
|
+
* - gestureEnabled is true AND
|
|
38
|
+
* - gestureDirection includes that direction
|
|
39
|
+
*
|
|
40
|
+
* For snap points, both directions on the axis are claimed automatically.
|
|
41
|
+
*/
|
|
42
|
+
export type ClaimedDirections = Record<Direction, boolean>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Empty claims - used when gestureEnabled is false.
|
|
46
|
+
*/
|
|
47
|
+
export const NO_CLAIMS: ClaimedDirections = {
|
|
48
|
+
vertical: false,
|
|
49
|
+
"vertical-inverted": false,
|
|
50
|
+
horizontal: false,
|
|
51
|
+
"horizontal-inverted": false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ownership status for a direction relative to the current screen.
|
|
56
|
+
*
|
|
57
|
+
* - 'self': Current screen owns this direction (should activate)
|
|
58
|
+
* - 'ancestor': An ancestor owns this direction (should fail to bubble up)
|
|
59
|
+
* - 'none': No one owns this direction (should fail, no gesture response)
|
|
60
|
+
*/
|
|
61
|
+
export type OwnershipStatus = "self" | "ancestor" | "none";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Map of ownership status for all four directions.
|
|
65
|
+
* Pre-computed during render for worklet access.
|
|
66
|
+
*/
|
|
67
|
+
export type DirectionOwnership = Record<Direction, OwnershipStatus>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Empty ownership - used when no gestures are configured anywhere.
|
|
71
|
+
*/
|
|
72
|
+
export const NO_OWNERSHIP: DirectionOwnership = {
|
|
73
|
+
vertical: "none",
|
|
74
|
+
"vertical-inverted": "none",
|
|
75
|
+
horizontal: "none",
|
|
76
|
+
"horizontal-inverted": "none",
|
|
77
|
+
};
|
|
@@ -77,9 +77,17 @@ export type ScreenTransitionConfig = {
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* How much the gesture's final velocity impacts the dismiss decision.
|
|
80
|
+
* @default 0.3
|
|
80
81
|
*/
|
|
81
82
|
gestureVelocityImpact?: number;
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* How much velocity affects snap point targeting. Lower values make snapping
|
|
86
|
+
* feel more deliberate (iOS-like), higher values make it more responsive to flicks.
|
|
87
|
+
* @default 0.1
|
|
88
|
+
*/
|
|
89
|
+
snapVelocityImpact?: number;
|
|
90
|
+
|
|
83
91
|
/**
|
|
84
92
|
* Distance threshold for gesture recognition throughout the screen.
|
|
85
93
|
*/
|
|
@@ -155,14 +163,29 @@ export type ScreenTransitionConfig = {
|
|
|
155
163
|
*/
|
|
156
164
|
initialSnapIndex?: number;
|
|
157
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Controls whether swiping to expand the sheet works from within a ScrollView.
|
|
168
|
+
*
|
|
169
|
+
* - `true` (Apple Maps style): Swiping up at scroll top expands the sheet
|
|
170
|
+
* - `false` (Instagram style): Expand only works via deadspace (non-scrollable areas)
|
|
171
|
+
*
|
|
172
|
+
* Collapse (swipe down at scroll top) always works regardless of this setting.
|
|
173
|
+
*
|
|
174
|
+
* Only applies to screens with `snapPoints` configured.
|
|
175
|
+
*
|
|
176
|
+
* @default true
|
|
177
|
+
*/
|
|
178
|
+
expandViaScrollView?: boolean;
|
|
179
|
+
|
|
158
180
|
/**
|
|
159
181
|
* Controls how touches interact with the backdrop area (outside the screen content).
|
|
160
182
|
*
|
|
161
183
|
* - `'block'`: Backdrop catches all touches (default for most screens)
|
|
162
184
|
* - `'passthrough'`: Touches pass through to content behind (default for component stacks)
|
|
163
185
|
* - `'dismiss'`: Tapping backdrop dismisses the screen
|
|
186
|
+
* - `'collapse'`: Tapping backdrop collapses to next lower snap point (dismisses at min)
|
|
164
187
|
*
|
|
165
188
|
* @default 'block' (or 'passthrough' for component stacks)
|
|
166
189
|
*/
|
|
167
|
-
backdropBehavior?: "block" | "passthrough" | "dismiss";
|
|
190
|
+
backdropBehavior?: "block" | "passthrough" | "dismiss" | "collapse";
|
|
168
191
|
};
|