react-native-screen-transitions 3.0.0-beta.0 → 3.0.0-beta.10
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/lib/commonjs/blank-stack/components/Overlay.js +33 -7
- package/lib/commonjs/blank-stack/components/Overlay.js.map +1 -1
- package/lib/commonjs/blank-stack/components/Screens.js +3 -3
- package/lib/commonjs/blank-stack/components/Screens.js.map +1 -1
- package/lib/commonjs/blank-stack/components/StackView.js +49 -40
- package/lib/commonjs/blank-stack/components/StackView.js.map +1 -1
- package/lib/commonjs/blank-stack/hooks/use-overlay-animation.js +3 -7
- package/lib/commonjs/blank-stack/hooks/use-overlay-animation.js.map +1 -1
- package/lib/commonjs/blank-stack/index.js +14 -0
- package/lib/commonjs/blank-stack/index.js.map +1 -1
- package/lib/commonjs/blank-stack/providers/blank-stack-state.js +65 -0
- package/lib/commonjs/blank-stack/providers/blank-stack-state.js.map +1 -0
- package/lib/commonjs/native-stack/views/NativeStackView.native.js +2 -1
- package/lib/commonjs/native-stack/views/NativeStackView.native.js.map +1 -1
- package/lib/commonjs/shared/components/controllers/screen-lifecycle.js +20 -6
- package/lib/commonjs/shared/components/controllers/screen-lifecycle.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/bounds/use-bound-registry.js +21 -0
- package/lib/commonjs/shared/hooks/bounds/use-bound-registry.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +40 -28
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/use-derived-value-state.js +33 -0
- package/lib/commonjs/shared/hooks/use-derived-value-state.js.map +1 -0
- package/lib/commonjs/shared/hooks/use-stable-callback-value.js +7 -1
- package/lib/commonjs/shared/hooks/use-stable-callback-value.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.js +6 -3
- package/lib/commonjs/shared/providers/gestures.js.map +1 -1
- package/lib/commonjs/shared/providers/utils/create-provider.js +41 -29
- package/lib/commonjs/shared/providers/utils/create-provider.js.map +1 -1
- package/lib/commonjs/shared/stores/animation-store.js +12 -3
- package/lib/commonjs/shared/stores/animation-store.js.map +1 -1
- package/lib/commonjs/shared/stores/bound-store/index.js +12 -1
- package/lib/commonjs/shared/stores/bound-store/index.js.map +1 -1
- package/lib/commonjs/shared/stores/gesture-store.js +10 -0
- package/lib/commonjs/shared/stores/gesture-store.js.map +1 -1
- package/lib/module/blank-stack/components/Overlay.js +33 -7
- package/lib/module/blank-stack/components/Overlay.js.map +1 -1
- package/lib/module/blank-stack/components/Screens.js +3 -3
- package/lib/module/blank-stack/components/Screens.js.map +1 -1
- package/lib/module/blank-stack/components/StackView.js +48 -40
- package/lib/module/blank-stack/components/StackView.js.map +1 -1
- package/lib/module/blank-stack/hooks/use-overlay-animation.js +4 -8
- package/lib/module/blank-stack/hooks/use-overlay-animation.js.map +1 -1
- package/lib/module/blank-stack/index.js +2 -0
- package/lib/module/blank-stack/index.js.map +1 -1
- package/lib/module/blank-stack/providers/blank-stack-state.js +59 -0
- package/lib/module/blank-stack/providers/blank-stack-state.js.map +1 -0
- package/lib/module/native-stack/views/NativeStackView.native.js +2 -1
- package/lib/module/native-stack/views/NativeStackView.native.js.map +1 -1
- package/lib/module/shared/components/controllers/screen-lifecycle.js +20 -6
- package/lib/module/shared/components/controllers/screen-lifecycle.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/bounds/use-bound-registry.js +22 -1
- package/lib/module/shared/hooks/bounds/use-bound-registry.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +42 -30
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/use-derived-value-state.js +30 -0
- package/lib/module/shared/hooks/use-derived-value-state.js.map +1 -0
- package/lib/module/shared/hooks/use-stable-callback-value.js +8 -2
- package/lib/module/shared/hooks/use-stable-callback-value.js.map +1 -1
- package/lib/module/shared/providers/gestures.js +6 -3
- package/lib/module/shared/providers/gestures.js.map +1 -1
- package/lib/module/shared/providers/utils/create-provider.js +40 -27
- package/lib/module/shared/providers/utils/create-provider.js.map +1 -1
- package/lib/module/shared/stores/animation-store.js +15 -4
- package/lib/module/shared/stores/animation-store.js.map +1 -1
- package/lib/module/shared/stores/bound-store/index.js +12 -1
- package/lib/module/shared/stores/bound-store/index.js.map +1 -1
- package/lib/module/shared/stores/gesture-store.js +11 -1
- package/lib/module/shared/stores/gesture-store.js.map +1 -1
- package/lib/typescript/blank-stack/components/Overlay.d.ts.map +1 -1
- package/lib/typescript/blank-stack/components/Screens.d.ts +2 -3
- package/lib/typescript/blank-stack/components/Screens.d.ts.map +1 -1
- package/lib/typescript/blank-stack/components/StackView.d.ts.map +1 -1
- package/lib/typescript/blank-stack/hooks/use-overlay-animation.d.ts +1 -1
- package/lib/typescript/blank-stack/hooks/use-overlay-animation.d.ts.map +1 -1
- package/lib/typescript/blank-stack/index.d.ts +2 -0
- package/lib/typescript/blank-stack/index.d.ts.map +1 -1
- package/lib/typescript/blank-stack/providers/blank-stack-state.d.ts +13 -0
- package/lib/typescript/blank-stack/providers/blank-stack-state.d.ts.map +1 -0
- package/lib/typescript/blank-stack/types.d.ts +11 -3
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/native-stack/views/NativeStackView.native.d.ts.map +1 -1
- package/lib/typescript/shared/components/controllers/screen-lifecycle.d.ts.map +1 -1
- package/lib/typescript/shared/configs/index.d.ts +9 -9
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/bounds/use-bound-registry.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +5 -2
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/use-derived-value-state.d.ts +9 -0
- package/lib/typescript/shared/hooks/use-derived-value-state.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/use-stable-callback-value.d.ts.map +1 -1
- package/lib/typescript/shared/index.d.ts +10 -8
- package/lib/typescript/shared/index.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.d.ts +4 -2
- package/lib/typescript/shared/providers/gestures.d.ts.map +1 -1
- package/lib/typescript/shared/providers/utils/create-provider.d.ts +11 -29
- package/lib/typescript/shared/providers/utils/create-provider.d.ts.map +1 -1
- package/lib/typescript/shared/stores/animation-store.d.ts +4 -2
- package/lib/typescript/shared/stores/animation-store.d.ts.map +1 -1
- package/lib/typescript/shared/stores/bound-store/index.d.ts +2 -0
- package/lib/typescript/shared/stores/bound-store/index.d.ts.map +1 -1
- package/lib/typescript/shared/stores/gesture-store.d.ts.map +1 -1
- package/package.json +87 -99
- package/src/blank-stack/components/Overlay.tsx +40 -5
- package/src/blank-stack/components/Screens.tsx +5 -5
- package/src/blank-stack/components/StackView.tsx +55 -46
- package/src/blank-stack/hooks/use-overlay-animation.tsx +6 -21
- package/src/blank-stack/index.ts +2 -0
- package/src/blank-stack/providers/blank-stack-state.tsx +90 -0
- package/src/blank-stack/types.ts +11 -2
- package/src/native-stack/views/NativeStackView.native.tsx +3 -1
- package/src/shared/components/controllers/screen-lifecycle.tsx +20 -7
- package/src/shared/hooks/animation/use-screen-animation.tsx +1 -1
- package/src/shared/hooks/bounds/use-bound-registry.tsx +32 -1
- package/src/shared/hooks/gestures/use-build-gestures.tsx +63 -35
- package/src/shared/hooks/use-derived-value-state.ts +41 -0
- package/src/shared/hooks/use-stable-callback-value.tsx +10 -1
- package/src/shared/index.ts +8 -0
- package/src/shared/providers/gestures.tsx +17 -8
- package/src/shared/providers/utils/create-provider.tsx +77 -0
- package/src/shared/stores/animation-store.ts +19 -3
- package/src/shared/stores/bound-store/index.ts +23 -0
- package/src/shared/stores/gesture-store.ts +15 -1
- package/lib/commonjs/shared/stores/navigator-dismiss-state.js +0 -23
- package/lib/commonjs/shared/stores/navigator-dismiss-state.js.map +0 -1
- package/lib/module/shared/stores/navigator-dismiss-state.js +0 -19
- package/lib/module/shared/stores/navigator-dismiss-state.js.map +0 -1
- package/lib/typescript/shared/stores/navigator-dismiss-state.d.ts +0 -7
- package/lib/typescript/shared/stores/navigator-dismiss-state.d.ts.map +0 -1
- package/src/shared/providers/utils/create-provider.ts +0 -64
- package/src/shared/stores/navigator-dismiss-state.ts +0 -17
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { NavigationRoute, ParamListBase } from "@react-navigation/native";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useDerivedValue } from "react-native-reanimated";
|
|
4
|
+
import { useSharedValueState } from "../../shared/hooks/use-shared-value-state";
|
|
5
|
+
import createProvider from "../../shared/providers/utils/create-provider";
|
|
6
|
+
import { AnimationStore } from "../../shared/stores/animation-store";
|
|
7
|
+
import { useStackNavigationContext } from "../utils/with-stack-navigation";
|
|
8
|
+
|
|
9
|
+
interface BlankStackStateProviderProps {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type BlankStackStateProviderContext = {
|
|
14
|
+
parentIndex: number;
|
|
15
|
+
parentRoutes: NavigationRoute<ParamListBase, string>[];
|
|
16
|
+
index: number;
|
|
17
|
+
routes: NavigationRoute<ParamListBase, string>[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
useBlankStackStateContext: useBlankStackState,
|
|
22
|
+
BlankStackStateProvider,
|
|
23
|
+
} = createProvider("BlankStackState", { guarded: false })<
|
|
24
|
+
BlankStackStateProviderProps,
|
|
25
|
+
BlankStackStateProviderContext
|
|
26
|
+
>(() => {
|
|
27
|
+
const { focusedIndex, routes: stackRoutes } = useStackNavigationContext();
|
|
28
|
+
const [index, setIndex] = useState(0);
|
|
29
|
+
const [routes, setRoutes] = useState<
|
|
30
|
+
NavigationRoute<ParamListBase, string>[]
|
|
31
|
+
>([]);
|
|
32
|
+
|
|
33
|
+
const progressValues = useMemo(
|
|
34
|
+
() => stackRoutes.map((route) => AnimationStore.getAll(route.key)),
|
|
35
|
+
[stackRoutes],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const parentIndex = useDerivedValue(() => {
|
|
39
|
+
"worklet";
|
|
40
|
+
|
|
41
|
+
const activeIndex = progressValues.length - 1;
|
|
42
|
+
const isOneDismissing = Number(
|
|
43
|
+
progressValues.some((value) => value.closing.value > 0),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const optimisticIndex = activeIndex - isOneDismissing;
|
|
47
|
+
|
|
48
|
+
return optimisticIndex;
|
|
49
|
+
}, [focusedIndex, progressValues]);
|
|
50
|
+
|
|
51
|
+
const parentIndexValue = useSharedValueState(parentIndex);
|
|
52
|
+
|
|
53
|
+
const _registerNested = useCallback(
|
|
54
|
+
(
|
|
55
|
+
nestedIndex: number,
|
|
56
|
+
nestedRoutes: NavigationRoute<ParamListBase, string>[],
|
|
57
|
+
) => {
|
|
58
|
+
if (nestedIndex !== index) {
|
|
59
|
+
setIndex(nestedIndex);
|
|
60
|
+
}
|
|
61
|
+
if (nestedRoutes.length !== routes.length) {
|
|
62
|
+
setRoutes(nestedRoutes);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[routes.length, index],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const context = useBlankStackState();
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
//@ts-expect-error Internally used
|
|
72
|
+
if (context?._registerNested) {
|
|
73
|
+
//@ts-expect-error Internally used
|
|
74
|
+
context._registerNested(parentIndexValue, stackRoutes);
|
|
75
|
+
}
|
|
76
|
+
//@ts-expect-error Internally used
|
|
77
|
+
}, [parentIndexValue, context?._registerNested, stackRoutes]);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
value: {
|
|
81
|
+
parentIndex: parentIndexValue,
|
|
82
|
+
parentRoutes: stackRoutes,
|
|
83
|
+
index,
|
|
84
|
+
routes,
|
|
85
|
+
_registerNested,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export { useBlankStackState, BlankStackStateProvider };
|
package/src/blank-stack/types.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { EdgeInsets } from "react-native-safe-area-context";
|
|
|
16
16
|
import type { ScreenProps } from "react-native-screens";
|
|
17
17
|
import type {
|
|
18
18
|
OverlayInterpolationProps,
|
|
19
|
+
ScreenInterpolationProps,
|
|
19
20
|
ScreenStyleInterpolator,
|
|
20
21
|
TransitionSpec,
|
|
21
22
|
} from "../shared/types/animation";
|
|
@@ -106,6 +107,10 @@ export type BlankStackOverlayProps = {
|
|
|
106
107
|
* Route object for the current screen.
|
|
107
108
|
*/
|
|
108
109
|
route: Route<string>;
|
|
110
|
+
/**
|
|
111
|
+
* Route object for the currently focused screen in the stack.
|
|
112
|
+
*/
|
|
113
|
+
focusedRoute: Route<string>;
|
|
109
114
|
/**
|
|
110
115
|
* Navigation prop for the overlay.
|
|
111
116
|
*/
|
|
@@ -115,9 +120,13 @@ export type BlankStackOverlayProps = {
|
|
|
115
120
|
*/
|
|
116
121
|
insets: EdgeInsets;
|
|
117
122
|
/**
|
|
118
|
-
*
|
|
123
|
+
* Animation values for the overlay.
|
|
124
|
+
*/
|
|
125
|
+
overlayAnimation: DerivedValue<OverlayInterpolationProps>;
|
|
126
|
+
/**
|
|
127
|
+
* Animation values for the screen.
|
|
119
128
|
*/
|
|
120
|
-
|
|
129
|
+
screenAnimation: DerivedValue<ScreenInterpolationProps>;
|
|
121
130
|
/**
|
|
122
131
|
* Index of the active route
|
|
123
132
|
*/
|
|
@@ -513,11 +513,13 @@ export function NativeStackView({
|
|
|
513
513
|
return acc;
|
|
514
514
|
}, {});
|
|
515
515
|
|
|
516
|
+
const routes = state.routes.concat(state.preloadedRoutes);
|
|
517
|
+
|
|
516
518
|
return (
|
|
517
519
|
<GestureHandlerRootView>
|
|
518
520
|
<SafeAreaProviderCompat>
|
|
519
521
|
<ScreenStack style={styles.container}>
|
|
520
|
-
{
|
|
522
|
+
{routes.map((route, index) => {
|
|
521
523
|
const descriptor =
|
|
522
524
|
descriptors[route.key] ?? preloadedDescriptors[route.key];
|
|
523
525
|
const isFocused = state.index === index;
|
|
@@ -4,10 +4,11 @@ import type { BlankStackDescriptor } from "../../../blank-stack/types";
|
|
|
4
4
|
import { useStackNavigationContext } from "../../../blank-stack/utils/with-stack-navigation";
|
|
5
5
|
import type { NativeStackDescriptor } from "../../../native-stack/types";
|
|
6
6
|
import { useParentGestureRegistry } from "../../hooks/gestures/use-parent-gesture-registry";
|
|
7
|
+
import { useDerivedValueState } from "../../hooks/use-derived-value-state";
|
|
7
8
|
import useStableCallback from "../../hooks/use-stable-callback";
|
|
9
|
+
import { useGestureContext } from "../../providers/gestures";
|
|
8
10
|
import { useKeys } from "../../providers/keys";
|
|
9
11
|
import { AnimationStore } from "../../stores/animation-store";
|
|
10
|
-
import { NavigatorDismissState } from "../../stores/navigator-dismiss-state";
|
|
11
12
|
import { resetStoresForScreen } from "../../stores/utils/reset-stores-for-screen";
|
|
12
13
|
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
13
14
|
|
|
@@ -22,19 +23,22 @@ export const NativeStackScreenLifecycleController = ({
|
|
|
22
23
|
children,
|
|
23
24
|
}: ScreenLifecycleProps) => {
|
|
24
25
|
const { current } = useKeys<NativeStackDescriptor>();
|
|
26
|
+
const { parentContext } = useGestureContext();
|
|
27
|
+
|
|
28
|
+
const isParentDismissingViaGesture = useDerivedValueState(() => {
|
|
29
|
+
"worklet";
|
|
30
|
+
return parentContext?.gestureAnimationValues.isDismissing?.value ?? false;
|
|
31
|
+
});
|
|
25
32
|
|
|
26
33
|
const animations = AnimationStore.getAll(current.route.key);
|
|
27
34
|
|
|
28
35
|
const handleBeforeRemove = useStableCallback((e: any) => {
|
|
29
|
-
const key = current.navigation.getParent()?.getState().key;
|
|
30
|
-
const requestedDismissOnNavigator = NavigatorDismissState.get(key);
|
|
31
|
-
|
|
32
36
|
const isEnabled = current.options.enableTransitions;
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
const isFirstScreen = current.navigation.getState().index === 0;
|
|
35
39
|
|
|
36
40
|
// If transitions are disabled, or the dismissal was on the local root, or this is the first screen of the stack, reset the stores
|
|
37
|
-
if (!isEnabled ||
|
|
41
|
+
if (!isEnabled || isParentDismissingViaGesture || isFirstScreen) {
|
|
38
42
|
resetStoresForScreen(current);
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
@@ -105,6 +109,10 @@ export const BlankStackScreenLifecycleController = ({
|
|
|
105
109
|
});
|
|
106
110
|
});
|
|
107
111
|
|
|
112
|
+
const handleCleanup = useStableCallback(() => {
|
|
113
|
+
resetStoresForScreen(current);
|
|
114
|
+
});
|
|
115
|
+
|
|
108
116
|
const handleCloseEnd = useStableCallback((finished: boolean) => {
|
|
109
117
|
if (!finished) {
|
|
110
118
|
return;
|
|
@@ -130,7 +138,12 @@ export const BlankStackScreenLifecycleController = ({
|
|
|
130
138
|
},
|
|
131
139
|
);
|
|
132
140
|
|
|
133
|
-
useLayoutEffect(
|
|
141
|
+
useLayoutEffect(() => {
|
|
142
|
+
handleInitialize();
|
|
143
|
+
return () => {
|
|
144
|
+
handleCleanup();
|
|
145
|
+
};
|
|
146
|
+
}, [handleInitialize, handleCleanup]);
|
|
134
147
|
|
|
135
148
|
// important for t.a scrollviews inside nested navigators.
|
|
136
149
|
useParentGestureRegistry();
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
NO_BOUNDS_MAP,
|
|
10
10
|
} from "../../constants";
|
|
11
11
|
import { type TransitionDescriptor, useKeys } from "../../providers/keys";
|
|
12
|
+
|
|
12
13
|
import { AnimationStore } from "../../stores/animation-store";
|
|
13
14
|
import { BoundStore } from "../../stores/bound-store";
|
|
14
15
|
import { GestureStore, type GestureStoreMap } from "../../stores/gesture-store";
|
|
@@ -121,7 +122,6 @@ export function _useScreenAnimation() {
|
|
|
121
122
|
focused,
|
|
122
123
|
activeBoundId,
|
|
123
124
|
progress,
|
|
124
|
-
|
|
125
125
|
active,
|
|
126
126
|
isActiveTransitioning,
|
|
127
127
|
isDismissing,
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
Fragment,
|
|
4
|
+
useContext,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react";
|
|
2
9
|
import type { View } from "react-native";
|
|
3
10
|
import {
|
|
4
11
|
type AnimatedRef,
|
|
@@ -132,6 +139,30 @@ export const useBoundsRegistry = ({
|
|
|
132
139
|
);
|
|
133
140
|
}, [IS_ROOT, sharedBoundTag, ROOT_SIGNAL]);
|
|
134
141
|
|
|
142
|
+
const prevNextRef = useRef(next);
|
|
143
|
+
/**
|
|
144
|
+
* Measure non-pressable elements when the screen goes from focused to blurred
|
|
145
|
+
* (or when a new `next` descriptor appears) so we capture final bounds
|
|
146
|
+
* right before the transition starts.
|
|
147
|
+
*/
|
|
148
|
+
useLayoutEffect(() => {
|
|
149
|
+
if (!sharedBoundTag || onPress) return;
|
|
150
|
+
|
|
151
|
+
const hadNext = !!prevNextRef.current;
|
|
152
|
+
const hasNext = !!next;
|
|
153
|
+
|
|
154
|
+
if (!hadNext && hasNext) {
|
|
155
|
+
runOnUI(maybeMeasureAndStore)({});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
prevNextRef.current = next;
|
|
159
|
+
}, [next, sharedBoundTag, onPress, maybeMeasureAndStore]);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Signal child shared elements (nested under this provider) to refresh their
|
|
163
|
+
* measurements when the root updates, while preventing them from marking
|
|
164
|
+
* themselves active during that sync.
|
|
165
|
+
*/
|
|
135
166
|
useAnimatedReaction(
|
|
136
167
|
() => ROOT_MEASUREMENT_SIGNAL?.updateSignal.value,
|
|
137
168
|
(current) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StackActions } from "@react-navigation/native";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
2
3
|
import { useWindowDimensions } from "react-native";
|
|
3
4
|
import {
|
|
4
5
|
Gesture,
|
|
@@ -9,11 +10,7 @@ import {
|
|
|
9
10
|
type PanGestureHandlerEventPayload,
|
|
10
11
|
} from "react-native-gesture-handler";
|
|
11
12
|
import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
|
|
12
|
-
import {
|
|
13
|
-
runOnJS,
|
|
14
|
-
type SharedValue,
|
|
15
|
-
useSharedValue,
|
|
16
|
-
} from "react-native-reanimated";
|
|
13
|
+
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
|
17
14
|
import {
|
|
18
15
|
DEFAULT_GESTURE_ACTIVATION_AREA,
|
|
19
16
|
DEFAULT_GESTURE_DIRECTION,
|
|
@@ -21,11 +18,14 @@ import {
|
|
|
21
18
|
DEFAULT_GESTURE_ENABLED,
|
|
22
19
|
GESTURE_VELOCITY_IMPACT,
|
|
23
20
|
} from "../../constants";
|
|
24
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
GestureContextType,
|
|
23
|
+
ScrollConfig,
|
|
24
|
+
} from "../../providers/gestures";
|
|
25
25
|
import { useKeys } from "../../providers/keys";
|
|
26
26
|
import { AnimationStore } from "../../stores/animation-store";
|
|
27
|
-
import { GestureStore } from "../../stores/gesture-store";
|
|
28
|
-
|
|
27
|
+
import { GestureStore, type GestureStoreMap } from "../../stores/gesture-store";
|
|
28
|
+
|
|
29
29
|
import { type GestureDirection, GestureOffsetState } from "../../types/gesture";
|
|
30
30
|
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
31
31
|
import { applyOffsetRules } from "../../utils/gesture/check-gesture-activation";
|
|
@@ -33,18 +33,20 @@ import { determineDismissal } from "../../utils/gesture/determine-dismissal";
|
|
|
33
33
|
import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
|
|
34
34
|
import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
|
|
35
35
|
import { velocity } from "../../utils/gesture/velocity";
|
|
36
|
-
import useStableCallback from "../use-stable-callback";
|
|
37
36
|
import useStableCallbackValue from "../use-stable-callback-value";
|
|
38
37
|
|
|
39
38
|
interface BuildGesturesHookProps {
|
|
40
39
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
40
|
+
parentContext?: GestureContextType | null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export const useBuildGestures = ({
|
|
44
44
|
scrollConfig,
|
|
45
|
+
parentContext,
|
|
45
46
|
}: BuildGesturesHookProps): {
|
|
46
47
|
panGesture: GestureType;
|
|
47
48
|
nativeGesture: GestureType;
|
|
49
|
+
gestureAnimationValues: GestureStoreMap;
|
|
48
50
|
} => {
|
|
49
51
|
const dimensions = useWindowDimensions();
|
|
50
52
|
const { current } = useKeys();
|
|
@@ -58,7 +60,9 @@ export const useBuildGestures = ({
|
|
|
58
60
|
GestureOffsetState.PENDING,
|
|
59
61
|
);
|
|
60
62
|
|
|
61
|
-
const
|
|
63
|
+
const gestureAnimationValues = GestureStore.getRouteGestures(
|
|
64
|
+
current.route.key,
|
|
65
|
+
);
|
|
62
66
|
const animations = AnimationStore.getAll(current.route.key);
|
|
63
67
|
|
|
64
68
|
const {
|
|
@@ -87,17 +91,29 @@ export const useBuildGestures = ({
|
|
|
87
91
|
};
|
|
88
92
|
}, [gestureDirection]);
|
|
89
93
|
|
|
90
|
-
const
|
|
91
|
-
|
|
94
|
+
const handleDismiss = useCallback(() => {
|
|
95
|
+
// If an ancestor navigator is already dismissing, skip this dismiss to
|
|
96
|
+
// avoid racing with the parent
|
|
97
|
+
if (parentContext?.gestureAnimationValues.isDismissing?.value) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
});
|
|
101
|
+
const state = current.navigation.getState();
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
const routeStillPresent = state.routes.some(
|
|
104
|
+
(route) => route.key === current.route.key,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!routeStillPresent) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
current.navigation.dispatch({
|
|
112
|
+
...StackActions.pop(),
|
|
113
|
+
source: current.route.key,
|
|
114
|
+
target: state.key,
|
|
115
|
+
});
|
|
116
|
+
}, [current, parentContext]);
|
|
101
117
|
|
|
102
118
|
const onTouchesDown = useStableCallbackValue((e: GestureTouchEvent) => {
|
|
103
119
|
"worklet";
|
|
@@ -110,6 +126,13 @@ export const useBuildGestures = ({
|
|
|
110
126
|
(e: GestureTouchEvent, manager: GestureStateManagerType) => {
|
|
111
127
|
"worklet";
|
|
112
128
|
|
|
129
|
+
// If an ancestor navigator is already dismissing via gesture, block new gestures here.
|
|
130
|
+
if (parentContext?.gestureAnimationValues.isDismissing?.value) {
|
|
131
|
+
gestureOffsetState.value = GestureOffsetState.FAILED;
|
|
132
|
+
manager.fail();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
113
136
|
const touch = e.changedTouches[0];
|
|
114
137
|
|
|
115
138
|
const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
|
|
@@ -130,7 +153,7 @@ export const useBuildGestures = ({
|
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
// Keep pending until thresholds are met; no eager activation.
|
|
133
|
-
if (
|
|
156
|
+
if (gestureAnimationValues.isDragging?.value) {
|
|
134
157
|
manager.activate();
|
|
135
158
|
return;
|
|
136
159
|
}
|
|
@@ -178,9 +201,9 @@ export const useBuildGestures = ({
|
|
|
178
201
|
if (
|
|
179
202
|
shouldActivate &&
|
|
180
203
|
gestureOffsetState.value === GestureOffsetState.PASSED &&
|
|
181
|
-
!
|
|
204
|
+
!gestureAnimationValues.isDismissing?.value
|
|
182
205
|
) {
|
|
183
|
-
|
|
206
|
+
gestureAnimationValues.direction.value = activatedDirection;
|
|
184
207
|
manager.activate();
|
|
185
208
|
return;
|
|
186
209
|
}
|
|
@@ -189,8 +212,8 @@ export const useBuildGestures = ({
|
|
|
189
212
|
|
|
190
213
|
const onStart = useStableCallbackValue(() => {
|
|
191
214
|
"worklet";
|
|
192
|
-
|
|
193
|
-
|
|
215
|
+
gestureAnimationValues.isDragging.value = 1;
|
|
216
|
+
gestureAnimationValues.isDismissing.value = 0;
|
|
194
217
|
});
|
|
195
218
|
|
|
196
219
|
const onUpdate = useStableCallbackValue(
|
|
@@ -202,13 +225,13 @@ export const useBuildGestures = ({
|
|
|
202
225
|
const { translationX, translationY } = event;
|
|
203
226
|
const { width, height } = dimensions;
|
|
204
227
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
228
|
+
gestureAnimationValues.x.value = translationX;
|
|
229
|
+
gestureAnimationValues.y.value = translationY;
|
|
230
|
+
gestureAnimationValues.normalizedX.value = Math.max(
|
|
208
231
|
-1,
|
|
209
232
|
Math.min(1, translationX / width),
|
|
210
233
|
);
|
|
211
|
-
|
|
234
|
+
gestureAnimationValues.normalizedY.value = Math.max(
|
|
212
235
|
-1,
|
|
213
236
|
Math.min(1, translationY / height),
|
|
214
237
|
);
|
|
@@ -275,16 +298,12 @@ export const useBuildGestures = ({
|
|
|
275
298
|
|
|
276
299
|
resetGestureValues({
|
|
277
300
|
spec,
|
|
278
|
-
gestures,
|
|
301
|
+
gestures: gestureAnimationValues,
|
|
279
302
|
shouldDismiss,
|
|
280
303
|
event,
|
|
281
304
|
dimensions,
|
|
282
305
|
});
|
|
283
306
|
|
|
284
|
-
if (shouldDismiss) {
|
|
285
|
-
runOnJS(setNavigatorDismissal)();
|
|
286
|
-
}
|
|
287
|
-
|
|
288
307
|
const initialVelocity = velocity.calculateProgressVelocity({
|
|
289
308
|
animations,
|
|
290
309
|
shouldDismiss,
|
|
@@ -319,6 +338,15 @@ export const useBuildGestures = ({
|
|
|
319
338
|
return {
|
|
320
339
|
panGesture,
|
|
321
340
|
nativeGesture,
|
|
341
|
+
gestureAnimationValues,
|
|
322
342
|
};
|
|
323
|
-
}, [
|
|
343
|
+
}, [
|
|
344
|
+
gestureEnabled,
|
|
345
|
+
onTouchesDown,
|
|
346
|
+
onTouchesMove,
|
|
347
|
+
onStart,
|
|
348
|
+
onUpdate,
|
|
349
|
+
onEnd,
|
|
350
|
+
gestureAnimationValues,
|
|
351
|
+
]);
|
|
324
352
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
executeOnUIRuntimeSync,
|
|
4
|
+
runOnJS,
|
|
5
|
+
type SharedValue,
|
|
6
|
+
useAnimatedReaction,
|
|
7
|
+
useDerivedValue,
|
|
8
|
+
} from "react-native-reanimated";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Derives React state from a Reanimated worklet.
|
|
12
|
+
*
|
|
13
|
+
* @param processor - The worklet function that calculates the value
|
|
14
|
+
* @param dependencies - Array of dependencies for the worklet
|
|
15
|
+
* @returns The derived value as React State
|
|
16
|
+
*/
|
|
17
|
+
export function useDerivedValueState<T>(
|
|
18
|
+
processor: () => T,
|
|
19
|
+
dependencies: any[] = [],
|
|
20
|
+
): T {
|
|
21
|
+
const derivedValue = useDerivedValue(processor, dependencies);
|
|
22
|
+
|
|
23
|
+
const [state, setState] = useState<T>(() => {
|
|
24
|
+
const readOnUI = executeOnUIRuntimeSync((sv: SharedValue<T>) => {
|
|
25
|
+
"worklet";
|
|
26
|
+
return sv.value;
|
|
27
|
+
});
|
|
28
|
+
return readOnUI(derivedValue);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
useAnimatedReaction(
|
|
32
|
+
() => derivedValue.value,
|
|
33
|
+
(curr, prev) => {
|
|
34
|
+
if (curr !== prev) {
|
|
35
|
+
runOnJS(setState)(curr);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { useCallback, useEffect, useState } from "react";
|
|
6
6
|
import {
|
|
7
|
+
cancelAnimation,
|
|
7
8
|
isWorkletFunction,
|
|
8
9
|
makeMutable,
|
|
9
10
|
runOnJS,
|
|
@@ -13,7 +14,15 @@ import {
|
|
|
13
14
|
type AnyFunction = (...args: Array<any>) => any;
|
|
14
15
|
|
|
15
16
|
function useMutableValue<T>(initialValue: T) {
|
|
16
|
-
|
|
17
|
+
const [mutable] = useState(() => makeMutable(initialValue));
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
return () => {
|
|
21
|
+
cancelAnimation(mutable);
|
|
22
|
+
};
|
|
23
|
+
}, [mutable]);
|
|
24
|
+
|
|
25
|
+
return mutable;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
// We cannot store a function as a SharedValue because reanimated will treat
|
package/src/shared/index.ts
CHANGED
|
@@ -19,3 +19,11 @@ export default {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export { useScreenAnimation } from "./hooks/animation/use-screen-animation";
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
AnimationConfig,
|
|
25
|
+
OverlayInterpolationProps,
|
|
26
|
+
ScreenInterpolationProps,
|
|
27
|
+
ScreenStyleInterpolator,
|
|
28
|
+
} from "./types/animation";
|
|
29
|
+
export type { ScreenTransitionConfig } from "./types/core";
|
|
@@ -5,6 +5,7 @@ import { GestureDetector } from "react-native-gesture-handler";
|
|
|
5
5
|
import type { SharedValue } from "react-native-reanimated";
|
|
6
6
|
import { useSharedValue } from "react-native-reanimated";
|
|
7
7
|
import { useBuildGestures } from "../hooks/gestures/use-build-gestures";
|
|
8
|
+
import type { GestureStoreMap } from "../stores/gesture-store";
|
|
8
9
|
|
|
9
10
|
export type ScrollConfig = {
|
|
10
11
|
x: number;
|
|
@@ -19,34 +20,42 @@ export interface GestureContextType {
|
|
|
19
20
|
panGesture: GestureType;
|
|
20
21
|
nativeGesture: GestureType;
|
|
21
22
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
23
|
+
gestureAnimationValues: GestureStoreMap;
|
|
22
24
|
parentContext: GestureContextType | null;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
type
|
|
27
|
+
type GestureProviderProps = {
|
|
26
28
|
children: React.ReactNode;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const GestureContext = createContext<GestureContextType | undefined>(undefined);
|
|
30
32
|
|
|
31
|
-
export const ScreenGestureProvider = ({
|
|
32
|
-
children,
|
|
33
|
-
}: ScreenGestureProviderProps) => {
|
|
33
|
+
export const ScreenGestureProvider = ({ children }: GestureProviderProps) => {
|
|
34
34
|
const parentContext = useContext(GestureContext);
|
|
35
35
|
|
|
36
36
|
const scrollConfig = useSharedValue<ScrollConfig | null>(null);
|
|
37
37
|
|
|
38
|
-
const { panGesture, nativeGesture } =
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const { panGesture, nativeGesture, gestureAnimationValues } =
|
|
39
|
+
useBuildGestures({
|
|
40
|
+
scrollConfig,
|
|
41
|
+
parentContext,
|
|
42
|
+
});
|
|
41
43
|
|
|
42
44
|
const value: GestureContextType = useMemo(
|
|
43
45
|
() => ({
|
|
44
46
|
panGesture,
|
|
45
47
|
scrollConfig,
|
|
46
48
|
nativeGesture,
|
|
49
|
+
gestureAnimationValues,
|
|
47
50
|
parentContext: parentContext || null,
|
|
48
51
|
}),
|
|
49
|
-
[
|
|
52
|
+
[
|
|
53
|
+
panGesture,
|
|
54
|
+
scrollConfig,
|
|
55
|
+
nativeGesture,
|
|
56
|
+
gestureAnimationValues,
|
|
57
|
+
parentContext,
|
|
58
|
+
],
|
|
50
59
|
);
|
|
51
60
|
|
|
52
61
|
return (
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THANK YOU @MatiPl01
|
|
3
|
+
* https://github.com/MatiPl01/react-native-sortables/blob/main/packages/react-native-sortables/src/providers/utils/createProvider.tsx
|
|
4
|
+
* SUPER COOL AMAZING UTILITY
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
type PropsWithChildren,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
useContext,
|
|
11
|
+
useMemo,
|
|
12
|
+
} from "react";
|
|
13
|
+
|
|
14
|
+
export default function createProvider<
|
|
15
|
+
ProviderName extends string,
|
|
16
|
+
Guarded extends boolean = true,
|
|
17
|
+
>(name: ProviderName, options?: { guarded?: Guarded }) {
|
|
18
|
+
return <ProviderProps extends PropsWithChildren<object>, ContextValue>(
|
|
19
|
+
factory: (props: ProviderProps) => {
|
|
20
|
+
value?: ContextValue;
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
},
|
|
24
|
+
) => {
|
|
25
|
+
const { guarded = true } = options ?? {};
|
|
26
|
+
|
|
27
|
+
const Context = createContext<ContextValue | null>(null);
|
|
28
|
+
Context.displayName = name;
|
|
29
|
+
|
|
30
|
+
const Provider: React.FC<ProviderProps> = (props) => {
|
|
31
|
+
const {
|
|
32
|
+
children = props.children,
|
|
33
|
+
enabled = true,
|
|
34
|
+
value,
|
|
35
|
+
} = factory(props);
|
|
36
|
+
|
|
37
|
+
if (!value) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`${name}Context value must be provided. You likely forgot to return it from the factory function.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const memoValue = useMemo(
|
|
44
|
+
() => (enabled ? value : null),
|
|
45
|
+
[enabled, value],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return <Context.Provider value={memoValue}>{children}</Context.Provider>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const useEnhancedContext = (): ContextValue | null => {
|
|
52
|
+
const context = useContext(Context);
|
|
53
|
+
|
|
54
|
+
if (guarded && context === null) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`${name} context must be used within a ${name}Provider`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return context;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
[`${name}Context`]: Context,
|
|
65
|
+
[`${name}Provider`]: Provider,
|
|
66
|
+
[`use${name}Context`]: useEnhancedContext,
|
|
67
|
+
} as {
|
|
68
|
+
[P in ProviderName as `${P}Context`]: React.Context<ContextValue>;
|
|
69
|
+
} & {
|
|
70
|
+
[P in ProviderName as `${P}Provider`]: React.FC<ProviderProps>;
|
|
71
|
+
} & {
|
|
72
|
+
[P in ProviderName as `use${P}Context`]: () => Guarded extends true
|
|
73
|
+
? ContextValue
|
|
74
|
+
: ContextValue | null;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
}
|