react-native-screen-transitions 3.2.0-beta.3 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +327 -672
  2. package/lib/commonjs/shared/components/screen-lifecycle.js +9 -133
  3. package/lib/commonjs/shared/components/screen-lifecycle.js.map +1 -1
  4. package/lib/commonjs/shared/constants.js +1 -0
  5. package/lib/commonjs/shared/constants.js.map +1 -1
  6. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +3 -0
  7. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
  8. package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +127 -0
  9. package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
  10. package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js +35 -0
  11. package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
  12. package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js +58 -0
  13. package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
  14. package/lib/commonjs/shared/hooks/navigation/use-history.js +24 -0
  15. package/lib/commonjs/shared/hooks/navigation/use-history.js.map +1 -0
  16. package/lib/commonjs/shared/index.js +7 -0
  17. package/lib/commonjs/shared/index.js.map +1 -1
  18. package/lib/commonjs/shared/providers/screen/keys.provider.js +0 -4
  19. package/lib/commonjs/shared/providers/screen/keys.provider.js.map +1 -1
  20. package/lib/commonjs/shared/providers/screen/screen-composer.js +7 -5
  21. package/lib/commonjs/shared/providers/screen/screen-composer.js.map +1 -1
  22. package/lib/commonjs/shared/providers/stack/direct.provider.js +9 -0
  23. package/lib/commonjs/shared/providers/stack/direct.provider.js.map +1 -1
  24. package/lib/commonjs/shared/providers/stack/managed.provider.js +9 -0
  25. package/lib/commonjs/shared/providers/stack/managed.provider.js.map +1 -1
  26. package/lib/commonjs/shared/stores/animation.store.js +3 -13
  27. package/lib/commonjs/shared/stores/animation.store.js.map +1 -1
  28. package/lib/commonjs/shared/stores/history.store.js +185 -0
  29. package/lib/commonjs/shared/stores/history.store.js.map +1 -0
  30. package/lib/commonjs/shared/types/stack.types.js.map +1 -1
  31. package/lib/commonjs/shared/utils/animation/start-screen-transition.js +5 -1
  32. package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +1 -1
  33. package/lib/commonjs/shared/utils/bounds/index.js +19 -4
  34. package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
  35. package/lib/module/shared/components/screen-lifecycle.js +9 -132
  36. package/lib/module/shared/components/screen-lifecycle.js.map +1 -1
  37. package/lib/module/shared/constants.js +1 -0
  38. package/lib/module/shared/constants.js.map +1 -1
  39. package/lib/module/shared/hooks/animation/use-screen-animation.js +3 -0
  40. package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
  41. package/lib/module/shared/hooks/lifecycle/use-close-transition.js +122 -0
  42. package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
  43. package/lib/module/shared/hooks/lifecycle/use-open-transition.js +32 -0
  44. package/lib/module/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
  45. package/lib/module/shared/hooks/lifecycle/use-screen-events.js +54 -0
  46. package/lib/module/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
  47. package/lib/module/shared/hooks/navigation/use-history.js +20 -0
  48. package/lib/module/shared/hooks/navigation/use-history.js.map +1 -0
  49. package/lib/module/shared/index.js +1 -0
  50. package/lib/module/shared/index.js.map +1 -1
  51. package/lib/module/shared/providers/screen/keys.provider.js +0 -4
  52. package/lib/module/shared/providers/screen/keys.provider.js.map +1 -1
  53. package/lib/module/shared/providers/screen/screen-composer.js +7 -5
  54. package/lib/module/shared/providers/screen/screen-composer.js.map +1 -1
  55. package/lib/module/shared/providers/stack/direct.provider.js +10 -1
  56. package/lib/module/shared/providers/stack/direct.provider.js.map +1 -1
  57. package/lib/module/shared/providers/stack/managed.provider.js +10 -1
  58. package/lib/module/shared/providers/stack/managed.provider.js.map +1 -1
  59. package/lib/module/shared/stores/animation.store.js +4 -14
  60. package/lib/module/shared/stores/animation.store.js.map +1 -1
  61. package/lib/module/shared/stores/history.store.js +181 -0
  62. package/lib/module/shared/stores/history.store.js.map +1 -0
  63. package/lib/module/shared/types/stack.types.js.map +1 -1
  64. package/lib/module/shared/utils/animation/start-screen-transition.js +5 -1
  65. package/lib/module/shared/utils/animation/start-screen-transition.js.map +1 -1
  66. package/lib/module/shared/utils/bounds/index.js +19 -4
  67. package/lib/module/shared/utils/bounds/index.js.map +1 -1
  68. package/lib/typescript/blank-stack/types.d.ts +0 -3
  69. package/lib/typescript/blank-stack/types.d.ts.map +1 -1
  70. package/lib/typescript/component-stack/types.d.ts +0 -3
  71. package/lib/typescript/component-stack/types.d.ts.map +1 -1
  72. package/lib/typescript/shared/components/screen-lifecycle.d.ts +4 -1
  73. package/lib/typescript/shared/components/screen-lifecycle.d.ts.map +1 -1
  74. package/lib/typescript/shared/constants.d.ts.map +1 -1
  75. package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
  76. package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts +13 -0
  77. package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts.map +1 -0
  78. package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts +11 -0
  79. package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts.map +1 -0
  80. package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts +7 -0
  81. package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts.map +1 -0
  82. package/lib/typescript/shared/hooks/navigation/use-history.d.ts +37 -0
  83. package/lib/typescript/shared/hooks/navigation/use-history.d.ts.map +1 -0
  84. package/lib/typescript/shared/index.d.ts +3 -2
  85. package/lib/typescript/shared/index.d.ts.map +1 -1
  86. package/lib/typescript/shared/providers/screen/keys.provider.d.ts +0 -6
  87. package/lib/typescript/shared/providers/screen/keys.provider.d.ts.map +1 -1
  88. package/lib/typescript/shared/providers/screen/screen-composer.d.ts.map +1 -1
  89. package/lib/typescript/shared/providers/stack/direct.provider.d.ts.map +1 -1
  90. package/lib/typescript/shared/providers/stack/managed.provider.d.ts.map +1 -1
  91. package/lib/typescript/shared/stores/animation.store.d.ts +3 -4
  92. package/lib/typescript/shared/stores/animation.store.d.ts.map +1 -1
  93. package/lib/typescript/shared/stores/history.store.d.ts +82 -0
  94. package/lib/typescript/shared/stores/history.store.d.ts.map +1 -0
  95. package/lib/typescript/shared/types/animation.types.d.ts +8 -0
  96. package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
  97. package/lib/typescript/shared/types/bounds.types.d.ts +1 -1
  98. package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
  99. package/lib/typescript/shared/types/stack.types.d.ts +1 -0
  100. package/lib/typescript/shared/types/stack.types.d.ts.map +1 -1
  101. package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +1 -1
  102. package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
  103. package/package.json +1 -1
  104. package/src/blank-stack/types.ts +0 -8
  105. package/src/component-stack/types.ts +0 -9
  106. package/src/shared/__tests__/history.store.test.ts +550 -0
  107. package/src/shared/components/screen-lifecycle.tsx +13 -149
  108. package/src/shared/constants.ts +1 -0
  109. package/src/shared/hooks/animation/use-screen-animation.tsx +4 -0
  110. package/src/shared/hooks/lifecycle/use-close-transition.ts +147 -0
  111. package/src/shared/hooks/lifecycle/use-open-transition.ts +30 -0
  112. package/src/shared/hooks/lifecycle/use-screen-events.ts +62 -0
  113. package/src/shared/hooks/navigation/use-history.ts +63 -0
  114. package/src/shared/index.ts +1 -0
  115. package/src/shared/providers/screen/keys.provider.tsx +0 -16
  116. package/src/shared/providers/screen/screen-composer.tsx +6 -10
  117. package/src/shared/providers/stack/direct.provider.tsx +11 -1
  118. package/src/shared/providers/stack/managed.provider.tsx +11 -1
  119. package/src/shared/stores/animation.store.ts +6 -20
  120. package/src/shared/stores/history.store.ts +201 -0
  121. package/src/shared/types/animation.types.ts +9 -0
  122. package/src/shared/types/bounds.types.ts +1 -0
  123. package/src/shared/types/stack.types.ts +1 -0
  124. package/src/shared/utils/animation/start-screen-transition.ts +4 -1
  125. package/src/shared/utils/bounds/index.ts +29 -3
  126. package/lib/commonjs/shared/utils/read-shared-value.js +0 -17
  127. package/lib/commonjs/shared/utils/read-shared-value.js.map +0 -1
  128. package/lib/module/shared/utils/read-shared-value.js +0 -14
  129. package/lib/module/shared/utils/read-shared-value.js.map +0 -1
  130. package/lib/typescript/shared/utils/read-shared-value.d.ts +0 -7
  131. package/lib/typescript/shared/utils/read-shared-value.d.ts.map +0 -1
  132. package/src/shared/utils/read-shared-value.ts +0 -15
