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