@lodev09/react-native-true-sheet 4.0.0-beta.3 → 4.0.0-beta.30

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 (244) hide show
  1. package/README.md +28 -5
  2. package/android/build.gradle +23 -1
  3. package/android/proguard-rules.pro +3 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +38 -109
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +127 -0
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +29 -3
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +234 -66
  14. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +491 -186
  15. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +64 -10
  16. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -14
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +135 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +23 -23
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +130 -6
  20. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  21. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +14 -17
  22. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
  23. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +148 -0
  24. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +33 -40
  25. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  26. package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
  27. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  28. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  29. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  30. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  31. package/android/src/main/jni/TrueSheetStateUpdater.cpp +37 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +114 -30
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +14 -3
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +5 -6
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +10 -6
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  46. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  47. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  48. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  49. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  50. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  51. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  52. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  53. package/ios/TrueSheetContainerView.h +23 -7
  54. package/ios/TrueSheetContainerView.mm +45 -25
  55. package/ios/TrueSheetContentView.h +44 -15
  56. package/ios/TrueSheetContentView.mm +221 -190
  57. package/ios/TrueSheetFooterView.mm +133 -28
  58. package/ios/TrueSheetModule.h +2 -7
  59. package/ios/TrueSheetModule.mm +18 -18
  60. package/ios/TrueSheetOverlayView.h +42 -0
  61. package/ios/TrueSheetOverlayView.mm +175 -0
  62. package/ios/TrueSheetView.h +2 -0
  63. package/ios/TrueSheetView.mm +268 -52
  64. package/ios/TrueSheetViewController.h +36 -3
  65. package/ios/TrueSheetViewController.mm +797 -102
  66. package/ios/core/RNScreensEventObserver.mm +31 -28
  67. package/ios/core/TrueSheetBlurView.h +0 -2
  68. package/ios/core/TrueSheetBlurView.mm +3 -36
  69. package/ios/core/TrueSheetDetentCalculator.h +18 -1
  70. package/ios/core/TrueSheetDetentCalculator.mm +44 -9
  71. package/ios/core/TrueSheetDimView.h +27 -0
  72. package/ios/core/TrueSheetDimView.mm +41 -0
  73. package/ios/core/TrueSheetGrabberView.mm +13 -3
  74. package/ios/core/TrueSheetKeyboardObserver.h +7 -0
  75. package/ios/core/TrueSheetKeyboardObserver.mm +107 -15
  76. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  77. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  78. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  79. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  80. package/ios/tvos/TrueSheetStubs.mm +12 -0
  81. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  82. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  83. package/lib/module/TrueSheet.js +222 -54
  84. package/lib/module/TrueSheet.js.map +1 -1
  85. package/lib/module/TrueSheet.web.js +364 -275
  86. package/lib/module/TrueSheet.web.js.map +1 -1
  87. package/lib/module/TrueSheetBackground.js +15 -0
  88. package/lib/module/TrueSheetBackground.js.map +1 -0
  89. package/lib/module/TrueSheetOverlay.js +30 -0
  90. package/lib/module/TrueSheetOverlay.js.map +1 -0
  91. package/lib/module/TrueSheetOverlay.web.js +35 -0
  92. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  93. package/lib/module/TrueSheetPeek.web.js +20 -15
  94. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  95. package/lib/module/TrueSheetProvider.web.js +13 -9
  96. package/lib/module/TrueSheetProvider.web.js.map +1 -1
  97. package/lib/module/detentBackgrounds.js +21 -0
  98. package/lib/module/detentBackgrounds.js.map +1 -0
  99. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  100. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  101. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  102. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +89 -73
  103. package/lib/module/index.js +1 -0
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/mocks/index.js +18 -2
  106. package/lib/module/mocks/index.js.map +1 -1
  107. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  108. package/lib/module/navigation/base-types.js +2 -0
  109. package/lib/module/navigation/base-types.js.map +1 -0
  110. package/lib/module/navigation/createTrueSheetRouter.js +158 -100
  111. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  112. package/lib/module/navigation/expo-router/base-types.js +2 -0
  113. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  114. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +71 -0
  115. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
  116. package/lib/module/navigation/expo-router/index.js +18 -7
  117. package/lib/module/navigation/expo-router/index.js.map +1 -1
  118. package/lib/module/navigation/expo-router/types.js +4 -0
  119. package/lib/module/navigation/expo-router/types.js.map +1 -0
  120. package/lib/module/navigation/index.js.map +1 -1
  121. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  122. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  123. package/lib/module/scrollable.js +28 -0
  124. package/lib/module/scrollable.js.map +1 -0
  125. package/lib/module/web/constants.js +1 -1
  126. package/lib/module/web/constants.js.map +1 -1
  127. package/lib/module/web/keyboard.js +69 -0
  128. package/lib/module/web/keyboard.js.map +1 -0
  129. package/lib/module/web/layout.js +168 -0
  130. package/lib/module/web/layout.js.map +1 -0
  131. package/lib/module/web/vaul/constants.js +9 -0
  132. package/lib/module/web/vaul/constants.js.map +1 -1
  133. package/lib/module/web/vaul/context.js +4 -0
  134. package/lib/module/web/vaul/context.js.map +1 -1
  135. package/lib/module/web/vaul/helpers.js +1 -0
  136. package/lib/module/web/vaul/helpers.js.map +1 -1
  137. package/lib/module/web/vaul/index.js +83 -21
  138. package/lib/module/web/vaul/index.js.map +1 -1
  139. package/lib/module/web/vaul/use-snap-points.js +57 -28
  140. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  141. package/lib/typescript/src/TrueSheet.d.ts +18 -1
  142. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  143. package/lib/typescript/src/TrueSheet.types.d.ts +209 -35
  144. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  145. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  146. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  147. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  148. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  149. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  150. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  151. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  152. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  153. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  154. package/lib/typescript/src/TrueSheetProvider.web.d.ts +5 -1
  155. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
  156. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  157. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  158. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -3
  159. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  160. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  161. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  162. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +76 -65
  163. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  164. package/lib/typescript/src/index.d.ts +1 -0
  165. package/lib/typescript/src/index.d.ts.map +1 -1
  166. package/lib/typescript/src/mocks/index.d.ts +4 -0
  167. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  168. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  169. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  170. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  171. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  172. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +36 -10
  173. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  174. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  175. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  176. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +33 -0
  177. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
  178. package/lib/typescript/src/navigation/expo-router/index.d.ts +9 -6
  179. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  180. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  181. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  182. package/lib/typescript/src/navigation/index.d.ts +3 -4
  183. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  184. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  185. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  186. package/lib/typescript/src/navigation/types.d.ts +43 -6
  187. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  188. package/lib/typescript/src/scrollable.d.ts +2 -0
  189. package/lib/typescript/src/scrollable.d.ts.map +1 -0
  190. package/lib/typescript/src/web/constants.d.ts +1 -1
  191. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  192. package/lib/typescript/src/web/keyboard.d.ts +15 -0
  193. package/lib/typescript/src/web/keyboard.d.ts.map +1 -0
  194. package/lib/typescript/src/web/layout.d.ts +20 -0
  195. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  196. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  197. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  198. package/lib/typescript/src/web/vaul/context.d.ts +5 -1
  199. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  200. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  201. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  202. package/lib/typescript/src/web/vaul/index.d.ts +20 -1
  203. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  204. package/lib/typescript/src/web/vaul/types.d.ts +9 -0
  205. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -1
  206. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +6 -4
  207. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  208. package/package.json +18 -8
  209. package/react-native.config.js +1 -0
  210. package/src/TrueSheet.tsx +257 -62
  211. package/src/TrueSheet.types.ts +225 -35
  212. package/src/TrueSheet.web.tsx +445 -333
  213. package/src/TrueSheetBackground.tsx +17 -0
  214. package/src/TrueSheetOverlay.tsx +24 -0
  215. package/src/TrueSheetOverlay.web.tsx +33 -0
  216. package/src/TrueSheetPeek.web.tsx +25 -23
  217. package/src/TrueSheetProvider.web.tsx +22 -13
  218. package/src/detentBackgrounds.ts +24 -0
  219. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  220. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  221. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  222. package/src/fabric/TrueSheetViewNativeComponent.ts +89 -73
  223. package/src/index.ts +1 -0
  224. package/src/mocks/index.ts +17 -2
  225. package/src/mocks/navigationExpoRouter.ts +6 -5
  226. package/src/navigation/base-types.ts +20 -0
  227. package/src/navigation/createTrueSheetRouter.ts +220 -144
  228. package/src/navigation/expo-router/base-types.ts +18 -0
  229. package/src/navigation/expo-router/extendTrueSheetRouter.ts +99 -0
  230. package/src/navigation/expo-router/index.ts +41 -30
  231. package/src/navigation/expo-router/types.ts +116 -0
  232. package/src/navigation/index.ts +6 -7
  233. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  234. package/src/navigation/types.ts +81 -23
  235. package/src/scrollable.ts +25 -0
  236. package/src/web/constants.ts +1 -1
  237. package/src/web/keyboard.ts +79 -0
  238. package/src/web/layout.ts +196 -0
  239. package/src/web/vaul/constants.ts +9 -0
  240. package/src/web/vaul/context.ts +7 -1
  241. package/src/web/vaul/helpers.ts +5 -0
  242. package/src/web/vaul/index.tsx +113 -16
  243. package/src/web/vaul/types.ts +10 -0
  244. package/src/web/vaul/use-snap-points.ts +86 -33