package/README.md CHANGED
@@ -9,10 +9,9 @@ Customizable screen transitions for React Native. Build gesture-driven, shared e
9
9
  ## Features
10
10
 
11
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
- - **Stack Progress** – Track animation progress across the entire stack, not just adjacent screens
12
+ - **Shared Elements** – Smooth transitions between screens using the Bounds API
13
+ - **Gesture Support** – Swipe-to-dismiss with edge or full-screen activation
14
+ - **Stack Progress** – Track animation progress across the entire stack
16
15
  - **Ready-Made Presets** – Instagram, Apple Music, X (Twitter) style transitions included
17
16
 
18
17
  ## Installation
@@ -34,65 +33,7 @@ npm install react-native-reanimated react-native-gesture-handler \
34
33
 
35
34
  ## Quick Start
36
35
 
37
- This package provides three stack navigators:
38
-
39
- | Stack | Description |
40
- | ----------------------------- | ---------------------------------------------------------------------------------- |
41
- | **Blank Stack** (recommended) | Pure JavaScript stack with full control over transitions, overlays, and gestures. |
42
- | **Native Stack** | Extends `@react-navigation/native-stack`. Full overlay support, native primitives. |
43
- | **Component Stack** | Standalone navigator without React Navigation. For internal/embedded navigation. |
44
-
45
- ### Choosing a Stack
46
-
47
- For most apps, **start with Blank Stack**. It's the most flexible and has full feature support.
48
-
49
- | Stack | Best For |
50
- | ------------------- | --------------------------------------------------------------------------------- |
51
- | **Blank Stack** | Most apps. Full control, all features, JS-based with native performance. |
52
- | **Native Stack** | When you need native screen primitives. Has some trade-offs. |
53
- | **Component Stack** | Embedded flows (wizards, popovers). Isolated from React Navigation. Experimental. |
54
-
55
- All three stacks share the same animation API – `screenStyleInterpolator`, gestures, overlays, and presets work identically across them.
56
-
57
- ### Blank Stack Philosophy
58
-
59
- The Blank Stack is intentionally **blank** - transparent screens with no default animations. Unlike platform navigators that impose iOS or Android-style transitions, the Blank Stack gives you a clean slate.
60
-
61
- **Why no defaults?**
62
-
63
- - **Full creative control** – You define exactly how screens appear, not the OS
64
- - **Consistency across platforms** – Same animation on iOS and Android
65
- - **No fighting the framework** – No need to override or disable built-in behaviors
66
-
67
- Every screen starts invisible and static. You bring it to life with your own `screenStyleInterpolator`. This encourages intentional, custom transitions rather than settling for platform defaults.
68
-
69
- Under the hood, the Blank Stack uses `react-native-screens` for native-level performance. All animation and gesture logic runs on the UI thread via Reanimated worklets.
70
-
71
- ```tsx
72
- // A screen with no options = invisible, no animation
73
- <Stack.Screen name="Detail" component={DetailScreen} />
74
-
75
- // Add your own transition
76
- <Stack.Screen
77
- name="Detail"
78
- component={DetailScreen}
79
- options={{
80
- screenStyleInterpolator: ({ progress, layouts }) => {
81
- "worklet";
82
- return {
83
- contentStyle: {
84
- opacity: progress,
85
- transform: [
86
- { translateY: interpolate(progress, [0, 1], [layouts.screen.height, 0]) }
87
- ],
88
- },
89
- };
90
- },
91
- }}
92
- />
93
- ```
94
-
95
- ### Blank Stack Setup
36
+ ### 1. Create a Stack
96
37
 
97
38
  ```tsx
98
39
  import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack";
@@ -116,17 +57,12 @@ function App() {
116
57
  }
117
58
  ```
118
59
 
119
- ### Blank Stack with Expo Router
60
+ ### 2. With Expo Router
120
61
 
121
62
  ```tsx
122
- import type {
123
- ParamListBase,
124
- StackNavigationState,
125
- } from "@react-navigation/native";
126
63
  import { withLayoutContext } from "expo-router";
127
64
  import {
128
65
  createBlankStackNavigator,
129
- type BlankStackNavigationEventMap,
130
66
  type BlankStackNavigationOptions,
131
67
  } from "react-native-screen-transitions/blank-stack";
132
68
 
@@ -134,9 +70,7 @@ const { Navigator } = createBlankStackNavigator();
134
70
 
135
71
  export const Stack = withLayoutContext<
136
72
  BlankStackNavigationOptions,
137
- typeof Navigator,
138
- StackNavigationState<ParamListBase>,
139
- BlankStackNavigationEventMap
73
+ typeof Navigator
140
74
  >(Navigator);
141
75
  ```
142
76
 
@@ -144,7 +78,7 @@ export const Stack = withLayoutContext<
144
78
 
145
79
  ## Presets
146
80
 
147
- Built-in animation presets you can spread into screen options:
81
+ Use built-in presets for common transitions:
148
82
 
149
83
  ```tsx
150
84
  <Stack.Screen
@@ -155,569 +89,486 @@ Built-in animation presets you can spread into screen options:
155
89
  />
156
90
  ```
