react-native-screen-transitions 3.0.0-rc.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -96
- package/lib/commonjs/blank-stack/components/overlay.js +1 -1
- package/lib/commonjs/blank-stack/components/overlay.js.map +1 -1
- package/lib/commonjs/blank-stack/components/screens.js +6 -2
- package/lib/commonjs/blank-stack/components/screens.js.map +1 -1
- package/lib/commonjs/blank-stack/components/stack-view.js +2 -5
- package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js +11 -10
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
- package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js +2 -0
- package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js.map +1 -1
- package/lib/commonjs/shared/components/create-transition-aware-component.js +2 -0
- package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +8 -4
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +29 -5
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture.js +26 -0
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture.js.map +1 -0
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +32 -60
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/index.js +7 -0
- package/lib/commonjs/shared/index.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.provider.js +21 -42
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/helpers/interpolate-style.js +30 -0
- package/lib/commonjs/shared/utils/bounds/helpers/interpolate-style.js.map +1 -0
- package/lib/commonjs/shared/utils/bounds/index.js +29 -1
- package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
- package/lib/commonjs/shared/utils/create-provider.js +16 -0
- package/lib/commonjs/shared/utils/create-provider.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +4 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/blank-stack/components/overlay.js +1 -1
- package/lib/module/blank-stack/components/overlay.js.map +1 -1
- package/lib/module/blank-stack/components/screens.js +6 -2
- package/lib/module/blank-stack/components/screens.js.map +1 -1
- package/lib/module/blank-stack/components/stack-view.js +2 -5
- package/lib/module/blank-stack/components/stack-view.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js +11 -10
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
- package/lib/module/shared/components/controllers/native-stack-lifecycle.js +1 -0
- package/lib/module/shared/components/controllers/native-stack-lifecycle.js.map +1 -1
- package/lib/module/shared/components/create-transition-aware-component.js +2 -0
- package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js +8 -4
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +30 -6
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture.js +22 -0
- package/lib/module/shared/hooks/gestures/use-screen-gesture.js.map +1 -0
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +32 -60
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/index.js +1 -0
- package/lib/module/shared/index.js.map +1 -1
- package/lib/module/shared/providers/gestures.provider.js +19 -41
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/utils/bounds/helpers/interpolate-style.js +26 -0
- package/lib/module/shared/utils/bounds/helpers/interpolate-style.js.map +1 -0
- package/lib/module/shared/utils/bounds/index.js +29 -1
- package/lib/module/shared/utils/bounds/index.js.map +1 -1
- package/lib/module/shared/utils/create-provider.js +17 -1
- package/lib/module/shared/utils/create-provider.js.map +1 -1
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +4 -4
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/typescript/blank-stack/components/screens.d.ts +1 -3
- package/lib/typescript/blank-stack/components/screens.d.ts.map +1 -1
- package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -1
- package/lib/typescript/blank-stack/types.d.ts +2 -14
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/index.d.ts.map +1 -1
- package/lib/typescript/shared/components/controllers/native-stack-lifecycle.d.ts.map +1 -1
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts +1 -0
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +1 -0
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture.d.ts +15 -0
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +1 -0
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
- package/lib/typescript/shared/index.d.ts +4 -2
- package/lib/typescript/shared/index.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.provider.d.ts +6 -13
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +44 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts +6 -0
- package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/core.types.d.ts +7 -0
- package/lib/typescript/shared/types/core.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/bounds/helpers/interpolate-style.d.ts +17 -0
- package/lib/typescript/shared/utils/bounds/helpers/interpolate-style.d.ts.map +1 -0
- package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
- package/lib/typescript/shared/utils/create-provider.d.ts +5 -1
- package/lib/typescript/shared/utils/create-provider.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +49 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/blank-stack/components/overlay.tsx +1 -1
- package/src/blank-stack/components/screens.tsx +4 -4
- package/src/blank-stack/components/stack-view.tsx +4 -11
- package/src/blank-stack/types.ts +2 -15
- package/src/blank-stack/utils/with-stack-navigation/index.tsx +17 -3
- package/src/shared/__tests__/derivations.test.ts +155 -0
- package/src/shared/__tests__/gesture-activation.test.ts +251 -0
- package/src/shared/components/controllers/native-stack-lifecycle.tsx +4 -2
- package/src/shared/components/create-transition-aware-component.tsx +2 -1
- package/src/shared/hooks/animation/use-screen-animation.tsx +8 -2
- package/src/shared/hooks/gestures/use-build-gestures.tsx +35 -8
- package/src/shared/hooks/gestures/use-screen-gesture.ts +19 -0
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +39 -59
- package/src/shared/index.ts +2 -0
- package/src/shared/providers/gestures.provider.tsx +35 -75
- package/src/shared/types/animation.types.ts +49 -0
- package/src/shared/types/bounds.types.ts +11 -0
- package/src/shared/types/core.types.ts +8 -0
- package/src/shared/utils/bounds/helpers/interpolate-style.ts +38 -0
- package/src/shared/utils/bounds/index.ts +31 -1
- package/src/shared/utils/create-provider.tsx +31 -1
- package/src/shared/utils/gesture/check-gesture-activation.ts +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StackActions } from "@react-navigation/native";
|
|
2
|
-
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
3
3
|
import { useWindowDimensions } from "react-native";
|
|
4
4
|
import {
|
|
5
5
|
Gesture,
|
|
@@ -48,6 +48,7 @@ export const useBuildGestures = ({
|
|
|
48
48
|
ancestorContext,
|
|
49
49
|
}: BuildGesturesHookProps): {
|
|
50
50
|
panGesture: GestureType;
|
|
51
|
+
panGestureRef: React.MutableRefObject<GestureType | undefined>;
|
|
51
52
|
nativeGesture: GestureType;
|
|
52
53
|
gestureAnimationValues: GestureStoreMap;
|
|
53
54
|
} => {
|
|
@@ -63,6 +64,9 @@ export const useBuildGestures = ({
|
|
|
63
64
|
GestureOffsetState.PENDING,
|
|
64
65
|
);
|
|
65
66
|
|
|
67
|
+
// Ref for external gesture coordination (e.g., swipeable lists)
|
|
68
|
+
const panGestureRef = useRef<GestureType | undefined>(undefined);
|
|
69
|
+
|
|
66
70
|
const gestureAnimationValues = GestureStore.getRouteGestures(
|
|
67
71
|
current.route.key,
|
|
68
72
|
);
|
|
@@ -325,26 +329,49 @@ export const useBuildGestures = ({
|
|
|
325
329
|
},
|
|
326
330
|
);
|
|
327
331
|
|
|
332
|
+
// Memoize gestures to keep stable references - critical for RNGH
|
|
333
|
+
// Child gestures reference ancestor's pan via requireExternalGestureToFail,
|
|
334
|
+
// so the pan gesture MUST be stable or children will reference stale objects
|
|
328
335
|
return useMemo(() => {
|
|
329
|
-
const nativeGesture = Gesture.Native();
|
|
330
|
-
|
|
331
336
|
const panGesture = Gesture.Pan()
|
|
337
|
+
.withRef(panGestureRef)
|
|
332
338
|
.enabled(gestureEnabled)
|
|
333
339
|
.manualActivation(true)
|
|
334
340
|
.onTouchesDown(onTouchesDown)
|
|
335
341
|
.onTouchesMove(onTouchesMove)
|
|
336
342
|
.onStart(onStart)
|
|
337
343
|
.onUpdate(onUpdate)
|
|
338
|
-
.onEnd(onEnd)
|
|
339
|
-
|
|
344
|
+
.onEnd(onEnd);
|
|
345
|
+
|
|
346
|
+
// Native gesture setup depends on whether this screen has gestures
|
|
347
|
+
let nativeGesture: GestureType;
|
|
348
|
+
|
|
349
|
+
if (gestureEnabled) {
|
|
350
|
+
// This screen has gestures - set up normal pan/native relationship
|
|
351
|
+
nativeGesture = Gesture.Native().requireExternalGestureToFail(panGesture);
|
|
352
|
+
panGesture.blocksExternalGesture(nativeGesture);
|
|
353
|
+
} else {
|
|
354
|
+
// This screen has no gestures
|
|
355
|
+
// Find nearest ancestor with gestureEnabled=true (attached pan)
|
|
356
|
+
let activePanAncestor = ancestorContext;
|
|
357
|
+
while (activePanAncestor && !activePanAncestor.gestureEnabled) {
|
|
358
|
+
activePanAncestor = activePanAncestor.ancestorContext;
|
|
359
|
+
}
|
|
340
360
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
361
|
+
if (activePanAncestor?.panGesture) {
|
|
362
|
+
// Found an ancestor with enabled pan - wait for it
|
|
363
|
+
nativeGesture = Gesture.Native().requireExternalGestureToFail(
|
|
364
|
+
activePanAncestor.panGesture,
|
|
365
|
+
);
|
|
366
|
+
} else {
|
|
367
|
+
// No ancestor with enabled pan - plain native
|
|
368
|
+
nativeGesture = Gesture.Native();
|
|
369
|
+
}
|
|
344
370
|
}
|
|
345
371
|
|
|
346
372
|
return {
|
|
347
373
|
panGesture,
|
|
374
|
+
panGestureRef,
|
|
348
375
|
nativeGesture,
|
|
349
376
|
gestureAnimationValues,
|
|
350
377
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useGestureContext } from "../../providers/gestures.provider";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a ref to the screen's navigation pan gesture.
|
|
5
|
+
* Use this to coordinate child gestures with the navigation gesture.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const screenGesture = useScreenGesture();
|
|
10
|
+
*
|
|
11
|
+
* const myPanGesture = Gesture.Pan()
|
|
12
|
+
* .waitFor(screenGesture) // Wait for navigation gesture to fail first
|
|
13
|
+
* .onUpdate(...);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const useScreenGesture = () => {
|
|
17
|
+
const ctx = useGestureContext();
|
|
18
|
+
return ctx?.panGestureRef ?? null;
|
|
19
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noNonNullAssertion: <Will always consume context from GestureProvider> */
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
1
4
|
import type { LayoutChangeEvent } from "react-native";
|
|
2
5
|
import { useAnimatedScrollHandler } from "react-native-reanimated";
|
|
3
6
|
import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescript/hook/commonTypes";
|
|
@@ -12,13 +15,26 @@ interface ScrollProgressHookProps {
|
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
15
|
-
const
|
|
18
|
+
const context = useGestureContext()!;
|
|
19
|
+
const { scrollConfig, ancestorContext } = context;
|
|
20
|
+
|
|
21
|
+
const ancestorScrollConfigs = useMemo(() => {
|
|
22
|
+
const configs: (typeof scrollConfig)[] = [];
|
|
23
|
+
let current = ancestorContext;
|
|
24
|
+
while (current) {
|
|
25
|
+
if (current.scrollConfig) {
|
|
26
|
+
configs.push(current.scrollConfig);
|
|
27
|
+
}
|
|
28
|
+
current = current.ancestorContext;
|
|
29
|
+
}
|
|
30
|
+
return configs;
|
|
31
|
+
}, [ancestorContext]);
|
|
16
32
|
|
|
17
33
|
const scrollHandler = useAnimatedScrollHandler({
|
|
18
34
|
onScroll: (event) => {
|
|
19
35
|
props.onScroll?.(event);
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
const updateScrollPosition = (v: Any) => {
|
|
22
38
|
"worklet";
|
|
23
39
|
if (v === null) {
|
|
24
40
|
return {
|
|
@@ -33,25 +49,13 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
33
49
|
v.x = event.contentOffset.x;
|
|
34
50
|
v.y = event.contentOffset.y;
|
|
35
51
|
return v;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
x: event.contentOffset.x,
|
|
44
|
-
y: event.contentOffset.y,
|
|
45
|
-
contentHeight: 0,
|
|
46
|
-
contentWidth: 0,
|
|
47
|
-
layoutHeight: 0,
|
|
48
|
-
layoutWidth: 0,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
v.x = event.contentOffset.x;
|
|
52
|
-
v.y = event.contentOffset.y;
|
|
53
|
-
return v;
|
|
54
|
-
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
scrollConfig.modify(updateScrollPosition);
|
|
55
|
+
|
|
56
|
+
// Sync to ALL ancestors, not just immediate parent
|
|
57
|
+
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
58
|
+
ancestorConfig.modify(updateScrollPosition);
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
61
|
});
|
|
@@ -60,7 +64,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
60
64
|
(width: number, height: number) => {
|
|
61
65
|
props.onContentSizeChange?.(width, height);
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
const updateContentSize = (v: Any) => {
|
|
64
68
|
"worklet";
|
|
65
69
|
if (v === null) {
|
|
66
70
|
return {
|
|
@@ -75,24 +79,12 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
75
79
|
v.contentWidth = width;
|
|
76
80
|
v.contentHeight = height;
|
|
77
81
|
return v;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
x: 0,
|
|
85
|
-
y: 0,
|
|
86
|
-
layoutHeight: 0,
|
|
87
|
-
layoutWidth: 0,
|
|
88
|
-
contentWidth: width,
|
|
89
|
-
contentHeight: height,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
v.contentWidth = width;
|
|
93
|
-
v.contentHeight = height;
|
|
94
|
-
return v;
|
|
95
|
-
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
scrollConfig.modify(updateContentSize);
|
|
85
|
+
|
|
86
|
+
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
87
|
+
ancestorConfig.modify(updateContentSize);
|
|
96
88
|
}
|
|
97
89
|
},
|
|
98
90
|
);
|
|
@@ -101,7 +93,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
101
93
|
props.onLayout?.(event);
|
|
102
94
|
const { width, height } = event.nativeEvent.layout;
|
|
103
95
|
|
|
104
|
-
|
|
96
|
+
const updateLayout = (v: Any) => {
|
|
105
97
|
"worklet";
|
|
106
98
|
if (v === null) {
|
|
107
99
|
return {
|
|
@@ -116,24 +108,12 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
116
108
|
v.layoutHeight = height;
|
|
117
109
|
v.layoutWidth = width;
|
|
118
110
|
return v;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
x: 0,
|
|
126
|
-
y: 0,
|
|
127
|
-
contentHeight: 0,
|
|
128
|
-
contentWidth: 0,
|
|
129
|
-
layoutHeight: height,
|
|
130
|
-
layoutWidth: width,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
v.layoutHeight = height;
|
|
134
|
-
v.layoutWidth = width;
|
|
135
|
-
return v;
|
|
136
|
-
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
scrollConfig.modify(updateLayout);
|
|
114
|
+
|
|
115
|
+
for (const ancestorConfig of ancestorScrollConfigs) {
|
|
116
|
+
ancestorConfig.modify(updateLayout);
|
|
137
117
|
}
|
|
138
118
|
});
|
|
139
119
|
|
package/src/shared/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export default {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export { useScreenAnimation } from "./hooks/animation/use-screen-animation";
|
|
22
|
+
export { useScreenGesture } from "./hooks/gestures/use-screen-gesture";
|
|
22
23
|
|
|
23
24
|
export type {
|
|
24
25
|
AnimationConfig,
|
|
@@ -26,4 +27,5 @@ export type {
|
|
|
26
27
|
ScreenInterpolationProps,
|
|
27
28
|
ScreenStyleInterpolator,
|
|
28
29
|
} from "./types/animation.types";
|
|
30
|
+
export type { BoundEntry, BoundsLink } from "./types/bounds.types";
|
|
29
31
|
export type { ScreenTransitionConfig } from "./types/core.types";
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo } from "react";
|
|
2
1
|
import { StyleSheet, View } from "react-native";
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
GestureDetector,
|
|
4
|
+
type GestureType,
|
|
5
|
+
} from "react-native-gesture-handler";
|
|
5
6
|
import type { SharedValue } from "react-native-reanimated";
|
|
6
7
|
import { useSharedValue } from "react-native-reanimated";
|
|
7
8
|
import { useBuildGestures } from "../hooks/gestures/use-build-gestures";
|
|
8
9
|
import type { GestureStoreMap } from "../stores/gesture.store";
|
|
10
|
+
import createProvider from "../utils/create-provider";
|
|
9
11
|
import { useKeys } from "./keys.provider";
|
|
10
12
|
|
|
11
13
|
export type ScrollConfig = {
|
|
@@ -19,94 +21,52 @@ export type ScrollConfig = {
|
|
|
19
21
|
|
|
20
22
|
export interface GestureContextType {
|
|
21
23
|
panGesture: GestureType;
|
|
24
|
+
panGestureRef: React.MutableRefObject<GestureType | undefined>;
|
|
22
25
|
nativeGesture: GestureType;
|
|
23
26
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
24
27
|
gestureAnimationValues: GestureStoreMap;
|
|
25
|
-
ancestorContext: GestureContextType |
|
|
28
|
+
ancestorContext: GestureContextType | null;
|
|
29
|
+
gestureEnabled: boolean;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* Provider that creates gesture handling for a screen.
|
|
36
|
-
* If the current screen doesn't have gestures enabled but a parent does,
|
|
37
|
-
* we pass through the parent's context so scrollable children can coordinate
|
|
38
|
-
* with the ancestor's gestures.
|
|
39
|
-
*/
|
|
40
|
-
export const ScreenGestureProvider = ({ children }: GestureProviderProps) => {
|
|
41
|
-
const ancestorContext = useContext(GestureContext);
|
|
32
|
+
export const {
|
|
33
|
+
ScreenGestureProvider,
|
|
34
|
+
useScreenGestureContext: useGestureContext,
|
|
35
|
+
} = createProvider("ScreenGesture", { guarded: false })<
|
|
36
|
+
{ children: React.ReactNode },
|
|
37
|
+
GestureContextType
|
|
38
|
+
>(({ children }) => {
|
|
42
39
|
const { current } = useKeys();
|
|
43
|
-
|
|
44
|
-
const hasOwnGestures = current.options.gestureEnabled === true;
|
|
45
|
-
|
|
46
|
-
// If this screen doesn't have its own gestures but an ancestor does,
|
|
47
|
-
// pass through so scrollable children coordinate with that ancestor
|
|
48
|
-
if (!hasOwnGestures && ancestorContext) {
|
|
49
|
-
return children;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<ScreenGestureProviderInner ancestorContext={ancestorContext}>
|
|
54
|
-
{children}
|
|
55
|
-
</ScreenGestureProviderInner>
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const ScreenGestureProviderInner = ({
|
|
60
|
-
children,
|
|
61
|
-
ancestorContext,
|
|
62
|
-
}: GestureProviderProps & {
|
|
63
|
-
ancestorContext: GestureContextType | undefined;
|
|
64
|
-
}) => {
|
|
40
|
+
const ancestorContext = useGestureContext();
|
|
65
41
|
const scrollConfig = useSharedValue<ScrollConfig | null>(null);
|
|
66
42
|
|
|
67
|
-
const
|
|
43
|
+
const hasGestures = current.options.gestureEnabled === true;
|
|
44
|
+
|
|
45
|
+
const { panGesture, panGestureRef, nativeGesture, gestureAnimationValues } =
|
|
68
46
|
useBuildGestures({
|
|
69
47
|
scrollConfig,
|
|
70
48
|
ancestorContext,
|
|
71
49
|
});
|
|
72
50
|
|
|
73
|
-
const value: GestureContextType =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
ancestorContext,
|
|
87
|
-
],
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<GestureContext.Provider value={value}>
|
|
51
|
+
const value: GestureContextType = {
|
|
52
|
+
panGesture,
|
|
53
|
+
panGestureRef,
|
|
54
|
+
scrollConfig,
|
|
55
|
+
nativeGesture,
|
|
56
|
+
gestureAnimationValues,
|
|
57
|
+
ancestorContext,
|
|
58
|
+
gestureEnabled: hasGestures,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
value,
|
|
63
|
+
children: (
|
|
92
64
|
<GestureDetector gesture={panGesture}>
|
|
93
65
|
<View style={styles.container}>{children}</View>
|
|
94
66
|
</GestureDetector>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export const useGestureContext = () => {
|
|
100
|
-
const context = useContext(GestureContext);
|
|
101
|
-
|
|
102
|
-
if (!context) {
|
|
103
|
-
throw new Error(
|
|
104
|
-
"useGestureContext must be used within a ScreenGestureProvider",
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return context;
|
|
109
|
-
};
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
110
70
|
|
|
111
71
|
const styles = StyleSheet.create({
|
|
112
72
|
container: {
|
|
@@ -21,10 +21,59 @@ export interface OverlayInterpolationProps {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export type ScreenTransitionState = {
|
|
24
|
+
/**
|
|
25
|
+
* Animation progress for this screen.
|
|
26
|
+
* - `0`: Screen is fully off-screen (entering)
|
|
27
|
+
* - `1`: Screen is fully visible (active)
|
|
28
|
+
*
|
|
29
|
+
* This value animates from 0 to 1 when the screen enters,
|
|
30
|
+
* and from 1 to 0 when it exits.
|
|
31
|
+
*/
|
|
24
32
|
progress: number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether this screen is in the process of being dismissed.
|
|
36
|
+
* - `0`: Screen is opening or active
|
|
37
|
+
* - `1`: Screen is closing/being dismissed
|
|
38
|
+
*
|
|
39
|
+
* Use this to trigger different animations when navigating back vs forward.
|
|
40
|
+
*/
|
|
25
41
|
closing: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether this screen is currently animating.
|
|
45
|
+
* - `0`: No animation in progress
|
|
46
|
+
* - `1`: Animation or gesture is in progress
|
|
47
|
+
*/
|
|
26
48
|
animating: number;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Live gesture values for this screen.
|
|
52
|
+
* Contains translation (x, y), normalized values (-1 to 1),
|
|
53
|
+
* and flags for dragging/dismissing state.
|
|
54
|
+
*/
|
|
27
55
|
gesture: GestureValues;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Custom metadata passed from screen options.
|
|
59
|
+
* Use this for conditional animation logic instead of checking route names.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // In screen options:
|
|
63
|
+
* options={{ meta: { scalesOthers: true } }}
|
|
64
|
+
*
|
|
65
|
+
* // In animation logic:
|
|
66
|
+
* if (props.next?.meta?.scalesOthers) { ... }
|
|
67
|
+
*/
|
|
68
|
+
meta?: Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The route object for this screen.
|
|
72
|
+
*
|
|
73
|
+
* @deprecated Use `meta` instead for conditional animation logic.
|
|
74
|
+
* Pass route params via options: `options={({ route }) => ({ meta: { id: route.params.id } })}`
|
|
75
|
+
* This field may be removed in a future version.
|
|
76
|
+
*/
|
|
28
77
|
route: RouteProp<ParamListBase>;
|
|
29
78
|
};
|
|
30
79
|
|
|
@@ -19,7 +19,18 @@ export type BoundEntry = {
|
|
|
19
19
|
styles: StyleProps;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
export type BoundsLink = {
|
|
23
|
+
source: BoundEntry | null;
|
|
24
|
+
destination: BoundEntry | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
22
27
|
export type BoundsAccessor = {
|
|
23
28
|
<T extends BoundsBuilderOptions>(options: T): BoundsReturnType<T>;
|
|
24
29
|
getSnapshot: (id: string, key?: string) => Snapshot | null;
|
|
30
|
+
getLink: (id: string) => BoundsLink | null;
|
|
31
|
+
interpolateStyle: (
|
|
32
|
+
id: string,
|
|
33
|
+
property: keyof StyleProps,
|
|
34
|
+
fallback?: number,
|
|
35
|
+
) => number;
|
|
25
36
|
};
|
|
@@ -100,4 +100,12 @@ export type ScreenTransitionConfig = {
|
|
|
100
100
|
* The area of the screen where the gesture is activated.
|
|
101
101
|
*/
|
|
102
102
|
gestureActivationArea?: GestureActivationArea;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Custom metadata passed through to animation props.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* options={{ meta: { scalesOthers: true } }}
|
|
109
|
+
*/
|
|
110
|
+
meta?: Record<string, unknown>;
|
|
103
111
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { interpolate } from "react-native-reanimated";
|
|
2
|
+
import { ENTER_RANGE, EXIT_RANGE } from "../../../constants";
|
|
3
|
+
import type { BoundsLink } from "../../../types/bounds.types";
|
|
4
|
+
|
|
5
|
+
type InterpolateStyleOptions = {
|
|
6
|
+
fallback?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Interpolates a numeric style property between source and destination bounds.
|
|
11
|
+
*
|
|
12
|
+
* @param link - The bounds link containing source and destination styles
|
|
13
|
+
* @param property - The style property to interpolate (e.g., "borderRadius", "opacity")
|
|
14
|
+
* @param progress - Animation progress value
|
|
15
|
+
* @param entering - Whether the screen is entering (focused) or exiting (unfocused)
|
|
16
|
+
* @param options - Optional configuration
|
|
17
|
+
* @returns The interpolated value
|
|
18
|
+
*/
|
|
19
|
+
export function interpolateLinkStyle(
|
|
20
|
+
link: BoundsLink | null,
|
|
21
|
+
property: string,
|
|
22
|
+
progress: number,
|
|
23
|
+
entering: boolean,
|
|
24
|
+
options: InterpolateStyleOptions = {},
|
|
25
|
+
): number {
|
|
26
|
+
"worklet";
|
|
27
|
+
|
|
28
|
+
const { fallback = 0 } = options;
|
|
29
|
+
|
|
30
|
+
const sourceValue =
|
|
31
|
+
(link?.source?.styles?.[property] as number | undefined) ?? fallback;
|
|
32
|
+
const destValue =
|
|
33
|
+
(link?.destination?.styles?.[property] as number | undefined) ?? fallback;
|
|
34
|
+
|
|
35
|
+
const range = entering ? ENTER_RANGE : EXIT_RANGE;
|
|
36
|
+
|
|
37
|
+
return interpolate(progress, range, [sourceValue, destValue], "clamp");
|
|
38
|
+
}
|
|
@@ -11,12 +11,13 @@ import type {
|
|
|
11
11
|
ScreenInterpolationProps,
|
|
12
12
|
ScreenTransitionState,
|
|
13
13
|
} from "../../types/animation.types";
|
|
14
|
-
import type { BoundsAccessor } from "../../types/bounds.types";
|
|
14
|
+
import type { BoundsAccessor, BoundsLink } from "../../types/bounds.types";
|
|
15
15
|
import type { Layout } from "../../types/core.types";
|
|
16
16
|
import {
|
|
17
17
|
computeContentTransformGeometry,
|
|
18
18
|
computeRelativeGeometry,
|
|
19
19
|
} from "./helpers/geometry";
|
|
20
|
+
import { interpolateLinkStyle } from "./helpers/interpolate-style";
|
|
20
21
|
import {
|
|
21
22
|
composeContentStyle,
|
|
22
23
|
composeSizeAbsolute,
|
|
@@ -207,7 +208,36 @@ export const createBounds = (
|
|
|
207
208
|
return BoundStore.getSnapshot(tag, key);
|
|
208
209
|
};
|
|
209
210
|
|
|
211
|
+
const getLink = (tag: string): BoundsLink | null => {
|
|
212
|
+
"worklet";
|
|
213
|
+
const link = BoundStore.getActiveLink(tag, props.current?.route.key);
|
|
214
|
+
if (!link) return null;
|
|
215
|
+
return {
|
|
216
|
+
source: link.source
|
|
217
|
+
? { bounds: link.source.bounds, styles: link.source.styles }
|
|
218
|
+
: null,
|
|
219
|
+
destination: link.destination
|
|
220
|
+
? { bounds: link.destination.bounds, styles: link.destination.styles }
|
|
221
|
+
: null,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const interpolateStyle = (
|
|
226
|
+
tag: string,
|
|
227
|
+
property: string,
|
|
228
|
+
fallback?: number,
|
|
229
|
+
): number => {
|
|
230
|
+
"worklet";
|
|
231
|
+
const link = getLink(tag);
|
|
232
|
+
const entering = !props.next;
|
|
233
|
+
return interpolateLinkStyle(link, property, props.progress, entering, {
|
|
234
|
+
fallback,
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
210
238
|
return Object.assign(boundsFunction, {
|
|
211
239
|
getSnapshot,
|
|
240
|
+
getLink,
|
|
241
|
+
interpolateStyle,
|
|
212
242
|
}) as BoundsAccessor;
|
|
213
243
|
};
|
|
@@ -9,8 +9,11 @@ import {
|
|
|
9
9
|
type ReactNode,
|
|
10
10
|
useContext,
|
|
11
11
|
useMemo,
|
|
12
|
+
useRef,
|
|
12
13
|
} from "react";
|
|
13
14
|
|
|
15
|
+
type InnerProviderComponent = (props: { children: ReactNode }) => ReactNode;
|
|
16
|
+
|
|
14
17
|
export default function createProvider<
|
|
15
18
|
ProviderName extends string,
|
|
16
19
|
Guarded extends boolean = true,
|
|
@@ -19,7 +22,13 @@ export default function createProvider<
|
|
|
19
22
|
factory: (props: ProviderProps) => {
|
|
20
23
|
value?: ContextValue;
|
|
21
24
|
enabled?: boolean;
|
|
22
|
-
children?:
|
|
25
|
+
children?:
|
|
26
|
+
| ReactNode
|
|
27
|
+
| ((
|
|
28
|
+
innerProvider: {
|
|
29
|
+
[K in ProviderName as `${K}Provider`]: InnerProviderComponent;
|
|
30
|
+
},
|
|
31
|
+
) => ReactNode);
|
|
23
32
|
},
|
|
24
33
|
) => {
|
|
25
34
|
const { guarded = true } = options ?? {};
|
|
@@ -45,6 +54,27 @@ export default function createProvider<
|
|
|
45
54
|
[enabled, value],
|
|
46
55
|
);
|
|
47
56
|
|
|
57
|
+
// Per-instance ref ensures InnerProvider reads latest value while keeping
|
|
58
|
+
// a stable component reference.
|
|
59
|
+
const valueRef = useRef<ContextValue | null>(memoValue);
|
|
60
|
+
valueRef.current = memoValue;
|
|
61
|
+
|
|
62
|
+
const InnerProvider = useMemo(
|
|
63
|
+
(): InnerProviderComponent =>
|
|
64
|
+
({ children }) => (
|
|
65
|
+
<Context.Provider value={valueRef.current}>
|
|
66
|
+
{children}
|
|
67
|
+
</Context.Provider>
|
|
68
|
+
),
|
|
69
|
+
[],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (typeof children === "function") {
|
|
73
|
+
return children({
|
|
74
|
+
[`${name}Provider`]: InnerProvider,
|
|
75
|
+
} as { [K in ProviderName as `${K}Provider`]: InnerProviderComponent });
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
return <Context.Provider value={memoValue}>{children}</Context.Provider>;
|
|
49
79
|
};
|
|
50
80
|
|
|
@@ -72,7 +72,7 @@ const DEFAULT_EDGE_DISTANCE_HORIZONTAL = 50;
|
|
|
72
72
|
const DEFAULT_EDGE_DISTANCE_VERTICAL = 135;
|
|
73
73
|
const DEFAULT_ACTIVATION_AREA = "screen" as const;
|
|
74
74
|
|
|
75
|
-
function normalizeSides(area?: GestureActivationArea): NormalizedSides {
|
|
75
|
+
export function normalizeSides(area?: GestureActivationArea): NormalizedSides {
|
|
76
76
|
"worklet";
|
|
77
77
|
if (!area || typeof area === "string") {
|
|
78
78
|
const mode: ActivationArea = area ?? DEFAULT_ACTIVATION_AREA;
|
|
@@ -88,7 +88,7 @@ function normalizeSides(area?: GestureActivationArea): NormalizedSides {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function computeEdgeConstraints(
|
|
91
|
+
export function computeEdgeConstraints(
|
|
92
92
|
initialTouch: { x: number; y: number },
|
|
93
93
|
dimensions: Layout,
|
|
94
94
|
sides: NormalizedSides,
|
|
@@ -108,7 +108,7 @@ function computeEdgeConstraints(
|
|
|
108
108
|
return { horizontalRight, horizontalLeft, verticalDown, verticalUp } as const;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function calculateSwipeDirs(deltaX: number, deltaY: number) {
|
|
111
|
+
export function calculateSwipeDirs(deltaX: number, deltaY: number) {
|
|
112
112
|
"worklet";
|
|
113
113
|
|
|
114
114
|
const isVerticalSwipe = Math.abs(deltaY) > Math.abs(deltaX);
|
|
@@ -129,7 +129,7 @@ function calculateSwipeDirs(deltaX: number, deltaY: number) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function shouldActivateOrFail(params: ShouldActivateOrFailProps) {
|
|
132
|
+
export function shouldActivateOrFail(params: ShouldActivateOrFailProps) {
|
|
133
133
|
"worklet";
|
|
134
134
|
|
|
135
135
|
const {
|