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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +8 -2
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +23 -6
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +9 -2
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +1 -0
  5. package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
  6. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +7 -0
  7. package/ios/TrueSheetContainerView.h +5 -0
  8. package/ios/TrueSheetContainerView.mm +4 -0
  9. package/ios/TrueSheetFooterView.h +6 -0
  10. package/ios/TrueSheetFooterView.mm +31 -2
  11. package/ios/TrueSheetView.mm +82 -17
  12. package/ios/TrueSheetViewController.h +9 -0
  13. package/ios/TrueSheetViewController.mm +109 -29
  14. package/lib/module/TrueSheet.js +53 -9
  15. package/lib/module/TrueSheet.js.map +1 -1
  16. package/lib/module/TrueSheet.web.js +11 -9
  17. package/lib/module/TrueSheet.web.js.map +1 -1
  18. package/lib/module/TrueSheetPeek.web.js +3 -3
  19. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  20. package/lib/module/mocks/navigation.js +31 -21
  21. package/lib/module/mocks/navigation.js.map +1 -1
  22. package/lib/module/mocks/navigationExpoRouter.js +21 -0
  23. package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
  24. package/lib/module/mocks/reanimated.js +1 -1
  25. package/lib/module/mocks/reanimated.js.map +1 -1
  26. package/lib/module/navigation/{TrueSheetView.js → TrueSheetNavigatorContent.js} +14 -12
  27. package/lib/module/navigation/TrueSheetNavigatorContent.js.map +1 -0
  28. package/lib/module/navigation/actions.js +50 -0
  29. package/lib/module/navigation/actions.js.map +1 -0
  30. package/lib/module/navigation/{TrueSheetRouter.js → createTrueSheetRouter.js} +20 -24
  31. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -0
  32. package/lib/module/navigation/expo-router/index.js +48 -0
  33. package/lib/module/navigation/expo-router/index.js.map +1 -0
  34. package/lib/module/navigation/index.js +43 -2
  35. package/lib/module/navigation/index.js.map +1 -1
  36. package/lib/module/navigation/navigator.js +10 -0
  37. package/lib/module/navigation/navigator.js.map +1 -0
  38. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +2 -2
  39. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
  40. package/lib/module/navigation/screen/TrueSheetScreen.js +2 -2
  41. package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
  42. package/lib/module/navigation/screen/useSheetScreenState.js +4 -4
  43. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  44. package/lib/module/navigation/useTrueSheetNavigation.js +1 -1
  45. package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -1
  46. package/lib/module/web/dom.js +10 -0
  47. package/lib/module/web/dom.js.map +1 -0
  48. package/lib/module/web/vaul/helpers.js +10 -4
  49. package/lib/module/web/vaul/helpers.js.map +1 -1
  50. package/lib/typescript/src/TrueSheet.d.ts +2 -0
  51. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  52. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  53. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  54. package/lib/typescript/src/mocks/navigation.d.ts +5 -1
  55. package/lib/typescript/src/mocks/navigation.d.ts.map +1 -1
  56. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +22 -0
  57. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -0
  58. package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -1
  59. package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts +5 -0
  60. package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts.map +1 -0
  61. package/lib/typescript/src/navigation/actions.d.ts +71 -0
  62. package/lib/typescript/src/navigation/actions.d.ts.map +1 -0
  63. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +14 -0
  64. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -0
  65. package/lib/typescript/src/navigation/expo-router/index.d.ts +37 -0
  66. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -0
  67. package/lib/typescript/src/navigation/index.d.ts +42 -2
  68. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  69. package/lib/typescript/src/navigation/navigator.d.ts +7 -0
  70. package/lib/typescript/src/navigation/navigator.d.ts.map +1 -0
  71. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +1 -1
  72. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -1
  73. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +1 -1
  74. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -1
  75. package/lib/typescript/src/navigation/screen/types.d.ts +3 -4
  76. package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -1
  77. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +3 -5
  78. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  79. package/lib/typescript/src/navigation/types.d.ts +24 -0
  80. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  81. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +1 -1
  82. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -1
  83. package/lib/typescript/src/web/dom.d.ts +7 -0
  84. package/lib/typescript/src/web/dom.d.ts.map +1 -0
  85. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  86. package/package.json +23 -8
  87. package/src/TrueSheet.tsx +55 -9
  88. package/src/TrueSheet.web.tsx +14 -18
  89. package/src/TrueSheetPeek.web.tsx +4 -3
  90. package/src/mocks/navigation.ts +32 -25
  91. package/src/mocks/navigationExpoRouter.ts +40 -0
  92. package/src/mocks/reanimated.ts +8 -9
  93. package/src/navigation/{TrueSheetView.tsx → TrueSheetNavigatorContent.tsx} +24 -17
  94. package/src/navigation/actions.ts +78 -0
  95. package/src/navigation/createTrueSheetRouter.ts +222 -0
  96. package/src/navigation/expo-router/index.ts +89 -0
  97. package/src/navigation/index.ts +69 -2
  98. package/src/navigation/navigator.ts +18 -0
  99. package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +2 -2
  100. package/src/navigation/screen/TrueSheetScreen.tsx +2 -2
  101. package/src/navigation/screen/types.ts +4 -6
  102. package/src/navigation/screen/useSheetScreenState.ts +8 -15
  103. package/src/navigation/types.ts +32 -0
  104. package/src/navigation/useTrueSheetNavigation.ts +1 -1
  105. package/src/web/dom.ts +9 -0
  106. package/src/web/vaul/helpers.ts +13 -6
  107. package/lib/module/navigation/TrueSheetRouter.js.map +0 -1
  108. package/lib/module/navigation/TrueSheetView.js.map +0 -1
  109. package/lib/module/navigation/createTrueSheetNavigator.js +0 -63
  110. package/lib/module/navigation/createTrueSheetNavigator.js.map +0 -1
  111. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +0 -57
  112. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +0 -1
  113. package/lib/typescript/src/navigation/TrueSheetView.d.ts +0 -10
  114. package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +0 -1
  115. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +0 -35
  116. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +0 -1
  117. package/src/navigation/TrueSheetRouter.ts +0 -234
  118. package/src/navigation/createTrueSheetNavigator.tsx +0 -93
