react-native-screen-transitions 3.3.0-rc.3 → 3.3.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/LICENSE +21 -0
- package/README.md +51 -7
- package/lib/commonjs/blank-stack/components/stack-view.js +2 -1
- package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -1
- package/lib/commonjs/blank-stack/components/stack-view.native.js +2 -1
- package/lib/commonjs/blank-stack/components/stack-view.native.js.map +1 -1
- package/lib/commonjs/component-stack/components/stack-view.js +2 -1
- package/lib/commonjs/component-stack/components/stack-view.js.map +1 -1
- package/lib/commonjs/shared/components/overlay/helpers/get-active-overlay.js +3 -2
- package/lib/commonjs/shared/components/overlay/helpers/get-active-overlay.js.map +1 -1
- package/lib/commonjs/shared/components/overlay/variations/float-overlay.js +46 -9
- package/lib/commonjs/shared/components/overlay/variations/float-overlay.js.map +1 -1
- package/lib/commonjs/shared/components/overlay/variations/overlay-host.js +7 -7
- package/lib/commonjs/shared/components/overlay/variations/overlay-host.js.map +1 -1
- package/lib/commonjs/shared/components/overlay/variations/screen-overlay.js +23 -3
- package/lib/commonjs/shared/components/overlay/variations/screen-overlay.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container.js +2 -1
- package/lib/commonjs/shared/components/screen-container.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +7 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +12 -12
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +5 -2
- package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -1
- package/lib/commonjs/shared/hooks/navigation/use-optimistic-focused-index.js +20 -0
- package/lib/commonjs/shared/hooks/navigation/use-optimistic-focused-index.js.map +1 -0
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +2 -6
- package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -1
- package/lib/commonjs/shared/hooks/reanimated/use-shared-value-state.js +4 -1
- package/lib/commonjs/shared/hooks/reanimated/use-shared-value-state.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.provider.js +17 -6
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/layout-anchor.provider.js +7 -5
- package/lib/commonjs/shared/providers/layout-anchor.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/styles.provider.js +1 -6
- package/lib/commonjs/shared/providers/screen/styles.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/stack/direct.provider.js +2 -1
- package/lib/commonjs/shared/providers/stack/direct.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/stack/managed.provider.js +2 -1
- package/lib/commonjs/shared/providers/stack/managed.provider.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js +9 -2
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/find-collapse-target.js +11 -1
- package/lib/commonjs/shared/utils/gesture/find-collapse-target.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/validate-snap-points.js +11 -2
- package/lib/commonjs/shared/utils/gesture/validate-snap-points.js.map +1 -1
- package/lib/commonjs/shared/utils/overlay/visibility.js +19 -0
- package/lib/commonjs/shared/utils/overlay/visibility.js.map +1 -0
- package/lib/module/blank-stack/components/stack-view.js +2 -1
- package/lib/module/blank-stack/components/stack-view.js.map +1 -1
- package/lib/module/blank-stack/components/stack-view.native.js +2 -1
- package/lib/module/blank-stack/components/stack-view.native.js.map +1 -1
- package/lib/module/component-stack/components/stack-view.js +2 -1
- package/lib/module/component-stack/components/stack-view.js.map +1 -1
- package/lib/module/shared/components/overlay/helpers/get-active-overlay.js +4 -2
- package/lib/module/shared/components/overlay/helpers/get-active-overlay.js.map +1 -1
- package/lib/module/shared/components/overlay/variations/float-overlay.js +47 -11
- package/lib/module/shared/components/overlay/variations/float-overlay.js.map +1 -1
- package/lib/module/shared/components/overlay/variations/overlay-host.js +7 -7
- package/lib/module/shared/components/overlay/variations/overlay-host.js.map +1 -1
- package/lib/module/shared/components/overlay/variations/screen-overlay.js +24 -5
- package/lib/module/shared/components/overlay/variations/screen-overlay.js.map +1 -1
- package/lib/module/shared/components/screen-container.js +2 -1
- package/lib/module/shared/components/screen-container.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +7 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +12 -12
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js +5 -2
- package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -1
- package/lib/module/shared/hooks/navigation/use-optimistic-focused-index.js +17 -0
- package/lib/module/shared/hooks/navigation/use-optimistic-focused-index.js.map +1 -0
- package/lib/module/shared/hooks/navigation/use-screen-state.js +2 -6
- package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -1
- package/lib/module/shared/hooks/reanimated/use-shared-value-state.js +4 -1
- package/lib/module/shared/hooks/reanimated/use-shared-value-state.js.map +1 -1
- package/lib/module/shared/providers/gestures.provider.js +17 -6
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/providers/layout-anchor.provider.js +7 -5
- package/lib/module/shared/providers/layout-anchor.provider.js.map +1 -1
- package/lib/module/shared/providers/screen/styles.provider.js +1 -6
- package/lib/module/shared/providers/screen/styles.provider.js.map +1 -1
- package/lib/module/shared/providers/stack/direct.provider.js +2 -1
- package/lib/module/shared/providers/stack/direct.provider.js.map +1 -1
- package/lib/module/shared/providers/stack/managed.provider.js +2 -1
- package/lib/module/shared/providers/stack/managed.provider.js.map +1 -1
- package/lib/module/shared/utils/gesture/determine-snap-target.js +9 -2
- package/lib/module/shared/utils/gesture/determine-snap-target.js.map +1 -1
- package/lib/module/shared/utils/gesture/find-collapse-target.js +11 -1
- package/lib/module/shared/utils/gesture/find-collapse-target.js.map +1 -1
- package/lib/module/shared/utils/gesture/validate-snap-points.js +11 -2
- package/lib/module/shared/utils/gesture/validate-snap-points.js.map +1 -1
- package/lib/module/shared/utils/overlay/visibility.js +12 -0
- package/lib/module/shared/utils/overlay/visibility.js.map +1 -0
- package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -1
- package/lib/typescript/blank-stack/components/stack-view.native.d.ts.map +1 -1
- package/lib/typescript/component-stack/components/stack-view.d.ts.map +1 -1
- package/lib/typescript/shared/components/overlay/helpers/get-active-overlay.d.ts +1 -1
- package/lib/typescript/shared/components/overlay/helpers/get-active-overlay.d.ts.map +1 -1
- package/lib/typescript/shared/components/overlay/variations/float-overlay.d.ts.map +1 -1
- package/lib/typescript/shared/components/overlay/variations/overlay-host.d.ts +7 -0
- package/lib/typescript/shared/components/overlay/variations/overlay-host.d.ts.map +1 -1
- package/lib/typescript/shared/components/overlay/variations/screen-overlay.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/navigation/use-optimistic-focused-index.d.ts +7 -0
- package/lib/typescript/shared/hooks/navigation/use-optimistic-focused-index.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/reanimated/use-shared-value-state.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/layout-anchor.provider.d.ts +1 -1
- package/lib/typescript/shared/providers/layout-anchor.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/styles.provider.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/types/screen.types.d.ts +16 -1
- package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/validate-snap-points.d.ts.map +1 -1
- package/lib/typescript/shared/utils/overlay/visibility.d.ts +11 -0
- package/lib/typescript/shared/utils/overlay/visibility.d.ts.map +1 -0
- package/package.json +8 -2
- package/src/blank-stack/components/stack-view.native.tsx +2 -1
- package/src/blank-stack/components/stack-view.tsx +2 -1
- package/src/component-stack/components/stack-view.tsx +2 -1
- package/src/shared/components/overlay/helpers/get-active-overlay.ts +3 -2
- package/src/shared/components/overlay/variations/float-overlay.tsx +53 -8
- package/src/shared/components/overlay/variations/overlay-host.tsx +16 -6
- package/src/shared/components/overlay/variations/screen-overlay.tsx +35 -3
- package/src/shared/components/screen-container.tsx +15 -9
- package/src/shared/hooks/gestures/use-build-gestures.tsx +5 -1
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +10 -6
- package/src/shared/hooks/lifecycle/use-close-transition.ts +6 -3
- package/src/shared/hooks/navigation/use-optimistic-focused-index.ts +19 -0
- package/src/shared/hooks/navigation/use-screen-state.tsx +4 -7
- package/src/shared/hooks/reanimated/use-shared-value-state.ts +4 -1
- package/src/shared/providers/gestures.provider.tsx +49 -22
- package/src/shared/providers/layout-anchor.provider.tsx +28 -25
- package/src/shared/providers/screen/styles.provider.tsx +1 -7
- package/src/shared/providers/stack/direct.provider.tsx +2 -2
- package/src/shared/providers/stack/managed.provider.tsx +2 -2
- package/src/shared/types/screen.types.ts +17 -1
- package/src/shared/utils/gesture/determine-snap-target.ts +15 -4
- package/src/shared/utils/gesture/find-collapse-target.ts +11 -1
- package/src/shared/utils/gesture/validate-snap-points.ts +15 -2
- package/src/shared/utils/overlay/visibility.ts +23 -0
|
@@ -25,6 +25,8 @@ export const ScreenContainer = memo(({ children }: Props) => {
|
|
|
25
25
|
const { pointerEvents, backdropBehavior } = useBackdropPointerEvents();
|
|
26
26
|
const gestureContext = useGestureContext();
|
|
27
27
|
|
|
28
|
+
const BackdropComponent = current.options.backdropComponent;
|
|
29
|
+
|
|
28
30
|
const isBackdropActive =
|
|
29
31
|
backdropBehavior === "dismiss" || backdropBehavior === "collapse";
|
|
30
32
|
|
|
@@ -101,15 +103,19 @@ export const ScreenContainer = memo(({ children }: Props) => {
|
|
|
101
103
|
|
|
102
104
|
return (
|
|
103
105
|
<View style={styles.container} pointerEvents={pointerEvents}>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
{BackdropComponent ? (
|
|
107
|
+
<BackdropComponent />
|
|
108
|
+
) : (
|
|
109
|
+
<Pressable
|
|
110
|
+
style={StyleSheet.absoluteFillObject}
|
|
111
|
+
pointerEvents={isBackdropActive ? "auto" : "none"}
|
|
112
|
+
onPress={isBackdropActive ? handleBackdropPress : undefined}
|
|
113
|
+
>
|
|
114
|
+
<Animated.View
|
|
115
|
+
style={[StyleSheet.absoluteFillObject, animatedBackdropStyle]}
|
|
116
|
+
/>
|
|
117
|
+
</Pressable>
|
|
118
|
+
)}
|
|
113
119
|
<GestureDetector gesture={gestureContext!.panGesture}>
|
|
114
120
|
<Animated.View
|
|
115
121
|
style={[styles.content, animatedContentStyle]}
|
|
@@ -12,6 +12,7 @@ import { GestureStore, type GestureStoreMap } from "../../stores/gesture.store";
|
|
|
12
12
|
import type { ClaimedDirections, Direction } from "../../types/ownership.types";
|
|
13
13
|
import { claimsAnyDirection } from "../../utils/gesture/compute-claimed-directions";
|
|
14
14
|
import { resolveOwnership } from "../../utils/gesture/resolve-ownership";
|
|
15
|
+
import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
|
|
15
16
|
import { useScreenGestureHandlers } from "./use-screen-gesture-handlers";
|
|
16
17
|
|
|
17
18
|
const DIRECTIONS: Direction[] = [
|
|
@@ -94,7 +95,10 @@ export const useBuildGestures = ({
|
|
|
94
95
|
const canDismiss = Boolean(
|
|
95
96
|
isFirstScreen ? false : current.options.gestureEnabled,
|
|
96
97
|
);
|
|
97
|
-
const hasSnapPoints =
|
|
98
|
+
const { hasSnapPoints } = useMemo(
|
|
99
|
+
() => validateSnapPoints({ snapPoints, canDismiss }),
|
|
100
|
+
[snapPoints, canDismiss],
|
|
101
|
+
);
|
|
98
102
|
const gestureEnabled = canDismiss || hasSnapPoints;
|
|
99
103
|
|
|
100
104
|
const ownershipStatus = useMemo(
|
|
@@ -123,18 +123,22 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
|
|
|
123
123
|
const setIsTouched = () => {
|
|
124
124
|
"worklet";
|
|
125
125
|
for (const scrollConfig of scrollConfigs) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
scrollConfig.modify((v) => {
|
|
127
|
+
"worklet";
|
|
128
|
+
if (v) v.isTouched = true;
|
|
129
|
+
return v;
|
|
130
|
+
});
|
|
129
131
|
}
|
|
130
132
|
};
|
|
131
133
|
|
|
132
134
|
const clearIsTouched = () => {
|
|
133
135
|
"worklet";
|
|
134
136
|
for (const scrollConfig of scrollConfigs) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
scrollConfig.modify((v) => {
|
|
138
|
+
"worklet";
|
|
139
|
+
if (v) v.isTouched = false;
|
|
140
|
+
return v;
|
|
141
|
+
});
|
|
138
142
|
}
|
|
139
143
|
};
|
|
140
144
|
|
|
@@ -47,9 +47,12 @@ const useManagedClose = ({
|
|
|
47
47
|
const transitionSpec = current.options.transitionSpec;
|
|
48
48
|
|
|
49
49
|
useAnimatedReaction(
|
|
50
|
-
() =>
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
() => {
|
|
51
|
+
const keys = closingRouteKeysShared.value;
|
|
52
|
+
return keys?.includes(routeKey) ?? false;
|
|
53
|
+
},
|
|
54
|
+
(isClosing, wasClosing) => {
|
|
55
|
+
if (!isClosing || wasClosing) return;
|
|
53
56
|
|
|
54
57
|
runOnJS(activate)();
|
|
55
58
|
animateToProgress({
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DerivedValue, useDerivedValue } from "react-native-reanimated";
|
|
2
|
+
import { useSharedValueState } from "../reanimated/use-shared-value-state";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a JS-focused index derived from optimisticFocusedIndex and clamped to route count.
|
|
6
|
+
* Keeps callers aligned on focus behavior during transitions with closing screens.
|
|
7
|
+
*/
|
|
8
|
+
export function useOptimisticFocusedIndex(
|
|
9
|
+
optimisticFocusedIndex: DerivedValue<number>,
|
|
10
|
+
routeCount: number,
|
|
11
|
+
): number {
|
|
12
|
+
return useSharedValueState(
|
|
13
|
+
useDerivedValue(() => {
|
|
14
|
+
const globalIndex = optimisticFocusedIndex.get();
|
|
15
|
+
if (routeCount <= 0) return 0;
|
|
16
|
+
return Math.max(0, Math.min(globalIndex, routeCount - 1));
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Route } from "@react-navigation/native";
|
|
2
2
|
import { useCallback, useMemo } from "react";
|
|
3
|
-
import { useDerivedValue } from "react-native-reanimated";
|
|
4
3
|
import { snapDescriptorToIndex } from "../../animation/snap-to";
|
|
5
4
|
import {
|
|
6
5
|
type BaseDescriptor,
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
} from "../../providers/screen/keys.provider";
|
|
9
8
|
import type { ScreenTransitionConfig } from "../../types/screen.types";
|
|
10
9
|
import type { BaseStackNavigation } from "../../types/stack.types";
|
|
11
|
-
import {
|
|
10
|
+
import { useOptimisticFocusedIndex } from "./use-optimistic-focused-index";
|
|
12
11
|
import { type StackContextValue, useStack } from "./use-stack";
|
|
13
12
|
|
|
14
13
|
export interface ScreenState<
|
|
@@ -74,11 +73,9 @@ export function useScreenState<
|
|
|
74
73
|
[routeKeys, current.route.key],
|
|
75
74
|
);
|
|
76
75
|
|
|
77
|
-
const focusedIndex =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return Math.max(0, Math.min(globalIndex, routeKeys.length - 1));
|
|
81
|
-
}),
|
|
76
|
+
const focusedIndex = useOptimisticFocusedIndex(
|
|
77
|
+
optimisticFocusedIndex,
|
|
78
|
+
routeKeys.length,
|
|
82
79
|
);
|
|
83
80
|
|
|
84
81
|
const focusedScene = useMemo(() => {
|
|
@@ -26,7 +26,10 @@ export function useSharedValueState<T>(sharedValue: SharedValue<T>): T {
|
|
|
26
26
|
|
|
27
27
|
useAnimatedReaction(
|
|
28
28
|
() => sharedValue.value,
|
|
29
|
-
(value) =>
|
|
29
|
+
(value, previousValue) => {
|
|
30
|
+
if (Object.is(value, previousValue)) return;
|
|
31
|
+
runOnJS(setState)(value);
|
|
32
|
+
},
|
|
30
33
|
);
|
|
31
34
|
|
|
32
35
|
return state;
|
|
@@ -22,6 +22,7 @@ import type { ClaimedDirections, Direction } from "../types/ownership.types";
|
|
|
22
22
|
import { StackType } from "../types/stack.types";
|
|
23
23
|
import createProvider from "../utils/create-provider";
|
|
24
24
|
import { computeClaimedDirections } from "../utils/gesture/compute-claimed-directions";
|
|
25
|
+
import { validateSnapPoints } from "../utils/gesture/validate-snap-points";
|
|
25
26
|
import { useKeys } from "./screen/keys.provider";
|
|
26
27
|
import { useStackCoreContext } from "./stack/core.provider";
|
|
27
28
|
|
|
@@ -159,30 +160,43 @@ export const {
|
|
|
159
160
|
} = createProvider("ScreenGesture", { guarded: false })<
|
|
160
161
|
ScreenGestureProviderProps,
|
|
161
162
|
GestureContextType
|
|
162
|
-
>(({ children }) => {
|
|
163
|
+
>(({ children }): { value: GestureContextType; children: React.ReactNode } => {
|
|
163
164
|
const { current } = useKeys();
|
|
164
165
|
const { flags } = useStackCoreContext();
|
|
165
|
-
const ancestorContext = useGestureContext();
|
|
166
|
-
|
|
167
|
-
const hasGestures = current.options.gestureEnabled === true;
|
|
166
|
+
const ancestorContext: GestureContextType | null = useGestureContext();
|
|
168
167
|
const isIsolated = flags.STACK_TYPE === StackType.COMPONENT;
|
|
168
|
+
const routeKey = current.route.key;
|
|
169
169
|
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
const isFirstScreen = useNavigationState((state) => {
|
|
171
|
+
const index = state.routes.findIndex((route) => route.key === routeKey);
|
|
172
|
+
return index === 0;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const canDismiss = Boolean(
|
|
176
|
+
isFirstScreen ? false : current.options.gestureEnabled,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const { hasSnapPoints } = useMemo(
|
|
180
|
+
() =>
|
|
181
|
+
validateSnapPoints({
|
|
182
|
+
snapPoints: current.options.snapPoints,
|
|
183
|
+
canDismiss,
|
|
184
|
+
}),
|
|
185
|
+
[current.options.snapPoints, canDismiss],
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const gestureEnabled = canDismiss || hasSnapPoints;
|
|
173
189
|
|
|
174
190
|
const claimedDirections = useMemo(
|
|
175
191
|
() =>
|
|
176
192
|
computeClaimedDirections(
|
|
177
|
-
|
|
193
|
+
gestureEnabled,
|
|
178
194
|
current.options.gestureDirection,
|
|
179
195
|
hasSnapPoints,
|
|
180
196
|
),
|
|
181
|
-
[
|
|
197
|
+
[gestureEnabled, current.options.gestureDirection, hasSnapPoints],
|
|
182
198
|
);
|
|
183
199
|
|
|
184
|
-
const routeKey = current.route.key;
|
|
185
|
-
|
|
186
200
|
// Check if this screen is the current (topmost) route in its navigator
|
|
187
201
|
const isCurrentRoute = useNavigationState(
|
|
188
202
|
(state) => state.routes[state.index]?.key === routeKey,
|
|
@@ -208,17 +222,30 @@ export const {
|
|
|
208
222
|
isIsolated,
|
|
209
223
|
});
|
|
210
224
|
|
|
211
|
-
const value
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const value = useMemo<GestureContextType>(
|
|
226
|
+
() => ({
|
|
227
|
+
panGesture,
|
|
228
|
+
panGestureRef,
|
|
229
|
+
scrollConfig,
|
|
230
|
+
gestureAnimationValues,
|
|
231
|
+
ancestorContext,
|
|
232
|
+
gestureEnabled,
|
|
233
|
+
isIsolated,
|
|
234
|
+
claimedDirections,
|
|
235
|
+
childDirectionClaims,
|
|
236
|
+
}),
|
|
237
|
+
[
|
|
238
|
+
panGesture,
|
|
239
|
+
panGestureRef,
|
|
240
|
+
scrollConfig,
|
|
241
|
+
gestureAnimationValues,
|
|
242
|
+
ancestorContext,
|
|
243
|
+
gestureEnabled,
|
|
244
|
+
isIsolated,
|
|
245
|
+
claimedDirections,
|
|
246
|
+
childDirectionClaims,
|
|
247
|
+
],
|
|
248
|
+
);
|
|
222
249
|
|
|
223
250
|
return {
|
|
224
251
|
value,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode, useCallback, useMemo } from "react";
|
|
2
2
|
import { useWindowDimensions, type View } from "react-native";
|
|
3
3
|
import {
|
|
4
4
|
type AnimatedRef,
|
|
@@ -44,35 +44,38 @@ const { LayoutAnchorProvider, useLayoutAnchorContext } = createProvider(
|
|
|
44
44
|
({ anchorRef, children }) => {
|
|
45
45
|
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
46
46
|
|
|
47
|
-
const correctMeasurement = (
|
|
48
|
-
measured: MeasuredDimensions
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!anchor) return measured;
|
|
47
|
+
const correctMeasurement = useCallback(
|
|
48
|
+
(measured: MeasuredDimensions): MeasuredDimensions => {
|
|
49
|
+
"worklet";
|
|
50
|
+
const anchor = measure(anchorRef);
|
|
51
|
+
if (!anchor) return measured;
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
// Compute scale factor by comparing anchor size to expected screen size.
|
|
54
|
+
// Anchor should be full-screen (absoluteFill), so any difference is from scale.
|
|
55
|
+
const scaleX = anchor.width > 0 ? anchor.width / screenWidth : 1;
|
|
56
|
+
const scaleY = anchor.height > 0 ? anchor.height / screenHeight : 1;
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
// Get element position relative to anchor (removes translation)
|
|
59
|
+
const relativeX = measured.pageX - anchor.pageX;
|
|
60
|
+
const relativeY = measured.pageY - anchor.pageY;
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
// Reverse scale: divide relative position and dimensions by scale factor
|
|
63
|
+
return {
|
|
64
|
+
x: measured.x,
|
|
65
|
+
y: measured.y,
|
|
66
|
+
width: scaleX !== 1 ? measured.width / scaleX : measured.width,
|
|
67
|
+
height: scaleY !== 1 ? measured.height / scaleY : measured.height,
|
|
68
|
+
pageX: scaleX !== 1 ? relativeX / scaleX : relativeX,
|
|
69
|
+
pageY: scaleY !== 1 ? relativeY / scaleY : relativeY,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
[anchorRef, screenWidth, screenHeight],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const value = useMemo(() => ({ correctMeasurement }), [correctMeasurement]);
|
|
73
76
|
|
|
74
77
|
return {
|
|
75
|
-
value
|
|
78
|
+
value,
|
|
76
79
|
children,
|
|
77
80
|
};
|
|
78
81
|
},
|
|
@@ -37,7 +37,7 @@ export function ScreenStylesProvider({ children }: Props) {
|
|
|
37
37
|
const stylesMap = useDerivedValue<TransitionInterpolatedStyle>(() => {
|
|
38
38
|
"worklet";
|
|
39
39
|
const props = screenInterpolatorProps.value;
|
|
40
|
-
const { current, next, progress
|
|
40
|
+
const { current, next, progress } = props;
|
|
41
41
|
const isDragging = current.gesture.isDragging;
|
|
42
42
|
const isNextClosing = !!next?.closing;
|
|
43
43
|
|
|
@@ -51,9 +51,6 @@ export function ScreenStylesProvider({ children }: Props) {
|
|
|
51
51
|
|
|
52
52
|
const isInGestureMode = isDragging || isGesturingDuringCloseAnimation.value;
|
|
53
53
|
|
|
54
|
-
const hasPushedScreenWhileClosing =
|
|
55
|
-
!isInGestureMode && isNextClosing && stackProgress > progress;
|
|
56
|
-
|
|
57
54
|
// Select interpolator
|
|
58
55
|
// - If in gesture mode, use current screen's interpolator since we're driving
|
|
59
56
|
// the animation from this screen (dragging back to dismiss next).
|
|
@@ -65,7 +62,6 @@ export function ScreenStylesProvider({ children }: Props) {
|
|
|
65
62
|
|
|
66
63
|
// Build effective props with corrected progress
|
|
67
64
|
// - Gesture mode: use current.progress only (avoids jumps during drag)
|
|
68
|
-
// - Pushed while closing: use stackProgress (includes new screen)
|
|
69
65
|
// - Normal: use derived progress as-is
|
|
70
66
|
|
|
71
67
|
let effectiveProgress = progress;
|
|
@@ -74,8 +70,6 @@ export function ScreenStylesProvider({ children }: Props) {
|
|
|
74
70
|
if (isInGestureMode) {
|
|
75
71
|
effectiveProgress = current.progress;
|
|
76
72
|
effectiveNext = undefined;
|
|
77
|
-
} else if (hasPushedScreenWhileClosing) {
|
|
78
|
-
effectiveProgress = stackProgress;
|
|
79
73
|
}
|
|
80
74
|
|
|
81
75
|
try {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type AnimationStoreMap,
|
|
21
21
|
} from "../../stores/animation.store";
|
|
22
22
|
import { HistoryStore } from "../../stores/history.store";
|
|
23
|
+
import { isFloatOverlayVisible } from "../../utils/overlay/visibility";
|
|
23
24
|
import { useStackCoreContext } from "./core.provider";
|
|
24
25
|
|
|
25
26
|
export interface DirectStackScene {
|
|
@@ -116,8 +117,7 @@ function useDirectStackValue(
|
|
|
116
117
|
const options = descriptor.options;
|
|
117
118
|
if (
|
|
118
119
|
options?.enableTransitions === true &&
|
|
119
|
-
options
|
|
120
|
-
options?.overlayShown === true
|
|
120
|
+
isFloatOverlayVisible(options)
|
|
121
121
|
) {
|
|
122
122
|
shouldShowFloatOverlay = true;
|
|
123
123
|
}
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
BaseStackScene,
|
|
23
23
|
BaseStackState,
|
|
24
24
|
} from "../../types/stack.types";
|
|
25
|
+
import { isFloatOverlayVisible } from "../../utils/overlay/visibility";
|
|
25
26
|
import { useStackCoreContext } from "./core.provider";
|
|
26
27
|
import { useLocalRoutes } from "./helpers/use-local-routes";
|
|
27
28
|
|
|
@@ -115,8 +116,7 @@ function useManagedStackValue<
|
|
|
115
116
|
animationMaps[i] = AnimationStore.getAll(route.key);
|
|
116
117
|
|
|
117
118
|
if (!shouldShowFloatOverlay) {
|
|
118
|
-
shouldShowFloatOverlay =
|
|
119
|
-
options?.overlayMode === "float" && options?.overlayShown === true;
|
|
119
|
+
shouldShowFloatOverlay = isFloatOverlayVisible(options);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
if (!stopLimit) {
|
|
@@ -77,7 +77,10 @@ export type ScreenTransitionConfig = {
|
|
|
77
77
|
transitionSpec?: TransitionSpec;
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
80
|
+
* Controls whether swipe-to-dismiss is enabled.
|
|
81
|
+
*
|
|
82
|
+
* For screens with `snapPoints`, gesture-driven snapping between non-dismiss
|
|
83
|
+
* snap points remains available even when this is `false`.
|
|
81
84
|
*/
|
|
82
85
|
gestureEnabled?: boolean;
|
|
83
86
|
|
|
@@ -210,4 +213,17 @@ export type ScreenTransitionConfig = {
|
|
|
210
213
|
* @default 'block' (or 'passthrough' for component stacks)
|
|
211
214
|
*/
|
|
212
215
|
backdropBehavior?: "block" | "passthrough" | "dismiss" | "collapse";
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Custom component to render as the backdrop layer.
|
|
219
|
+
* When provided, replaces the default backdrop entirely — including press handling.
|
|
220
|
+
*
|
|
221
|
+
* Use `useScreenAnimation()` inside the component to access animation values.
|
|
222
|
+
* Use your navigation method of choice (e.g. `router.back()`) to handle dismissal.
|
|
223
|
+
*
|
|
224
|
+
* `backdropBehavior` still controls container-level pointer events when this is set.
|
|
225
|
+
*
|
|
226
|
+
* @default undefined
|
|
227
|
+
*/
|
|
228
|
+
backdropComponent?: React.FC;
|
|
213
229
|
};
|
|
@@ -43,10 +43,21 @@ export function determineSnapTarget({
|
|
|
43
43
|
// Project where we'd end up with velocity
|
|
44
44
|
const projectedProgress = currentProgress - velocityInProgress;
|
|
45
45
|
|
|
46
|
+
const sanitizedSnapPoints = snapPoints.filter((point) =>
|
|
47
|
+
canDismiss ? Number.isFinite(point) : Number.isFinite(point) && point > 0,
|
|
48
|
+
);
|
|
49
|
+
|
|
46
50
|
// Build all possible targets: dismiss (0) only if allowed, plus all snap points
|
|
47
|
-
const allTargets =
|
|
48
|
-
? [0, ...
|
|
49
|
-
|
|
51
|
+
const allTargets = Array.from(
|
|
52
|
+
new Set(canDismiss ? [0, ...sanitizedSnapPoints] : sanitizedSnapPoints),
|
|
53
|
+
).sort((a, b) => a - b);
|
|
54
|
+
|
|
55
|
+
if (allTargets.length === 0) {
|
|
56
|
+
return {
|
|
57
|
+
targetProgress: currentProgress,
|
|
58
|
+
shouldDismiss: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
50
61
|
|
|
51
62
|
// Find the target whose zone contains the projected progress
|
|
52
63
|
// Zones are split at midpoints between adjacent targets
|
|
@@ -74,6 +85,6 @@ export function determineSnapTarget({
|
|
|
74
85
|
|
|
75
86
|
return {
|
|
76
87
|
targetProgress,
|
|
77
|
-
shouldDismiss: targetProgress === 0,
|
|
88
|
+
shouldDismiss: canDismiss && targetProgress === 0,
|
|
78
89
|
};
|
|
79
90
|
}
|
|
@@ -22,7 +22,17 @@ export function findCollapseTarget(
|
|
|
22
22
|
): FindCollapseTargetResult {
|
|
23
23
|
"worklet";
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const normalized = snapPoints.filter((point) =>
|
|
26
|
+
canDismiss ? Number.isFinite(point) : Number.isFinite(point) && point > 0,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (normalized.length === 0) {
|
|
30
|
+
return canDismiss
|
|
31
|
+
? { target: 0, shouldDismiss: true }
|
|
32
|
+
: { target: currentProgress, shouldDismiss: false };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sorted = [...normalized].sort((a, b) => a - b);
|
|
26
36
|
const minSnap = sorted[0];
|
|
27
37
|
|
|
28
38
|
// Find next lower snap point
|
|
@@ -14,7 +14,7 @@ export const validateSnapPoints = ({
|
|
|
14
14
|
snapPoints,
|
|
15
15
|
canDismiss,
|
|
16
16
|
}: ValidateSnapPointsOptions): ValidateSnapPointsResult => {
|
|
17
|
-
if (!snapPoints) {
|
|
17
|
+
if (!snapPoints || snapPoints.length === 0) {
|
|
18
18
|
return {
|
|
19
19
|
hasSnapPoints: false,
|
|
20
20
|
snapPoints: [],
|
|
@@ -23,7 +23,20 @@ export const validateSnapPoints = ({
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const normalizedSnaps = snapPoints.filter((point) =>
|
|
27
|
+
canDismiss ? Number.isFinite(point) : Number.isFinite(point) && point > 0,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (normalizedSnaps.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
hasSnapPoints: false,
|
|
33
|
+
snapPoints: [],
|
|
34
|
+
minSnapPoint: -1,
|
|
35
|
+
maxSnapPoint: -1,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sortedSnaps = normalizedSnaps.slice().sort((a, b) => a - b);
|
|
27
40
|
// Clamp to snap point bounds (dismiss at 0 only if allowed)
|
|
28
41
|
const minProgress = canDismiss ? 0 : sortedSnaps[0];
|
|
29
42
|
const maxProgress = sortedSnaps[sortedSnaps.length - 1];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { OverlayMode } from "../../types/overlay.types";
|
|
2
|
+
|
|
3
|
+
type OverlayOptionsLike = {
|
|
4
|
+
overlay?: unknown;
|
|
5
|
+
overlayMode?: OverlayMode;
|
|
6
|
+
overlayShown?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const isOverlayVisible = (options?: OverlayOptionsLike): boolean => {
|
|
10
|
+
return Boolean(options?.overlay) && options?.overlayShown !== false;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const isFloatOverlayVisible = (
|
|
14
|
+
options?: OverlayOptionsLike,
|
|
15
|
+
): boolean => {
|
|
16
|
+
return isOverlayVisible(options) && options?.overlayMode !== "screen";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const isScreenOverlayVisible = (
|
|
20
|
+
options?: OverlayOptionsLike,
|
|
21
|
+
): boolean => {
|
|
22
|
+
return isOverlayVisible(options) && options?.overlayMode === "screen";
|
|
23
|
+
};
|