157
91
 
158
- | Preset | Description |
159
- | -------------------------------------- | ----------------------------------------------- |
160
- | `SlideFromTop()` | Slides in from top, vertical gesture dismiss |
161
- | `SlideFromBottom()` | Slides in from bottom, vertical gesture dismiss |
162
- | `ZoomIn()` | Scales in with fade, no gesture |
163
- | `DraggableCard()` | Multi-directional drag with card scaling |
164
- | `ElasticCard()` | Elastic drag with overlay darkening |
165
- | `SharedIGImage({ sharedBoundTag })` | Instagram-style shared image transition |
166
- | `SharedAppleMusic({ sharedBoundTag })` | Apple Music-style shared element |
167
- | `SharedXImage({ sharedBoundTag })` | X (Twitter)-style image transition |
92
+ | Preset | Description |
93
+ | -------------------------------------- | --------------------------------------- |
94
+ | `SlideFromTop()` | Slides in from top |
95
+ | `SlideFromBottom()` | Slides in from bottom (modal-style) |
96
+ | `ZoomIn()` | Scales in with fade |
97
+ | `DraggableCard()` | Multi-directional drag with scaling |
98
+ | `ElasticCard()` | Elastic drag with overlay |
99
+ | `SharedIGImage({ sharedBoundTag })` | Instagram-style shared image |
100
+ | `SharedAppleMusic({ sharedBoundTag })` | Apple Music-style shared element |
101
+ | `SharedXImage({ sharedBoundTag })` | X (Twitter)-style image transition |
168
102
 
169
103
  ---
170
104
 
171
105
  ## Custom Animations
172
106
 
173
- ### Understanding Progress
107
+ ### The Basics
174
108
 
175
- The `progress` value tells you where a screen is in its lifecycle:
109
+ Every screen has a `progress` value that goes from 0 1 2:
176
110
 
177
111
  ```
178
- 0 ──────────────── 1 ──────────────── 2
179
- │ │ │
180
- Screen is Screen is Screen is
181
- OFF-SCREEN FULLY VISIBLE OFF-SCREEN
182
- (entering) (active) (exiting)
112
+ 0 ─────────── 1 ─────────── 2
113
+ entering visible exiting
183
114
  ```
184
115
 
185
- When you navigate from Screen A to Screen B:
116
+ When navigating from A to B:
117
+ - **Screen B**: progress goes `0 → 1` (entering)
118
+ - **Screen A**: progress goes `1 → 2` (exiting)
186
119
 
187
- - **Screen B** (entering): `progress` animates from `0` → `1`
188
- - **Screen A** (exiting): `progress` animates from `1` → `2`
189
-
190
- ### Basic Examples
191
-
192
- **Fade in/out:**
120
+ ### Simple Fade
193
121
 
194
122
  ```tsx
195
- screenStyleInterpolator: ({ progress }) => {
196
- "worklet";
197
- // At 0: opacity = 0 (invisible)
198
- // At 1: opacity = 1 (visible)
199
- // At 2: opacity = 0 (invisible again)
200
- return {
201
- contentStyle: {
202
- opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
203
- },
204
- };
205
- };
123
+ options={{
124
+ screenStyleInterpolator: ({ progress }) => {
125
+ "worklet";
126
+ return {
127
+ contentStyle: {
128
+ opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
129
+ },
130
+ };
131
+ },
132
+ }}
206
133
  ```
207
134
 
208
- **Slide from right:**
135
+ ### Slide from Right
209
136
 
210
137
  ```tsx
211
- screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
212
- "worklet";
213
- // At 0: off-screen to the right
214
- // At 1: centered (0)
215
- // At 2: off-screen to the left
216
- return {
217
- contentStyle: {
218
- transform: [
219
- {
138
+ options={{
139
+ screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
140
+ "worklet";
141
+ return {
142
+ contentStyle: {
143
+ transform: [{
220
144
  translateX: interpolate(
221
145
  progress,
222
146
  [0, 1, 2],
223
147
  [screen.width, 0, -screen.width * 0.3]
224
148
  ),
225
- },
226
- ],
227
- },
228
- };
229
- };
149
+ }],
150
+ },
151
+ };
152
+ },
153
+ }}
230
154
  ```
231
155
 
232
- **Slide from bottom (modal-style):**
156
+ ### Slide from Bottom
233
157
 
234
158
  ```tsx
235
- screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
236
- "worklet";
237
- return {
238
- contentStyle: {
239
- transform: [
240
- {
159
+ options={{
160
+ screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
161
+ "worklet";
162
+ return {
163
+ contentStyle: {
164
+ transform: [{
241
165
  translateY: interpolate(progress, [0, 1], [screen.height, 0]),
242
- },
243
- ],
244
- },
245
- };
246
- };
166
+ }],
167
+ },
168
+ };
169
+ },
170
+ }}
247
171
  ```
248
172
 
249
- ### Writing Your Interpolator
250
-
251
- Every `screenStyleInterpolator` must:
173
+ ### Return Styles
252
174
 
253
- 1. Be marked with `"worklet"` (runs on UI thread)
254
- 2. Return an object with style properties
175
+ Your interpolator can return:
255
176
 
256
177
  ```tsx
257
- import { interpolate } from "react-native-reanimated";
258
-
259
- <Stack.Screen
260
- name="Detail"
261
- options={{
262
- screenStyleInterpolator: ({ progress, layouts: { screen } }) => {
263
- "worklet";
264
-
265
- return {
266
- contentStyle: {
267
- // Your animated styles here
268
- },
269
- };
270
- },
271
- transitionSpec: {
272
- open: Transition.Specs.DefaultSpec,
273
- close: Transition.Specs.DefaultSpec,
274
- },
275
- }}
276
- />;
178
+ return {
179
+ contentStyle: { ... }, // Main screen
180
+ overlayStyle: { ... }, // Semi-transparent backdrop
181
+ ["my-id"]: { ... }, // Specific element via styleId
182
+ };
277
183
  ```
278
184
 
279
- ### Interpolator Props
185
+ ### Animation Specs
280
186
 
281
- | Prop | Description |
282
- | ---------------- | -------------------------------------------------------- |
283
- | `progress` | Combined progress (0-2). 0=entering, 1=active, 2=exiting |
284
- | `stackProgress` | Accumulated progress across entire stack (0, 1, 2, 3...) |
285
- | `current` | Current screen state (progress, closing, gesture, meta) |
286
- | `previous` | Previous screen state (may be undefined) |
287
- | `next` | Next screen state (may be undefined) |
288
- | `layouts.screen` | Screen dimensions `{ width, height }` |
289
- | `insets` | Safe area insets `{ top, right, bottom, left }` |
290
- | `focused` | Whether current screen is the topmost |
291
- | `active` | The screen driving the transition |
292
- | `inactive` | The screen NOT driving the transition |
293
- | `bounds` | Function to access shared element positions |
187
+ Control timing with spring configs:
294
188
 