@@ -0,0 +1,40 @@
1
+ import type { ParamListBase } from '@react-navigation/native';
2
+
3
+ import type { TrueSheetNavigationProp } from '../navigation/types';
4
+ import {
5
+ TrueSheetActions,
6
+ useTrueSheetNavigation as useTrueSheetNavigationMock,
7
+ } from './navigation';
8
+
9
+ /**
10
+ * Mock Sheet layout for testing.
11
+ * Import from '@lodev09/react-native-true-sheet/navigation/expo-router/mock' in your test setup.
12
+ */
13
+ export const Sheet = Object.assign(
14
+ jest.fn(({ children }: { children: React.ReactNode }) => children),
15
+ {
16
+ Screen: jest.fn(() => null),
17
+ Protected: jest.fn(() => null),
18
+ }
19
+ );
20
+
21
+ /**
22
+ * Mock useTrueSheetNavigation hook for testing.
23
+ */
24
+ export const useTrueSheetNavigation = jest.fn(
25
+ <T extends ParamListBase = ParamListBase>(): TrueSheetNavigationProp<T> =>
26
+ useTrueSheetNavigationMock() as TrueSheetNavigationProp<T>
27
+ );
28
+
29
+ export { TrueSheetActions };
30
+
31
+ export type { TrueSheetActionType } from '../navigation/actions';
32
+ export type { DetentInfoEventPayload, PositionChangeEventPayload } from '../TrueSheet.types';
33
+ export type {
34
+ TrueSheetNavigationEventMap,
35
+ TrueSheetNavigationHelpers,
36
+ TrueSheetNavigationOptions,
37
+ TrueSheetNavigationProp,
38
+ TrueSheetNavigationState,
39
+ TrueSheetScreenProps,
40
+ } from '../navigation/types';
@@ -14,15 +14,14 @@ interface MockReanimatedTrueSheetContextValue {
14
14
  animatedDetent: SharedValue<number>;
15
15
  }