@@ -1,29 +1,10 @@
1
- import type {
2
- NavigationAction,
3
- NavigationState,
4
- ParamListBase,
5
- Router,
6
- StackRouterOptions,
7
- } from '@react-navigation/core';
8
-
9
1
  import { TrueSheetActions, type TrueSheetActionType } from './actions';
10
- import type { TrueSheetNavigationState } from './types';
11
-
12
- export type TrueSheetRouterOptions = StackRouterOptions;
13
-
14
- /**
15
- * The base `StackRouter` factory to wrap, typed generically over the state and
16
- * action types the wrapper threads through — `StackRouter` is structural and
17
- * never touches the sheet-specific fields.
18
- * Injected by each entry point so the shared router never imports
19
- * `@react-navigation/*` at runtime (Expo Router apps don't install it).
20
- */
21
- export type StackRouterFactory = <
22
- State extends NavigationState<ParamListBase>,
23
- Action extends NavigationAction,
24
- >(
25
- options: TrueSheetRouterOptions
26
- ) => Router<State, Action>;
2
+ import type {
3
+ TrueSheetRouter,
4
+ TrueSheetRouterFactory,
5
+ TrueSheetRouterOptions,
6
+ TrueSheetRouterState,
7
+ } from './types';
27
8
 
28
9
  const uid = () => Math.random().toString(36).slice(2, 10);
