react-native-screen-transitions 3.4.0-alpha.4 → 3.4.0-alpha.6
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 +968 -11
- package/lib/commonjs/shared/components/create-boundary-component/index.js +13 -0
- package/lib/commonjs/shared/components/create-boundary-component/index.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container/deferred-visibility-host.js +3 -1
- package/lib/commonjs/shared/components/screen-container/deferred-visibility-host.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container/hooks/use-backdrop-pointer-events.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container/index.js +10 -2
- package/lib/commonjs/shared/components/screen-container/index.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container/layers/backdrop.js +4 -6
- package/lib/commonjs/shared/components/screen-container/layers/backdrop.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container/layers/content.js +3 -6
- package/lib/commonjs/shared/components/screen-container/layers/content.js.map +1 -1
- package/lib/commonjs/shared/constants.js +2 -0
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures/helpers/gesture-physics.js +3 -2
- package/lib/commonjs/shared/providers/gestures/helpers/gesture-physics.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures/helpers/gesture-reset.js +7 -5
- package/lib/commonjs/shared/providers/gestures/helpers/gesture-reset.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures/helpers/gesture-targets.js +9 -6
- package/lib/commonjs/shared/providers/gestures/helpers/gesture-targets.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/animation/helpers/hydrate-transition-state.js +33 -1
- package/lib/commonjs/shared/providers/screen/animation/helpers/hydrate-transition-state.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/animation/helpers/pipeline.js +2 -0
- package/lib/commonjs/shared/providers/screen/animation/helpers/pipeline.js.map +1 -1
- package/lib/commonjs/shared/providers/screen/animation/helpers/use-build-transition-state.js +1 -0
- package/lib/commonjs/shared/providers/screen/animation/helpers/use-build-transition-state.js.map +1 -1
- package/lib/commonjs/shared/utils/bounds/zoom/build.js +13 -8
- package/lib/commonjs/shared/utils/bounds/zoom/build.js.map +1 -1
- package/lib/module/shared/components/create-boundary-component/index.js +13 -0
- package/lib/module/shared/components/create-boundary-component/index.js.map +1 -1
- package/lib/module/shared/components/screen-container/deferred-visibility-host.js +3 -1
- package/lib/module/shared/components/screen-container/deferred-visibility-host.js.map +1 -1
- package/lib/module/shared/components/screen-container/hooks/use-backdrop-pointer-events.js.map +1 -1
- package/lib/module/shared/components/screen-container/index.js +10 -2
- package/lib/module/shared/components/screen-container/index.js.map +1 -1
- package/lib/module/shared/components/screen-container/layers/backdrop.js +4 -6
- package/lib/module/shared/components/screen-container/layers/backdrop.js.map +1 -1
- package/lib/module/shared/components/screen-container/layers/content.js +3 -6
- package/lib/module/shared/components/screen-container/layers/content.js.map +1 -1
- package/lib/module/shared/constants.js +2 -0
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/providers/gestures/helpers/gesture-physics.js +3 -2
- package/lib/module/shared/providers/gestures/helpers/gesture-physics.js.map +1 -1
- package/lib/module/shared/providers/gestures/helpers/gesture-reset.js +7 -5
- package/lib/module/shared/providers/gestures/helpers/gesture-reset.js.map +1 -1
- package/lib/module/shared/providers/gestures/helpers/gesture-targets.js +9 -6
- package/lib/module/shared/providers/gestures/helpers/gesture-targets.js.map +1 -1
- package/lib/module/shared/providers/screen/animation/helpers/hydrate-transition-state.js +32 -1
- package/lib/module/shared/providers/screen/animation/helpers/hydrate-transition-state.js.map +1 -1
- package/lib/module/shared/providers/screen/animation/helpers/pipeline.js +2 -0
- package/lib/module/shared/providers/screen/animation/helpers/pipeline.js.map +1 -1
- package/lib/module/shared/providers/screen/animation/helpers/use-build-transition-state.js +1 -0
- package/lib/module/shared/providers/screen/animation/helpers/use-build-transition-state.js.map +1 -1
- package/lib/module/shared/utils/bounds/zoom/build.js +13 -8
- package/lib/module/shared/utils/bounds/zoom/build.js.map +1 -1
- package/lib/typescript/shared/components/create-boundary-component/index.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container/deferred-visibility-host.d.ts +2 -1
- package/lib/typescript/shared/components/screen-container/deferred-visibility-host.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container/hooks/use-backdrop-pointer-events.d.ts +1 -1
- package/lib/typescript/shared/components/screen-container/hooks/use-backdrop-pointer-events.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container/index.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container/layers/backdrop.d.ts +5 -1
- package/lib/typescript/shared/components/screen-container/layers/backdrop.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container/layers/content.d.ts +3 -1
- package/lib/typescript/shared/components/screen-container/layers/content.d.ts.map +1 -1
- package/lib/typescript/shared/constants.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures/helpers/gesture-physics.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures/helpers/gesture-reset.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures/helpers/gesture-targets.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/animation/helpers/hydrate-transition-state.d.ts +16 -0
- package/lib/typescript/shared/providers/screen/animation/helpers/hydrate-transition-state.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/animation/helpers/pipeline.d.ts.map +1 -1
- package/lib/typescript/shared/providers/screen/animation/helpers/use-build-transition-state.d.ts +1 -0
- package/lib/typescript/shared/providers/screen/animation/helpers/use-build-transition-state.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +13 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/screen.types.d.ts +2 -1
- package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/bounds/zoom/build.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/components/create-boundary-component/index.tsx +13 -0
- package/src/shared/components/screen-container/deferred-visibility-host.tsx +19 -12
- package/src/shared/components/screen-container/hooks/use-backdrop-pointer-events.ts +1 -2
- package/src/shared/components/screen-container/index.tsx +13 -4
- package/src/shared/components/screen-container/layers/backdrop.tsx +9 -3
- package/src/shared/components/screen-container/layers/content.tsx +46 -42
- package/src/shared/constants.ts +2 -0
- package/src/shared/providers/gestures/helpers/gesture-physics.ts +4 -2
- package/src/shared/providers/gestures/helpers/gesture-reset.ts +9 -5
- package/src/shared/providers/gestures/helpers/gesture-targets.ts +14 -6
- package/src/shared/providers/screen/animation/helpers/hydrate-transition-state.ts +49 -1
- package/src/shared/providers/screen/animation/helpers/pipeline.ts +2 -0
- package/src/shared/providers/screen/animation/helpers/use-build-transition-state.ts +2 -0
- package/src/shared/types/animation.types.ts +15 -0
- package/src/shared/types/screen.types.ts +3 -1
- package/src/shared/utils/bounds/zoom/build.ts +33 -9
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ import { useAutoSourceMeasurement } from "./hooks/use-auto-source-measurement";
|
|
|
17
17
|
import { useBoundaryMeasureAndStore } from "./hooks/use-boundary-measure-and-store";
|
|
18
18
|
import { useBoundaryPresence } from "./hooks/use-boundary-presence";
|
|
19
19
|
import { useGroupActiveMeasurement } from "./hooks/use-group-active-measurement";
|
|
20
|
+
import { useGroupActiveSourceMeasurement } from "./hooks/use-group-active-source-measurement";
|
|
20
21
|
import { useInitialLayoutHandler } from "./hooks/use-initial-layout-handler";
|
|
21
22
|
import { usePendingDestinationMeasurement } from "./hooks/use-pending-destination-measurement";
|
|
22
23
|
import { usePendingDestinationRetryMeasurement } from "./hooks/use-pending-destination-retry-measurement";
|
|
@@ -172,6 +173,18 @@ export function createBoundaryComponent<P extends object>(
|
|
|
172
173
|
maybeMeasureAndStore,
|
|
173
174
|
});
|
|
174
175
|
|
|
176
|
+
// Source-side grouped retargeting: when an unfocused/source boundary
|
|
177
|
+
// becomes the active member, refresh its snapshot and source link so
|
|
178
|
+
// close transitions do not use stale pre-scroll geometry.
|
|
179
|
+
useGroupActiveSourceMeasurement({
|
|
180
|
+
enabled: runtimeEnabled,
|
|
181
|
+
group,
|
|
182
|
+
id,
|
|
183
|
+
hasNextScreen,
|
|
184
|
+
isAnimating,
|
|
185
|
+
maybeMeasureAndStore,
|
|
186
|
+
});
|
|
187
|
+
|
|
175
188
|
// While idle on source screens, re-measure after scroll settles so a later
|
|
176
189
|
// close transition starts from up-to-date source geometry.
|
|
177
190
|
useScrollSettledMeasurement({
|
|
@@ -6,6 +6,7 @@ import { useScreenStyles } from "../../providers/screen/styles.provider";
|
|
|
6
6
|
|
|
7
7
|
type Props = {
|
|
8
8
|
children: React.ReactNode;
|
|
9
|
+
pointerEvents: "box-none" | undefined;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -15,20 +16,26 @@ type Props = {
|
|
|
15
16
|
* This sits above backdrop/content/mask/surface so a deferred transition does
|
|
16
17
|
* not leak raw first-paint UI from nested layers.
|
|
17
18
|
*/
|
|
18
|
-
export const DeferredVisibilityHost = memo(
|
|
19
|
-
|
|
19
|
+
export const DeferredVisibilityHost = memo(
|
|
20
|
+
({ children, pointerEvents }: Props) => {
|
|
21
|
+
const { resolutionMode } = useScreenStyles();
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
24
|
+
"worklet";
|
|
25
|
+
return resolutionMode.value === "deferred" ? HIDDEN_STYLE : VISIBLE_STYLE;
|
|
26
|
+
});
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
return (
|
|
29
|
+
<Animated.View
|
|
30
|
+
collapsable={false}
|
|
31
|
+
style={[styles.host, animatedStyle]}
|
|
32
|
+
pointerEvents={pointerEvents}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
</Animated.View>
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
);
|
|
32
39
|
|
|
33
40
|
const styles = StyleSheet.create({
|
|
34
41
|
host: {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useDescriptors } from "../../../providers/screen/descriptors";
|
|
2
2
|
import { useStackCoreContext } from "../../../providers/stack/core.provider";
|
|
3
|
+
import type { BackdropBehavior } from "../../../types/screen.types";
|
|
3
4
|
import { StackType } from "../../../types/stack.types";
|
|
4
5
|
|
|
5
|
-
type BackdropBehavior = "block" | "passthrough" | "dismiss" | "collapse";
|
|
6
|
-
|
|
7
6
|
interface BackdropPointerEventsResult {
|
|
8
7
|
pointerEvents: "box-none" | undefined;
|
|
9
8
|
backdropBehavior: BackdropBehavior;
|
|
@@ -10,13 +10,22 @@ type Props = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export const ScreenContainer = memo(({ children }: Props) => {
|
|
13
|
-
const { pointerEvents } =
|
|
13
|
+
const { pointerEvents, isBackdropActive, backdropBehavior } =
|
|
14
|
+
useBackdropPointerEvents();
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
17
|
<View style={styles.container} pointerEvents={pointerEvents}>
|
|
17
|
-
<DeferredVisibilityHost>
|
|
18
|
-
<BackdropLayer
|
|
19
|
-
|
|
18
|
+
<DeferredVisibilityHost pointerEvents={pointerEvents}>
|
|
19
|
+
<BackdropLayer
|
|
20
|
+
isBackdropActive={isBackdropActive}
|
|
21
|
+
backdropBehavior={backdropBehavior}
|
|
22
|
+
/>
|
|
23
|
+
<ContentLayer
|
|
24
|
+
pointerEvents={pointerEvents}
|
|
25
|
+
isBackdropActive={isBackdropActive}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</ContentLayer>
|
|
20
29
|
</DeferredVisibilityHost>
|
|
21
30
|
</View>
|
|
22
31
|
);
|
|
@@ -13,15 +13,20 @@ import { useScreenStyles } from "../../../providers/screen/styles.provider";
|
|
|
13
13
|
import { AnimationStore } from "../../../stores/animation.store";
|
|
14
14
|
import { GestureStore } from "../../../stores/gesture.store";
|
|
15
15
|
import { SystemStore } from "../../../stores/system.store";
|
|
16
|
+
import type { BackdropBehavior } from "../../../types/screen.types";
|
|
16
17
|
import { animateToProgress } from "../../../utils/animation/animate-to-progress";
|
|
17
18
|
import { findCollapseTarget } from "../../../utils/gesture/find-collapse-target";
|
|
18
|
-
import { useBackdropPointerEvents } from "../hooks/use-backdrop-pointer-events";
|
|
19
19
|
|
|
20
|
-
export const BackdropLayer = memo(function BackdropLayer(
|
|
20
|
+
export const BackdropLayer = memo(function BackdropLayer({
|
|
21
|
+
backdropBehavior,
|
|
22
|
+
isBackdropActive,
|
|
23
|
+
}: {
|
|
24
|
+
backdropBehavior: BackdropBehavior;
|
|
25
|
+
isBackdropActive: boolean;
|
|
26
|
+
}) {
|
|
21
27
|
const { stylesMap } = useScreenStyles();
|
|
22
28
|
const { current } = useDescriptors();
|
|
23
29
|
const { dismissScreen } = useNavigationHelpers();
|
|
24
|
-
const { isBackdropActive, backdropBehavior } = useBackdropPointerEvents();
|
|
25
30
|
|
|
26
31
|
const BackdropComponent = current.options.backdropComponent;
|
|
27
32
|
const routeKey = current.route.key;
|
|
@@ -39,6 +44,7 @@ export const BackdropLayer = memo(function BackdropLayer() {
|
|
|
39
44
|
: null,
|
|
40
45
|
[BackdropComponent],
|
|
41
46
|
);
|
|
47
|
+
|
|
42
48
|
const handleBackdropPress = useCallback(() => {
|
|
43
49
|
if (backdropBehavior === "dismiss") {
|
|
44
50
|
dismissScreen();
|
|
@@ -11,64 +11,68 @@ import { useGestureContext } from "../../../providers/gestures";
|
|
|
11
11
|
import { useDescriptors } from "../../../providers/screen/descriptors";
|
|
12
12
|
import { useScreenStyles } from "../../../providers/screen/styles.provider";
|
|
13
13
|
import { resolveNavigationMaskEnabled } from "../../../utils/resolve-screen-transition-options";
|
|
14
|
-
import { useBackdropPointerEvents } from "../hooks/use-backdrop-pointer-events";
|
|
15
14
|
import { useContentLayout } from "../hooks/use-content-layout";
|
|
16
15
|
import { MaybeMaskedNavigationContainer } from "./maybe-masked-navigation-container";
|
|
17
16
|
import { SurfaceContainer } from "./surface-container";
|
|
18
17
|
|
|
19
18
|
type Props = {
|
|
20
19
|
children: React.ReactNode;
|
|
20
|
+
pointerEvents: "box-none" | undefined;
|
|
21
|
+
isBackdropActive: boolean;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
export const ContentLayer = memo(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
export const ContentLayer = memo(
|
|
25
|
+
({ children, pointerEvents, isBackdropActive }: Props) => {
|
|
26
|
+
const { stylesMap } = useScreenStyles();
|
|
27
|
+
const { current } = useDescriptors();
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const gestureContext = useGestureContext();
|
|
30
|
+
const isNavigationMaskEnabled = resolveNavigationMaskEnabled(
|
|
31
|
+
current.options,
|
|
32
|
+
);
|
|
33
|
+
const contentPointerEvents = isBackdropActive ? "box-none" : pointerEvents;
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
const hasAutoSnapPoint =
|
|
36
|
+
current.options.snapPoints?.includes("auto") ?? false;
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
const handleContentLayout = useContentLayout();
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const animatedContentStyle = useAnimatedStyle(() => {
|
|
41
|
+
"worklet";
|
|
42
|
+
return stylesMap.value.content?.style || NO_STYLES;
|
|
43
|
+
});
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const animatedContentProps = useAnimatedProps(() => {
|
|
46
|
+
"worklet";
|
|
47
|
+
return stylesMap.value.content?.props ?? NO_PROPS;
|
|
48
|
+
});
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
pointerEvents={contentPointerEvents}
|
|
53
|
-
>
|
|
54
|
-
<MaybeMaskedNavigationContainer
|
|
50
|
+
return (
|
|
51
|
+
<GestureDetector gesture={gestureContext!.panGesture}>
|
|
52
|
+
<Animated.View
|
|
53
|
+
style={[styles.content, animatedContentStyle]}
|
|
54
|
+
animatedProps={animatedContentProps}
|
|
55
55
|
pointerEvents={contentPointerEvents}
|
|
56
|
-
enabled={isNavigationMaskEnabled}
|
|
57
56
|
>
|
|
58
|
-
<
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
<MaybeMaskedNavigationContainer
|
|
58
|
+
pointerEvents={contentPointerEvents}
|
|
59
|
+
enabled={isNavigationMaskEnabled}
|
|
60
|
+
>
|
|
61
|
+
<SurfaceContainer pointerEvents={contentPointerEvents}>
|
|
62
|
+
{hasAutoSnapPoint ? (
|
|
63
|
+
<View collapsable={false} onLayout={handleContentLayout}>
|
|
64
|
+
{children}
|
|
65
|
+
</View>
|
|
66
|
+
) : (
|
|
67
|
+
children
|
|
68
|
+
)}
|
|
69
|
+
</SurfaceContainer>
|
|
70
|
+
</MaybeMaskedNavigationContainer>
|
|
71
|
+
</Animated.View>
|
|
72
|
+
</GestureDetector>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
72
76
|
|
|
73
77
|
const styles = StyleSheet.create({
|
|
74
78
|
content: {
|
package/src/shared/constants.ts
CHANGED
|
@@ -57,6 +57,7 @@ export const createScreenTransitionState = (
|
|
|
57
57
|
closing: 0,
|
|
58
58
|
animating: 0,
|
|
59
59
|
settled: 1,
|
|
60
|
+
logicallySettled: 1,
|
|
60
61
|
entering: 0,
|
|
61
62
|
gesture: { ...DEFAULT_GESTURE_VALUES },
|
|
62
63
|
route,
|
|
@@ -79,6 +80,7 @@ export const DEFAULT_SCREEN_TRANSITION_STATE: ScreenTransitionState =
|
|
|
79
80
|
closing: 0,
|
|
80
81
|
animating: 0,
|
|
81
82
|
settled: 1,
|
|
83
|
+
logicallySettled: 1,
|
|
82
84
|
entering: 0,
|
|
83
85
|
gesture: DEFAULT_GESTURE_VALUES,
|
|
84
86
|
route: {} as RouteProp<ParamListBase>,
|
|
@@ -31,10 +31,12 @@ type GestureAxisCandidate = {
|
|
|
31
31
|
export const normalizeVelocity = (
|
|
32
32
|
velocityPixelsPerSecond: number,
|
|
33
33
|
screenSize: number,
|
|
34
|
-
maxMagnitude
|
|
34
|
+
maxMagnitude?: number,
|
|
35
35
|
) => {
|
|
36
36
|
"worklet";
|
|
37
|
-
const
|
|
37
|
+
const resolvedMaxMagnitude =
|
|
38
|
+
maxMagnitude ?? DEFAULT_GESTURE_RELEASE_VELOCITY_MAX;
|
|
39
|
+
const max = Math.max(0, Math.abs(resolvedMaxMagnitude));
|
|
38
40
|
return clamp(velocityPixelsPerSecond / Math.max(1, screenSize), -max, max);
|
|
39
41
|
};
|
|
40
42
|
|
|
@@ -32,14 +32,18 @@ export const resetGestureValues = ({
|
|
|
32
32
|
shouldDismiss,
|
|
33
33
|
event,
|
|
34
34
|
dimensions,
|
|
35
|
-
gestureReleaseVelocityScale
|
|
36
|
-
gestureReleaseVelocityMax
|
|
35
|
+
gestureReleaseVelocityScale,
|
|
36
|
+
gestureReleaseVelocityMax,
|
|
37
37
|
}: ResetGestureValuesProps) => {
|
|
38
38
|
"worklet";
|
|
39
|
+
const resolvedGestureReleaseVelocityScale =
|
|
40
|
+
gestureReleaseVelocityScale ?? DEFAULT_GESTURE_RELEASE_VELOCITY_SCALE;
|
|
41
|
+
const resolvedGestureReleaseVelocityMax =
|
|
42
|
+
gestureReleaseVelocityMax ?? DEFAULT_GESTURE_RELEASE_VELOCITY_MAX;
|
|
39
43
|
|
|
40
44
|
const effectiveReleaseVelocityMax = Math.max(
|
|
41
45
|
0,
|
|
42
|
-
Math.abs(
|
|
46
|
+
Math.abs(resolvedGestureReleaseVelocityMax),
|
|
43
47
|
);
|
|
44
48
|
|
|
45
49
|
const vxNorm = normalizeVelocity(
|
|
@@ -67,11 +71,11 @@ export const resetGestureValues = ({
|
|
|
67
71
|
// so the spring carries the gesture's momentum. The spec controls the
|
|
68
72
|
// spring character — use an underdamped spec (e.g. FlingSpec) for orbit/fling.
|
|
69
73
|
const resetVX = shouldDismiss
|
|
70
|
-
? vxNorm *
|
|
74
|
+
? vxNorm * resolvedGestureReleaseVelocityScale
|
|
71
75
|
: vxTowardZero;
|
|
72
76
|
|
|
73
77
|
const resetVY = shouldDismiss
|
|
74
|
-
? vyNorm *
|
|
78
|
+
? vyNorm * resolvedGestureReleaseVelocityScale
|
|
75
79
|
: vyTowardZero;
|
|
76
80
|
|
|
77
81
|
animateMany({
|
|
@@ -111,22 +111,30 @@ export function determineSnapTarget({
|
|
|
111
111
|
snapPoints,
|
|
112
112
|
velocity,
|
|
113
113
|
dimension,
|
|
114
|
-
velocityFactor
|
|
115
|
-
canDismiss
|
|
114
|
+
velocityFactor,
|
|
115
|
+
canDismiss,
|
|
116
116
|
}: DetermineSnapTargetProps): DetermineSnapTargetResult {
|
|
117
117
|
"worklet";
|
|
118
|
+
const resolvedVelocityFactor =
|
|
119
|
+
velocityFactor ?? DEFAULT_GESTURE_SNAP_VELOCITY_IMPACT;
|
|
120
|
+
const resolvedCanDismiss = canDismiss ?? true;
|
|
118
121
|
|
|
119
122
|
// Convert velocity to progress units (positive = toward dismiss = decreasing progress)
|
|
120
|
-
const velocityInProgress = (velocity / dimension) *
|
|
123
|
+
const velocityInProgress = (velocity / dimension) * resolvedVelocityFactor;
|
|
121
124
|
|
|
122
125
|
// Project where we'd end up with velocity
|
|
123
126
|
const projectedProgress = currentProgress - velocityInProgress;
|
|
124
127
|
|
|
125
|
-
const sanitizedSnapPoints = sanitizeSnapPoints(
|
|
128
|
+
const sanitizedSnapPoints = sanitizeSnapPoints(
|
|
129
|
+
snapPoints,
|
|
130
|
+
resolvedCanDismiss,
|
|
131
|
+
);
|
|
126
132
|
|
|
127
133
|
// Build all possible targets: dismiss (0) only if allowed, plus all snap points
|
|
128
134
|
const allTargets = Array.from(
|
|
129
|
-
new Set(
|
|
135
|
+
new Set(
|
|
136
|
+
resolvedCanDismiss ? [0, ...sanitizedSnapPoints] : sanitizedSnapPoints,
|
|
137
|
+
),
|
|
130
138
|
).sort((a, b) => a - b);
|
|
131
139
|
|
|
132
140
|
if (allTargets.length === 0) {
|
|
@@ -162,6 +170,6 @@ export function determineSnapTarget({
|
|
|
162
170
|
|
|
163
171
|
return {
|
|
164
172
|
targetProgress,
|
|
165
|
-
shouldDismiss:
|
|
173
|
+
shouldDismiss: resolvedCanDismiss && targetProgress === 0,
|
|
166
174
|
};
|
|
167
175
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { SharedValue } from "react-native-reanimated";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ANIMATION_SNAP_THRESHOLD,
|
|
4
|
+
EPSILON,
|
|
5
|
+
FALSE,
|
|
6
|
+
TRUE,
|
|
7
|
+
} from "../../../../constants";
|
|
3
8
|
import type { GestureStoreMap } from "../../../../stores/gesture.store";
|
|
4
9
|
import type { ScreenTransitionState } from "../../../../types/animation.types";
|
|
5
10
|
import type { Layout } from "../../../../types/screen.types";
|
|
@@ -13,6 +18,7 @@ type BuiltState = {
|
|
|
13
18
|
gesture: GestureStoreMap;
|
|
14
19
|
route: BaseStackRoute;
|
|
15
20
|
meta?: Record<string, unknown>;
|
|
21
|
+
targetProgress: SharedValue<number>;
|
|
16
22
|
resolvedAutoSnapPoint: SharedValue<number>;
|
|
17
23
|
measuredContentLayout: SharedValue<Layout | null>;
|
|
18
24
|
hasAutoSnapPoint: boolean;
|
|
@@ -20,6 +26,13 @@ type BuiltState = {
|
|
|
20
26
|
unwrapped: ScreenTransitionState;
|
|
21
27
|
};
|
|
22
28
|
|
|
29
|
+
interface ComputeLogicallySettledParams {
|
|
30
|
+
progress: number;
|
|
31
|
+
targetProgress: number;
|
|
32
|
+
settled: number;
|
|
33
|
+
dragging: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Computes the animated snap index based on progress and snap points.
|
|
25
38
|
* Returns -1 if no snap points, otherwise interpolates between indices.
|
|
@@ -41,6 +54,35 @@ const computeSnapIndex = (progress: number, snapPoints: number[]): number => {
|
|
|
41
54
|
return snapPoints.length - 1;
|
|
42
55
|
};
|
|
43
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Determines whether the screen transition is logically settled.
|
|
59
|
+
*
|
|
60
|
+
* A transition is considered logically settled when:
|
|
61
|
+
* - The `settled` flag is explicitly set, OR
|
|
62
|
+
* - The screen is not being dragged AND the progress is within
|
|
63
|
+
* {@link ANIMATION_SNAP_THRESHOLD} of the target progress.
|
|
64
|
+
*/
|
|
65
|
+
export const computeLogicallySettled = ({
|
|
66
|
+
progress,
|
|
67
|
+
targetProgress,
|
|
68
|
+
settled,
|
|
69
|
+
dragging,
|
|
70
|
+
}: ComputeLogicallySettledParams) => {
|
|
71
|
+
"worklet";
|
|
72
|
+
|
|
73
|
+
if (settled) {
|
|
74
|
+
return TRUE;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (dragging) {
|
|
78
|
+
return FALSE;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Math.abs(progress - targetProgress) <= ANIMATION_SNAP_THRESHOLD
|
|
82
|
+
? TRUE
|
|
83
|
+
: FALSE;
|
|
84
|
+
};
|
|
85
|
+
|
|
44
86
|
export const hydrateTransitionState = (
|
|
45
87
|
s: BuiltState,
|
|
46
88
|
dimensions: Layout,
|
|
@@ -72,6 +114,12 @@ export const hydrateTransitionState = (
|
|
|
72
114
|
out.gesture.isDragging = out.gesture.dragging;
|
|
73
115
|
|
|
74
116
|
out.settled = out.animating || out.gesture.dismissing || out.closing ? 0 : 1;
|
|
117
|
+
out.logicallySettled = computeLogicallySettled({
|
|
118
|
+
progress: out.progress,
|
|
119
|
+
targetProgress: s.targetProgress.value,
|
|
120
|
+
settled: out.settled,
|
|
121
|
+
dragging: out.gesture.dragging,
|
|
122
|
+
});
|
|
75
123
|
|
|
76
124
|
out.meta = s.meta;
|
|
77
125
|
out.layouts.screen.width = dimensions.width;
|
|
@@ -71,6 +71,7 @@ export function useScreenAnimationPipeline(): ScreenAnimationPipeline {
|
|
|
71
71
|
progress: 0,
|
|
72
72
|
stackProgress: 0,
|
|
73
73
|
snapIndex: -1,
|
|
74
|
+
logicallySettled: 1,
|
|
74
75
|
focused: true,
|
|
75
76
|
active: DEFAULT_SCREEN_TRANSITION_STATE,
|
|
76
77
|
navigationMaskEnabled: currentNavigationMaskEnabled,
|
|
@@ -121,6 +122,7 @@ export function useScreenAnimationPipeline(): ScreenAnimationPipeline {
|
|
|
121
122
|
progress,
|
|
122
123
|
stackProgress,
|
|
123
124
|
snapIndex: current.snapIndex,
|
|
125
|
+
logicallySettled: helpers.active.logicallySettled,
|
|
124
126
|
...helpers,
|
|
125
127
|
};
|
|
126
128
|
|
|
@@ -23,6 +23,7 @@ type BuiltState = {
|
|
|
23
23
|
gesture: GestureStoreMap;
|
|
24
24
|
route: BaseStackRoute;
|
|
25
25
|
meta?: Record<string, unknown>;
|
|
26
|
+
targetProgress: SharedValue<number>;
|
|
26
27
|
resolvedAutoSnapPoint: SharedValue<number>;
|
|
27
28
|
measuredContentLayout: SharedValue<Layout | null>;
|
|
28
29
|
hasAutoSnapPoint: boolean;
|
|
@@ -62,6 +63,7 @@ export const useBuildTransitionState = (
|
|
|
62
63
|
closing: AnimationStore.getValue(key, "closing"),
|
|
63
64
|
entering: AnimationStore.getValue(key, "entering"),
|
|
64
65
|
animating: AnimationStore.getValue(key, "animating"),
|
|
66
|
+
targetProgress: SystemStore.getValue(key, "targetProgress"),
|
|
65
67
|
resolvedAutoSnapPoint: SystemStore.getValue(key, "resolvedAutoSnapPoint"),
|
|
66
68
|
measuredContentLayout: SystemStore.getValue(key, "measuredContentLayout"),
|
|
67
69
|
hasAutoSnapPoint: snapPoints?.includes("auto") ?? false,
|
|
@@ -53,6 +53,15 @@ export type ScreenTransitionState = {
|
|
|
53
53
|
*/
|
|
54
54
|
settled: number;
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Whether this screen is logically complete for choreography purposes.
|
|
58
|
+
* - `0`: The screen is still meaningfully away from its animation target
|
|
59
|
+
* - `1`: The screen is visually close enough to its target to be treated as done
|
|
60
|
+
*
|
|
61
|
+
* Unlike `settled`, this may become `1` before the underlying spring fully stops.
|
|
62
|
+
*/
|
|
63
|
+
logicallySettled: number;
|
|
64
|
+
|
|
56
65
|
/**
|
|
57
66
|
* Live gesture values for this screen.
|
|
58
67
|
* Contains translation (x, y), normalized values (-1 to 1),
|
|
@@ -176,6 +185,12 @@ export interface ScreenInterpolationProps {
|
|
|
176
185
|
*/
|
|
177
186
|
snapIndex: number;
|
|
178
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Whether the active transition is visually close enough to its target to be
|
|
190
|
+
* treated as complete, even if the animation is still physically settling.
|
|
191
|
+
*/
|
|
192
|
+
logicallySettled: number;
|
|
193
|
+
|
|
179
194
|
/**
|
|
180
195
|
* Function that provides access to bounds helpers for shared screen transitions.
|
|
181
196
|
*/
|
|
@@ -22,6 +22,8 @@ export type SheetScrollGestureBehavior =
|
|
|
22
22
|
*/
|
|
23
23
|
export type SnapPoint = number | "auto";
|
|
24
24
|
|
|
25
|
+
export type BackdropBehavior = "block" | "passthrough" | "dismiss" | "collapse";
|
|
26
|
+
|
|
25
27
|
export type TransitionAwareProps<T extends object> = AnimatedProps<T> & {
|
|
26
28
|
/**
|
|
27
29
|
* Connects this component to custom animated styles defined in screenStyleInterpolator.
|
|
@@ -282,7 +284,7 @@ export type ScreenTransitionConfig = {
|
|
|
282
284
|
*
|
|
283
285
|
* @default 'block' (or 'passthrough' for component stacks)
|
|
284
286
|
*/
|
|
285
|
-
backdropBehavior?:
|
|
287
|
+
backdropBehavior?: BackdropBehavior;
|
|
286
288
|
|
|
287
289
|
/**
|
|
288
290
|
* Custom component to render as the backdrop layer (between screens).
|
|
@@ -143,7 +143,10 @@ export const buildZoomStyles = ({
|
|
|
143
143
|
? NAVIGATION_MASK_CONTAINER_STYLE_ID
|
|
144
144
|
: "content";
|
|
145
145
|
|
|
146
|
-
if
|
|
146
|
+
// To avoid initial flickering, we'll want to hide if there are no source bounds
|
|
147
|
+
// But to also avoid scenarios where activeId changes in dst and theres a failed measurement,
|
|
148
|
+
// we should only hide if entering and there is no source bounds.
|
|
149
|
+
if (!resolvedPair.sourceBounds && props.active.entering) {
|
|
147
150
|
return {
|
|
148
151
|
[focusedContainerStyleId]: HIDDEN_STYLE,
|
|
149
152
|
};
|
|
@@ -281,11 +284,12 @@ export const buildZoomStyles = ({
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
const unfocusedFade = props.active?.closing
|
|
284
|
-
? interpolate(progress, [1.6, 2], [1, debug ?
|
|
285
|
-
: interpolate(progress, [1, 1.5], [1, debug ?
|
|
287
|
+
? interpolate(progress, [1.6, 2], [1, debug ? 1 : 0], "clamp")
|
|
288
|
+
: interpolate(progress, [1, 1.5], [1, debug ? 1 : 0], "clamp");
|
|
286
289
|
|
|
287
290
|
const unfocusedScale = interpolate(progress, [1, 2], [1, 0.95], "clamp");
|
|
288
291
|
const isUnfocusedIdle = props.active.settled === 1;
|
|
292
|
+
const shouldHideUnfocusedIdle = isUnfocusedIdle && !debug;
|
|
289
293
|
const elementTarget =
|
|
290
294
|
explicitTarget !== undefined || resolvedPair.destinationBounds
|
|
291
295
|
? getZoomContentTarget({
|
|
@@ -355,7 +359,7 @@ export const buildZoomStyles = ({
|
|
|
355
359
|
toNumber(elementRaw.translateY) + compensatedGestureY;
|
|
356
360
|
const elementScaleX = toNumber(elementRaw.scaleX, 1) * dragScale;
|
|
357
361
|
const elementScaleY = toNumber(elementRaw.scaleY, 1) * dragScale;
|
|
358
|
-
const resolvedElementStyle =
|
|
362
|
+
const resolvedElementStyle = shouldHideUnfocusedIdle
|
|
359
363
|
? {
|
|
360
364
|
transform: [
|
|
361
365
|
{ translateX: 0 },
|
|
@@ -369,12 +373,32 @@ export const buildZoomStyles = ({
|
|
|
369
373
|
}
|
|
370
374
|
: {
|
|
371
375
|
transform: [
|
|
372
|
-
{
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
+
{
|
|
377
|
+
translateX:
|
|
378
|
+
props.active.logicallySettled && !props.active.closing
|
|
379
|
+
? 0
|
|
380
|
+
: elementTranslateX,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
translateY:
|
|
384
|
+
props.active.logicallySettled && !props.active.closing
|
|
385
|
+
? 0
|
|
386
|
+
: elementTranslateY,
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
scaleX:
|
|
390
|
+
props.active.logicallySettled && !props.active.closing
|
|
391
|
+
? 1
|
|
392
|
+
: elementScaleX,
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
scaleY:
|
|
396
|
+
props.active.logicallySettled && !props.active.closing
|
|
397
|
+
? 1
|
|
398
|
+
: elementScaleY,
|
|
399
|
+
},
|
|
376
400
|
],
|
|
377
|
-
opacity: unfocusedFade,
|
|
401
|
+
opacity: debug ? 0.5 : unfocusedFade,
|
|
378
402
|
zIndex: 9999,
|
|
379
403
|
elevation: 9999,
|
|
380
404
|
};
|