16
16
 
17
- const createMockSharedValue = <T>(initialValue: T): SharedValue<T> =>
18
- ({
19
- value: initialValue,
20
- get: () => initialValue,
21
- set: () => {},
22
- addListener: () => () => {},
23
- removeListener: () => {},
24
- modify: () => {},
25
- }) as unknown as SharedValue<T>;
17
+ const createMockSharedValue = <T>(initialValue: T): SharedValue<T> => ({
18
+ value: initialValue,
19
+ get: () => initialValue,
20
+ set: () => {},
21
+ addListener: () => {},
22
+ removeListener: () => {},
23
+ modify: () => {},
24
+ });
26
25
 
27
26
  /**
28
27
  * Mock ReanimatedTrueSheet component for testing.
@@ -1,9 +1,9 @@
1
- import type { ParamListBase } from '@react-navigation/core';
1
+ import type { NavigatorArgs } from 'standard-navigation';
2
2
 
3
3
  import type {
4
- TrueSheetDescriptorMap,
5
- TrueSheetNavigationHelpers,
6
- TrueSheetNavigationState,
4
+ TrueSheetNavigationOptions,
5
+ TrueSheetNavigatorContentExtraProps,
6
+ TrueSheetStandardEventMap,
7
7
  } from './types';
8
8
  import { TrueSheetScreen, type TrueSheetScreenProps } from './screen';
9
9
 
@@ -22,15 +22,22 @@ const DEFAULT_DETENTS: ('auto' | number)[] = ['auto'];
22
22
  const clampDetentIndex = (index: number, detentsLength: number): number =>
23
23
  Math.min(index, Math.max(detentsLength - 1, 0));
24
24
 
25
- interface TrueSheetViewProps {
26
- state: TrueSheetNavigationState<ParamListBase>;
27
- navigation: TrueSheetNavigationHelpers;
28
- descriptors: TrueSheetDescriptorMap;
29
- }
25
+ export type TrueSheetNavigatorContentProps = NavigatorArgs<
26
+ TrueSheetNavigationOptions,
27
+ TrueSheetStandardEventMap
28
+ > &
29
+ TrueSheetNavigatorContentExtraProps;
30
30
 
31
- export const TrueSheetView = ({ state, navigation, descriptors }: TrueSheetViewProps) => {
32
- // First route is the base screen, rest are sheets
33
- const [baseRoute, ...sheetRoutes] = state.routes;
31
+ export const TrueSheetNavigatorContent = ({
32
+ routes,
33
+ dispatch,
34
+ descriptors,
35
+ emitter,
36
+ }: TrueSheetNavigatorContentProps) => {
37
+ // First route is the base screen, rest are sheets.
38
+ // Rendered from the raw routes (not the standard state) so custom fields are
39
+ // available and preloaded routes never present as sheets.
40
+ const [baseRoute, ...sheetRoutes] = routes;
34
41
 
35
42
  const baseDescriptor = baseRoute ? descriptors[baseRoute.key] : null;
36
43
 
@@ -47,7 +54,7 @@ export const TrueSheetView = ({ state, navigation, descriptors }: TrueSheetViewP
47
54
  return null;
48
55
  }
49
56
 
50
- const { options, navigation: screenNavigation, render } = descriptor;
57
+ const { options, render } = descriptor;
51
58
  const {
52
59
  detentIndex = 0,
53
60
  detents = DEFAULT_DETENTS,
@@ -67,8 +74,8 @@ export const TrueSheetView = ({ state, navigation, descriptors }: TrueSheetViewP
67
74
  detentIndex={resolvedIndex}
68
75
  resizeKey={route.resizeKey}
69
76
  detents={detents}
70
- navigation={screenNavigation}
71
- emit={navigation.emit}
77
+ dispatch={dispatch}
78
+ emit={emitter.emit}
72
79
  positionChangeHandler={positionChangeHandler}
73
80
  {...sheetProps}
74
81
  >
@@ -85,8 +92,8 @@ export const TrueSheetView = ({ state, navigation, descriptors }: TrueSheetViewP
85
92
  detentIndex={resolvedIndex}
86
93
  resizeKey={route.resizeKey}
87
94
  detents={detents}
88
- navigation={screenNavigation}
89
- emit={navigation.emit}
95
+ dispatch={dispatch}
96
+ emit={emitter.emit}
90
97
  positionChangeHandler={positionChangeHandler}
91
98
  {...sheetProps}
92
99
  >
@@ -0,0 +1,78 @@
1
+ export type TrueSheetActionType =
2
+ | {
3
+ type: 'PUSH';
4
+ payload: { name: string; params?: object };
5
+ source?: string;
6
+ target?: string;
7
+ }
8
+ | {
9
+ type: 'POP';
10
+ payload: { count: number };
11
+ source?: string;
12
+ target?: string;
13
+ }
14
+ | {
15
+ type: 'POP_TO';
16
+ payload: { name: string; params?: object; merge?: boolean };
17
+ source?: string;
18
+ target?: string;
19
+ }
20
+ | {
21
+ type: 'POP_TO_TOP';
22
+ source?: string;
23
+ target?: string;
24
+ }
25
+ | {
26
+ type: 'REPLACE';
27
+ payload: { name: string; params?: object };
28
+ source?: string;
29
+ target?: string;
30
+ }
31
+ | {
32
+ type: 'GO_BACK';
33
+ source?: string;
34
+ target?: string;
35
+ }
36
+ | {
37
+ type: 'RESIZE';
38
+ index: number;
39
+ source?: string;
40
+ target?: string;
41
+ }
42
+ | {
43
+ type: 'DISMISS';
44
+ source?: string;
45
+ target?: string;
46
+ }
47
+ | {
48
+ type: 'REMOVE';
49
+ source?: string;
50
+ target?: string;
51
+ };
52
+
53
+ /**
54
+ * Action creators for the TrueSheet navigator.
55
+ * Plain objects matching `StackActions` shapes, plus sheet-specific actions.
56
+ */
57
+ export const TrueSheetActions = {
58
+ push: (name: string, params?: object): TrueSheetActionType => ({
59
+ type: 'PUSH',
60
+ payload: { name, params },
61
+ }),
62
+ pop: (count: number = 1): TrueSheetActionType => ({
63
+ type: 'POP',
64
+ payload: { count },
65
+ }),
66
+ popTo: (name: string, params?: object, options?: { merge?: boolean }): TrueSheetActionType => ({
67
+ type: 'POP_TO',
68
+ payload: { name, params, merge: options?.merge },
69
+ }),
70
+ popToTop: (): TrueSheetActionType => ({ type: 'POP_TO_TOP' }),
71
+ replace: (name: string, params?: object): TrueSheetActionType => ({
72
+ type: 'REPLACE',
73
+ payload: { name, params },
74
+ }),
75
+ resize: (index: number): TrueSheetActionType => ({ type: 'RESIZE', index }),
76
+ dismiss: (): TrueSheetActionType => ({ type: 'DISMISS' }),
77
+ remove: (): TrueSheetActionType => ({ type: 'REMOVE' }),
78
+ };
@@ -0,0 +1,222 @@
1
+ import type {
2
+ NavigationAction,
3
+ NavigationState,
4
+ ParamListBase,
5
+ Router,
6
+ StackRouterOptions,
7
+ } from '@react-navigation/core';
8
+
9
+ 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>;
27
+
28
+ const uid = () => Math.random().toString(36).slice(2, 10);
29
+
30
+ const ensureBaseRoute = <T extends { routes: { name: string }[] }>(
31
+ state: T,
32
+ baseRouteName: string | undefined,
33
+ routeParamList: Record<string, object | undefined> | undefined
34
+ ): T & { index: number; routes: T['routes'] } => {
35
+ if (!baseRouteName) {
36
+ return state as T & { index: number; routes: T['routes'] };
37
+ }
38
+
39
+ const hasBaseRoute = state.routes.some((r) => r.name === baseRouteName);
40
+
41
+ if (!hasBaseRoute) {
42
+ const baseRoute = {
43
+ key: `${baseRouteName}-${uid()}`,
44
+ name: baseRouteName,
45
+ params: routeParamList?.[baseRouteName],
46
+ };
47
+
48
+ return {
49
+ ...state,
50
+ index: state.routes.length,
51
+ routes: [baseRoute, ...state.routes],
52
+ } as T & { index: number; routes: T['routes'] };
53
+ }
54
+
55
+ return state as T & { index: number; routes: T['routes'] };
56
+ };
57
+
58
+ 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
+ );
66
+
67
+ return {
68
+ ...baseRouter,
69
+ type: 'true-sheet',
70
+
71
+ getInitialState(options) {
72
+ const state = baseRouter.getInitialState(options);
73
+ const baseRouteName = routerOptions.initialRouteName ?? options.routeNames[0];
74
+ const stateWithBaseRoute = ensureBaseRoute(state, baseRouteName, options.routeParamList);
75
+
76
+ return {
77
+ ...stateWithBaseRoute,
78
+ stale: false,
79
+ type: 'true-sheet',
80
+ key: `true-sheet-${uid()}`,
81
+ };
82
+ },
83
+
84
+ 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
+ }
197
+ },
198
+
199
+ getRehydratedState(partialState, { routeNames, routeParamList, routeGetIdList }) {
200
+ if (partialState.stale === false) {
201
+ return partialState;
202
+ }
203
+
204
+ const state = baseRouter.getRehydratedState(partialState, {
205
+ routeNames,
206
+ routeParamList,
207
+ routeGetIdList,
208
+ });
209
+
210
+ const baseRouteName = routerOptions.initialRouteName ?? routeNames[0];
211
+ const stateWithBaseRoute = ensureBaseRoute(state, baseRouteName, routeParamList);
212
+
213
+ return {
214
+ ...stateWithBaseRoute,
215
+ type: 'true-sheet',
216
+ key: `true-sheet-${uid()}`,
217
+ };
218
+ },
219
+
220
+ actionCreators: TrueSheetActions,
221
+ };
222
+ };
@@ -0,0 +1,89 @@
1
+ import type { ParamListBase } from '@react-navigation/core';
2
+ import { StackRouter, unstable_integrateWithRouter, useNavigation } from 'expo-router';
3
+
4
+ import {
5
+ createTrueSheetRouter,
6
+ type StackRouterFactory,
7
+ type TrueSheetRouterOptions,
8
+ } from '../createTrueSheetRouter';
9
+ import { trueSheetNavigator } from '../navigator';
10
+ import type {
11
+ TrueSheetNavigationOptions,
12
+ TrueSheetNavigationProp,
13
+ TrueSheetNavigationState,
14
+ TrueSheetNavigatorContentExtraProps,
15
+ TrueSheetNavigatorProps,
16
+ TrueSheetStandardEventMap,
17
+ } from '../types';
18
+
19
+ const SheetNavigator = unstable_integrateWithRouter<
20
+ TrueSheetNavigationOptions,
21
+ TrueSheetNavigationState<ParamListBase>,
22
+ 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
+ );
36
+
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'>> &
46
+ Pick<TrueSheetNavigatorProps, 'screenListeners'>;
47
+
48
+ /**
49
+ * TrueSheet navigator layout for Expo Router.
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * // app/sheet/_layout.tsx
54
+ * import { Sheet } from '@lodev09/react-native-true-sheet/navigation/expo-router';
55
+ *
56
+ * export default function SheetLayout() {
57
+ * return (
58
+ * <Sheet initialRouteName="(home)">
59
+ * <Sheet.Screen name="details" options={{ detents: [0.5, 1] }} />
60
+ * </Sheet>
61
+ * );
62
+ * }
63
+ * ```
64
+ */
65
+ export const Sheet = SheetNavigator as React.ComponentType<SheetProps> & {
66
+ Screen: typeof SheetNavigator.Screen;
67
+ Protected: typeof SheetNavigator.Protected;
68
+ };
69
+
70
+ /**
71
+ * Hook to access TrueSheet navigation with the resize helper.
72
+ * Built on expo-router's `useNavigation`.
73
+ */
74
+ export const useTrueSheetNavigation = <
75
+ T extends ParamListBase = ParamListBase,
76
+ >(): TrueSheetNavigationProp<T> => useNavigation<TrueSheetNavigationProp<T>>();
77
+
78
+ export { TrueSheetActions, type TrueSheetActionType } from '../actions';
79
+
80
+ export type { DetentInfoEventPayload, PositionChangeEventPayload } from '../../TrueSheet.types';
81
+
82
+ export type {
83
+ TrueSheetNavigationEventMap,
84
+ TrueSheetNavigationHelpers,
85
+ TrueSheetNavigationOptions,
86
+ TrueSheetNavigationProp,
87
+ TrueSheetNavigationState,
88
+ TrueSheetScreenProps,
89
+ } from '../types';
@@ -1,5 +1,72 @@
1
- export { createTrueSheetNavigator } from './createTrueSheetNavigator';
2
- export { TrueSheetActions, type TrueSheetActionType } from './TrueSheetRouter';
1
+ import {
2
+ createStandardNavigationFactories,
3
+ StackRouter,
4
+ type StandardNavigationTypeBagBase,
5
+ } from '@react-navigation/native';
6
+
7
+ import { createTrueSheetRouter, type StackRouterFactory } from './createTrueSheetRouter';
8
+ import { trueSheetNavigator } from './navigator';
9
+ import type {
10
+ TrueSheetActionHelpers,
11
+ TrueSheetNavigationOptions,
12
+ TrueSheetNavigationEventMap,
13
+ TrueSheetNavigationState,
14
+ TrueSheetNavigatorContentExtraProps,
15
+ } from './types';
16
+ import type { TrueSheetRouterOptions } from './createTrueSheetRouter';
17
+
18
+ export interface TrueSheetNavigationTypeBag extends StandardNavigationTypeBagBase {
19
+ State: TrueSheetNavigationState<this['ParamList']>;
20
+ ActionHelpers: TrueSheetActionHelpers<this['ParamList']>;
21
+ ScreenOptions: TrueSheetNavigationOptions;
22
+ EventMap: TrueSheetNavigationEventMap;
23
+ RouterOptions: TrueSheetRouterOptions;
24
+ }
25
+
26
+ const { createNavigator, createScreen } = createStandardNavigationFactories<
27
+ TrueSheetNavigationTypeBag,
28
+ {},
29
+ TrueSheetNavigatorContentExtraProps
30
+ >(
31
+ trueSheetNavigator,
32
+ createTrueSheetRouter(StackRouter as StackRouterFactory),
33
+ ({ state, navigation }) => ({ routes: state.routes, dispatch: navigation.dispatch })
34
+ );
35
+
36
+ /**
37
+ * Creates a TrueSheet navigator.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * const Sheet = createTrueSheetNavigator();
42
+ *
43
+ * function App() {
44
+ * return (
45
+ * <Sheet.Navigator>
46
+ * <Sheet.Screen name="Home" component={HomeScreen} />
47
+ * <Sheet.Screen
48
+ * name="Details"
49
+ * component={DetailsSheet}
50
+ * options={{ detents: [0.5, 1] }}
51
+ * />
52
+ * </Sheet.Navigator>
53
+ * );
54
+ * }
55
+ * ```
56
+ */
57
+ export const createTrueSheetNavigator = createNavigator;
58
+
59
+ /**
60
+ * Creates a screen configuration for the static API.
61
+ */
62
+ export const createTrueSheetScreen = createScreen;
63
+
64
+ export { TrueSheetActions, type TrueSheetActionType } from './actions';
65
+ export {
66
+ createTrueSheetRouter,
67
+ type StackRouterFactory,
68
+ type TrueSheetRouterOptions,
69
+ } from './createTrueSheetRouter';
3
70
  export { useTrueSheetNavigation } from './useTrueSheetNavigation';
