react-native-screen-transitions 3.0.0-rc.2 → 3.0.0-rc.3
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 +421 -371
- package/lib/commonjs/blank-stack/components/{Overlay.js → overlay.js} +7 -5
- package/lib/commonjs/blank-stack/components/overlay.js.map +1 -0
- package/lib/commonjs/blank-stack/components/{Screens.js → screens.js} +8 -10
- package/lib/commonjs/blank-stack/components/screens.js.map +1 -0
- package/lib/commonjs/blank-stack/components/stack-view.js +95 -0
- package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -0
- package/lib/commonjs/blank-stack/index.js +1 -8
- package/lib/commonjs/blank-stack/index.js.map +1 -1
- package/lib/commonjs/blank-stack/navigators/{createBlankStackNavigator.js → create-blank-stack-navigator.js} +3 -3
- package/lib/commonjs/blank-stack/navigators/create-blank-stack-navigator.js.map +1 -0
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js +1 -11
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js +1 -12
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js +49 -55
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/{_types.js → types.js} +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/types.js.map +1 -0
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +38 -22
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/providers/flags.provider.js +25 -0
- package/lib/commonjs/shared/providers/flags.provider.js.map +1 -0
- package/lib/commonjs/shared/providers/register-bounds.provider.js +71 -45
- package/lib/commonjs/shared/providers/register-bounds.provider.js.map +1 -1
- package/lib/commonjs/shared/stores/bounds.store.js +91 -47
- package/lib/commonjs/shared/stores/bounds.store.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/helpers/is-bounds-equal.js +1 -1
- package/lib/commonjs/shared/utils/bounds/helpers/is-bounds-equal.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/index.js +4 -5
- package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
- package/lib/commonjs/shared/utils/create-provider.js +20 -1
- package/lib/commonjs/shared/utils/create-provider.js.map +1 -1
- package/lib/commonjs/shared/utils/reset-stores-for-screen.js +2 -0
- package/lib/commonjs/shared/utils/reset-stores-for-screen.js.map +1 -1
- package/lib/module/blank-stack/components/{Overlay.js → overlay.js} +7 -5
- package/lib/module/blank-stack/components/overlay.js.map +1 -0
- package/lib/module/blank-stack/components/{Screens.js → screens.js} +8 -10
- package/lib/module/blank-stack/components/screens.js.map +1 -0
- package/lib/module/blank-stack/components/stack-view.js +90 -0
- package/lib/module/blank-stack/components/stack-view.js.map +1 -0
- package/lib/module/blank-stack/index.js +1 -2
- package/lib/module/blank-stack/index.js.map +1 -1
- package/lib/module/blank-stack/navigators/{createBlankStackNavigator.js → create-blank-stack-navigator.js} +2 -2
- package/lib/module/blank-stack/navigators/create-blank-stack-navigator.js.map +1 -0
- package/lib/module/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js +1 -11
- package/lib/module/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js +1 -12
- package/lib/module/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js +48 -54
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/types.js +4 -0
- package/lib/module/blank-stack/utils/with-stack-navigation/types.js.map +1 -0
- package/lib/module/shared/hooks/animation/use-screen-animation.js +38 -22
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/providers/flags.provider.js +19 -0
- package/lib/module/shared/providers/flags.provider.js.map +1 -0
- package/lib/module/shared/providers/register-bounds.provider.js +71 -45
- package/lib/module/shared/providers/register-bounds.provider.js.map +1 -1
- package/lib/module/shared/stores/bounds.store.js +91 -47
- package/lib/module/shared/stores/bounds.store.js.map +1 -1
- package/lib/module/shared/utils/bounds/helpers/is-bounds-equal.js +1 -1
- package/lib/module/shared/utils/bounds/helpers/is-bounds-equal.js.map +1 -1
- package/lib/module/shared/utils/bounds/index.js +4 -5
- package/lib/module/shared/utils/bounds/index.js.map +1 -1
- package/lib/module/shared/utils/create-provider.js +20 -1
- package/lib/module/shared/utils/create-provider.js.map +1 -1
- package/lib/module/shared/utils/reset-stores-for-screen.js +2 -0
- package/lib/module/shared/utils/reset-stores-for-screen.js.map +1 -1
- package/lib/typescript/blank-stack/components/{Overlay.d.ts → overlay.d.ts} +1 -1
- package/lib/typescript/blank-stack/components/overlay.d.ts.map +1 -0
- package/lib/typescript/blank-stack/components/{Screens.d.ts → screens.d.ts} +1 -1
- package/lib/typescript/blank-stack/components/{Screens.d.ts.map → screens.d.ts.map} +1 -1
- package/lib/typescript/blank-stack/components/stack-view.d.ts +3 -0
- package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -0
- package/lib/typescript/blank-stack/index.d.ts +1 -2
- package/lib/typescript/blank-stack/index.d.ts.map +1 -1
- package/lib/typescript/blank-stack/navigators/{createBlankStackNavigator.d.ts → create-blank-stack-navigator.d.ts} +1 -1
- package/lib/typescript/blank-stack/navigators/create-blank-stack-navigator.d.ts.map +1 -0
- package/lib/typescript/blank-stack/types.d.ts +4 -0
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.d.ts +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/index.d.ts +3 -5
- package/lib/typescript/blank-stack/utils/with-stack-navigation/index.d.ts.map +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/{_types.d.ts → types.d.ts} +1 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/types.d.ts.map +1 -0
- 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.map +1 -1
- package/lib/typescript/shared/index.d.ts +20 -20
- package/lib/typescript/shared/providers/flags.provider.d.ts +10 -0
- package/lib/typescript/shared/providers/flags.provider.d.ts.map +1 -0
- package/lib/typescript/shared/providers/register-bounds.provider.d.ts.map +1 -1
- package/lib/typescript/shared/stores/bounds.store.d.ts +23 -11
- package/lib/typescript/shared/stores/bounds.store.d.ts.map +1 -1
- package/lib/typescript/shared/types/bounds.types.d.ts +2 -2
- package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
- package/lib/typescript/shared/utils/create-provider.d.ts +2 -2
- package/lib/typescript/shared/utils/create-provider.d.ts.map +1 -1
- package/lib/typescript/shared/utils/reset-stores-for-screen.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/blank-stack/components/{Overlay.tsx → overlay.tsx} +4 -3
- package/src/blank-stack/components/{Screens.tsx → screens.tsx} +7 -9
- package/src/blank-stack/components/stack-view.tsx +104 -0
- package/src/blank-stack/index.ts +1 -2
- package/src/blank-stack/navigators/{createBlankStackNavigator.tsx → create-blank-stack-navigator.tsx} +1 -1
- package/src/blank-stack/types.ts +5 -7
- package/src/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.ts +1 -8
- package/src/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.tsx +1 -12
- package/src/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.tsx +1 -1
- package/src/blank-stack/utils/with-stack-navigation/index.tsx +42 -62
- package/src/shared/__tests__/bounds.store.test.ts +398 -167
- package/src/shared/__tests__/determine-dismissal.test.ts +2 -12
- package/src/shared/__tests__/geometry.test.ts +1 -1
- package/src/shared/__tests__/gesture.velocity.test.ts +2 -10
- package/src/shared/hooks/animation/use-screen-animation.tsx +55 -29
- package/src/shared/hooks/gestures/use-build-gestures.tsx +4 -1
- package/src/shared/providers/flags.provider.tsx +21 -0
- package/src/shared/providers/register-bounds.provider.tsx +85 -54
- package/src/shared/stores/bounds.store.ts +90 -54
- package/src/shared/types/bounds.types.ts +2 -2
- package/src/shared/utils/bounds/helpers/is-bounds-equal.ts +1 -1
- package/src/shared/utils/bounds/index.ts +7 -10
- package/src/shared/utils/create-provider.tsx +35 -1
- package/src/shared/utils/reset-stores-for-screen.ts +2 -0
- package/lib/commonjs/blank-stack/components/Overlay.js.map +0 -1
- package/lib/commonjs/blank-stack/components/Screens.js.map +0 -1
- package/lib/commonjs/blank-stack/components/StackView.js +0 -93
- package/lib/commonjs/blank-stack/components/StackView.js.map +0 -1
- package/lib/commonjs/blank-stack/navigators/createBlankStackNavigator.js.map +0 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/_types.js.map +0 -1
- package/lib/module/blank-stack/components/Overlay.js.map +0 -1
- package/lib/module/blank-stack/components/Screens.js.map +0 -1
- package/lib/module/blank-stack/components/StackView.js +0 -88
- package/lib/module/blank-stack/components/StackView.js.map +0 -1
- package/lib/module/blank-stack/navigators/createBlankStackNavigator.js.map +0 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/_types.js +0 -4
- package/lib/module/blank-stack/utils/with-stack-navigation/_types.js.map +0 -1
- package/lib/typescript/blank-stack/components/Overlay.d.ts.map +0 -1
- package/lib/typescript/blank-stack/components/StackView.d.ts +0 -2
- package/lib/typescript/blank-stack/components/StackView.d.ts.map +0 -1
- package/lib/typescript/blank-stack/navigators/createBlankStackNavigator.d.ts.map +0 -1
- package/lib/typescript/blank-stack/utils/with-stack-navigation/_types.d.ts.map +0 -1
- package/src/blank-stack/components/StackView.tsx +0 -108
- /package/src/blank-stack/utils/with-stack-navigation/{_types.ts → types.ts} +0 -0
|
@@ -5,6 +5,7 @@ import { type SharedValue, useDerivedValue } from "react-native-reanimated";
|
|
|
5
5
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
6
|
import type { NativeStackScreenTransitionConfig } from "../../../native-stack/types";
|
|
7
7
|
import { DEFAULT_SCREEN_TRANSITION_STATE } from "../../constants";
|
|
8
|
+
import { useFlagsContext } from "../../providers/flags.provider";
|
|
8
9
|
import {
|
|
9
10
|
type TransitionDescriptor,
|
|
10
11
|
useKeys,
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
16
17
|
ScreenTransitionState,
|
|
17
18
|
} from "../../types/animation.types";
|
|
18
19
|
import type { ScreenTransitionConfig } from "../../types/core.types";
|
|
20
|
+
import type { GestureDirection } from "../../types/gesture.types";
|
|
19
21
|
import { derivations } from "../../utils/animation/derivations";
|
|
20
22
|
import { createBounds } from "../../utils/bounds";
|
|
21
23
|
|
|
@@ -25,30 +27,45 @@ type BuiltState = {
|
|
|
25
27
|
animating: SharedValue<number>;
|
|
26
28
|
gesture: GestureStoreMap;
|
|
27
29
|
route: RouteProp<ParamListBase>;
|
|
30
|
+
unwrapped: ScreenTransitionState;
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const createScreenTransitionState = (
|
|
34
|
+
route: RouteProp<ParamListBase>,
|
|
35
|
+
): ScreenTransitionState => ({
|
|
36
|
+
progress: 0,
|
|
37
|
+
closing: 0,
|
|
38
|
+
animating: 0,
|
|
39
|
+
gesture: {
|
|
40
|
+
x: 0,
|
|
41
|
+
y: 0,
|
|
42
|
+
normalizedX: 0,
|
|
43
|
+
normalizedY: 0,
|
|
44
|
+
isDismissing: 0,
|
|
45
|
+
isDragging: 0,
|
|
46
|
+
direction: null,
|
|
47
|
+
},
|
|
48
|
+
route,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const unwrapInto = (s: BuiltState): ScreenTransitionState => {
|
|
34
52
|
"worklet";
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
53
|
+
const out = s.unwrapped;
|
|
54
|
+
out.progress = s.progress.value;
|
|
55
|
+
out.closing = s.closing.value;
|
|
56
|
+
out.animating = s.animating.value;
|
|
57
|
+
out.gesture.x = s.gesture.x.value;
|
|
58
|
+
out.gesture.y = s.gesture.y.value;
|
|
59
|
+
out.gesture.normalizedX = s.gesture.normalizedX.value;
|
|
60
|
+
out.gesture.normalizedY = s.gesture.normalizedY.value;
|
|
61
|
+
out.gesture.isDismissing = s.gesture.isDismissing.value;
|
|
62
|
+
out.gesture.isDragging = s.gesture.isDragging.value;
|
|
63
|
+
out.gesture.direction = s.gesture.direction.value as Omit<
|
|
64
|
+
GestureDirection,
|
|
65
|
+
"bidirectional"
|
|
66
|
+
> | null;
|
|
67
|
+
|
|
68
|
+
return out;
|
|
52
69
|
};
|
|
53
70
|
|
|
54
71
|
const useBuildScreenTransitionState = (
|
|
@@ -65,18 +82,25 @@ const useBuildScreenTransitionState = (
|
|
|
65
82
|
animating: AnimationStore.getAnimation(key, "animating"),
|
|
66
83
|
gesture: GestureStore.getRouteGestures(key),
|
|
67
84
|
route: descriptor.route,
|
|
85
|
+
unwrapped: createScreenTransitionState(descriptor.route),
|
|
68
86
|
};
|
|
69
87
|
}, [key, descriptor?.route]);
|
|
70
88
|
};
|
|
71
89
|
|
|
72
|
-
const hasTransitionsEnabled = (
|
|
90
|
+
const hasTransitionsEnabled = (
|
|
91
|
+
options: ScreenTransitionConfig | undefined,
|
|
92
|
+
alwaysOn: boolean,
|
|
93
|
+
) => {
|
|
73
94
|
"worklet";
|
|
95
|
+
if (alwaysOn) return true;
|
|
74
96
|
return !!(options as NativeStackScreenTransitionConfig)?.enableTransitions;
|
|
75
97
|
};
|
|
76
98
|
|
|
77
99
|
export function _useScreenAnimation() {
|
|
78
100
|
const dimensions = useWindowDimensions();
|
|
79
101
|
const insets = useSafeAreaInsets();
|
|
102
|
+
const flags = useFlagsContext();
|
|
103
|
+
const transitionsAlwaysOn = flags?.TRANSITIONS_ALWAYS_ON ?? false;
|
|
80
104
|
|
|
81
105
|
const {
|
|
82
106
|
current: currentDescriptor,
|
|
@@ -93,15 +117,17 @@ export function _useScreenAnimation() {
|
|
|
93
117
|
>(() => {
|
|
94
118
|
"worklet";
|
|
95
119
|
|
|
96
|
-
const previous =
|
|
120
|
+
const previous = prevAnimation ? unwrapInto(prevAnimation) : undefined;
|
|
97
121
|
|
|
98
|
-
const next =
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
const next =
|
|
123
|
+
nextAnimation &&
|
|
124
|
+
hasTransitionsEnabled(nextDescriptor?.options, transitionsAlwaysOn)
|
|
125
|
+
? unwrapInto(nextAnimation)
|
|
126
|
+
: undefined;
|
|
101
127
|
|
|
102
|
-
const current =
|
|
103
|
-
|
|
104
|
-
DEFAULT_SCREEN_TRANSITION_STATE;
|
|
128
|
+
const current = currentAnimation
|
|
129
|
+
? unwrapInto(currentAnimation)
|
|
130
|
+
: DEFAULT_SCREEN_TRANSITION_STATE;
|
|
105
131
|
|
|
106
132
|
const helpers = derivations({
|
|
107
133
|
current,
|
|
@@ -26,7 +26,10 @@ import { useKeys } from "../../providers/keys.provider";
|
|
|
26
26
|
import { AnimationStore } from "../../stores/animation.store";
|
|
27
27
|
import { GestureStore, type GestureStoreMap } from "../../stores/gesture.store";
|
|
28
28
|
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
type GestureDirection,
|
|
31
|
+
GestureOffsetState,
|
|
32
|
+
} from "../../types/gesture.types";
|
|
30
33
|
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
31
34
|
import { applyOffsetRules } from "../../utils/gesture/check-gesture-activation";
|
|
32
35
|
import { determineDismissal } from "../../utils/gesture/determine-dismissal";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import createProvider from "../utils/create-provider";
|
|
2
|
+
|
|
3
|
+
interface FlagsValue {
|
|
4
|
+
TRANSITIONS_ALWAYS_ON: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface FlagsProviderProps {
|
|
8
|
+
TRANSITIONS_ALWAYS_ON?: boolean;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { FlagsProvider, useFlagsContext } = createProvider("Flags", {
|
|
13
|
+
guarded: false,
|
|
14
|
+
})<FlagsProviderProps, FlagsValue>(
|
|
15
|
+
({ TRANSITIONS_ALWAYS_ON = false, children }) => ({
|
|
16
|
+
value: { TRANSITIONS_ALWAYS_ON },
|
|
17
|
+
children,
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export { FlagsProvider, useFlagsContext };
|
|
@@ -43,27 +43,26 @@ interface RegisterBoundsContextValue {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
* Returns
|
|
46
|
+
* Builds the full ancestor key chain for nested navigators.
|
|
47
|
+
* Returns an array of screen keys from immediate parent to root.
|
|
48
|
+
* [parentKey, grandparentKey, greatGrandparentKey, ...]
|
|
48
49
|
*/
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!existsInParent && parentState.index !== undefined) {
|
|
63
|
-
return parentState.routes[parentState.index]?.key;
|
|
50
|
+
const getAncestorKeys = (current: TransitionDescriptor): string[] => {
|
|
51
|
+
const ancestors: string[] = [];
|
|
52
|
+
let nav = current.navigation.getParent();
|
|
53
|
+
|
|
54
|
+
while (nav) {
|
|
55
|
+
const state = nav.getState();
|
|
56
|
+
if (state?.routes && state.index !== undefined) {
|
|
57
|
+
const focusedRoute = state.routes[state.index];
|
|
58
|
+
if (focusedRoute?.key) {
|
|
59
|
+
ancestors.push(focusedRoute.key);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
nav = nav.getParent();
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
return
|
|
65
|
+
return ancestors;
|
|
67
66
|
};
|
|
68
67
|
|
|
69
68
|
/**
|
|
@@ -73,13 +72,13 @@ const getParentScreenKey = (current: TransitionDescriptor) => {
|
|
|
73
72
|
const useInitialLayoutHandler = (params: {
|
|
74
73
|
sharedBoundTag?: string;
|
|
75
74
|
currentScreenKey: string;
|
|
76
|
-
|
|
75
|
+
ancestorKeys: string[];
|
|
77
76
|
maybeMeasureAndStore: (options: MaybeMeasureAndStoreParams) => void;
|
|
78
77
|
}) => {
|
|
79
78
|
const {
|
|
80
79
|
sharedBoundTag,
|
|
81
80
|
currentScreenKey,
|
|
82
|
-
|
|
81
|
+
ancestorKeys,
|
|
83
82
|
maybeMeasureAndStore,
|
|
84
83
|
} = params;
|
|
85
84
|
|
|
@@ -87,57 +86,84 @@ const useInitialLayoutHandler = (params: {
|
|
|
87
86
|
currentScreenKey,
|
|
88
87
|
"animating",
|
|
89
88
|
);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
|
|
90
|
+
// Check if any ancestor is animating
|
|
91
|
+
const ancestorAnimations = ancestorKeys.map((key) =>
|
|
92
|
+
AnimationStore.getAnimation(key, "animating"),
|
|
93
|
+
);
|
|
93
94
|
|
|
94
95
|
const hasMeasuredOnLayout = useSharedValue(false);
|
|
95
96
|
|
|
96
97
|
return useCallback(() => {
|
|
97
98
|
"worklet";
|
|
98
|
-
if (!sharedBoundTag || hasMeasuredOnLayout.
|
|
99
|
-
|
|
99
|
+
if (!sharedBoundTag || hasMeasuredOnLayout.get()) return;
|
|
100
|
+
|
|
101
|
+
// Check if current or any ancestor is animating
|
|
102
|
+
let isAnyAnimating = isAnimating.get();
|
|
103
|
+
for (let i = 0; i < ancestorAnimations.length; i++) {
|
|
104
|
+
if (ancestorAnimations[i].get()) {
|
|
105
|
+
isAnyAnimating = 1;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!isAnyAnimating) return;
|
|
100
111
|
|
|
101
112
|
maybeMeasureAndStore({
|
|
102
113
|
shouldSetSource: false,
|
|
103
114
|
shouldSetDestination: true,
|
|
104
115
|
});
|
|
105
116
|
|
|
106
|
-
hasMeasuredOnLayout.
|
|
117
|
+
hasMeasuredOnLayout.set(true);
|
|
107
118
|
}, [
|
|
108
119
|
sharedBoundTag,
|
|
109
120
|
hasMeasuredOnLayout,
|
|
110
121
|
isAnimating,
|
|
111
|
-
|
|
122
|
+
ancestorAnimations,
|
|
112
123
|
maybeMeasureAndStore,
|
|
113
124
|
]);
|
|
114
125
|
};
|
|
115
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Measures non-pressable elements when screen becomes blurred.
|
|
129
|
+
* Captures bounds right before transition starts.
|
|
130
|
+
*/
|
|
116
131
|
/**
|
|
117
132
|
* Measures non-pressable elements when screen becomes blurred.
|
|
118
133
|
* Captures bounds right before transition starts.
|
|
119
134
|
*/
|
|
120
135
|
const useBlurMeasurement = (params: {
|
|
121
136
|
sharedBoundTag?: string;
|
|
137
|
+
ancestorKeys: string[];
|
|
122
138
|
maybeMeasureAndStore: (options: MaybeMeasureAndStoreParams) => void;
|
|
123
139
|
}) => {
|
|
124
|
-
const {
|
|
125
|
-
const
|
|
140
|
+
const { current } = useKeys();
|
|
141
|
+
const { sharedBoundTag, ancestorKeys, maybeMeasureAndStore } = params;
|
|
126
142
|
const hasCapturedSource = useRef(false);
|
|
127
143
|
|
|
144
|
+
const ancestorClosing = [current.route.key, ...ancestorKeys].map((key) =>
|
|
145
|
+
AnimationStore.getAnimation(key, "closing"),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const maybeMeasureOnBlur = useStableCallbackValue(() => {
|
|
149
|
+
"worklet";
|
|
150
|
+
|
|
151
|
+
// Don't measure if current / any ancestor is closing
|
|
152
|
+
const isOneClosing = ancestorClosing.some((closing) => closing.get());
|
|
153
|
+
if (isOneClosing) return;
|
|
154
|
+
|
|
155
|
+
maybeMeasureAndStore({ shouldSetSource: true });
|
|
156
|
+
});
|
|
157
|
+
|
|
128
158
|
useFocusEffect(
|
|
129
159
|
useCallback(() => {
|
|
130
|
-
isFocused.current = true;
|
|
131
160
|
hasCapturedSource.current = false;
|
|
132
161
|
|
|
133
162
|
return () => {
|
|
134
|
-
if (!sharedBoundTag) return;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
isFocused.current = false;
|
|
138
|
-
runOnUI(maybeMeasureAndStore)({ shouldSetSource: true });
|
|
163
|
+
if (!sharedBoundTag || hasCapturedSource.current) return;
|
|
164
|
+
runOnUI(maybeMeasureOnBlur)();
|
|
139
165
|
};
|
|
140
|
-
}, [sharedBoundTag,
|
|
166
|
+
}, [sharedBoundTag, maybeMeasureOnBlur]),
|
|
141
167
|
);
|
|
142
168
|
|
|
143
169
|
return {
|
|
@@ -157,7 +183,7 @@ const useParentSyncReaction = (params: {
|
|
|
157
183
|
const { parentContext, maybeMeasureAndStore } = params;
|
|
158
184
|
|
|
159
185
|
useAnimatedReaction(
|
|
160
|
-
() => parentContext?.updateSignal.
|
|
186
|
+
() => parentContext?.updateSignal.get(),
|
|
161
187
|
(value) => {
|
|
162
188
|
"worklet";
|
|
163
189
|
if (value === 0 || value === undefined) return;
|
|
@@ -173,11 +199,12 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
173
199
|
({ style, onPress, sharedBoundTag, animatedRef, children }) => {
|
|
174
200
|
const { current } = useKeys();
|
|
175
201
|
const currentScreenKey = current.route.key;
|
|
176
|
-
const
|
|
202
|
+
const ancestorKeys = useMemo(() => getAncestorKeys(current), [current]);
|
|
177
203
|
|
|
178
204
|
// Context & signals
|
|
179
205
|
const parentContext: RegisterBoundsContextValue | null =
|
|
180
206
|
useRegisterBoundsContext();
|
|
207
|
+
|
|
181
208
|
const ownSignal = useSharedValue(0);
|
|
182
209
|
const updateSignal: SharedValue<number> =
|
|
183
210
|
parentContext?.updateSignal ?? ownSignal;
|
|
@@ -191,7 +218,7 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
191
218
|
const emitUpdate = useStableCallbackValue(() => {
|
|
192
219
|
"worklet";
|
|
193
220
|
const isRoot = !parentContext;
|
|
194
|
-
if (isRoot) updateSignal.
|
|
221
|
+
if (isRoot) updateSignal.set(updateSignal.get() + 1);
|
|
195
222
|
});
|
|
196
223
|
|
|
197
224
|
const maybeMeasureAndStore = useStableCallbackValue(
|
|
@@ -208,28 +235,31 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
208
235
|
|
|
209
236
|
emitUpdate();
|
|
210
237
|
|
|
211
|
-
|
|
212
|
-
BoundStore.registerOccurrence(
|
|
238
|
+
BoundStore.registerSnapshot(
|
|
213
239
|
sharedBoundTag,
|
|
214
240
|
currentScreenKey,
|
|
215
241
|
measured,
|
|
216
242
|
preparedStyles,
|
|
243
|
+
ancestorKeys,
|
|
217
244
|
);
|
|
218
245
|
|
|
219
|
-
// Set as source (on press or blur)
|
|
220
246
|
if (shouldSetSource) {
|
|
221
|
-
if (isAnimating.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
226
|
-
BoundStore.setLinkSource(
|
|
247
|
+
if (isAnimating.get()) {
|
|
248
|
+
// If animation is already in progress,
|
|
249
|
+
// lets use the existing measuremenets.
|
|
250
|
+
const existing = BoundStore.getSnapshot(
|
|
227
251
|
sharedBoundTag,
|
|
228
252
|
currentScreenKey,
|
|
229
|
-
existing.bounds,
|
|
230
|
-
preparedStyles,
|
|
231
|
-
parentScreenKey,
|
|
232
253
|
);
|
|
254
|
+
if (existing) {
|
|
255
|
+
BoundStore.setLinkSource(
|
|
256
|
+
sharedBoundTag,
|
|
257
|
+
currentScreenKey,
|
|
258
|
+
existing.bounds,
|
|
259
|
+
preparedStyles,
|
|
260
|
+
ancestorKeys,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
233
263
|
return;
|
|
234
264
|
}
|
|
235
265
|
BoundStore.setLinkSource(
|
|
@@ -237,7 +267,7 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
237
267
|
currentScreenKey,
|
|
238
268
|
measured,
|
|
239
269
|
preparedStyles,
|
|
240
|
-
|
|
270
|
+
ancestorKeys,
|
|
241
271
|
);
|
|
242
272
|
}
|
|
243
273
|
|
|
@@ -248,7 +278,7 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
248
278
|
currentScreenKey,
|
|
249
279
|
measured,
|
|
250
280
|
preparedStyles,
|
|
251
|
-
|
|
281
|
+
ancestorKeys,
|
|
252
282
|
);
|
|
253
283
|
}
|
|
254
284
|
|
|
@@ -259,7 +289,7 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
259
289
|
const handleInitialLayout = useInitialLayoutHandler({
|
|
260
290
|
sharedBoundTag,
|
|
261
291
|
currentScreenKey,
|
|
262
|
-
|
|
292
|
+
ancestorKeys,
|
|
263
293
|
maybeMeasureAndStore,
|
|
264
294
|
});
|
|
265
295
|
|
|
@@ -267,6 +297,7 @@ const { RegisterBoundsProvider, useRegisterBoundsContext } = createProvider(
|
|
|
267
297
|
const { markSourceCaptured } = useBlurMeasurement({
|
|
268
298
|
sharedBoundTag,
|
|
269
299
|
maybeMeasureAndStore,
|
|
300
|
+
ancestorKeys,
|
|
270
301
|
});
|
|
271
302
|
|
|
272
303
|
useParentSyncReaction({ parentContext, maybeMeasureAndStore });
|
|
@@ -8,51 +8,42 @@ import type { Any } from "../types/utils.types";
|
|
|
8
8
|
type TagID = string;
|
|
9
9
|
type ScreenKey = string;
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type Snapshot = {
|
|
12
12
|
bounds: MeasuredDimensions;
|
|
13
13
|
styles: StyleProps;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
type ScreenIdentifier = {
|
|
17
17
|
screenKey: ScreenKey;
|
|
18
|
-
|
|
18
|
+
ancestorKeys?: ScreenKey[];
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
type TagLink = {
|
|
22
|
-
source: ScreenIdentifier &
|
|
23
|
-
destination: (ScreenIdentifier &
|
|
22
|
+
source: ScreenIdentifier & Snapshot;
|
|
23
|
+
destination: (ScreenIdentifier & Snapshot) | null;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
type TagState = {
|
|
27
|
-
|
|
27
|
+
snapshots: Record<ScreenKey, Snapshot & { ancestorKeys?: ScreenKey[] }>;
|
|
28
28
|
linkStack: TagLink[];
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* Note on cleanup: We intentionally skip automatic cleanup of old links.
|
|
33
|
-
* The linkStack grows by one entry per navigation, but `getActiveLink`
|
|
34
|
-
* finds the correct link via screenKey matching regardless of stack size.
|
|
35
|
-
* This is unlikely to cause performance issues in typical apps, but if
|
|
36
|
-
* memory becomes a concern in apps with heavy navigation (hundreds of
|
|
37
|
-
* transitions), we should consider implementing cleanup on screen unmount using
|
|
38
|
-
* screenKey filtering.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
31
|
const registry = makeMutable<Record<TagID, TagState>>({});
|
|
42
32
|
|
|
43
|
-
function
|
|
33
|
+
function registerSnapshot(
|
|
44
34
|
tag: TagID,
|
|
45
35
|
screenKey: ScreenKey,
|
|
46
36
|
bounds: MeasuredDimensions,
|
|
47
37
|
styles: StyleProps = {},
|
|
38
|
+
ancestorKeys?: ScreenKey[],
|
|
48
39
|
) {
|
|
49
40
|
"worklet";
|
|
50
41
|
registry.modify((state: Any) => {
|
|
51
42
|
"worklet";
|
|
52
43
|
if (!state[tag]) {
|
|
53
|
-
state[tag] = {
|
|
44
|
+
state[tag] = { snapshots: {}, linkStack: [] };
|
|
54
45
|
}
|
|
55
|
-
state[tag].
|
|
46
|
+
state[tag].snapshots[screenKey] = { bounds, styles, ancestorKeys };
|
|
56
47
|
return state;
|
|
57
48
|
});
|
|
58
49
|
}
|
|
@@ -62,16 +53,15 @@ function setLinkSource(
|
|
|
62
53
|
screenKey: ScreenKey,
|
|
63
54
|
bounds: MeasuredDimensions,
|
|
64
55
|
styles: StyleProps = {},
|
|
65
|
-
|
|
56
|
+
ancestorKeys?: ScreenKey[],
|
|
66
57
|
) {
|
|
67
58
|
"worklet";
|
|
68
59
|
registry.modify((state: Any) => {
|
|
69
60
|
"worklet";
|
|
70
|
-
if (!state[tag]) state[tag] = {
|
|
61
|
+
if (!state[tag]) state[tag] = { snapshots: {}, linkStack: [] };
|
|
71
62
|
|
|
72
|
-
// Push new link onto stack
|
|
73
63
|
state[tag].linkStack.push({
|
|
74
|
-
source: { screenKey,
|
|
64
|
+
source: { screenKey, ancestorKeys, bounds, styles },
|
|
75
65
|
destination: null,
|
|
76
66
|
});
|
|
77
67
|
return state;
|
|
@@ -83,7 +73,7 @@ function setLinkDestination(
|
|
|
83
73
|
screenKey: ScreenKey,
|
|
84
74
|
bounds: MeasuredDimensions,
|
|
85
75
|
styles: StyleProps = {},
|
|
86
|
-
|
|
76
|
+
ancestorKeys?: ScreenKey[],
|
|
87
77
|
) {
|
|
88
78
|
"worklet";
|
|
89
79
|
registry.modify((state: Any) => {
|
|
@@ -94,7 +84,7 @@ function setLinkDestination(
|
|
|
94
84
|
// Find the topmost link without a destination
|
|
95
85
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
96
86
|
if (stack[i].destination === null) {
|
|
97
|
-
stack[i].destination = { screenKey,
|
|
87
|
+
stack[i].destination = { screenKey, ancestorKeys, bounds, styles };
|
|
98
88
|
break;
|
|
99
89
|
}
|
|
100
90
|
}
|
|
@@ -102,22 +92,53 @@ function setLinkDestination(
|
|
|
102
92
|
});
|
|
103
93
|
}
|
|
104
94
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Helper to check if a screen identifier matches a given key
|
|
95
|
+
/**
|
|
96
|
+
* Helper to check if a screen identifier matches a given key.
|
|
97
|
+
* Checks both direct screenKey match and ancestor chain.
|
|
98
|
+
*/
|
|
111
99
|
function matchesScreenKey(
|
|
112
100
|
identifier: ScreenIdentifier | null | undefined,
|
|
113
101
|
key: ScreenKey,
|
|
114
102
|
): boolean {
|
|
115
103
|
"worklet";
|
|
116
104
|
if (!identifier) return false;
|
|
117
|
-
|
|
105
|
+
|
|
106
|
+
// Direct match
|
|
107
|
+
if (identifier.screenKey === key) return true;
|
|
108
|
+
|
|
109
|
+
// Check ancestor chain
|
|
110
|
+
return identifier.ancestorKeys?.includes(key) ?? false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get snapshot by tag and optional key.
|
|
115
|
+
* If key is provided, supports ancestor matching - if the key matches any ancestor
|
|
116
|
+
* of a stored snapshot, that snapshot will be returned.
|
|
117
|
+
* If key is omitted, returns the most recently registered snapshot.
|
|
118
|
+
*/
|
|
119
|
+
function getSnapshot(tag: TagID, key: ScreenKey): Snapshot | null {
|
|
120
|
+
"worklet";
|
|
121
|
+
const tagState = registry.value[tag];
|
|
122
|
+
if (!tagState) return null;
|
|
123
|
+
|
|
124
|
+
// Direct match in occurrences
|
|
125
|
+
if (tagState.snapshots[key]) {
|
|
126
|
+
const snap = tagState.snapshots[key];
|
|
127
|
+
return { bounds: snap.bounds, styles: snap.styles };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Ancestor match
|
|
131
|
+
for (const screenKey in tagState.snapshots) {
|
|
132
|
+
const snap = tagState.snapshots[screenKey];
|
|
133
|
+
if (snap.ancestorKeys?.includes(key)) {
|
|
134
|
+
return { bounds: snap.bounds, styles: snap.styles };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
118
139
|
}
|
|
119
140
|
|
|
120
|
-
function getActiveLink(tag: TagID, screenKey?: ScreenKey
|
|
141
|
+
function getActiveLink(tag: TagID, screenKey?: ScreenKey): TagLink | null {
|
|
121
142
|
"worklet";
|
|
122
143
|
const stack = registry.value[tag]?.linkStack;
|
|
123
144
|
|
|
@@ -127,40 +148,55 @@ function getActiveLink(tag: TagID, screenKey?: ScreenKey, isClosing?: boolean) {
|
|
|
127
148
|
|
|
128
149
|
// If screenKey provided, find link involving that screen
|
|
129
150
|
if (screenKey) {
|
|
130
|
-
// When closing (backward nav), we want the link where this screen is the DESTINATION
|
|
131
|
-
// When opening (forward nav), we want the link where this screen is the DESTINATION too
|
|
132
|
-
// The source is always the "from" screen, destination is the "to" screen
|
|
133
|
-
|
|
134
|
-
if (isClosing) {
|
|
135
|
-
// Backward: find link where I am the destination (I'm going back to source)
|
|
136
|
-
for (let i = stack.length - 1; i >= 0; i--) {
|
|
137
|
-
const link = stack[i];
|
|
138
|
-
if (matchesScreenKey(link.destination, screenKey)) {
|
|
139
|
-
return link;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Forward or fallback: find any link involving this screen
|
|
145
151
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
146
152
|
const link = stack[i];
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
153
|
+
if (!link.destination) continue;
|
|
154
|
+
|
|
155
|
+
const isSource = matchesScreenKey(link.source, screenKey);
|
|
156
|
+
const isDestination = matchesScreenKey(link.destination, screenKey);
|
|
157
|
+
|
|
158
|
+
if (isSource || isDestination) {
|
|
159
|
+
// If I match the source, I'm closing (going back to where I came from)
|
|
151
160
|
return link;
|
|
152
161
|
}
|
|
153
162
|
}
|
|
154
163
|
return null;
|
|
155
164
|
}
|
|
156
165
|
|
|
157
|
-
|
|
166
|
+
const lastLink = stack[stack.length - 1];
|
|
167
|
+
return lastLink ? lastLink : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear all snapshots and links for a screen across all tags.
|
|
172
|
+
* Called when a screen unmounts.
|
|
173
|
+
*/
|
|
174
|
+
function clear(screenKey: ScreenKey) {
|
|
175
|
+
"worklet";
|
|
176
|
+
registry.modify((state: Any) => {
|
|
177
|
+
"worklet";
|
|
178
|
+
for (const tag in state) {
|
|
179
|
+
// Remove snapshot
|
|
180
|
+
if (state[tag].snapshots[screenKey]) {
|
|
181
|
+
delete state[tag].snapshots[screenKey];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Remove links involving this screen
|
|
185
|
+
state[tag].linkStack = state[tag].linkStack.filter((link: TagLink) => {
|
|
186
|
+
const sourceMatches = matchesScreenKey(link.source, screenKey);
|
|
187
|
+
const destMatches = matchesScreenKey(link.destination, screenKey);
|
|
188
|
+
return !sourceMatches && !destMatches;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return state;
|
|
192
|
+
});
|
|
158
193
|
}
|
|
159
194
|
|
|
160
195
|
export const BoundStore = {
|
|
161
|
-
|
|
196
|
+
registerSnapshot,
|
|
162
197
|
setLinkSource,
|
|
163
198
|
setLinkDestination,
|
|
164
199
|
getActiveLink,
|
|
165
|
-
|
|
200
|
+
getSnapshot,
|
|
201
|
+
clear,
|
|
166
202
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MeasuredDimensions, StyleProps } from "react-native-reanimated";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Snapshot } from "../stores/bounds.store";
|
|
3
3
|
import type {
|
|
4
4
|
BoundsBuilderOptions,
|
|
5
5
|
BoundsReturnType,
|
|
@@ -21,5 +21,5 @@ export type BoundEntry = {
|
|
|
21
21
|
|
|
22
22
|
export type BoundsAccessor = {
|
|
23
23
|
<T extends BoundsBuilderOptions>(options: T): BoundsReturnType<T>;
|
|
24
|
-
|
|
24
|
+
getSnapshot: (id: string, key?: string) => Snapshot | null;
|
|
25
25
|
};
|