react-native-screen-transitions 3.2.0-beta.3 → 3.2.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 +327 -672
- package/lib/commonjs/shared/components/screen-lifecycle.js +9 -133
- package/lib/commonjs/shared/components/screen-lifecycle.js.map +1 -1
- package/lib/commonjs/shared/constants.js +1 -0
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +3 -0
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +127 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js +35 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js +58 -0
- package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
- package/lib/commonjs/shared/hooks/navigation/use-history.js +24 -0
- package/lib/commonjs/shared/hooks/navigation/use-history.js.map +1 -0
- package/lib/commonjs/shared/index.js +7 -0
- package/lib/commonjs/shared/index.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/keys.provider.js +0 -4
- package/lib/commonjs/shared/providers/screen/keys.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/screen-composer.js +7 -5
- package/lib/commonjs/shared/providers/screen/screen-composer.js.map +1 -1
- package/lib/commonjs/shared/providers/stack/direct.provider.js +9 -0
- package/lib/commonjs/shared/providers/stack/direct.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/stack/managed.provider.js +9 -0
- package/lib/commonjs/shared/providers/stack/managed.provider.js.map +1 -1
- package/lib/commonjs/shared/stores/animation.store.js +3 -13
- package/lib/commonjs/shared/stores/animation.store.js.map +1 -1
- package/lib/commonjs/shared/stores/history.store.js +185 -0
- package/lib/commonjs/shared/stores/history.store.js.map +1 -0
- package/lib/commonjs/shared/types/stack.types.js.map +1 -1
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js +5 -1
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/index.js +19 -4
- package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
- package/lib/module/shared/components/screen-lifecycle.js +9 -132
- package/lib/module/shared/components/screen-lifecycle.js.map +1 -1
- package/lib/module/shared/constants.js +1 -0
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js +3 -0
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js +122 -0
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
- package/lib/module/shared/hooks/lifecycle/use-open-transition.js +32 -0
- package/lib/module/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
- package/lib/module/shared/hooks/lifecycle/use-screen-events.js +54 -0
- package/lib/module/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
- package/lib/module/shared/hooks/navigation/use-history.js +20 -0
- package/lib/module/shared/hooks/navigation/use-history.js.map +1 -0
- package/lib/module/shared/index.js +1 -0
- package/lib/module/shared/index.js.map +1 -1
- package/lib/module/shared/providers/screen/keys.provider.js +0 -4
- package/lib/module/shared/providers/screen/keys.provider.js.map +1 -1
- package/lib/module/shared/providers/screen/screen-composer.js +7 -5
- package/lib/module/shared/providers/screen/screen-composer.js.map +1 -1
- package/lib/module/shared/providers/stack/direct.provider.js +10 -1
- package/lib/module/shared/providers/stack/direct.provider.js.map +1 -1
- package/lib/module/shared/providers/stack/managed.provider.js +10 -1
- package/lib/module/shared/providers/stack/managed.provider.js.map +1 -1
- package/lib/module/shared/stores/animation.store.js +4 -14
- package/lib/module/shared/stores/animation.store.js.map +1 -1
- package/lib/module/shared/stores/history.store.js +181 -0
- package/lib/module/shared/stores/history.store.js.map +1 -0
- package/lib/module/shared/types/stack.types.js.map +1 -1
- package/lib/module/shared/utils/animation/start-screen-transition.js +5 -1
- package/lib/module/shared/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/module/shared/utils/bounds/index.js +19 -4
- package/lib/module/shared/utils/bounds/index.js.map +1 -1
- package/lib/typescript/blank-stack/types.d.ts +0 -3
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/component-stack/types.d.ts +0 -3
- package/lib/typescript/component-stack/types.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-lifecycle.d.ts +4 -1
- package/lib/typescript/shared/components/screen-lifecycle.d.ts.map +1 -1
- package/lib/typescript/shared/constants.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts +13 -0
- package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts +11 -0
- package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts +7 -0
- package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/navigation/use-history.d.ts +37 -0
- package/lib/typescript/shared/hooks/navigation/use-history.d.ts.map +1 -0
- package/lib/typescript/shared/index.d.ts +3 -2
- package/lib/typescript/shared/index.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/keys.provider.d.ts +0 -6
- package/lib/typescript/shared/providers/screen/keys.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/screen-composer.d.ts.map +1 -1
- package/lib/typescript/shared/providers/stack/direct.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/stack/managed.provider.d.ts.map +1 -1
- package/lib/typescript/shared/stores/animation.store.d.ts +3 -4
- package/lib/typescript/shared/stores/animation.store.d.ts.map +1 -1
- package/lib/typescript/shared/stores/history.store.d.ts +82 -0
- package/lib/typescript/shared/stores/history.store.d.ts.map +1 -0
- package/lib/typescript/shared/types/animation.types.d.ts +8 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/stack.types.d.ts +1 -0
- package/lib/typescript/shared/types/stack.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +1 -1
- package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/blank-stack/types.ts +0 -8
- package/src/component-stack/types.ts +0 -9
- package/src/shared/__tests__/history.store.test.ts +550 -0
- package/src/shared/components/screen-lifecycle.tsx +13 -149
- package/src/shared/constants.ts +1 -0
- package/src/shared/hooks/animation/use-screen-animation.tsx +4 -0
- package/src/shared/hooks/lifecycle/use-close-transition.ts +147 -0
- package/src/shared/hooks/lifecycle/use-open-transition.ts +30 -0
- package/src/shared/hooks/lifecycle/use-screen-events.ts +62 -0
- package/src/shared/hooks/navigation/use-history.ts +63 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/providers/screen/keys.provider.tsx +0 -16
- package/src/shared/providers/screen/screen-composer.tsx +6 -10
- package/src/shared/providers/stack/direct.provider.tsx +11 -1
- package/src/shared/providers/stack/managed.provider.tsx +11 -1
- package/src/shared/stores/animation.store.ts +6 -20
- package/src/shared/stores/history.store.ts +201 -0
- package/src/shared/types/animation.types.ts +9 -0
- package/src/shared/types/bounds.types.ts +1 -0
- package/src/shared/types/stack.types.ts +1 -0
- package/src/shared/utils/animation/start-screen-transition.ts +4 -1
- package/src/shared/utils/bounds/index.ts +29 -3
- package/lib/commonjs/shared/utils/read-shared-value.js +0 -17
- package/lib/commonjs/shared/utils/read-shared-value.js.map +0 -1
- package/lib/module/shared/utils/read-shared-value.js +0 -14
- package/lib/module/shared/utils/read-shared-value.js.map +0 -1
- package/lib/typescript/shared/utils/read-shared-value.d.ts +0 -7
- package/lib/typescript/shared/utils/read-shared-value.d.ts.map +0 -1
- package/src/shared/utils/read-shared-value.ts +0 -15
|
@@ -1,168 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
useDerivedValue,
|
|
6
|
-
} from "react-native-reanimated";
|
|
7
|
-
import { useHighRefreshRate } from "../hooks/animation/use-high-refresh-rate";
|
|
8
|
-
import { useSharedValueState } from "../hooks/reanimated/use-shared-value-state";
|
|
9
|
-
import useStableCallback from "../hooks/use-stable-callback";
|
|
10
|
-
import { useGestureContext } from "../providers/gestures.provider";
|
|
11
|
-
import {
|
|
12
|
-
type BaseDescriptor,
|
|
13
|
-
useKeys,
|
|
14
|
-
} from "../providers/screen/keys.provider";
|
|
15
|
-
import { useStackCoreContext } from "../providers/stack/core.provider";
|
|
16
|
-
import { useManagedStackContext } from "../providers/stack/managed.provider";
|
|
1
|
+
import { useCloseTransition } from "../hooks/lifecycle/use-close-transition";
|
|
2
|
+
import { useOpenTransition } from "../hooks/lifecycle/use-open-transition";
|
|
3
|
+
import { useScreenEvents } from "../hooks/lifecycle/use-screen-events";
|
|
4
|
+
import type { BaseDescriptor } from "../providers/screen/keys.provider";
|
|
17
5
|
import { AnimationStore } from "../stores/animation.store";
|
|
18
|
-
import { StackType } from "../types/stack.types";
|
|
19
|
-
import { startScreenTransition } from "../utils/animation/start-screen-transition";
|
|
20
|
-
import { resetStoresForScreen } from "../utils/reset-stores-for-screen";
|
|
21
6
|
|
|
22
7
|
interface Props {
|
|
23
8
|
children: React.ReactNode;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface CloseHookParams {
|
|
27
9
|
current: BaseDescriptor;
|
|
28
|
-
|
|
29
|
-
activate: () => void;
|
|
30
|
-
deactivate: () => void;
|
|
10
|
+
previous?: BaseDescriptor;
|
|
31
11
|
}
|
|
32
12
|
|
|
33
|
-
/**
|
|
34
|
-
* Managed close - reacts to closingRouteKeysShared from ManagedStackContext.
|
|
35
|
-
* Used by blank-stack and component-stack.
|
|
36
|
-
*/
|
|
37
|
-
const useManagedClose = ({
|
|
38
|
-
current,
|
|
39
|
-
animations,
|
|
40
|
-
activate,
|
|
41
|
-
deactivate,
|
|
42
|
-
}: CloseHookParams) => {
|
|
43
|
-
const { handleCloseRoute, closingRouteKeysShared } = useManagedStackContext();
|
|
44
|
-
|
|
45
|
-
const handleCloseEnd = useStableCallback((finished: boolean) => {
|
|
46
|
-
if (!finished) return;
|
|
47
|
-
handleCloseRoute({ route: current.route });
|
|
48
|
-
requestAnimationFrame(() => {
|
|
49
|
-
deactivate();
|
|
50
|
-
resetStoresForScreen(current);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
useAnimatedReaction(
|
|
55
|
-
() => closingRouteKeysShared.value,
|
|
56
|
-
(keys) => {
|
|
57
|
-
if (!keys?.includes(current.route.key)) return;
|
|
58
|
-
|
|
59
|
-
runOnJS(activate)();
|
|
60
|
-
startScreenTransition({
|
|
61
|
-
target: "close",
|
|
62
|
-
spec: current.options.transitionSpec,
|
|
63
|
-
animations,
|
|
64
|
-
onAnimationFinish: handleCloseEnd,
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Native stack close - listens to beforeRemove navigation event.
|
|
72
|
-
*/
|
|
73
|
-
const useNativeStackClose = ({
|
|
74
|
-
current,
|
|
75
|
-
animations,
|
|
76
|
-
activate,
|
|
77
|
-
deactivate,
|
|
78
|
-
}: CloseHookParams) => {
|
|
79
|
-
const gestureCtx = useGestureContext();
|
|
80
|
-
|
|
81
|
-
const isAncestorDismissingViaGesture = useSharedValueState(
|
|
82
|
-
useDerivedValue(() => {
|
|
83
|
-
"worklet";
|
|
84
|
-
return (
|
|
85
|
-
gestureCtx?.ancestorContext?.gestureAnimationValues.isDismissing
|
|
86
|
-
?.value ?? false
|
|
87
|
-
);
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const handleBeforeRemove = useStableCallback((e: any) => {
|
|
92
|
-
const options = current.options as { enableTransitions?: boolean };
|
|
93
|
-
const isEnabled = options.enableTransitions;
|
|
94
|
-
const navigation = current.navigation;
|
|
95
|
-
const isFirstScreen = navigation.getState().index === 0;
|
|
96
|
-
|
|
97
|
-
// If transitions are disabled, ancestor is dismissing, or first screen - let native handle it
|
|
98
|
-
if (!isEnabled || isAncestorDismissingViaGesture || isFirstScreen) {
|
|
99
|
-
animations.closing.set(1);
|
|
100
|
-
resetStoresForScreen(current);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
e.preventDefault();
|
|
105
|
-
activate();
|
|
106
|
-
|
|
107
|
-
startScreenTransition({
|
|
108
|
-
target: "close",
|
|
109
|
-
spec: current.options.transitionSpec,
|
|
110
|
-
animations,
|
|
111
|
-
onAnimationFinish: (finished: boolean) => {
|
|
112
|
-
deactivate();
|
|
113
|
-
if (finished) {
|
|
114
|
-
navigation.dispatch(e.data.action);
|
|
115
|
-
requestAnimationFrame(() => {
|
|
116
|
-
resetStoresForScreen(current);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: Only re-subscribe when navigation changes
|
|
124
|
-
useLayoutEffect(() => {
|
|
125
|
-
return current.navigation.addListener?.("beforeRemove", handleBeforeRemove);
|
|
126
|
-
}, [current.navigation]);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
13
|
/**
|
|
130
14
|
* Unified lifecycle controller for all stack types.
|
|
131
15
|
*/
|
|
132
|
-
export const ScreenLifecycle = ({ children }: Props) => {
|
|
133
|
-
const { flags } = useStackCoreContext();
|
|
134
|
-
const { current } = useKeys();
|
|
16
|
+
export const ScreenLifecycle = ({ children, current, previous }: Props) => {
|
|
135
17
|
const animations = AnimationStore.getAll(current.route.key);
|
|
136
|
-
const { activateHighRefreshRate, deactivateHighRefreshRate } =
|
|
137
|
-
useHighRefreshRate(current);
|
|
138
18
|
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: Must only run once on mount
|
|
142
|
-
useLayoutEffect(() => {
|
|
143
|
-
activateHighRefreshRate();
|
|
144
|
-
startScreenTransition({
|
|
145
|
-
target: "open",
|
|
146
|
-
spec: current.options.transitionSpec,
|
|
147
|
-
animations,
|
|
148
|
-
onAnimationFinish: deactivateHighRefreshRate,
|
|
149
|
-
});
|
|
150
|
-
}, []);
|
|
19
|
+
const { activateHighRefreshRate, deactivateHighRefreshRate } =
|
|
20
|
+
useOpenTransition(current, animations);
|
|
151
21
|
|
|
152
|
-
|
|
22
|
+
useCloseTransition(
|
|
153
23
|
current,
|
|
154
24
|
animations,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
25
|
+
activateHighRefreshRate,
|
|
26
|
+
deactivateHighRefreshRate,
|
|
27
|
+
);
|
|
158
28
|
|
|
159
|
-
|
|
160
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: STACK_TYPE is stable per screen instance
|
|
161
|
-
useNativeStackClose(closeParams);
|
|
162
|
-
} else {
|
|
163
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: STACK_TYPE is stable per screen instance
|
|
164
|
-
useManagedClose(closeParams);
|
|
165
|
-
}
|
|
29
|
+
useScreenEvents(current, previous, animations);
|
|
166
30
|
|
|
167
31
|
return children;
|
|
168
32
|
};
|
package/src/shared/constants.ts
CHANGED
|
@@ -24,6 +24,7 @@ type BuiltState = {
|
|
|
24
24
|
progress: SharedValue<number>;
|
|
25
25
|
closing: SharedValue<number>;
|
|
26
26
|
animating: SharedValue<number>;
|
|
27
|
+
entering: SharedValue<number>;
|
|
27
28
|
gesture: GestureStoreMap;
|
|
28
29
|
route: BaseStackRoute;
|
|
29
30
|
meta?: Record<string, unknown>;
|
|
@@ -37,6 +38,7 @@ const createScreenTransitionState = (
|
|
|
37
38
|
progress: 0,
|
|
38
39
|
closing: 0,
|
|
39
40
|
animating: 0,
|
|
41
|
+
entering: 1,
|
|
40
42
|
gesture: {
|
|
41
43
|
x: 0,
|
|
42
44
|
y: 0,
|
|
@@ -55,6 +57,7 @@ const unwrapInto = (s: BuiltState): ScreenTransitionState => {
|
|
|
55
57
|
const out = s.unwrapped;
|
|
56
58
|
out.progress = s.progress.value;
|
|
57
59
|
out.closing = s.closing.value;
|
|
60
|
+
out.entering = s.entering.value;
|
|
58
61
|
out.animating = s.animating.value;
|
|
59
62
|
out.gesture.x = s.gesture.x.value;
|
|
60
63
|
out.gesture.y = s.gesture.y.value;
|
|
@@ -80,6 +83,7 @@ const useBuildScreenTransitionState = (
|
|
|
80
83
|
return {
|
|
81
84
|
progress: AnimationStore.getAnimation(key, "progress"),
|
|
82
85
|
closing: AnimationStore.getAnimation(key, "closing"),
|
|
86
|
+
entering: AnimationStore.getAnimation(key, "entering"),
|
|
83
87
|
animating: AnimationStore.getAnimation(key, "animating"),
|
|
84
88
|
gesture: GestureStore.getRouteGestures(key),
|
|
85
89
|
route: descriptor.route,
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
runOnJS,
|
|
4
|
+
useAnimatedReaction,
|
|
5
|
+
useDerivedValue,
|
|
6
|
+
} from "react-native-reanimated";
|
|
7
|
+
import { useGestureContext } from "../../providers/gestures.provider";
|
|
8
|
+
import type { BaseDescriptor } from "../../providers/screen/keys.provider";
|
|
9
|
+
import { useStackCoreContext } from "../../providers/stack/core.provider";
|
|
10
|
+
import { useManagedStackContext } from "../../providers/stack/managed.provider";
|
|
11
|
+
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
12
|
+
import { StackType } from "../../types/stack.types";
|
|
13
|
+
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
14
|
+
import { resetStoresForScreen } from "../../utils/reset-stores-for-screen";
|
|
15
|
+
import { useSharedValueState } from "../reanimated/use-shared-value-state";
|
|
16
|
+
import useStableCallback from "../use-stable-callback";
|
|
17
|
+
|
|
18
|
+
export interface CloseHookParams {
|
|
19
|
+
current: BaseDescriptor;
|
|
20
|
+
animations: AnimationStoreMap;
|
|
21
|
+
activate: () => void;
|
|
22
|
+
deactivate: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Managed close - reacts to closingRouteKeysShared from ManagedStackContext.
|
|
27
|
+
* Used by blank-stack and component-stack.
|
|
28
|
+
*/
|
|
29
|
+
const useManagedClose = ({
|
|
30
|
+
current,
|
|
31
|
+
animations,
|
|
32
|
+
activate,
|
|
33
|
+
deactivate,
|
|
34
|
+
}: CloseHookParams) => {
|
|
35
|
+
const { handleCloseRoute, closingRouteKeysShared } = useManagedStackContext();
|
|
36
|
+
|
|
37
|
+
const handleCloseEnd = useStableCallback((finished: boolean) => {
|
|
38
|
+
if (!finished) return;
|
|
39
|
+
handleCloseRoute({ route: current.route });
|
|
40
|
+
requestAnimationFrame(() => {
|
|
41
|
+
deactivate();
|
|
42
|
+
resetStoresForScreen(current);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
useAnimatedReaction(
|
|
47
|
+
() => closingRouteKeysShared.value,
|
|
48
|
+
(keys) => {
|
|
49
|
+
if (!keys?.includes(current.route.key)) return;
|
|
50
|
+
|
|
51
|
+
runOnJS(activate)();
|
|
52
|
+
startScreenTransition({
|
|
53
|
+
target: "close",
|
|
54
|
+
spec: current.options.transitionSpec,
|
|
55
|
+
animations,
|
|
56
|
+
onAnimationFinish: handleCloseEnd,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Native stack close - listens to beforeRemove navigation event.
|
|
64
|
+
*/
|
|
65
|
+
const useNativeStackClose = ({
|
|
66
|
+
current,
|
|
67
|
+
animations,
|
|
68
|
+
activate,
|
|
69
|
+
deactivate,
|
|
70
|
+
}: CloseHookParams) => {
|
|
71
|
+
const gestureCtx = useGestureContext();
|
|
72
|
+
|
|
73
|
+
const isAncestorDismissingViaGesture = useSharedValueState(
|
|
74
|
+
useDerivedValue(() => {
|
|
75
|
+
"worklet";
|
|
76
|
+
return (
|
|
77
|
+
gestureCtx?.ancestorContext?.gestureAnimationValues.isDismissing
|
|
78
|
+
?.value ?? false
|
|
79
|
+
);
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const handleBeforeRemove = useStableCallback((e: any) => {
|
|
84
|
+
const options = current.options as { enableTransitions?: boolean };
|
|
85
|
+
const isEnabled = options.enableTransitions;
|
|
86
|
+
const navigation = current.navigation;
|
|
87
|
+
const isFirstScreen = navigation.getState().index === 0;
|
|
88
|
+
|
|
89
|
+
// If transitions are disabled, ancestor is dismissing, or first screen - let native handle it
|
|
90
|
+
if (!isEnabled || isAncestorDismissingViaGesture || isFirstScreen) {
|
|
91
|
+
animations.closing.set(1);
|
|
92
|
+
resetStoresForScreen(current);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
activate();
|
|
98
|
+
|
|
99
|
+
startScreenTransition({
|
|
100
|
+
target: "close",
|
|
101
|
+
spec: current.options.transitionSpec,
|
|
102
|
+
animations,
|
|
103
|
+
onAnimationFinish: (finished: boolean) => {
|
|
104
|
+
deactivate();
|
|
105
|
+
if (finished) {
|
|
106
|
+
navigation.dispatch(e.data.action);
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
resetStoresForScreen(current);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Only re-subscribe when navigation changes
|
|
116
|
+
useLayoutEffect(() => {
|
|
117
|
+
return current.navigation.addListener?.("beforeRemove", handleBeforeRemove);
|
|
118
|
+
}, [current.navigation]);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Unified close handler that branches on stack type.
|
|
123
|
+
*/
|
|
124
|
+
export function useCloseTransition(
|
|
125
|
+
current: BaseDescriptor,
|
|
126
|
+
animations: AnimationStoreMap,
|
|
127
|
+
activate: () => void,
|
|
128
|
+
deactivate: () => void,
|
|
129
|
+
) {
|
|
130
|
+
const { flags } = useStackCoreContext();
|
|
131
|
+
const isNativeStack = flags.STACK_TYPE === StackType.NATIVE;
|
|
132
|
+
|
|
133
|
+
const closeParams: CloseHookParams = {
|
|
134
|
+
current,
|
|
135
|
+
animations,
|
|
136
|
+
activate,
|
|
137
|
+
deactivate,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (isNativeStack) {
|
|
141
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: STACK_TYPE is stable per screen instance
|
|
142
|
+
useNativeStackClose(closeParams);
|
|
143
|
+
} else {
|
|
144
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: STACK_TYPE is stable per screen instance
|
|
145
|
+
useManagedClose(closeParams);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
import type { BaseDescriptor } from "../../providers/screen/keys.provider";
|
|
3
|
+
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
4
|
+
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
5
|
+
import { useHighRefreshRate } from "../animation/use-high-refresh-rate";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handles opening animation on mount.
|
|
9
|
+
* Returns activate/deactivate functions for high refresh rate.
|
|
10
|
+
*/
|
|
11
|
+
export function useOpenTransition(
|
|
12
|
+
current: BaseDescriptor,
|
|
13
|
+
animations: AnimationStoreMap,
|
|
14
|
+
) {
|
|
15
|
+
const { activateHighRefreshRate, deactivateHighRefreshRate } =
|
|
16
|
+
useHighRefreshRate(current);
|
|
17
|
+
|
|
18
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Must only run once on mount
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
activateHighRefreshRate();
|
|
21
|
+
startScreenTransition({
|
|
22
|
+
target: "open",
|
|
23
|
+
spec: current.options.transitionSpec,
|
|
24
|
+
animations,
|
|
25
|
+
onAnimationFinish: deactivateHighRefreshRate,
|
|
26
|
+
});
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
return { activateHighRefreshRate, deactivateHighRefreshRate };
|
|
30
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { runOnJS, useAnimatedReaction } from "react-native-reanimated";
|
|
3
|
+
import type { BaseDescriptor } from "../../providers/screen/keys.provider";
|
|
4
|
+
import type { AnimationStoreMap } from "../../stores/animation.store";
|
|
5
|
+
import { HistoryStore } from "../../stores/history.store";
|
|
6
|
+
import useStableCallback from "../use-stable-callback";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if a screen is a leaf (renders visible content) vs a navigator container.
|
|
10
|
+
* Navigator containers have nested state with routes.
|
|
11
|
+
*/
|
|
12
|
+
function isLeafScreen(navigation: BaseDescriptor["navigation"]): boolean {
|
|
13
|
+
const state = navigation.getState();
|
|
14
|
+
const currentRoute = state.routes[state.index];
|
|
15
|
+
return !("state" in currentRoute);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Updates the HistoryStore for navigation history tracking.
|
|
20
|
+
*/
|
|
21
|
+
export function useScreenEvents(
|
|
22
|
+
current: BaseDescriptor,
|
|
23
|
+
previous: BaseDescriptor | undefined,
|
|
24
|
+
animations: AnimationStoreMap,
|
|
25
|
+
) {
|
|
26
|
+
const navigatorKey = current.navigation.getState()?.key ?? "";
|
|
27
|
+
|
|
28
|
+
// Track history via focus listener - waits for nested navigators to initialize
|
|
29
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Must only run once on mount
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
// Check on mount (after paint, nested navs initialized)
|
|
32
|
+
if (isLeafScreen(current.navigation)) {
|
|
33
|
+
HistoryStore.focus(current, navigatorKey);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Also listen for focus events
|
|
37
|
+
const unsubscribe = current.navigation.addListener?.("focus", () => {
|
|
38
|
+
if (isLeafScreen(current.navigation)) {
|
|
39
|
+
HistoryStore.focus(current, navigatorKey);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return () => unsubscribe?.();
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
// When closing starts, focus previous in history
|
|
47
|
+
const handleBlur = useStableCallback(() => {
|
|
48
|
+
if (previous && isLeafScreen(previous.navigation)) {
|
|
49
|
+
const prevNavigatorKey = previous.navigation.getState()?.key ?? "";
|
|
50
|
+
HistoryStore.focus(previous, prevNavigatorKey);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
useAnimatedReaction(
|
|
55
|
+
() => animations.closing.get(),
|
|
56
|
+
(closing, prevClosing) => {
|
|
57
|
+
if (closing && !prevClosing) {
|
|
58
|
+
runOnJS(handleBlur)();
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
import { type HistoryEntry, HistoryStore } from "../../stores/history.store";
|
|
3
|
+
import type { ScreenKey } from "../../types/screen.types";
|
|
4
|
+
|
|
5
|
+
export interface UseHistoryReturn {
|
|
6
|
+
/**
|
|
7
|
+
* The full history map.
|
|
8
|
+
*/
|
|
9
|
+
history: ReadonlyMap<ScreenKey, HistoryEntry>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get N most recent history entries.
|
|
13
|
+
* Most recent first.
|
|
14
|
+
*/
|
|
15
|
+
getRecent: (n: number) => HistoryEntry[];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get history entries for a specific navigator.
|
|
19
|
+
* Most recent first.
|
|
20
|
+
*/
|
|
21
|
+
getByNavigator: (navigatorKey: string) => HistoryEntry[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the path between two screens (for multi-waypoint interpolation).
|
|
25
|
+
* Returns screen keys in order from 'from' to 'to'.
|
|
26
|
+
*/
|
|
27
|
+
getPath: (fromKey: ScreenKey, toKey: ScreenKey) => ScreenKey[];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get a specific history entry by screen key.
|
|
31
|
+
*/
|
|
32
|
+
get: (screenKey: ScreenKey) => HistoryEntry | undefined;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the most recent history entry (for forward navigation).
|
|
36
|
+
*/
|
|
37
|
+
getMostRecent: () => HistoryEntry | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to history store changes.
|
|
42
|
+
* Returns the full history map and helper methods.
|
|
43
|
+
*/
|
|
44
|
+
export function useHistory(): UseHistoryReturn {
|
|
45
|
+
const history = useSyncExternalStore(
|
|
46
|
+
HistoryStore.subscribe,
|
|
47
|
+
HistoryStore.getSnapshot,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
history,
|
|
53
|
+
getRecent: (n: number) => HistoryStore.getRecent(n),
|
|
54
|
+
getByNavigator: (navigatorKey: string) =>
|
|
55
|
+
HistoryStore.getByNavigator(navigatorKey),
|
|
56
|
+
getPath: (fromKey: ScreenKey, toKey: ScreenKey) =>
|
|
57
|
+
HistoryStore.getPath(fromKey, toKey),
|
|
58
|
+
get: (screenKey: ScreenKey) => HistoryStore.get(screenKey),
|
|
59
|
+
getMostRecent: () => HistoryStore.getMostRecent(),
|
|
60
|
+
}),
|
|
61
|
+
[history],
|
|
62
|
+
);
|
|
63
|
+
}
|
package/src/shared/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export default {
|
|
|
20
20
|
|
|
21
21
|
export { useScreenAnimation } from "./hooks/animation/use-screen-animation";
|
|
22
22
|
export { useScreenGesture } from "./hooks/gestures/use-screen-gesture";
|
|
23
|
+
export { useHistory } from "./hooks/navigation/use-history";
|
|
23
24
|
export {
|
|
24
25
|
type ScreenState,
|
|
25
26
|
useScreenState,
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Descriptor,
|
|
3
|
-
NavigationProp,
|
|
4
|
-
ParamListBase,
|
|
5
|
-
RouteProp,
|
|
6
|
-
} from "@react-navigation/native";
|
|
7
1
|
import { createContext, useContext, useMemo } from "react";
|
|
8
|
-
import type { ScreenTransitionConfig } from "../../types/screen.types";
|
|
9
2
|
import type { BaseStackDescriptor } from "../../types/stack.types";
|
|
10
3
|
|
|
11
4
|
/**
|
|
@@ -15,15 +8,6 @@ import type { BaseStackDescriptor } from "../../types/stack.types";
|
|
|
15
8
|
*/
|
|
16
9
|
export type BaseDescriptor = BaseStackDescriptor;
|
|
17
10
|
|
|
18
|
-
/**
|
|
19
|
-
* React Navigation specific descriptor - extends base with full typing
|
|
20
|
-
*/
|
|
21
|
-
export type TransitionDescriptor = Descriptor<
|
|
22
|
-
ScreenTransitionConfig,
|
|
23
|
-
NavigationProp<ParamListBase>,
|
|
24
|
-
RouteProp<ParamListBase>
|
|
25
|
-
>;
|
|
26
|
-
|
|
27
11
|
interface KeysContextType<TDescriptor extends BaseDescriptor = BaseDescriptor> {
|
|
28
12
|
previous?: TDescriptor;
|
|
29
13
|
current: TDescriptor;
|
|
@@ -19,18 +19,14 @@ export function ScreenComposer<TDescriptor extends BaseDescriptor>({
|
|
|
19
19
|
children,
|
|
20
20
|
}: Props<TDescriptor>) {
|
|
21
21
|
return (
|
|
22
|
-
<
|
|
23
|
-
previous={previous}
|
|
24
|
-
|
|
25
|
-
next={next}
|
|
26
|
-
>
|
|
27
|
-
<ScreenGestureProvider>
|
|
28
|
-
<ScreenLifecycle>
|
|
22
|
+
<ScreenLifecycle current={current} previous={previous}>
|
|
23
|
+
<KeysProvider previous={previous} current={current} next={next}>
|
|
24
|
+
<ScreenGestureProvider>
|
|
29
25
|
<ScreenStylesProvider>
|
|
30
26
|
<RootTransitionAware>{children}</RootTransitionAware>
|
|
31
27
|
</ScreenStylesProvider>
|
|
32
|
-
</
|
|
33
|
-
</
|
|
34
|
-
</
|
|
28
|
+
</ScreenGestureProvider>
|
|
29
|
+
</KeysProvider>
|
|
30
|
+
</ScreenLifecycle>
|
|
35
31
|
);
|
|
36
32
|
}
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
StackNavigationState,
|
|
5
5
|
} from "@react-navigation/native";
|
|
6
6
|
import * as React from "react";
|
|
7
|
-
import { useMemo } from "react";
|
|
7
|
+
import { useEffect, useMemo } from "react";
|
|
8
8
|
import { type DerivedValue, useDerivedValue } from "react-native-reanimated";
|
|
9
9
|
import type {
|
|
10
10
|
NativeStackDescriptor,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type StackContextValue,
|
|
17
17
|
} from "../../hooks/navigation/use-stack";
|
|
18
18
|
import { AnimationStore } from "../../stores/animation.store";
|
|
19
|
+
import { HistoryStore } from "../../stores/history.store";
|
|
19
20
|
import { useStackCoreContext } from "./core.provider";
|
|
20
21
|
|
|
21
22
|
export interface DirectStackScene {
|
|
@@ -216,6 +217,15 @@ function withDirectStack<TProps extends DirectStackProps>(
|
|
|
216
217
|
Component: React.ComponentType<DirectStackContextValue>,
|
|
217
218
|
): React.FC<TProps> {
|
|
218
219
|
return function DirectStackProvider(props: TProps) {
|
|
220
|
+
const navigatorKey = props.state.key;
|
|
221
|
+
|
|
222
|
+
// Clean up history when navigator unmounts
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
return () => {
|
|
225
|
+
HistoryStore.clearNavigator(navigatorKey);
|
|
226
|
+
};
|
|
227
|
+
}, [navigatorKey]);
|
|
228
|
+
|
|
219
229
|
const { stackContextValue, ...lifecycleValue } = useDirectStackValue(props);
|
|
220
230
|
|
|
221
231
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Route } from "@react-navigation/native";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { useMemo } from "react";
|
|
3
|
+
import { useEffect, useMemo } from "react";
|
|
4
4
|
import {
|
|
5
5
|
type DerivedValue,
|
|
6
6
|
type SharedValue,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type StackContextValue,
|
|
12
12
|
} from "../../hooks/navigation/use-stack";
|
|
13
13
|
import { AnimationStore } from "../../stores/animation.store";
|
|
14
|
+
import { HistoryStore } from "../../stores/history.store";
|
|
14
15
|
import type {
|
|
15
16
|
BaseStackDescriptor,
|
|
16
17
|
BaseStackNavigation,
|
|
@@ -211,6 +212,15 @@ function withManagedStack<
|
|
|
211
212
|
return function ManagedStackProvider(
|
|
212
213
|
props: ManagedStackProps<TDescriptor, TNavigation>,
|
|
213
214
|
) {
|
|
215
|
+
const navigatorKey = props.state.key;
|
|
216
|
+
|
|
217
|
+
// Clean up history when navigator unmounts
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
return () => {
|
|
220
|
+
HistoryStore.clearNavigator(navigatorKey);
|
|
221
|
+
};
|
|
222
|
+
}, [navigatorKey]);
|
|
223
|
+
|
|
214
224
|
const { stackContextValue, ...lifecycleValue } = useManagedStackValue<
|
|
215
225
|
TDescriptor,
|
|
216
226
|
TNavigation
|