4
71
 
5
72
  export type { DetentInfoEventPayload, PositionChangeEventPayload } from '../TrueSheet.types';
@@ -0,0 +1,18 @@
1
+ import { createStandardNavigator } from 'standard-navigation';
2
+
3
+ import { TrueSheetNavigatorContent } from './TrueSheetNavigatorContent';
4
+ import type {
5
+ TrueSheetNavigationOptions,
6
+ TrueSheetNavigatorContentExtraProps,
7
+ TrueSheetStandardEventMap,
8
+ } from './types';
9
+
10
+ /**
11
+ * Framework-agnostic TrueSheet navigator.
12
+ * Wired into React Navigation via `./index` and Expo Router via `./expo-router`.
13
+ */
14
+ export const trueSheetNavigator = createStandardNavigator<
15
+ TrueSheetNavigationOptions,
16
+ TrueSheetStandardEventMap,
17
+ TrueSheetNavigatorContentExtraProps
18
+ >(TrueSheetNavigatorContent);
@@ -12,7 +12,7 @@ const AnimatedTrueSheet = Animated.createAnimatedComponent(TrueSheet);
12
12
  export const ReanimatedTrueSheetScreen = ({
13
13
  detentIndex,
14
14
  resizeKey,
15
- navigation,
15
+ dispatch,
16
16
  emit,
17
17
  routeKey,
18
18
  closing,
@@ -29,7 +29,7 @@ export const ReanimatedTrueSheetScreen = ({
29
29
  detentIndex,
30
30
  resizeKey,
31
31
  closing,
32
- navigation,
32
+ dispatch,
33
33
  routeKey,
34
34
  emit,
35
35
  });
@@ -8,7 +8,7 @@ import { useSheetScreenState } from './useSheetScreenState';
8
8
  export const TrueSheetScreen = ({
9
9
  detentIndex,
10
10
  resizeKey,
11
- navigation,
11
+ dispatch,
12
12
  emit,
13
13
  routeKey,
14
14
  closing,
@@ -25,7 +25,7 @@ export const TrueSheetScreen = ({
25
25
  detentIndex,
26
26
  resizeKey,
27
27
  closing,
28
- navigation,
28
+ dispatch,
29
29
  routeKey,
30
30
  emit,
31
31
  });
@@ -1,18 +1,16 @@
1
- import type { ParamListBase } from '@react-navigation/core';
2
-
3
1
  import type { TrueSheetProps } from '../../TrueSheet.types';
4
2
  import type {
5
3
  PositionChangeHandler,
6
- TrueSheetNavigationHelpers,
7
- TrueSheetNavigationProp,
4
+ TrueSheetDispatch,
5
+ TrueSheetEmitFn,
8
6
  TrueSheetNavigationSheetProps,
9
7
  } from '../types';
10
8
 
11
9
  export interface TrueSheetScreenProps extends TrueSheetNavigationSheetProps {
12
10
  detentIndex: number;
13
11
  resizeKey?: number;
14
- navigation: TrueSheetNavigationProp<ParamListBase>;
15
- emit: TrueSheetNavigationHelpers['emit'];
12
+ dispatch: TrueSheetDispatch;
13
+ emit: TrueSheetEmitFn;
16
14
  routeKey: string;
17
15
  closing?: boolean;
18
16
  detents: TrueSheetProps['detents'];