295
- ### Screen State (`current`, `previous`, `next`, `active`, `inactive`)
296
-
297
- Each screen state contains:
298
-
299
- | Property | Description |
300
- | ----------- | ----------------------------------------------------- |
301
- | `progress` | Animation progress for this screen (0 or 1) |
302
- | `closing` | Whether screen is closing (0 or 1) |
303
- | `animating` | Whether screen is currently animating (0 or 1) |
304
- | `gesture` | Gesture values (x, y, normalizedX, normalizedY, etc.) |
305
- | `meta` | Custom metadata from screen options |
189
+ ```tsx
190
+ options={{
191
+ screenStyleInterpolator: myInterpolator,
192
+ transitionSpec: {
193
+ open: { stiffness: 1000, damping: 500, mass: 3 },
194
+ close: { stiffness: 1000, damping: 500, mass: 3 },
195
+ },
196
+ }}
197
+ ```
306
198
 
307
- ### Understanding `active` and `inactive`
199
+ ---
308
200
 
309
- The `active` and `inactive` props help you write cleaner conditional logic:
201
+ ## Gestures
310
202
 
311
- - **`active`** – The screen driving the transition. When focused, this is `current`. When not focused, this is `next`.
312
- - **`inactive`** – The screen NOT driving the transition. When focused, this is `previous`. When not focused, this is `current`.
203
+ Enable swipe-to-dismiss:
313
204
 
314
205
  ```tsx
315
- // Check if the inactive screen wants to disable an animation
316
- const disableTranslateY = props.inactive?.meta?.disableTranslateYAnimation;
317
-
318
- // Check if the active screen is animating or closing
319
- const isAnimating = props.active.animating;
320
- const isClosing = props.active.closing;
206
+ options={{
207
+ gestureEnabled: true,
208
+ gestureDirection: "vertical",
209
+ ...Transition.Presets.SlideFromBottom(),
210
+ }}
321
211
  ```
322
212
 
323
- ### Using `meta` for Conditional Logic
213
+ ### Gesture Options
214
+
215
+ | Option | Description |
216
+ | ------------------------- | -------------------------------------- |
217
+ | `gestureEnabled` | Enable swipe-to-dismiss |
218
+ | `gestureDirection` | Direction(s) for swipe gesture |
219
+ | `gestureActivationArea` | Where gesture can start |
220
+ | `gestureResponseDistance` | Pixel threshold for activation |
221
+ | `gestureVelocityImpact` | How much velocity affects dismissal |
324
222
 
325
- Use `meta` to pass custom data for conditional animation logic. This is more robust than checking route names:
223
+ ### Gesture Direction
326
224
 
327
225
  ```tsx
328
- // Screen A sets meta to affect how Screen B animates
329
- <Stack.Screen
330
- name="ScreenA"
331
- options={{
332
- meta: { disableTranslateYAnimation: true },
333
- }}
334
- />
226
+ gestureDirection: "horizontal" // swipe left to dismiss
227
+ gestureDirection: "horizontal-inverted" // swipe right to dismiss
228
+ gestureDirection: "vertical" // swipe down to dismiss
229
+ gestureDirection: "vertical-inverted" // swipe up to dismiss
230
+ gestureDirection: "bidirectional" // any direction
335
231
 
336
- // Screen B checks inactive screen's meta
337
- <Stack.Screen
338
- name="ScreenB"
339
- options={{
340
- screenStyleInterpolator: (props) => {
341
- "worklet";
342
-
343
- // When entering from ScreenA, inactive = ScreenA (previous)
344
- // When going back to ScreenA, inactive = ScreenB (current)
345
- const disableY = props.inactive?.meta?.disableTranslateYAnimation;
346
-
347
- return {
348
- contentStyle: {
349
- transform: [{ translateY: disableY ? 0 : translateY }],
350
- },
351
- };
352
- },
353
- }}
354
- />
232
+ // Or combine multiple:
233
+ gestureDirection: ["horizontal", "vertical"]
355
234
  ```
356
235
 
357
- You can also react to screen state changes within components:
236
+ ### Gesture Activation Area
358
237
 
359
238
  ```tsx
360
- const animation = useScreenAnimation();
361
-
362
- useAnimatedReaction(
363
- () => animation.value,
364
- (props) => {
365
- // React to next screen's meta
366
- if (props.next?.meta?.scalesOthers) {
367
- scale.value = withTiming(0);
368
- }
369
- }
370
- );
371
- ```
372
-
373
- ### Return Value
239
+ // Simple - same for all edges
240
+ gestureActivationArea: "edge" // only from screen edges
241
+ gestureActivationArea: "screen" // anywhere on screen
374
242
 
375
- ```tsx
376
- return {
377
- contentStyle: { ... }, // Main screen content
378
- overlayStyle: { ... }, // Semi-transparent overlay
379
- ["my-element"]: { ... }, // Styles for Transition.View with styleId="my-element"
380
- };
243
+ // Per-side configuration
244
+ gestureActivationArea: {
245
+ left: "edge",
246
+ right: "screen",
247
+ top: "edge",
248
+ bottom: "screen",
249
+ }
381
250
  ```
382
251
 
383
- ### Using `styleId` for Individual Elements
252
+ ### With ScrollViews
384
253
 
385
- Animate specific elements within a screen:
254
+ Use transition-aware scrollables so gestures work correctly:
386
255
 
387
256
  ```tsx
388
- // In screen options
389
- screenStyleInterpolator: ({ progress }) => {
390
- "worklet";
391
- return {
392
- "hero-image": {
393
- opacity: interpolate(progress, [0, 1], [0, 1]),
394
- transform: [{ scale: interpolate(progress, [0, 1], [0.8, 1]) }],
395
- },
396
- };
397
- };
257
+ <Transition.ScrollView>
258
+ {/* content */}
259
+ </Transition.ScrollView>
398
260
 
399
- // In component
400
- <Transition.View styleId="hero-image">
401
- <Image source={...} />
402
- </Transition.View>
261
+ <Transition.FlatList data={items} renderItem={...} />
403
262
  ```
404
263
 
264
+ Gesture rules with scrollables:
265
+ - **vertical** – only activates when scrolled to top
266
+ - **vertical-inverted** – only activates when scrolled to bottom
267
+ - **horizontal** – only activates at left/right scroll edges
268
+
405
269
  ---
406
270
 
407
271
  ## Shared Elements (Bounds API)
408
272
 
409
- Animate elements between screens by measuring their positions.
273
+ Animate elements between screens by tagging them.
410
274
 
411
- ### 1. Tag Elements on Both Screens
275
+ ### 1. Tag the Source
412
276
 
413
277
  ```tsx
414
- // Source screen
415
278
  <Transition.Pressable
416
279
  sharedBoundTag="avatar"
417
280
  onPress={() => navigation.navigate("Profile")}
418
281
  >
419
282
  <Image source={avatar} style={{ width: 50, height: 50 }} />
420
283
  </Transition.Pressable>
284
+ ```
285
+
286
+ ### 2. Tag the Destination
421
287
 