29
10
 
@@ -55,14 +36,211 @@ const ensureBaseRoute = <T extends { routes: { name: string }[] }>(
55
36
  return state as T & { index: number; routes: T['routes'] };
56
37
  };
57
38
 
39
+ type SheetRoute = {
40
+ key: string;
41
+ name: string;
42
+ closing?: boolean;
43
+ dismissing?: boolean;
44
+ resizeIndex?: number;
45
+ resizeKey?: number;
46
+ };
47
+ type SheetState = { key: string; index: number; routes: SheetRoute[] };
48
+
49
+ /**
50
+ * Index of the route a `RESIZE` targets: the dispatching screen, else the focused route.
51
+ */
52
+ export const getResizeRouteIndex = (
53
+ state: SheetState,
54
+ action: { source?: string; target?: string }
55
+ ): number =>
56
+ action.target === state.key && action.source
57
+ ? state.routes.findIndex((r) => r.key === action.source)
58
+ : state.index;
59
+
60
+ /**
61
+ * Reduces the sheet-specific actions shared by every router flavour.
62
+ * Returns `undefined` for actions the base `StackRouter` should handle.
63
+ */
64
+ export const getTrueSheetStateForAction = <State extends SheetState>(
65
+ state: State,
66
+ action: TrueSheetActionType
67
+ ): State | null | undefined => {
68
+ switch (action.type) {
69
+ case 'RESIZE': {
70
+ const routeIndex = getResizeRouteIndex(state, action);
71
+
72
+ return {
73
+ ...state,
74
+ routes: state.routes.map((route, i) =>
75
+ i === routeIndex
76
+ ? {
77
+ ...route,
78
+ resizeIndex: action.index,
79
+ resizeKey: (route.resizeKey ?? 0) + 1,
80
+ }
81
+ : route
82
+ ),
83
+ };
84
+ }
85
+
86
+ case 'GO_BACK':
87
+ case 'DISMISS': {
88
+ return getTrueSheetStateForAction(state, TrueSheetActions.pop(1));
89
+ }
90
+
91
+ case 'POP': {
92
+ // Only base screen remains - let parent navigator handle it
93
+ if (state.routes.length <= 1) {
94
+ return null;
95
+ }
96
+
97
+ const count =
98
+ 'payload' in action && typeof action.payload?.count === 'number' ? action.payload.count : 1;
99
+
100
+ // Calculate how many routes we can actually pop (don't pop base screen)
101
+ const maxPopCount = state.routes.length - 1;
102
+ const actualCount = Math.min(count, maxPopCount);
103
+
104
+ // Base screen - let parent navigator handle it
105
+ if (actualCount <= 0) {
106
+ return null;
107
+ }
108
+
109
+ // Target index is the route we want to stay on (land on after pop)
110
+ // closingIndex is the first route to be dismissed (the one after target)
111
+ const targetIndex = state.routes.length - 1 - actualCount;
112
+ const closingIndex = targetIndex + 1;
113
+
114
+ // Mark only the bottom-most route to pop as closing
115
+ // The sheet's dismiss() will handle dismissing sheets above it first.
116
+ // The index stays on the top sheet until REMOVE: Expo Router 58 treats the routes
117
+ // after `index` as preloaded.
118
+ return {
119
+ ...state,
120
+ routes: state.routes.map((route, i) => {
121
+ if (i === closingIndex) return { ...route, closing: true };
122
+ if (i > closingIndex) return { ...route, dismissing: true };
123
+ return route;
124
+ }),
125
+ };
126
+ }
127
+
128
+ case 'POP_TO_TOP': {
129
+ const popCount = state.routes.length - 1;
130
+ return getTrueSheetStateForAction(state, TrueSheetActions.pop(popCount));
131
+ }
132
+
133
+ case 'POP_TO': {
134
+ const targetName =
135
+ 'payload' in action && typeof action.payload?.name === 'string'
136
+ ? action.payload.name
137
+ : null;
138
+
139
+ if (!targetName) {
140
+ return null;
141
+ }
142
+
143
+ const targetIndex = state.routes.findIndex((r) => r.name === targetName);
144
+
145
+ // Target not found or is the current route
146
+ if (targetIndex === -1 || targetIndex >= state.index) {
147
+ return null;
148
+ }
149
+
150
+ const popCount = state.routes.length - 1 - targetIndex;
151
+ return getTrueSheetStateForAction(state, TrueSheetActions.pop(popCount));
152
+ }
153
+
154
+ case 'REMOVE': {
155
+ // Actually remove the closing route and all routes above it
156
+ const routeKey = action.source;
157
+ const routeIndex = routeKey
158
+ ? state.routes.findIndex((r) => r.key === routeKey)
159
+ : state.routes.findIndex((r) => r.closing);
160
+
161
+ if (routeIndex === -1) {
162
+ return state;
163
+ }
164
+
165
+ // Remove the route and the routes dismissed with it. A popped route keeps the
166
+ // routes pushed after the pop started: they aren't stacked on it natively.
167
+ const removed = state.routes[routeIndex]!;
168
+ const isPopped = removed.closing || removed.dismissing;
169
+ const routes = state.routes.filter(
170
+ (route, i) => i < routeIndex || (i > routeIndex && isPopped && !route.dismissing)
171
+ );
172
+
173
+ return {
174
+ ...state,
175
+ index: Math.min(state.index, routes.length - 1),
176
+ routes,
177
+ };
178
+ }
179
+
180
+ default:
181
+ return undefined;
182
+ }
183
+ };
184
+
185
+ const BASE_ROUTE_ACTIONS = new Set([
186
+ 'NAVIGATE',
187
+ 'NAVIGATE_DEPRECATED',
188
+ 'PUSH',
189
+ 'REPLACE',
190
+ 'POP_TO',
191
+ ]);
192
+
193
+ /**
194
+ * Reduces an action that targets the base route by name. React Navigation 7 adds another base
195
+ * route for a `PUSH`, a `REPLACE` or a `NAVIGATE` without `pop`, which presents the base screen
196
+ * as a sheet, and this router's `POP_TO` drops the params and nested state. This goes back to it
197
+ * instead: `getPoppedState` reduces the action as a `NAVIGATE` with `pop` (a `PUSH` ignores
198
+ * `pop`) to update the base route (params, nested state), then the sheets above it are dismissed.
199
+ * Returns `undefined` for other actions, a `REPLACE` with no sheet (it swaps the base route), or
200
+ * when the base router doesn't land on the base route.
201
+ */
202
+ export const getStateForNavigateToBase = <State extends SheetState>(
203
+ state: State,
204
+ action: TrueSheetActionType,
205
+ getPoppedState: (action: TrueSheetActionType) => State | null | undefined
206
+ ): State | undefined => {
207
+ const { type, payload } = action as { type: string; payload?: { name?: string } };
208
+ const baseRoute = state.routes[0]!;
209
+ const hasSheets = state.routes.length > 1;
210
+
211
+ if (
212
+ !BASE_ROUTE_ACTIONS.has(type) ||
213
+ payload?.name !== baseRoute.name ||
214
+ (type === 'REPLACE' && !hasSheets)
215
+ ) {
216
+ return undefined;
217
+ }
218
+
219
+ const popped = getPoppedState({
220
+ ...action,
221
+ type: 'NAVIGATE',
222
+ payload: { ...payload, pop: true },
223
+ } as unknown as TrueSheetActionType);
224
+ const base = popped?.routes[popped.index];
225
+
226
+ if (base?.key !== baseRoute.key) {
227
+ return undefined;
228
+ }
229
+
230
+ if (!hasSheets) {
231
+ return popped!;
232
+ }
233
+
234
+ return getTrueSheetStateForAction(
235
+ { ...state, routes: [base, ...state.routes.slice(1)] },
236
+ TrueSheetActions.pop(state.routes.length - 1)
237
+ )!;
238
+ };
239
+
58
240
  export const createTrueSheetRouter =
