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
package/README.md
CHANGED
|
@@ -1,34 +1,27 @@
|
|
|
1
1
|
# react-native-screen-transitions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> This documentation is for **v3 beta 10**. API and features may change.
|
|
5
|
-
|
|
3
|
+
Customizable screen transitions for React Native. Build gesture-driven, shared element, and fully custom animations with a simple API.
|
|
6
4
|
|
|
7
5
|
| iOS | Android |
|
|
8
6
|
| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
9
7
|
| <video src="https://github.com/user-attachments/assets/c0d17b8f-7268-421c-9051-e242f8ddca76" width="300" height="600" controls></video> | <video src="https://github.com/user-attachments/assets/3f8d5fb1-96d2-4fe3-860d-62f6fb5a687e" width="300" controls></video> |
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## Features
|
|
12
10
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- 🎭 **Ready-Made Presets** – Instagram, Apple Music, X (Twitter) style transitions included
|
|
11
|
+
- **Full Animation Control** – Define exactly how screens enter, exit, and respond to gestures
|
|
12
|
+
- **Shared Elements** – Measure-driven transitions between screens using the Bounds API
|
|
13
|
+
- **Gesture Support** – Swipe-to-dismiss with edge or full-screen activation, works with ScrollViews
|
|
14
|
+
- **Two Stack Options** – Pure JS stack (recommended) or native stack integration
|
|
15
|
+
- **Ready-Made Presets** – Instagram, Apple Music, X (Twitter) style transitions included
|
|
16
|
+
- **TypeScript First** – Fully typed for better development experience
|
|
20
17
|
|
|
21
18
|
## Installation
|
|
22
19
|
|
|
23
20
|
```bash
|
|
24
21
|
npm install react-native-screen-transitions
|
|
25
|
-
# or
|
|
26
|
-
yarn add react-native-screen-transitions
|
|
27
|
-
# or
|
|
28
|
-
bun add react-native-screen-transitions
|
|
29
22
|
```
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
### Peer Dependencies
|
|
32
25
|
|
|
33
26
|
```bash
|
|
34
27
|
npm install react-native-reanimated react-native-gesture-handler \
|
|
@@ -37,9 +30,42 @@ npm install react-native-reanimated react-native-gesture-handler \
|
|
|
37
30
|
react-native-safe-area-context
|
|
38
31
|
```
|
|
39
32
|
|
|
40
|
-
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
This package provides two stack navigators:
|
|
38
|
+
|
|
39
|
+
| Stack | Description |
|
|
40
|
+
|-------|-------------|
|
|
41
|
+
| **Blank Stack** (recommended) | Pure JavaScript stack with full control over transitions, overlays, and gestures. No native limitations. |
|
|
42
|
+
| **Native Stack** | Extends `@react-navigation/native-stack`. More limited due to native constraints. |
|
|
41
43
|
|
|
42
|
-
###
|
|
44
|
+
### Blank Stack Setup
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack";
|
|
48
|
+
import Transition from "react-native-screen-transitions";
|
|
49
|
+
|
|
50
|
+
const Stack = createBlankStackNavigator();
|
|
51
|
+
|
|
52
|
+
function App() {
|
|
53
|
+
return (
|
|
54
|
+
<Stack.Navigator>
|
|
55
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
56
|
+
<Stack.Screen
|
|
57
|
+
name="Detail"
|
|
58
|
+
component={DetailScreen}
|
|
59
|
+
options={{
|
|
60
|
+
...Transition.Presets.SlideFromBottom(),
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
</Stack.Navigator>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Blank Stack with Expo Router
|
|
43
69
|
|
|
44
70
|
```tsx
|
|
45
71
|
import type {
|
|
@@ -48,506 +74,530 @@ import type {
|
|
|
48
74
|
} from "@react-navigation/native";
|
|
49
75
|
import { withLayoutContext } from "expo-router";
|
|
50
76
|
import {
|
|
51
|
-
|
|
52
|
-
type
|
|
53
|
-
type
|
|
54
|
-
} from "react-native-screen-transitions/
|
|
77
|
+
createBlankStackNavigator,
|
|
78
|
+
type BlankStackNavigationEventMap,
|
|
79
|
+
type BlankStackNavigationOptions,
|
|
80
|
+
} from "react-native-screen-transitions/blank-stack";
|
|
55
81
|
|
|
56
|
-
const { Navigator } =
|
|
82
|
+
const { Navigator } = createBlankStackNavigator();
|
|
57
83
|
|
|
58
84
|
export const Stack = withLayoutContext<
|
|
59
|
-
|
|
85
|
+
BlankStackNavigationOptions,
|
|
60
86
|
typeof Navigator,
|
|
61
87
|
StackNavigationState<ParamListBase>,
|
|
62
|
-
|
|
88
|
+
BlankStackNavigationEventMap
|
|
63
89
|
>(Navigator);
|
|
64
90
|
```
|
|
65
91
|
|
|
66
|
-
|
|
92
|
+
---
|
|
67
93
|
|
|
68
|
-
|
|
94
|
+
## Presets
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
No extra setup is required—just import and use as usual:
|
|
96
|
+
Built-in animation presets you can spread into screen options:
|
|
72
97
|
|
|
73
98
|
```tsx
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
<Stack.Screen
|
|
100
|
+
name="Detail"
|
|
101
|
+
options={{
|
|
102
|
+
...Transition.Presets.SlideFromBottom(),
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
79
105
|
```
|
|
80
106
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `gestureEnabled` | `boolean` | Whether swipe-to-dismiss is allowed. |
|
|
92
|
-
| `gestureDirection` | `GestureDirection \| GestureDirection[]` | Allowed swipe directions (`vertical`, `horizontal`, etc.). |
|
|
93
|
-
| `gestureVelocityImpact` | `number` | How much the gesture’s velocity affects dismissal. |
|
|
94
|
-
| `gestureResponseDistance` | `number` | Distance from screen where the gesture is recognized. |
|
|
95
|
-
| `gestureDrivesProgress` | `boolean` | Whether the gesture directly drives the transition progress. |
|
|
96
|
-
| `gestureActivationArea` | `GestureActivationArea` | Where a gesture may start. `'edge' | 'screen'`or per-side`{ left | right | top | bottom: 'edge' | 'screen' }`. |
|
|
97
|
-
|
|
98
|
-
### Renamed native options (extended stack)
|
|
99
|
-
|
|
100
|
-
To avoid collisions with the new options above, the built-in React Navigation gesture props are renamed:
|
|
107
|
+
| Preset | Description |
|
|
108
|
+
|--------|-------------|
|
|
109
|
+
| `SlideFromTop()` | Slides in from top, vertical gesture dismiss |
|
|
110
|
+
| `SlideFromBottom()` | Slides in from bottom, vertical gesture dismiss |
|
|
111
|
+
| `ZoomIn()` | Scales in with fade, no gesture |
|
|
112
|
+
| `DraggableCard()` | Multi-directional drag with card scaling |
|
|
113
|
+
| `ElasticCard()` | Elastic drag with overlay darkening |
|
|
114
|
+
| `SharedIGImage({ sharedBoundTag })` | Instagram-style shared image transition |
|
|
115
|
+
| `SharedAppleMusic({ sharedBoundTag })` | Apple Music-style shared element |
|
|
116
|
+
| `SharedXImage({ sharedBoundTag })` | X (Twitter)-style image transition |
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
| ------------------------- | ------------------------------- |
|
|
104
|
-
| `gestureDirection` | `nativeGestureDirection` |
|
|
105
|
-
| `gestureEnabled` | `nativeGestureEnabled` |
|
|
106
|
-
| `gestureResponseDistance` | `nativeGestureResponseDistance` |
|
|
107
|
-
|
|
108
|
-
All other React Navigation native-stack options keep their original names.
|
|
109
|
-
|
|
110
|
-
## Blank Stack (New in v3)
|
|
118
|
+
---
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
Unlike `native-stack`, it does not rely on native screen primitives for transitions, giving you full control over animations without fighting the OS.
|
|
120
|
+
## Custom Animations
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
- **"Blank" Canvas:** No default OS-style animations. You build or choose your transitions.
|
|
117
|
-
- **High Performance:** Logic is handled in JS (Reanimated), avoiding native-side interruptions.
|
|
118
|
-
- **No Hacks:** Does not use transparent modals or `beforeRemove` listeners.
|
|
122
|
+
### Using `screenStyleInterpolator`
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
Define custom transitions directly in screen options. The interpolator receives animation state and returns styles:
|
|
121
125
|
|
|
122
126
|
```tsx
|
|
123
|
-
import {
|
|
124
|
-
|
|
125
|
-
const Stack = createBlankStackNavigator();
|
|
126
|
-
|
|
127
|
-
function App() {
|
|
128
|
-
return (
|
|
129
|
-
<Stack.Navigator screenOptions={{ ...Transition.Presets.SlideFromBottom() }}>
|
|
130
|
-
<Stack.Screen name="Home" component={HomeScreen} />
|
|
131
|
-
<Stack.Screen name="Detail" component={DetailScreen} />
|
|
132
|
-
</Stack.Navigator>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Overlay
|
|
138
|
-
|
|
139
|
-
The Blank Stack introduces a powerful `overlay` prop for animating headers, footers, or floating elements that persist or transition between screens.
|
|
140
|
-
|
|
141
|
-
- **Modes:**
|
|
142
|
-
- `float`: Persists above the stack (like a native header). Smartly handles transitions so it doesn't disappear prematurely.
|
|
143
|
-
- `screen`: Moves with the screen content.
|
|
127
|
+
import { interpolate } from "react-native-reanimated";
|
|
144
128
|
|
|
145
|
-
```tsx
|
|
146
129
|
<Stack.Screen
|
|
147
|
-
name="
|
|
130
|
+
name="Detail"
|
|
148
131
|
options={{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
132
|
+
screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
|
|
133
|
+
"worklet";
|
|
134
|
+
|
|
135
|
+
const translateX = interpolate(
|
|
136
|
+
progress,
|
|
137
|
+
[0, 1, 2],
|
|
138
|
+
[screen.width, 0, -screen.width]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
contentStyle: {
|
|
143
|
+
transform: [{ translateX }],
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
transitionSpec: {
|
|
148
|
+
open: Transition.Specs.DefaultSpec,
|
|
149
|
+
close: Transition.Specs.DefaultSpec,
|
|
150
|
+
},
|
|
155
151
|
}}
|
|
156
|
-
|
|
152
|
+
/>;
|
|
157
153
|
```
|
|
158
154
|
|
|
159
|
-
|
|
155
|
+
### Interpolator Props
|
|
160
156
|
|
|
161
|
-
|
|
157
|
+
| Prop | Description |
|
|
158
|
+
|------|-------------|
|
|
159
|
+
| `progress` | Combined progress (0-2). 0=entering, 1=active, 2=exiting |
|
|
160
|
+
| `current` | Current screen state (progress, closing, gesture, route) |
|
|
161
|
+
| `previous` | Previous screen state (may be undefined) |
|
|
162
|
+
| `next` | Next screen state (may be undefined) |
|
|
163
|
+
| `layouts.screen` | Screen dimensions `{ width, height }` |
|
|
164
|
+
| `insets` | Safe area insets `{ top, right, bottom, left }` |
|
|
165
|
+
| `focused` | Whether current screen is the topmost |
|
|
166
|
+
| `active` | The screen driving the transition |
|
|
167
|
+
| `isActiveTransitioning` | Whether active screen is animating |
|
|
168
|
+
| `isDismissing` | Whether active screen is being dismissed |
|
|
169
|
+
| `bounds` | Function to access shared element positions |
|
|
162
170
|
|
|
163
|
-
|
|
164
|
-
The incoming screen automatically controls the previous screen.
|
|
171
|
+
### Return Value
|
|
165
172
|
|
|
166
173
|
```tsx
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
...Transition.Presets.SlideFromTop(),
|
|
173
|
-
}}
|
|
174
|
-
/>
|
|
175
|
-
<Stack.Screen
|
|
176
|
-
name="c"
|
|
177
|
-
options={{
|
|
178
|
-
...Transition.Presets.SlideFromBottom(),
|
|
179
|
-
}}
|
|
180
|
-
/>
|
|
181
|
-
</Stack>
|
|
174
|
+
return {
|
|
175
|
+
contentStyle: { ... }, // Main screen content
|
|
176
|
+
overlayStyle: { ... }, // Semi-transparent overlay
|
|
177
|
+
["my-element"]: { ... }, // Styles for Transition.View with styleId="my-element"
|
|
178
|
+
};
|
|
182
179
|
```
|
|
183
180
|
|
|
184
|
-
|
|
181
|
+
### Using `styleId` for Individual Elements
|
|
185
182
|
|
|
186
|
-
|
|
183
|
+
Animate specific elements within a screen:
|
|
187
184
|
|
|
188
185
|
```tsx
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
// In screen options
|
|
187
|
+
screenStyleInterpolator: ({ progress }) => {
|
|
188
|
+
"worklet";
|
|
189
|
+
return {
|
|
190
|
+
"hero-image": {
|
|
191
|
+
opacity: interpolate(progress, [0, 1], [0, 1]),
|
|
192
|
+
transform: [{ scale: interpolate(progress, [0, 1], [0.8, 1]) }],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// In component
|
|
198
|
+
<Transition.View styleId="hero-image">
|
|
199
|
+
<Image source={...} />
|
|
200
|
+
</Transition.View>
|
|
196
201
|
```
|
|
197
202
|
|
|
198
|
-
|
|
203
|
+
---
|
|
199
204
|
|
|
200
|
-
|
|
205
|
+
## Shared Elements (Bounds API)
|
|
201
206
|
|
|
202
|
-
|
|
207
|
+
Animate elements between screens by measuring their positions.
|
|
203
208
|
|
|
204
|
-
|
|
209
|
+
### 1. Tag Elements on Both Screens
|
|
205
210
|
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
```tsx
|
|
212
|
+
// Source screen
|
|
213
|
+
<Transition.Pressable
|
|
214
|
+
sharedBoundTag="avatar"
|
|
215
|
+
onPress={() => navigation.navigate("Profile")}
|
|
216
|
+
>
|
|
217
|
+
<Image source={avatar} style={{ width: 50, height: 50 }} />
|
|
218
|
+
</Transition.Pressable>
|
|
209
219
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
220
|
+
// Destination screen
|
|
221
|
+
<Transition.View sharedBoundTag="avatar">
|
|
222
|
+
<Image source={avatar} style={{ width: 200, height: 200 }} />
|
|
223
|
+
</Transition.View>
|
|
213
224
|
```
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
### 2. Use Bounds in Interpolator
|
|
216
227
|
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
```tsx
|
|
229
|
+
screenStyleInterpolator: ({ bounds }) => {
|
|
230
|
+
"worklet";
|
|
231
|
+
|
|
232
|
+
const avatarStyles = bounds({
|
|
233
|
+
id: "avatar",
|
|
234
|
+
method: "transform", // "transform" | "size" | "content"
|
|
235
|
+
space: "relative", // "relative" | "absolute"
|
|
236
|
+
scaleMode: "match", // "match" | "none" | "uniform"
|
|
237
|
+
anchor: "center", // positioning anchor
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
avatar: avatarStyles,
|
|
242
|
+
};
|
|
243
|
+
};
|
|
221
244
|
```
|
|
222
245
|
|
|
223
|
-
|
|
246
|
+
### Bounds Options
|
|
247
|
+
|
|
248
|
+
| Option | Values | Description |
|
|
249
|
+
|--------|--------|-------------|
|
|
250
|
+
| `id` | string | The `sharedBoundTag` to match |
|
|
251
|
+
| `method` | `"transform"` `"size"` `"content"` | How to animate (scale vs width/height) |
|
|
252
|
+
| `space` | `"relative"` `"absolute"` | Coordinate space |
|
|
253
|
+
| `scaleMode` | `"match"` `"none"` `"uniform"` | How to handle aspect ratio |
|
|
254
|
+
| `anchor` | `"center"` `"top"` `"topLeading"` etc. | Transform origin |
|
|
255
|
+
| `target` | `"bound"` `"fullscreen"` or custom | Destination target |
|
|
256
|
+
| `raw` | boolean | Return raw values instead of styles |
|
|
257
|
+
|
|
258
|
+
### Raw Values
|
|
224
259
|
|
|
225
260
|
```tsx
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<Transition.MaskedView style={{ flex: 1, backgroundColor: "white" }}>
|
|
229
|
-
{/* screen content, including the destination bound */}
|
|
230
|
-
</Transition.MaskedView>
|
|
231
|
-
);
|
|
232
|
-
}
|
|
261
|
+
const raw = bounds({ id: "avatar", method: "transform", raw: true });
|
|
262
|
+
// { translateX, translateY, scaleX, scaleY }
|
|
233
263
|
```
|
|
234
264
|
|
|
235
|
-
> **💡 Fallback behavior**: `Transition.MaskedView` will fall back to a plain `View` if the masked view library is missing, but this breaks the shared element effect and may cause errors like "bounds is not a function".
|
|
236
|
-
|
|
237
265
|
---
|
|
238
266
|
|
|
239
|
-
|
|
267
|
+
## Gestures
|
|
240
268
|
|
|
241
|
-
|
|
242
|
-
`screenStyleInterpolator` receives an object with the following useful fields:
|
|
243
|
-
|
|
244
|
-
- `progress` – combined progress of current and next screen transitions, ranging from 0-2.
|
|
245
|
-
- `current` – state for the current screen being interpolated (includes `progress`, `closing`, `gesture`, `route`, etc.).
|
|
246
|
-
- `previous` – state for the screen that came before the current one in the navigation stack (may be `undefined`).
|
|
247
|
-
- `next` – state for the screen that comes after the current one in the navigation stack (may be `undefined`).
|
|
248
|
-
- `layouts.screen` – `{ width, height }` of the container.
|
|
249
|
-
- `insets` – `{ top, right, bottom, left }` safe-area insets.
|
|
250
|
-
- `bounds(options)` – function that provides access to bounds builders for creating shared element transitions. See "Bounds" below.
|
|
251
|
-
- `activeBoundId` – ID of the currently active shared bound (e.g., 'a' when Transition.Pressable has sharedBoundTag='a').
|
|
252
|
-
- `focused` – whether the current screen is the focused (topmost) screen in the stack.
|
|
253
|
-
- `active` – the screen state that is currently driving the transition (either current or next, whichever is focused).
|
|
254
|
-
- `isActiveTransitioning` – whether the active screen is currently transitioning (either being dragged or animating).
|
|
255
|
-
- `isDismissing` – whether the active screen is in the process of being dismissed/closed.
|
|
269
|
+
Enable swipe-to-dismiss on screens:
|
|
256
270
|
|
|
257
271
|
```tsx
|
|
258
|
-
import { interpolate } from "react-native-reanimated";
|
|
259
|
-
|
|
260
272
|
<Stack.Screen
|
|
261
|
-
name="
|
|
273
|
+
name="Detail"
|
|
262
274
|
options={{
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
progress,
|
|
269
|
-
}) => {
|
|
270
|
-
"worklet";
|
|
271
|
-
|
|
272
|
-
const x = interpolate(progress, [0, 1, 2], [width, 0, -width]);
|
|
273
|
-
return {
|
|
274
|
-
contentStyle: {
|
|
275
|
-
transform: [{ translateX: x }],
|
|
276
|
-
},
|
|
277
|
-
};
|
|
278
|
-
},
|
|
279
|
-
transitionSpec: {
|
|
280
|
-
close: Transition.Specs.DefaultSpec,
|
|
281
|
-
open: Transition.Specs.DefaultSpec,
|
|
282
|
-
},
|
|
275
|
+
gestureEnabled: true,
|
|
276
|
+
gestureDirection: "vertical", // or "horizontal", ["vertical", "horizontal"]
|
|
277
|
+
gestureActivationArea: "edge", // or "screen", or { left: "edge", top: "screen" }
|
|
278
|
+
gestureResponseDistance: 50,
|
|
279
|
+
gestureVelocityImpact: 0.3,
|
|
283
280
|
}}
|
|
284
|
-
|
|
281
|
+
/>
|
|
285
282
|
```
|
|
286
283
|
|
|
287
|
-
|
|
284
|
+
### Gesture Options
|
|
288
285
|
|
|
289
|
-
|
|
286
|
+
| Option | Description |
|
|
287
|
+
|--------|-------------|
|
|
288
|
+
| `gestureEnabled` | Enable/disable gesture |
|
|
289
|
+
| `gestureDirection` | `"horizontal"` `"vertical"` `"horizontal-inverted"` `"vertical-inverted"` or array |
|
|
290
|
+
| `gestureActivationArea` | `"edge"` `"screen"` or per-side config |
|
|
291
|
+
| `gestureResponseDistance` | Distance threshold for gesture recognition |
|
|
292
|
+
| `gestureVelocityImpact` | How much velocity affects dismissal decision |
|
|
293
|
+
| `gestureDrivesProgress` | Whether gesture directly drives animation (default: true) |
|
|
290
294
|
|
|
291
|
-
|
|
295
|
+
### Gestures with ScrollViews
|
|
292
296
|
|
|
293
|
-
|
|
294
|
-
import { useScreenAnimation } from "react-native-screen-transitions";
|
|
295
|
-
import Animated, {
|
|
296
|
-
useAnimatedStyle,
|
|
297
|
-
interpolate,
|
|
298
|
-
} from "react-native-reanimated";
|
|
299
|
-
|
|
300
|
-
export default function BScreen() {
|
|
301
|
-
const props = useScreenAnimation();
|
|
302
|
-
|
|
303
|
-
const animatedStyle = useAnimatedStyle(() => {
|
|
304
|
-
const {
|
|
305
|
-
current: { progress },
|
|
306
|
-
} = props.value;
|
|
307
|
-
return {
|
|
308
|
-
opacity: progress,
|
|
309
|
-
};
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
return (
|
|
313
|
-
<Animated.View style={[{ flex: 1 }, animatedStyle]}>
|
|
314
|
-
{/* Your content */}
|
|
315
|
-
</Animated.View>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## Swipe-to-dismiss with scrollables
|
|
321
|
-
|
|
322
|
-
You can drag a screen away even when it contains a scroll view.
|
|
323
|
-
Just swap the regular scrollable for a transition-aware one:
|
|
297
|
+
Use transition-aware scrollables so gestures work correctly:
|
|
324
298
|
|
|
325
299
|
```tsx
|
|
326
300
|
import Transition from "react-native-screen-transitions";
|
|
327
|
-
import { LegendList } from "@legendapp/list";
|
|
328
|
-
import { FlashList } from "@shopify/flash-list";
|
|
329
301
|
|
|
330
302
|
// Drop-in replacements
|
|
331
|
-
|
|
332
|
-
|
|
303
|
+
<Transition.ScrollView>
|
|
304
|
+
{/* content */}
|
|
305
|
+
</Transition.ScrollView>
|
|
333
306
|
|
|
334
|
-
|
|
307
|
+
<Transition.FlatList
|
|
308
|
+
data={items}
|
|
309
|
+
renderItem={...}
|
|
310
|
+
/>
|
|
311
|
+
|
|
312
|
+
// Wrap custom lists
|
|
335
313
|
const TransitionFlashList = Transition.createTransitionAwareComponent(
|
|
336
314
|
FlashList,
|
|
337
315
|
{ isScrollable: true }
|
|
338
316
|
);
|
|
339
|
-
|
|
340
|
-
const TransitionLegendList = Transition.createTransitionAwareComponent(
|
|
341
|
-
LegendList,
|
|
342
|
-
{ isScrollable: true }
|
|
343
|
-
);
|
|
344
317
|
```
|
|
345
318
|
|
|
346
|
-
|
|
319
|
+
Gesture rules with scrollables:
|
|
320
|
+
- **vertical** – only starts when scrolled to top
|
|
321
|
+
- **vertical-inverted** – only starts when scrolled to bottom
|
|
322
|
+
- **horizontal** – only starts at left/right edge
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Overlays (Blank Stack)
|
|
327
|
+
|
|
328
|
+
The Blank Stack supports persistent overlays that animate across screen transitions.
|
|
329
|
+
|
|
330
|
+
### Float Overlay
|
|
331
|
+
|
|
332
|
+
A single overlay that persists above all screens:
|
|
347
333
|
|
|
348
334
|
```tsx
|
|
335
|
+
const FloatingHeader = ({ focusedIndex, routes, overlayAnimation }) => {
|
|
336
|
+
const style = useAnimatedStyle(() => ({
|
|
337
|
+
opacity: interpolate(overlayAnimation.value.progress, [0, 1], [0, 1]),
|
|
338
|
+
}));
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<Animated.View style={[styles.header, style]}>
|
|
342
|
+
<Text>Screen {focusedIndex + 1} of {routes.length}</Text>
|
|
343
|
+
</Animated.View>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
349
347
|
<Stack.Screen
|
|
350
|
-
name="
|
|
348
|
+
name="Home"
|
|
351
349
|
options={{
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
350
|
+
overlay: FloatingHeader,
|
|
351
|
+
overlayMode: "float",
|
|
352
|
+
overlayShown: true,
|
|
355
353
|
}}
|
|
356
354
|
/>
|
|
357
355
|
```
|
|
358
356
|
|
|
359
|
-
|
|
357
|
+
### Screen Overlay
|
|
358
|
+
|
|
359
|
+
An overlay that moves with screen content:
|
|
360
360
|
|
|
361
361
|
```tsx
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
362
|
+
<Stack.Screen
|
|
363
|
+
name="Detail"
|
|
364
|
+
options={{
|
|
365
|
+
overlay: DetailOverlay,
|
|
366
|
+
overlayMode: "screen",
|
|
367
|
+
overlayShown: true,
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
365
370
|
```
|
|
366
371
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
- **vertical** – only starts when the list is at the very top
|
|
370
|
-
- **vertical-inverted** – only starts when the list is at the very bottom
|
|
371
|
-
- **horizontal** / **horizontal-inverted** – only starts when the list is at the left or right edge
|
|
372
|
-
|
|
373
|
-
These rules apply **only when the screen contains a scrollable**.
|
|
374
|
-
If no scroll view is present, the gesture can begin from **anywhere on the screen**—not restricted to the edges.
|
|
372
|
+
### Overlay Props
|
|
375
373
|
|
|
376
|
-
|
|
374
|
+
| Prop | Description |
|
|
375
|
+
|------|-------------|
|
|
376
|
+
| `focusedRoute` | Currently focused route |
|
|
377
|
+
| `focusedIndex` | Index of focused screen |
|
|
378
|
+
| `routes` | All routes in the stack |
|
|
379
|
+
| `overlayOptions` | Custom options passed from screen |
|
|
380
|
+
| `navigation` | Navigation prop |
|
|
381
|
+
| `overlayAnimation` | Animation values for overlay |
|
|
382
|
+
| `screenAnimation` | Animation values for screens |
|
|
377
383
|
|
|
378
|
-
|
|
384
|
+
### Passing Custom Data
|
|
379
385
|
|
|
380
386
|
```tsx
|
|
381
|
-
|
|
382
|
-
|
|
387
|
+
<Stack.Screen
|
|
388
|
+
options={{
|
|
389
|
+
overlay: MyOverlay,
|
|
390
|
+
overlayOptions: {
|
|
391
|
+
title: "Step 1",
|
|
392
|
+
showProgress: true,
|
|
393
|
+
},
|
|
394
|
+
}}
|
|
395
|
+
/>
|
|
383
396
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
397
|
+
// In overlay
|
|
398
|
+
const MyOverlay = ({ overlayOptions }) => {
|
|
399
|
+
return <Text>{overlayOptions.title}</Text>;
|
|
400
|
+
};
|
|
387
401
|
```
|
|
388
402
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
> **⚠️ Breaking Change in v3**: The `sharedBoundTag` is now **strictly required** for all dynamic animations. The new architecture uses a Link Stack system that relies on this ID to correctly pair source and destination views. Without it, animations will fail.
|
|
403
|
+
---
|
|
392
404
|
|
|
393
|
-
|
|
405
|
+
## Transition Components
|
|
394
406
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
407
|
+
| Component | Description |
|
|
408
|
+
|-----------|-------------|
|
|
409
|
+
| `Transition.View` | Animated view, supports `styleId` and `sharedBoundTag` |
|
|
410
|
+
| `Transition.Pressable` | Pressable with bounds measurement on press |
|
|
411
|
+
| `Transition.ScrollView` | ScrollView with gesture coordination |
|
|
412
|
+
| `Transition.FlatList` | FlatList with gesture coordination |
|
|
413
|
+
| `Transition.MaskedView` | For clipping during shared element transitions |
|
|
398
414
|
|
|
399
|
-
|
|
415
|
+
### Creating Custom Components
|
|
400
416
|
|
|
401
417
|
```tsx
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
style={{ width: 100, height: 100 }}
|
|
407
|
-
>
|
|
408
|
-
<Image source={...} />
|
|
409
|
-
</Transition.Pressable>
|
|
410
|
-
|
|
411
|
-
// Destination screen
|
|
412
|
-
<Transition.Pressable
|
|
413
|
-
sharedBoundTag="hero"
|
|
414
|
-
onPress={() => {/* handle press */}}
|
|
415
|
-
style={{ width: 200, height: 200 }}
|
|
416
|
-
>
|
|
417
|
-
<Image source={...} />
|
|
418
|
-
</Transition.Pressable>
|
|
418
|
+
const TransitionImage = Transition.createTransitionAwareComponent(
|
|
419
|
+
Animated.Image,
|
|
420
|
+
{ isScrollable: false }
|
|
421
|
+
);
|
|
419
422
|
```
|
|
420
423
|
|
|
421
|
-
|
|
424
|
+
---
|
|
422
425
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
onPress={() => router.push("/detail")}
|
|
427
|
-
>
|
|
428
|
-
{/* These children will be automatically measured when parent is pressed */}
|
|
429
|
-
<Transition.View sharedBoundTag="title">
|
|
430
|
-
<Text>Title</Text>
|
|
431
|
-
</Transition.View>
|
|
432
|
-
<Transition.View sharedBoundTag="subtitle">
|
|
433
|
-
<Text>Subtitle</Text>
|
|
434
|
-
</Transition.View>
|
|
435
|
-
</Transition.Pressable>
|
|
436
|
-
```
|
|
426
|
+
## Hooks
|
|
427
|
+
|
|
428
|
+
### `useScreenAnimation`
|
|
437
429
|
|
|
438
|
-
|
|
430
|
+
Access animation state within a screen component:
|
|
439
431
|
|
|
440
432
|
```tsx
|
|
441
|
-
|
|
442
|
-
"worklet";
|
|
433
|
+
import { useScreenAnimation } from "react-native-screen-transitions";
|
|
443
434
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
435
|
+
function DetailScreen() {
|
|
436
|
+
const animation = useScreenAnimation();
|
|
437
|
+
|
|
438
|
+
const style = useAnimatedStyle(() => {
|
|
439
|
+
const { current } = animation.value;
|
|
440
|
+
return {
|
|
441
|
+
opacity: current.progress,
|
|
442
|
+
};
|
|
451
443
|
});
|
|
452
444
|
|
|
453
|
-
return {
|
|
454
|
-
}
|
|
445
|
+
return <Animated.View style={style}>...</Animated.View>;
|
|
446
|
+
}
|
|
455
447
|
```
|
|
456
448
|
|
|
457
|
-
|
|
449
|
+
---
|
|
458
450
|
|
|
459
|
-
|
|
460
|
-
const raw = bounds({ method: "transform", raw: true });
|
|
461
|
-
// { translateX, translateY, scaleX, scaleY }
|
|
462
|
-
```
|
|
451
|
+
## Animation Specs
|
|
463
452
|
|
|
464
|
-
|
|
453
|
+
Configure spring/timing animations:
|
|
465
454
|
|
|
466
455
|
```tsx
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
- `anchor`: "topLeading" | "top" | "topTrailing" | "leading" | "center" | "trailing" | "bottomLeading" | "bottom" | "bottomTrailing"
|
|
482
|
-
- `scaleMode`: "match" | "none" | "uniform"
|
|
456
|
+
transitionSpec: {
|
|
457
|
+
open: {
|
|
458
|
+
stiffness: 1000,
|
|
459
|
+
damping: 500,
|
|
460
|
+
mass: 3,
|
|
461
|
+
overshootClamping: true,
|
|
462
|
+
},
|
|
463
|
+
close: {
|
|
464
|
+
stiffness: 1000,
|
|
465
|
+
damping: 500,
|
|
466
|
+
mass: 3,
|
|
467
|
+
overshootClamping: true,
|
|
468
|
+
},
|
|
469
|
+
}
|
|
483
470
|
|
|
484
|
-
|
|
471
|
+
// Or use the default
|
|
472
|
+
transitionSpec: {
|
|
473
|
+
open: Transition.Specs.DefaultSpec,
|
|
474
|
+
close: Transition.Specs.DefaultSpec,
|
|
475
|
+
}
|
|
476
|
+
```
|
|
485
477
|
|
|
486
|
-
|
|
487
|
-
- `space`: "relative" (within layout constraints) or "absolute" (window coordinates)
|
|
478
|
+
---
|
|
488
479
|
|
|
489
|
-
|
|
480
|
+
## Masked View Setup
|
|
490
481
|
|
|
491
|
-
|
|
482
|
+
Required for `SharedIGImage` and `SharedAppleMusic` presets.
|
|
492
483
|
|
|
493
|
-
|
|
484
|
+
> **Note**: Requires native code. Will not work in Expo Go.
|
|
494
485
|
|
|
495
|
-
|
|
486
|
+
```bash
|
|
487
|
+
# Expo
|
|
488
|
+
npx expo install @react-native-masked-view/masked-view
|
|
496
489
|
|
|
497
|
-
|
|
490
|
+
# Bare React Native
|
|
491
|
+
npm install @react-native-masked-view/masked-view
|
|
492
|
+
cd ios && pod install
|
|
493
|
+
```
|
|
498
494
|
|
|
499
|
-
|
|
495
|
+
Wrap destination screen content:
|
|
500
496
|
|
|
501
497
|
```tsx
|
|
502
|
-
|
|
498
|
+
export default function DetailScreen() {
|
|
499
|
+
return (
|
|
500
|
+
<Transition.MaskedView style={{ flex: 1 }}>
|
|
501
|
+
{/* screen content */}
|
|
502
|
+
</Transition.MaskedView>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
503
505
|
```
|
|
504
506
|
|
|
505
|
-
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Native Stack
|
|
510
|
+
|
|
511
|
+
For cases where you need native screen primitives, use the native stack integration. This extends `@react-navigation/native-stack` with custom transition support.
|
|
506
512
|
|
|
507
|
-
|
|
513
|
+
> **Note**: The native stack has limitations. It uses `beforeRemove` listeners and transparent modals to intercept transitions. The Blank Stack is recommended for most use cases.
|
|
508
514
|
|
|
509
|
-
|
|
515
|
+
### Setup
|
|
510
516
|
|
|
511
517
|
```tsx
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
518
|
+
import { createNativeStackNavigator } from "react-native-screen-transitions/native-stack";
|
|
519
|
+
|
|
520
|
+
const Stack = createNativeStackNavigator();
|
|
521
|
+
|
|
522
|
+
<Stack.Screen
|
|
523
|
+
name="Detail"
|
|
524
|
+
options={{
|
|
525
|
+
enableTransitions: true, // Required to enable custom transitions
|
|
526
|
+
...Transition.Presets.SlideFromBottom(),
|
|
527
|
+
}}
|
|
515
528
|
/>
|
|
516
529
|
```
|
|
517
530
|
|
|
518
|
-
|
|
531
|
+
### Expo Router Setup
|
|
519
532
|
|
|
520
533
|
```tsx
|
|
521
|
-
|
|
522
|
-
|
|
534
|
+
import type {
|
|
535
|
+
ParamListBase,
|
|
536
|
+
StackNavigationState,
|
|
537
|
+
} from "@react-navigation/native";
|
|
538
|
+
import { withLayoutContext } from "expo-router";
|
|
539
|
+
import {
|
|
540
|
+
createNativeStackNavigator,
|
|
541
|
+
type NativeStackNavigationEventMap,
|
|
542
|
+
type NativeStackNavigationOptions,
|
|
543
|
+
} from "react-native-screen-transitions/native-stack";
|
|
523
544
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
545
|
+
const { Navigator } = createNativeStackNavigator();
|
|
546
|
+
|
|
547
|
+
export const Stack = withLayoutContext<
|
|
548
|
+
NativeStackNavigationOptions,
|
|
549
|
+
typeof Navigator,
|
|
550
|
+
StackNavigationState<ParamListBase>,
|
|
551
|
+
NativeStackNavigationEventMap
|
|
552
|
+
>(Navigator);
|
|
530
553
|
```
|
|
531
554
|
|
|
532
|
-
|
|
555
|
+
### Native Stack Options
|
|
533
556
|
|
|
534
|
-
|
|
557
|
+
All standard `@react-navigation/native-stack` options are available, plus:
|
|
535
558
|
|
|
536
|
-
|
|
559
|
+
| Option | Type | Description |
|
|
560
|
+
|--------|------|-------------|
|
|
561
|
+
| `enableTransitions` | `boolean` | Enable custom transitions (sets presentation to transparent modal) |
|
|
562
|
+
| `screenStyleInterpolator` | `ScreenStyleInterpolator` | Function that returns animated styles |
|
|
563
|
+
| `transitionSpec` | `TransitionSpec` | Animation config for open/close |
|
|
564
|
+
| `gestureEnabled` | `boolean` | Whether swipe-to-dismiss is allowed |
|
|
565
|
+
| `gestureDirection` | `GestureDirection \| GestureDirection[]` | Allowed swipe directions |
|
|
566
|
+
| `gestureVelocityImpact` | `number` | How much velocity affects dismissal |
|
|
567
|
+
| `gestureResponseDistance` | `number` | Distance threshold for gesture |
|
|
568
|
+
| `gestureDrivesProgress` | `boolean` | Whether gesture drives animation |
|
|
569
|
+
| `gestureActivationArea` | `GestureActivationArea` | Where gesture can start |
|
|
537
570
|
|
|
538
|
-
|
|
571
|
+
### Renamed Native Options
|
|
539
572
|
|
|
540
|
-
|
|
573
|
+
To avoid collisions with custom gesture options, some native options are renamed:
|
|
574
|
+
|
|
575
|
+
| React Navigation | Renamed to |
|
|
576
|
+
|------------------|------------|
|
|
577
|
+
| `gestureDirection` | `nativeGestureDirection` |
|
|
578
|
+
| `gestureEnabled` | `nativeGestureEnabled` |
|
|
579
|
+
| `gestureResponseDistance` | `nativeGestureResponseDistance` |
|
|
541
580
|
|
|
542
|
-
|
|
543
|
-
|
|
581
|
+
### Limitations
|
|
582
|
+
|
|
583
|
+
- Overlay system not available
|
|
584
|
+
- Relies on `beforeRemove` listener to intercept navigation
|
|
585
|
+
- Uses transparent modal presentation
|
|
586
|
+
- Some edge cases with rapid navigation
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Known Issues
|
|
591
|
+
|
|
592
|
+
- **Delayed Touch Events** – There may be a noticeable delay in touch events when transitions finish. If this affects your app, consider using the Blank Stack.
|
|
593
|
+
|
|
594
|
+
---
|
|
544
595
|
|
|
545
|
-
|
|
596
|
+
## Support
|
|
546
597
|
|
|
547
|
-
|
|
598
|
+
This package is developed in my spare time. Updates and bug fixes may take time.
|
|
548
599
|
|
|
549
|
-
|
|
550
|
-
If you’d like to fuel the next release, [buy me a coffee](https://buymeacoffee.com/trpfsu)
|
|
600
|
+
If you'd like to fuel the next release, [buy me a coffee](https://buymeacoffee.com/trpfsu)
|
|
551
601
|
|
|
552
602
|
## License
|
|
553
603
|
|