422
- // Destination screen
288
+ ```tsx
423
289
  <Transition.View sharedBoundTag="avatar">
424
290
  <Image source={avatar} style={{ width: 200, height: 200 }} />
425
291
  </Transition.View>
426
292
  ```
427
293
 
428
- ### 2. Use Bounds in Interpolator
294
+ ### 3. Use in Interpolator
429
295
 
430
296
  ```tsx
431
297
  screenStyleInterpolator: ({ bounds }) => {
432
298
  "worklet";
433
-
434
- const avatarStyles = bounds({
435
- id: "avatar",
436
- method: "transform", // "transform" | "size" | "content"
437
- space: "relative", // "relative" | "absolute"
438
- scaleMode: "match", // "match" | "none" | "uniform"
439
- anchor: "center", // positioning anchor
440
- });
441
-
442
299
  return {
443
- avatar: avatarStyles,
300
+ avatar: bounds({ id: "avatar", method: "transform" }),
444
301
  };
445
302
  };
446
303
  ```
447
304
 
448
305
  ### Bounds Options
449
306
 
450
- | Option | Values | Description |
451
- | ----------- | -------------------------------------- | -------------------------------------- |
452
- | `id` | string | The `sharedBoundTag` to match |
453
- | `method` | `"transform"` `"size"` `"content"` | How to animate (scale vs width/height) |
454
- | `space` | `"relative"` `"absolute"` | Coordinate space |
455
- | `scaleMode` | `"match"` `"none"` `"uniform"` | How to handle aspect ratio |
456
- | `anchor` | `"center"` `"top"` `"topLeading"` etc. | Transform origin |
457
- | `target` | `"bound"` `"fullscreen"` or custom | Destination target |
458
- | `raw` | boolean | Return raw values instead of styles |
307
+ | Option | Values | Description |
308
+ | ----------- | ---------------------------------- | ----------------------------- |
309
+ | `id` | string | The `sharedBoundTag` to match |
310
+ | `method` | `"transform"` `"size"` `"content"` | How to animate |
311
+ | `space` | `"relative"` `"absolute"` | Coordinate space |
312
+ | `scaleMode` | `"match"` `"none"` `"uniform"` | Aspect ratio handling |
313
+ | `raw` | boolean | Return raw values |
459
314
 
460
- ### Raw Values
461
-
462
- ```tsx
463
- const raw = bounds({ id: "avatar", method: "transform", raw: true });
464
- // { translateX, translateY, scaleX, scaleY }
465
- ```
315
+ ---
466
316
 
467
- ### Bounds Utilities
317
+ ## Overlays
468
318
 
469
- Access additional bounds data for custom animations:
319
+ Persistent UI that animates with the stack:
470
320
 
471
321
  ```tsx
472
- screenStyleInterpolator: ({ bounds, progress }) => {
473
- "worklet";
322
+ const TabBar = ({ focusedIndex, progress }) => {
323
+ const style = useAnimatedStyle(() => ({
324
+ transform: [{ translateY: interpolate(progress.value, [0, 1], [100, 0]) }],
325
+ }));
326
+ return <Animated.View style={[styles.tabBar, style]} />;
327
+ };
474
328
 
475
- // Get the active link between source and destination
476
- const link = bounds.getLink("avatar");
477
- // { source: { bounds, styles }, destination: { bounds, styles } }
329
+ <Stack.Screen
330
+ name="Home"
331
+ options={{
332
+ overlay: TabBar,
333
+ overlayShown: true,
334
+ }}
335
+ />
336
+ ```
478
337
 
479
- // Interpolate a style property (e.g., borderRadius) between source and destination
480
- const borderRadius = bounds.interpolateStyle("avatar", "borderRadius");
338
+ ### Overlay Props
481
339
 
482
- // Or access raw values for custom logic
483
- const sourceBorderRadius = link?.source?.styles?.borderRadius ?? 0;
340
+ | Prop | Description |
341
+ | -------------- | ------------------------------ |
342
+ | `focusedRoute` | Currently focused route |
343
+ | `focusedIndex` | Index of focused screen |
344
+ | `routes` | All routes in the stack |
345
+ | `progress` | Stack progress (derived value) |
346
+ | `navigation` | Navigation prop |
347
+ | `meta` | Custom metadata from options |
484
348
 
485
- return {
486
- avatar: {
487
- ...bounds({ id: "avatar" }),
488
- borderRadius,
489
- },
490
- };
491
- };
492
- ```
349
+ ---
350
+
351
+ ## Transition Components
493
352
 
494
- | Method | Description |
495
- | ---------------------------------------------- | --------------------------------------------------- |
496
- | `bounds.getLink(id)` | Get source/destination bounds and styles for a tag |
497
- | `bounds.interpolateStyle(id, prop, fallback?)` | Interpolate a numeric style between source and dest |
498
- | `bounds.getSnapshot(id, key)` | Manual lookup by specific screen key (edge cases) |
353
+ | Component | Description |
354
+ | ----------------------- | -------------------------------------- |
355
+ | `Transition.View` | Animated view with `sharedBoundTag` |
356
+ | `Transition.Pressable` | Pressable that measures bounds |
357
+ | `Transition.ScrollView` | ScrollView with gesture coordination |
358
+ | `Transition.FlatList` | FlatList with gesture coordination |
359
+ | `Transition.MaskedView` | For reveal effects (requires native) |
499
360
 
500
361
  ---
501
362
 
502
- ## Gestures
363
+ ## Hooks
364
+
365
+ ### useScreenAnimation
503
366
 
504
- Enable swipe-to-dismiss on screens:
367
+ Access animation state inside a screen:
505
368
 
506
369
  ```tsx
507
- <Stack.Screen
508
- name="Detail"
509
- options={{
510
- gestureEnabled: true,
511
- gestureDirection: "vertical", // or "horizontal", ["vertical", "horizontal"]
512
- gestureActivationArea: "edge", // or "screen", or { left: "edge", top: "screen" }
513
- gestureResponseDistance: 50,
514
- gestureVelocityImpact: 0.3,
515
- }}
516
- />
517
- ```
370
+ import { useScreenAnimation } from "react-native-screen-transitions";
518
371
 
519
- ### Gesture Options
372
+ function DetailScreen() {
373
+ const animation = useScreenAnimation();
520
374
 
521
- | Option | Description |
522
- | ------------------------- | ---------------------------------------------------------------------------------- |
523
- | `gestureEnabled` | Enable/disable gesture |
524
- | `gestureDirection` | `"horizontal"` `"vertical"` `"horizontal-inverted"` `"vertical-inverted"` or array |
525
- | `gestureActivationArea` | `"edge"` `"screen"` or per-side config |
526
- | `gestureResponseDistance` | Distance threshold for gesture recognition |
527
- | `gestureVelocityImpact` | How much velocity affects dismissal decision |
528
- | `gestureDrivesProgress` | Whether gesture directly drives animation (default: true) |
375
+ const style = useAnimatedStyle(() => ({
376
+ opacity: animation.value.current.progress,
377
+ }));
529
378
 
530
- ### Gestures with ScrollViews
379
+ return <Animated.View style={style}>...</Animated.View>;
380
+ }
381
+ ```
531
382
 