59
- (stackRouter: StackRouterFactory) =>
60
- (
61
- routerOptions: TrueSheetRouterOptions
62
- ): Router<TrueSheetNavigationState<ParamListBase>, TrueSheetActionType> => {
63
- const baseRouter = stackRouter<TrueSheetNavigationState<ParamListBase>, TrueSheetActionType>(
64
- routerOptions
65
- );
241
+ (stackRouter: TrueSheetRouterFactory) =>
242
+ (routerOptions: TrueSheetRouterOptions): TrueSheetRouter => {
243
+ const baseRouter = stackRouter(routerOptions);
66
244
 
67
245
  return {
68
246
  ...baseRouter,
@@ -82,118 +260,16 @@ export const createTrueSheetRouter =
82
260
  },
83
261
 
84
262
  getStateForAction(state, action, options) {
85
- switch (action.type) {
86
- case 'RESIZE': {
87
- const routeIndex =
88
- action.target === state.key && action.source
89
- ? state.routes.findIndex((r) => r.key === action.source)
90
- : state.index;
91
-
92
- return {
93
- ...state,
94
- routes: state.routes.map((route, i) =>
95
- i === routeIndex
96
- ? {
97
- ...route,
98
- resizeIndex: action.index,
99
- resizeKey: (route.resizeKey ?? 0) + 1,
100
- }
101
- : route
102
- ),
103
- };
104
- }
105
-
106
- case 'GO_BACK':
107
- case 'DISMISS': {
108
- return this.getStateForAction(state, TrueSheetActions.pop(1), options);
109
- }
110
-
111
- case 'POP': {
112
- // Only base screen remains - let parent navigator handle it
113
- if (state.routes.length <= 1) {
114
- return null;
115
- }
116
-
117
- const count =
118
- 'payload' in action && typeof action.payload?.count === 'number'
119
- ? action.payload.count
120
- : 1;
121
-
122
- // Calculate how many routes we can actually pop (don't pop base screen)
123
- const maxPopCount = state.routes.length - 1;
124
- const actualCount = Math.min(count, maxPopCount);
125
-
126
- // Base screen - let parent navigator handle it
127
- if (actualCount <= 0) {
128
- return null;
129
- }
130
-
131
- // Target index is the route we want to stay on (land on after pop)
132
- // closingIndex is the first route to be dismissed (the one after target)
133
- const targetIndex = state.routes.length - 1 - actualCount;
134
- const closingIndex = targetIndex + 1;
135
-
136
- // Mark only the bottom-most route to pop as closing
137
- // The sheet's dismiss() will handle dismissing sheets above it first
138
- return {
139
- ...state,
140
- index: closingIndex,
141
- routes: state.routes.map((route, i) =>
142
- i === closingIndex ? { ...route, closing: true } : route
143
- ),
144
- };
145
- }
146
-
147
- case 'POP_TO_TOP': {
148
- const popCount = state.routes.length - 1;
149
- return this.getStateForAction(state, TrueSheetActions.pop(popCount), options);
150
- }
151
-
152
- case 'POP_TO': {
153
- const targetName =
154
- 'payload' in action && typeof action.payload?.name === 'string'
155
- ? action.payload.name
156
- : null;
157
-
158
- if (!targetName) {
159
- return null;
160
- }
161
-
162
- const targetIndex = state.routes.findIndex((r) => r.name === targetName);
163
-
164
- // Target not found or is the current route
165
- if (targetIndex === -1 || targetIndex >= state.index) {
166
- return null;
167
- }
168
-
169
- const popCount = state.routes.length - 1 - targetIndex;
170
- return this.getStateForAction(state, TrueSheetActions.pop(popCount), options);
171
- }
172
-
173
- case 'REMOVE': {
174
- // Actually remove the closing route and all routes above it
175
- const routeKey = action.source;
176
- const routeIndex = routeKey
177
- ? state.routes.findIndex((r) => r.key === routeKey)
178
- : state.routes.findIndex((r) => r.closing);
179
-
180
- if (routeIndex === -1) {
181
- return state;
182
- }
183
-
184
- // Remove the route and all routes above it (they were dismissed together)
185
- const routes = state.routes.filter((_, i) => i < routeIndex);
186
-
187
- return {
188
- ...state,
189
- index: Math.min(state.index, routes.length - 1),
190
- routes,
191
- };
192
- }
193
-
194
- default:
195
- return baseRouter.getStateForAction(state, action, options);
196
- }
263
+ const result =
264
+ getStateForNavigateToBase(
265
+ state,
266
+ action,
267
+ // NAVIGATE never returns a partial state
268
+ (popAction) =>
269
+ baseRouter.getStateForAction(state, popAction, options) as TrueSheetRouterState | null
270
+ ) ?? getTrueSheetStateForAction(state, action);
271
+
272
+ return result === undefined ? baseRouter.getStateForAction(state, action, options) : result;
197
273
  },
198
274
 
199
275
  getRehydratedState(partialState, { routeNames, routeParamList, routeGetIdList }) {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Base navigation types for the Expo Router entry point.
3
+ *
4
+ * Sourced from Expo Router's vendored React Navigation so apps that only install
5
+ * `expo-router` still get real types — see `../base-types` for the React Navigation copy.
6
+ */
7
+ export type {
8
+ DefaultNavigatorOptions,
9
+ NavigationAction,
10
+ NavigationHelpers,
11
+ NavigationProp,
12
+ NavigationState,
13
+ ParamListBase,
14
+ RouteProp,
15
+ Router,
16
+ StackActionHelpers,
17
+ StackRouterOptions,
18
+ } from 'expo-router/react-navigation';
@@ -0,0 +1,99 @@
1
+ import { TrueSheetActions, type TrueSheetActionType } from '../actions';
2
+ import {
3
+ getResizeRouteIndex,
4
+ getStateForNavigateToBase,
5
+ getTrueSheetStateForAction,
6
+ } from '../createTrueSheetRouter';
7
+ import type { StackRouterFactory, TrueSheetRouterFactory, TrueSheetRouterState } from './types';
8
+
9
+ type ActionResult = { state: TrueSheetRouterState; affectedRouteKey?: string };
10
+
11
+ /**
12
+ * `RouterActionHandling.Unhandled` from Expo Router 58+, which deprecates `null`.
13
+ */
14
+ export type Unhandled = 'UNHANDLED';
15
+
16
+ type RouterExtension = (context: {
17
+ baseRouter: {
18
+ getStateForAction(
19
+ state: TrueSheetRouterState,
20
+ action: TrueSheetActionType,
21
+ options: object
22
+ ): ActionResult | Unhandled;
23
+ };
24
+ options: { initialRouteName?: string };
25
+ }) => object;
26
+
27
+ /**
28
+ * `extendRouter` from Expo Router 58+. Declared locally because the
29
+ * Expo Router types this package builds against predate it.
30
+ */
31
+ export type ExtendRouter = (
32
+ base: StackRouterFactory,
33
+ extension: RouterExtension,
34
+ options: { type: string }
35
+ ) => TrueSheetRouterFactory;
36
+
37
+ /**
38
+ * Router for Expo Router 58+, where navigator state is seeded by Expo Router
39
+ * (no `getInitialState`/`getRehydratedState`) and `getStateForAction` returns
40
+ * `{ state, affectedRouteKey }`.
41
+ */
42
+ export const extendTrueSheetRouter = (
43
+ extendRouter: ExtendRouter,
44
+ stackRouter: StackRouterFactory,
45
+ unhandled: Unhandled
46
+ ): TrueSheetRouterFactory =>
47
+ extendRouter(
48
+ stackRouter,
49
+ ({ baseRouter, options }) => {
50
+ if (__DEV__ && options.initialRouteName === undefined) {
51
+ console.warn(
52
+ "TrueSheet: this Sheet layout has no `unstable_settings.anchor`. On Expo Router 58+, a sheet route opened directly (deep link or push from another navigator) renders without its base screen. Export `unstable_settings = { anchor: '<base route>' }` from the layout."
53
+ );
54
+ }
55
+
56
+ return {
57
+ getStateForAction(
58
+ state: TrueSheetRouterState,
59
+ action: TrueSheetActionType,
60
+ config: object
61
+ ): ActionResult | Unhandled {
62
+ const { type, payload } = action as { type: string; payload?: { name?: string } };
63
+
64
+ // Expo Router keeps preloaded routes in `routes`, and the navigator presents every
65
+ // route there as a sheet. A parent navigator's route bubbles up to it, as in a `Stack`.
66
+ if (type === 'PRELOAD') {
67
+ return payload?.name && state.routeNames.includes(payload.name) ? { state } : unhandled;
68
+ }
69
+
70
+ const navigated = getStateForNavigateToBase(state, action, (popAction) => {
71
+ const popped = baseRouter.getStateForAction(state, popAction, config);
72
+ return popped === unhandled ? undefined : popped.state;
73
+ });
74
+
75
+ // Expo Router resolves the destination's nested state in the affected route
76
+ if (navigated) {
77
+ return { state: navigated, affectedRouteKey: navigated.routes[0]?.key };
78
+ }
79
+
80
+ const result = getTrueSheetStateForAction(state, action);
81
+
82
+ if (result === undefined) {
83
+ return baseRouter.getStateForAction(state, action, config);
84
+ }
85
+
86
+ if (result === null) {
87
+ return unhandled;
88
+ }
89
+
90
+ const affectedIndex =
91
+ action.type === 'RESIZE' ? getResizeRouteIndex(state, action) : result.index;
92
+
93
+ return { state: result, affectedRouteKey: result.routes[affectedIndex]?.key };
94
+ },
95
+ actionCreators: TrueSheetActions,
96
+ };
97
+ },
98
+ { type: 'true-sheet' }
99
+ );
@@ -1,48 +1,59 @@
1
- import type { ParamListBase } from '@react-navigation/core';
1
+ import * as ExpoRouter from 'expo-router';
2
2
  import { StackRouter, unstable_integrateWithRouter, useNavigation } from 'expo-router';
3
3
 
4
- import {
5
- createTrueSheetRouter,
6
- type StackRouterFactory,
7
- type TrueSheetRouterOptions,
8
- } from '../createTrueSheetRouter';
4
+ import { createTrueSheetRouter } from '../createTrueSheetRouter';
9
5
  import { trueSheetNavigator } from '../navigator';
6
+ import type { ParamListBase } from './base-types';
7
+ import { extendTrueSheetRouter, type ExtendRouter, type Unhandled } from './extendTrueSheetRouter';
10
8
  import type {
9
+ StackRouterFactory,
11
10
  TrueSheetNavigationOptions,
12
11
  TrueSheetNavigationProp,
13
12
  TrueSheetNavigationState,
14
13
  TrueSheetNavigatorContentExtraProps,
15
14
  TrueSheetNavigatorProps,
15
+ TrueSheetRouterFactory,
16
+ TrueSheetRouterOptions,
16
17
  TrueSheetStandardEventMap,
17
- } from '../types';
18
+ } from './types';
18
19
 
20
+ // Expo Router 58+ replaced the React Navigation router contract and ships `extendRouter`
21
+ // and `RouterActionHandling` for it. Before that, the vendored StackRouter is structurally
22
+ // identical to @react-navigation's. Pinned to this entry's own router types:
23
+ // `createTrueSheetRouter` is shared and typed against React Navigation, which resolves to
24
+ // `any` in apps that only install expo-router.
25
+ const { extendRouter, RouterActionHandling } = ExpoRouter as {
26
+ extendRouter?: ExtendRouter;
27
+ RouterActionHandling?: { Unhandled: Unhandled };
28
+ };
29
+
30
+ const sheetRouter: TrueSheetRouterFactory = extendRouter
31
+ ? extendTrueSheetRouter(
32
+ extendRouter,
33
+ StackRouter as StackRouterFactory,
34
+ RouterActionHandling!.Unhandled
35
+ )
36
+ : createTrueSheetRouter(StackRouter as StackRouterFactory);
37
+
38
+ // `routes`/`dispatch` are injected by `createProps`, so they are declared as
39
+ // CreateProps (last generic) and excluded from the public navigator props
19
40
  const SheetNavigator = unstable_integrateWithRouter<
20
41
  TrueSheetNavigationOptions,
21
42
  TrueSheetNavigationState<ParamListBase>,
22
43
  TrueSheetStandardEventMap,
23
- TrueSheetNavigatorContentExtraProps,
24
- TrueSheetRouterOptions
25
- >(
26
- trueSheetNavigator,
27
- // expo-router's vendored StackRouter is structurally identical to @react-navigation's
28
- createTrueSheetRouter(StackRouter as StackRouterFactory),
29
- {
30
- createProps: ({ state, dispatch }) => ({
31
- routes: state.routes,
32
- dispatch,
33
- }),
34
- }
35
- );
44
+ object,
45
+ TrueSheetRouterOptions,
46
+ TrueSheetNavigatorContentExtraProps
47
+ >(trueSheetNavigator, sheetRouter, {
48
+ createProps: ({ state, dispatch }) => ({
49
+ routes: state.routes,
50
+ dispatch,
51
+ }),
52
+ });
36
53
 
37
- // Public props: `routes`/`dispatch` are injected by `createProps` (optional here
38
- // so the navigator type stays comparable), and expo-router intersects the event
39
- // map with an index signature which breaks precisely typed `screenListeners`
40
- // callbacks - replace it with the exact map
41
- type SheetProps = Omit<
42
- React.ComponentProps<typeof SheetNavigator>,
43
- 'screenListeners' | 'routes' | 'dispatch'
44
- > &
45
- Partial<Pick<React.ComponentProps<typeof SheetNavigator>, 'routes' | 'dispatch'>> &
54
+ // expo-router intersects the event map with an index signature which breaks
55
+ // precisely typed `screenListeners` callbacks - replace it with the exact map
56
+ type SheetProps = Omit<React.ComponentProps<typeof SheetNavigator>, 'screenListeners'> &
46
57
  Pick<TrueSheetNavigatorProps, 'screenListeners'>;
47
58
 
48
59
  /**
@@ -86,4 +97,4 @@ export type {
86
97
  TrueSheetNavigationProp,
87
98
  TrueSheetNavigationState,
88
99
  TrueSheetScreenProps,
89
- } from '../types';
100
+ } from './types';