532
- Use transition-aware scrollables so gestures work correctly:
383
+ ### useScreenState
384
+
385
+ Get navigation state without animation values:
533
386
 
534
387
  ```tsx
535
- import Transition from "react-native-screen-transitions";
388
+ import { useScreenState } from "react-native-screen-transitions";
536
389
 
537
- // Drop-in replacements
538
- <Transition.ScrollView>
539
- {/* content */}
540
- </Transition.ScrollView>
390
+ function DetailScreen() {
391
+ const { index, focusedRoute, routes, navigation } = useScreenState();
392
+ // ...
393
+ }
394
+ ```
541
395
 
542
- <Transition.FlatList
543
- data={items}
544
- renderItem={...}
545
- />
396
+ ### useHistory
546
397
 
547
- // Wrap custom lists
548
- const TransitionFlashList = Transition.createTransitionAwareComponent(
549
- FlashList,
550
- { isScrollable: true }
551
- );
552
- ```
398
+ Access navigation history across the app:
553
399
 
554
- Gesture rules with scrollables:
400
+ ```tsx
401
+ import { useHistory } from "react-native-screen-transitions";
402
+
403
+ function MyComponent() {
404
+ const { getRecent, getPath } = useHistory();
555
405
 
556
- - **vertical** only starts when scrolled to top
557
- - **vertical-inverted** only starts when scrolled to bottom
558
- - **horizontal** – only starts at left/right edge
406
+ const recentScreens = getRecent(5); // Last 5 screens
407
+ const path = getPath(fromKey, toKey); // Path between screens
408
+ }
409
+ ```
559
410
 
560
411
  ---
561
412
 
562
- ## Overlays
413
+ ## Advanced Animation Props
563
414
 
564
- Both Blank Stack and Native Stack support persistent overlays that animate across screen transitions.
415
+ The full `screenStyleInterpolator` receives these props:
565
416
 
566
- ### Float Overlay
417
+ | Prop | Description |
418
+ | ---------------- | -------------------------------------------------------- |
419
+ | `progress` | Combined progress (0-2) |
420
+ | `stackProgress` | Accumulated progress across entire stack |
421
+ | `current` | Current screen state |
422
+ | `previous` | Previous screen state |
423
+ | `next` | Next screen state |
424
+ | `active` | Screen driving the transition |
425
+ | `inactive` | Screen NOT driving the transition |
426
+ | `layouts.screen` | Screen dimensions |
427
+ | `insets` | Safe area insets |
428
+ | `bounds` | Shared element bounds function |
429
+
430
+ ### Screen State Properties
431
+
432
+ Each screen state (`current`, `previous`, `next`, `active`, `inactive`) contains:
433
+
434
+ | Property | Description |
435
+ | ----------- | ---------------------------------------- |
436
+ | `progress` | Animation progress (0 or 1) |
437
+ | `closing` | Whether closing (0 or 1) |
438
+ | `entering` | Whether entering (0 or 1) |
439
+ | `animating` | Whether animating (0 or 1) |
440
+ | `gesture` | Gesture values (x, y, normalized values) |
441
+ | `meta` | Custom metadata from options |
442
+
443
+ ### Using `meta` for Conditional Logic
567
444
 
568
- A single overlay that persists above all screens:
445
+ Pass custom data between screens:
569
446
 
570
447
  ```tsx
571
- const FloatingHeader = ({ focusedIndex, routes, overlayAnimation }) => {
572
- const style = useAnimatedStyle(() => ({
573
- opacity: interpolate(overlayAnimation.value.progress, [0, 1], [0, 1]),
574
- }));
448
+ // Screen A
449
+ options={{ meta: { hideTabBar: true } }}
575
450
 
576
- return (
577
- <Animated.View style={[styles.header, style]}>
578
- <Text>
579
- Screen {focusedIndex + 1} of {routes.length}
580
- </Text>
581
- </Animated.View>
582
- );
451
+ // Screen B reads it
452
+ screenStyleInterpolator: (props) => {
453
+ "worklet";
454
+ const hideTabBar = props.inactive?.meta?.hideTabBar;
455
+ // ...
583
456
  };
584
-
585
- <Stack.Screen
586
- name="Home"
587
- options={{
588
- overlay: FloatingHeader,
589
- overlayMode: "float",
590
- overlayShown: true,
591
- }}
592
- />;
593
457
  ```
594
458
 
595
- ### Screen Overlay
459
+ ### Animate Individual Elements
596
460
 
597
- An overlay that moves with screen content:
461
+ Use `styleId` to target specific elements:
598
462
 
599
463
  ```tsx
600
- <Stack.Screen
601
- name="Detail"
602
- options={{
603
- overlay: DetailOverlay,
604
- overlayMode: "screen",
605
- overlayShown: true,
606
- }}
607
- />
464
+ // In options
465
+ screenStyleInterpolator: ({ progress }) => {
466
+ "worklet";
467
+ return {
468
+ "hero-image": {
469
+ opacity: interpolate(progress, [0, 1], [0, 1]),
470
+ },
471
+ };
472
+ };
473
+
474
+ // In component
475
+ <Transition.View styleId="hero-image">
476
+ <Image source={...} />
477
+ </Transition.View>
608
478
  ```
609
479
 
610
- ### Overlay Props
480
+ ---
481
+
482
+ ## Stack Types
483
+
484
+ All three stacks share the same animation API. Choose based on your needs:
485
+
486
+ | Stack | Best For |
487
+ | ------------------- | --------------------------------------------------------- |
488
+ | **Blank Stack** | Most apps. Full control, all features. |
489
+ | **Native Stack** | When you need native screen primitives. |
490
+ | **Component Stack** | Embedded flows, isolated from React Navigation. *(Experimental)* |
611
491
 
612
- | Prop | Description |
613
- | ------------------ | --------------------------------------------------------- |
614
- | `focusedRoute` | Currently focused route |
615
- | `focusedIndex` | Index of focused screen |
616
- | `routes` | All routes in the stack |
617
- | `meta` | Custom metadata passed from screen options |
618
- | `navigation` | Navigation prop |
619
- | `overlayAnimation` | Animation values with `progress` accumulated across stack |
620
- | `screenAnimation` | Animation values for the current focused screen |
492
+ ### Blank Stack
621
493
 
622
- ### Passing Custom Data
494
+ The default choice. Pure JavaScript with native-level performance via `react-native-screens`.
623
495
 
624
496
  ```tsx
497
+ import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack";
498
+ ```
499
+
500
+ ### Native Stack
501
+
502
+ Extends `@react-navigation/native-stack`. Requires `enableTransitions: true`.
503
+
504
+ ```tsx
505
+ import { createNativeStackNavigator } from "react-native-screen-transitions/native-stack";
506
+
625
507
  <Stack.Screen
508
+ name="Detail"
626
509
  options={{
627
- overlay: MyOverlay,
628
- meta: {
629
- title: "Step 1",
630
- showProgress: true,
631
- },
510
+ enableTransitions: true,
511
+ ...Transition.Presets.SlideFromBottom(),
632
512
  }}
633
- />;
634
-
635
- // In overlay
636
- const MyOverlay = ({ meta }) => {
637
- return <Text>{meta?.title}</Text>;
638
- };
513
+ />
639
514
  ```
640
515
 
641
- ---
642
-
643
- ## Transition Components
516
+ ### Component Stack (Experimental)
644
517
 
645
- | Component | Description |
646
- | ----------------------- | ------------------------------------------------------ |
647
- | `Transition.View` | Animated view, supports `styleId` and `sharedBoundTag` |
648
- | `Transition.Pressable` | Pressable with bounds measurement on press |
649
- | `Transition.ScrollView` | ScrollView with gesture coordination |
650
- | `Transition.FlatList` | FlatList with gesture coordination |
651
- | `Transition.MaskedView` | For clipping during shared element transitions |
518
+ > **Note:** This API is experimental and may change based on community feedback.
652
519
 
653
- ### Creating Custom Components
520
+ Standalone navigator, not connected to React Navigation. Ideal for embedded flows.
654
521
 
655
522
  ```tsx
656
- const TransitionImage = Transition.createTransitionAwareComponent(
657
- Animated.Image,
658
- { isScrollable: false }
659
- );
523
+ import { createComponentNavigator } from "react-native-screen-transitions/component-stack";
524
+
525
+ const Stack = createComponentNavigator();
526
+
527
+ <Stack.Navigator initialRoute="step1">
528
+ <Stack.Screen name="step1" component={Step1} />
529
+ <Stack.Screen name="step2" component={Step2} />
530
+ </Stack.Navigator>
660
531
  ```
661
532
 
662
533
  ---
663
534
 
664
- ## Hooks
535
+ ## Caveats & Trade-offs
665
536
 
666
- ### `useScreenAnimation`
537
+ ### Native Stack
667
538
 
668
- Access animation state within a screen component:
539
+ The Native Stack uses transparent modal presentation to intercept transitions. This has trade-offs:
669
540
 
670
- ```tsx
671
- import { useScreenAnimation } from "react-native-screen-transitions";
541
+ - **Delayed touch events** – Exiting screens may have briefly delayed touch response
542
+ - **beforeRemove listeners** Relies on navigation lifecycle events
543
+ - **Rapid navigation** – Some edge cases with very fast navigation sequences
672
544
 
673
- function DetailScreen() {
674
- const animation = useScreenAnimation();
545
+ For most apps, Blank Stack avoids these issues entirely.
675
546
 
676
- const style = useAnimatedStyle(() => {
677
- const { current } = animation.value;
678
- return {
679
- opacity: current.progress,
680
- };
681
- });
547
+ ### Component Stack (Experimental)
682
548
 
683
- return <Animated.View style={style}>...</Animated.View>;
684
- }
685
- ```
549
+ - **No deep linking** – Routes aren't part of your URL structure
550
+ - **Isolated state** – Doesn't affect parent navigation
551
+ - **Touch pass-through** – Uses `pointerEvents="box-none"` by default
686
552
 
687
553
  ---
688
554
 
689
- ## Animation Specs
555
+ ## Experimental Features
690
556
 
691
- Configure spring/timing animations:
557
+ ### High Refresh Rate
692
558
 
693
- ```tsx
694
- transitionSpec: {
695
- open: {
696
- stiffness: 1000,
697
- damping: 500,
698
- mass: 3,
699
- overshootClamping: true,
700
- },
701
- close: {
702
- stiffness: 1000,
703
- damping: 500,
704
- mass: 3,
705
- overshootClamping: true,
706
- },
707
- }
559
+ Force maximum refresh rate during transitions (for 90Hz/120Hz displays):
708
560
 
709
- // Or use the default
710
- transitionSpec: {
711
- open: Transition.Specs.DefaultSpec,
712
- close: Transition.Specs.DefaultSpec,
713
- }
561
+ ```tsx
562
+ options={{
563
+ experimental_enableHighRefreshRate: true,
564
+ }}
714
565
  ```
715
566
 
716
567
  ---
717
568
 
718
569
  ## Masked View Setup
719
570
 
720
- Required for `SharedIGImage` and `SharedAppleMusic` presets. The masked view creates the "reveal" effect where content appears to expand from the shared element.
571
+ Required for `SharedIGImage` and `SharedAppleMusic` presets. The masked view creates the "reveal" effect where content expands from the shared element.
721
572
 
722
573
  > **Note**: Requires native code. Will not work in Expo Go.
723
574
 
@@ -732,11 +583,9 @@ npm install @react-native-masked-view/masked-view
732
583
  cd ios && pod install
733
584
  ```
734
585
 
735
- ### Complete Example
736
-
737
- Here's a full example showing how to set up an Apple Music-style shared element transition:
586
+ ### Full Example
738
587
 
739
- **1. Source Screen** – Tag pressable elements with `sharedBoundTag`:
588
+ **1. Source Screen** – Tag pressable elements:
740
589
 
741
590
  ```tsx
742
591
  // app/index.tsx
@@ -767,7 +616,7 @@ export default function HomeScreen() {
767
616
  }
768
617
  ```
769
618
 
770
- **2. Destination Screen** – Wrap content with `MaskedView` and match the `sharedBoundTag`:
619
+ **2. Destination Screen** – Wrap with MaskedView and match the tag:
771
620
 
772
621
  ```tsx
773
622
  // app/details.tsx
@@ -795,7 +644,7 @@ export default function DetailsScreen() {
795
644
  }
796
645
  ```
797
646
 
798
- **3. Layout** – Apply the shared element preset with dynamic `sharedBoundTag`:
647
+ **3. Layout** – Apply the preset with dynamic tag:
799
648
 
800
649
  ```tsx
801
650
  // app/_layout.tsx
@@ -821,210 +670,16 @@ export default function RootLayout() {
821
670
 
822
671
  ### How It Works
823
672
 
824
- 1. `Transition.Pressable` measures its bounds when pressed and stores them with the `sharedBoundTag`
825
- 2. `Transition.View` on the destination screen registers as the target for that tag
826
- 3. `Transition.MaskedView` clips the destination content to the animating shared element bounds
827
- 4. The preset interpolates position, size, and the mask to create the seamless expand/collapse effect
828
-
829
- ---
830
-
831
- ## Native Stack
832
-
833
- For cases where you need native screen primitives, use the native stack integration. This extends `@react-navigation/native-stack` with custom transition support.
834
-
835
- > **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.
836
-
837
- ### Setup
838
-
839
- ```tsx
840
- import { createNativeStackNavigator } from "react-native-screen-transitions/native-stack";
841
-
842
- const Stack = createNativeStackNavigator();
843
-
844
- <Stack.Screen
845
- name="Detail"
846
- options={{
847
- enableTransitions: true, // Required to enable custom transitions
848
- ...Transition.Presets.SlideFromBottom(),
849
- }}
850
- />;
851
- ```
852
-
853
- ### Expo Router Setup
854
-
855
- ```tsx
856
- import type {
857
- ParamListBase,
858
- StackNavigationState,
859
- } from "@react-navigation/native";
860
- import { withLayoutContext } from "expo-router";
861
- import {
862
- createNativeStackNavigator,
863
- type NativeStackNavigationEventMap,
864
- type NativeStackNavigationOptions,
865
- } from "react-native-screen-transitions/native-stack";
866
-
867
- const { Navigator } = createNativeStackNavigator();
868
-
869
- export const Stack = withLayoutContext<
870
- NativeStackNavigationOptions,
871
- typeof Navigator,
872
- StackNavigationState<ParamListBase>,
873
- NativeStackNavigationEventMap
874
- >(Navigator);
875
- ```
876
-
877
- ### Native Stack Options
878
-
879
- All standard `@react-navigation/native-stack` options are available, plus:
880
-
881
- | Option | Type | Description |
882
- | ------------------------- | ---------------------------------------- | ------------------------------------------------------------------ |
883
- | `enableTransitions` | `boolean` | Enable custom transitions (sets presentation to transparent modal) |
884
- | `screenStyleInterpolator` | `ScreenStyleInterpolator` | Function that returns animated styles |
885
- | `transitionSpec` | `TransitionSpec` | Animation config for open/close |
886
- | `gestureEnabled` | `boolean` | Whether swipe-to-dismiss is allowed |
887
- | `gestureDirection` | `GestureDirection \| GestureDirection[]` | Allowed swipe directions |
888
- | `gestureVelocityImpact` | `number` | How much velocity affects dismissal |
889
- | `gestureResponseDistance` | `number` | Distance threshold for gesture |
890
- | `gestureDrivesProgress` | `boolean` | Whether gesture drives animation |
891
- | `gestureActivationArea` | `GestureActivationArea` | Where gesture can start |
892
- | `meta` | `Record<string, unknown>` | Custom metadata for conditional animation logic |
893
-
894
- ### Renamed Native Options
895
-
896
- To avoid collisions with custom gesture options, some native options are renamed:
897
-
898
- | React Navigation | Renamed to |
899
- | ------------------------- | ------------------------------- |
900
- | `gestureDirection` | `nativeGestureDirection` |
901
- | `gestureEnabled` | `nativeGestureEnabled` |
902
- | `gestureResponseDistance` | `nativeGestureResponseDistance` |
903
-
904
- ### Limitations
905
-
906
- - Relies on `beforeRemove` listener to intercept navigation
907
- - Uses transparent modal presentation
908
- - Some edge cases with rapid navigation
909
-
910
- ---
911
-
912
- ## Component Stack (Experimental)
913
-
914
- A standalone navigator that operates **independently** from React Navigation. It uses React Navigation's isolation API under the hood but doesn't affect your primary navigation state.
915
-
916
- > **Experimental**: This API may change in future versions.
917
-
918
- ### How It Differs
919
-
920
- Unlike Blank Stack and Native Stack which integrate with React Navigation:
921
-
922
- - **No deep linking** – Routes aren't part of your app's URL structure
923
- - **No navigation state sharing** – Completely isolated from parent navigators
924
- - **Transparent by default** – All screens use `pointerEvents="box-none"`, allowing touches to pass through to content behind. Your screen content handles its own touch areas.
925
-
926
- This makes Component Stack ideal for **overlay-style navigation** within a screen – think embedded wizards, popovers, or flows that shouldn't affect the main navigation.
927
-
928
- ### Setup
929
-
930
- ```tsx
931
- import {
932
- createComponentNavigator,
933
- useComponentNavigation,
934
- } from "react-native-screen-transitions/component-stack";
935
- import Transition from "react-native-screen-transitions";
936
-
937
- const Stack = createComponentNavigator();
938
-
939
- function OnboardingFlow() {
940
- return (
941
- <Stack.Navigator initialRoute="step1">
942
- <Stack.Screen
943
- name="step1"
944
- component={Step1}
945
- options={{
946
- ...Transition.Presets.SlideFromBottom(),
947
- }}
948
- />
949
- <Stack.Screen
950
- name="step2"
951
- component={Step2}
952
- options={{
953
- ...Transition.Presets.SlideFromBottom(),
954
- }}
955
- />
956
- </Stack.Navigator>
957
- );
958
- }
959
-
960
- function Step1() {
961
- const navigation = useComponentNavigation();
962
-
963
- return (
964
- <View>
965
- <Text>Step 1</Text>
966
- <Button title="Next" onPress={() => navigation.push("step2")} />
967
- </View>
968
- );
969
- }
970
- ```
971
-
972
- ### Navigation API
973
-
974
- ```tsx
975
- const navigation = useComponentNavigation();
976
-
977
- navigation.push("screenName", { param: "value" }); // Push a screen
978
- navigation.pop(); // Go back
979
- navigation.popTo("screenName"); // Pop to a specific screen
980
- navigation.reset("screenName"); // Reset to a single screen
981
- ```
982
-
983
- ### When to Use Component Stack
984
-
985
- - **Onboarding flows** – Self-contained multi-step experiences
986
- - **Wizards/Forms** – Multi-step forms within a screen
987
- - **Embedded navigation** – Navigation inside a modal or sheet
988
- - **Non-URL navigation** – When you don't need deep linking
989
-
990
- ---
991
-
992
- ## Experimental Features
993
-
994
- ### High Refresh Rate
995
-
996
- Force the display to run at maximum refresh rate during transitions. This can make animations smoother on high refresh rate displays (90Hz, 120Hz).
997
-
998
- ```tsx
999
- <Stack.Screen
1000
- name="Detail"
1001
- options={{
1002
- experimental_enableHighRefreshRate: true,
1003
- ...Transition.Presets.SlideFromBottom(),
1004
- }}
1005
- />
1006
- ```
1007
-
1008
- > **Note**: This is experimental and may have performance implications. Test on target devices.
1009
-
1010
- ---
1011
-
1012
- ## Migrating from Earlier Versions
1013
-
1014
- ### Deprecated Props
1015
-
1016
- The following props are deprecated and will be removed in a future version:
1017
-
1018
- | Deprecated Prop | Use Instead |
1019
- | ----------------------- | ------------------ |
1020
- | `isActiveTransitioning` | `active.animating` |
1021
- | `isDismissing` | `active.closing` |
673
+ 1. `Transition.Pressable` measures its bounds on press and stores them with the tag
674
+ 2. `Transition.View` on the destination registers as the target for that tag
675
+ 3. `Transition.MaskedView` clips content to the animating shared element bounds
676
+ 4. The preset interpolates position, size, and mask for a seamless expand/collapse effect
1022
677
 
1023
678
  ---
1024
679
 
1025
680
  ## Support
1026
681
 
1027
- This package is developed in my spare time. Updates and bug fixes may take time.
682
+ This package is developed in my spare time.
1028
683
 
1029
684
  If you'd like to fuel the next release, [buy me a coffee](https://buymeacoffee.com/trpfsu)
1030
685