@lodev09/react-native-true-sheet 4.0.0-beta.2 → 4.0.0-beta.20

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 (203) hide show
  1. package/README.md +25 -4
  2. package/android/build.gradle +22 -1
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +302 -184
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +21 -110
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +116 -0
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +119 -50
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +360 -163
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +54 -12
  14. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
  15. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
  16. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  19. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  20. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  21. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  22. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  23. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  24. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  25. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
  26. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  27. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  28. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  29. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  30. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
  31. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  45. package/ios/TrueSheetContainerView.h +27 -7
  46. package/ios/TrueSheetContainerView.mm +31 -11
  47. package/ios/TrueSheetContentView.h +43 -15
  48. package/ios/TrueSheetContentView.mm +211 -189
  49. package/ios/TrueSheetFooterView.h +6 -0
  50. package/ios/TrueSheetFooterView.mm +93 -17
  51. package/ios/TrueSheetModule.h +2 -7
  52. package/ios/TrueSheetModule.mm +7 -16
  53. package/ios/TrueSheetOverlayView.h +32 -0
  54. package/ios/TrueSheetOverlayView.mm +161 -0
  55. package/ios/TrueSheetView.mm +212 -55
  56. package/ios/TrueSheetViewController.h +31 -3
  57. package/ios/TrueSheetViewController.mm +393 -94
  58. package/ios/core/TrueSheetBlurView.h +0 -2
  59. package/ios/core/TrueSheetBlurView.mm +3 -36
  60. package/ios/core/TrueSheetDetentCalculator.h +7 -1
  61. package/ios/core/TrueSheetDetentCalculator.mm +27 -4
  62. package/ios/core/TrueSheetGrabberView.mm +13 -3
  63. package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
  64. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  65. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  66. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  67. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  68. package/ios/tvos/TrueSheetStubs.mm +12 -0
  69. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  70. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  71. package/lib/module/TrueSheet.js +173 -39
  72. package/lib/module/TrueSheet.js.map +1 -1
  73. package/lib/module/TrueSheet.web.js +218 -243
  74. package/lib/module/TrueSheet.web.js.map +1 -1
  75. package/lib/module/TrueSheetBackground.js +15 -0
  76. package/lib/module/TrueSheetBackground.js.map +1 -0
  77. package/lib/module/TrueSheetOverlay.js +30 -0
  78. package/lib/module/TrueSheetOverlay.js.map +1 -0
  79. package/lib/module/TrueSheetOverlay.web.js +35 -0
  80. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  81. package/lib/module/TrueSheetPeek.web.js +20 -15
  82. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  83. package/lib/module/detentBackgrounds.js +21 -0
  84. package/lib/module/detentBackgrounds.js.map +1 -0
  85. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  86. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  87. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +14 -10
  88. package/lib/module/index.js +1 -0
  89. package/lib/module/index.js.map +1 -1
  90. package/lib/module/mocks/index.js +18 -2
  91. package/lib/module/mocks/index.js.map +1 -1
  92. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  93. package/lib/module/navigation/base-types.js +2 -0
  94. package/lib/module/navigation/base-types.js.map +1 -0
  95. package/lib/module/navigation/createTrueSheetRouter.js +0 -9
  96. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  97. package/lib/module/navigation/expo-router/base-types.js +2 -0
  98. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  99. package/lib/module/navigation/expo-router/index.js +10 -7
  100. package/lib/module/navigation/expo-router/index.js.map +1 -1
  101. package/lib/module/navigation/expo-router/types.js +4 -0
  102. package/lib/module/navigation/expo-router/types.js.map +1 -0
  103. package/lib/module/navigation/index.js.map +1 -1
  104. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  105. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  106. package/lib/module/web/constants.js +1 -1
  107. package/lib/module/web/constants.js.map +1 -1
  108. package/lib/module/web/layout.js +168 -0
  109. package/lib/module/web/layout.js.map +1 -0
  110. package/lib/module/web/vaul/constants.js +4 -0
  111. package/lib/module/web/vaul/constants.js.map +1 -1
  112. package/lib/module/web/vaul/context.js.map +1 -1
  113. package/lib/module/web/vaul/helpers.js +1 -0
  114. package/lib/module/web/vaul/helpers.js.map +1 -1
  115. package/lib/module/web/vaul/index.js +44 -6
  116. package/lib/module/web/vaul/index.js.map +1 -1
  117. package/lib/module/web/vaul/use-snap-points.js +6 -8
  118. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  119. package/lib/typescript/src/TrueSheet.d.ts +6 -0
  120. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  121. package/lib/typescript/src/TrueSheet.types.d.ts +135 -34
  122. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  123. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  124. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  125. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  126. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  127. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  128. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  129. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  130. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  131. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  132. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  133. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  134. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  135. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  136. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +11 -7
  137. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  138. package/lib/typescript/src/index.d.ts +1 -0
  139. package/lib/typescript/src/index.d.ts.map +1 -1
  140. package/lib/typescript/src/mocks/index.d.ts +4 -0
  141. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  142. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  143. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  144. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  145. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  146. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
  147. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  148. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  149. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  150. package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
  151. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  152. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  153. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  154. package/lib/typescript/src/navigation/index.d.ts +3 -4
  155. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  156. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  157. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  158. package/lib/typescript/src/navigation/types.d.ts +41 -5
  159. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  160. package/lib/typescript/src/web/constants.d.ts +1 -1
  161. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  162. package/lib/typescript/src/web/layout.d.ts +19 -0
  163. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  164. package/lib/typescript/src/web/vaul/constants.d.ts +1 -0
  165. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  166. package/lib/typescript/src/web/vaul/context.d.ts +1 -0
  167. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  168. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  169. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  170. package/lib/typescript/src/web/vaul/index.d.ts +7 -1
  171. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  172. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  173. package/package.json +17 -8
  174. package/react-native.config.js +1 -0
  175. package/src/TrueSheet.tsx +206 -48
  176. package/src/TrueSheet.types.ts +142 -35
  177. package/src/TrueSheet.web.tsx +270 -290
  178. package/src/TrueSheetBackground.tsx +17 -0
  179. package/src/TrueSheetOverlay.tsx +24 -0
  180. package/src/TrueSheetOverlay.web.tsx +33 -0
  181. package/src/TrueSheetPeek.web.tsx +25 -23
  182. package/src/detentBackgrounds.ts +24 -0
  183. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  184. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  185. package/src/fabric/TrueSheetViewNativeComponent.ts +14 -10
  186. package/src/index.ts +1 -0
  187. package/src/mocks/index.ts +17 -2
  188. package/src/mocks/navigationExpoRouter.ts +6 -5
  189. package/src/navigation/base-types.ts +20 -0
  190. package/src/navigation/createTrueSheetRouter.ts +5 -33
  191. package/src/navigation/expo-router/base-types.ts +18 -0
  192. package/src/navigation/expo-router/index.ts +28 -30
  193. package/src/navigation/expo-router/types.ts +116 -0
  194. package/src/navigation/index.ts +6 -7
  195. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  196. package/src/navigation/types.ts +75 -22
  197. package/src/web/constants.ts +1 -1
  198. package/src/web/layout.ts +196 -0
  199. package/src/web/vaul/constants.ts +4 -0
  200. package/src/web/vaul/context.ts +1 -0
  201. package/src/web/vaul/helpers.ts +5 -0
  202. package/src/web/vaul/index.tsx +67 -5
  203. package/src/web/vaul/use-snap-points.ts +6 -14
package/src/TrueSheet.tsx CHANGED
@@ -4,8 +4,10 @@ import {
4
4
  createRef,
5
5
  type ReactNode,
6
6
  type ComponentRef,
7
+ type ComponentType,
7
8
  isValidElement,
8
9
  createElement,
10
+ useEffect,
9
11
  } from 'react';
10
12
 
11
13
  import type {
@@ -21,6 +23,7 @@ import type {
21
23
  PositionChangeEvent,
22
24
  DidDismissEvent,
23
25
  WillDismissEvent,
26
+ DismissAttemptEvent,
24
27
  MountEvent,
25
28
  WillFocusEvent,
26
29
  DidFocusEvent,
@@ -34,17 +37,38 @@ import TrueSheetHeaderViewNativeComponent from './fabric/TrueSheetHeaderViewNati
34
37
  import TrueSheetFooterViewNativeComponent from './fabric/TrueSheetFooterViewNativeComponent';
35
38
 
36
39
  import TrueSheetModule from './specs/NativeTrueSheetModule';
40
+ import { TrueSheetBackground } from './TrueSheetBackground';
41
+ import { normalizeDetentBackground } from './detentBackgrounds';
37
42
 
38
43
  import {
39
44
  Platform,
40
45
  StyleSheet,
41
46
  BackHandler,
47
+ RootTagContext,
42
48
  findNodeHandle,
43
49
  processColor,
44
50
  type NativeEventSubscription,
45
51
  type GestureResponderEvent,
52
+ type RootTag,
53
+ type StyleProp,
54
+ type ViewStyle,
46
55
  } from 'react-native';
47
56
 
57
+ interface AppContainerProps {
58
+ rootTag: RootTag;
59
+ rootViewStyle?: StyleProp<ViewStyle>;
60
+ internal_excludeLogBox?: boolean;
61
+ children?: ReactNode;
62
+ }
63
+
64
+ // RN has no public API for hosting the element inspector inside a presented view;
65
+ // this is the internal RN's Modal uses. Dev-only so prod bundles never resolve it.
66
+ // The template literal keeps the RN babel preset's deep-import warning off while
67
+ // Metro still resolves the module statically.
68
+ const AppContainer: ComponentType<AppContainerProps> | null = __DEV__
69
+ ? require(`react-native/Libraries/ReactNative/AppContainer`).default
70
+ : null;
71
+
48
72
  const LINKING_ERROR =
49
73
  `The package '@lodev09/react-native-true-sheet' doesn't seem to be linked. Make sure: \n\n` +
50
74
  Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
@@ -58,23 +82,49 @@ if (!TrueSheetModule) {
58
82
 
59
83
  type NativeRef = ComponentRef<typeof TrueSheetViewNativeComponent>;
60
84
 
61
- // Claim touches that no descendant claimed so the responder negotiation
62
- // doesn't bubble up to touchables outside the sheet
63
- const absorbUnclaimedTouches = () => true;
64
-
65
85
  // Stop raw touch events from bubbling past the sheet to outside ancestors
66
86
  const stopTouchPropagation = (event: GestureResponderEvent) => event.stopPropagation();
67
87
 
88
+ // Stop the responder negotiation at the sheet boundary without claiming the
89
+ // responder. Views inside the sheet are asked before this one, so in-sheet
90
+ // touchables work normally; views outside are asked after, so stopping here
91
+ // keeps unclaimed touches from leaking to touchables behind the sheet.
92
+ // Claiming instead (returning true) would steal the responder from a pressed
93
+ // touchable whenever the negotiation re-runs mid-gesture — e.g. a second
94
+ // finger, or the duplicate touch stream from a nested react-native-screens
95
+ // screen that attaches its own touch handler inside the sheet.
96
+ const stopResponderNegotiation = (event: GestureResponderEvent) => {
97
+ event.stopPropagation();
98
+ return false;
99
+ };
100
+
68
101
  interface TrueSheetState {
69
102
  shouldRenderNativeView: boolean;
103
+ initialPresentReady: boolean;
104
+ scrollableHandle: number | null;
70
105
  }
71
106
 
107
+ // Gates auto-present (initialDetentIndex) by one commit. Rendered last in the
108
+ // sheet subtree so its mount effect runs after the content's mount effects —
109
+ // updates they schedule (e.g. a navigation footer via setOptions) batch with
110
+ // onReady's, so native receives initialDetentIndex together with the settled
111
+ // tree and presents at the final height instead of resizing mid-flight.
112
+ const InitialPresentGate = ({ onReady }: { onReady: () => void }) => {
113
+ useEffect(() => {
114
+ onReady();
115
+ }, [onReady]);
116
+
117
+ return null;
118
+ };
119
+
72
120
  export class TrueSheet
73
121
  extends PureComponent<TrueSheetProps, TrueSheetState>
74
122
  implements TrueSheetMethods
75
123
  {
76
124
  displayName = 'TrueSheet';
77
125
 
126
+ static contextType = RootTagContext;
127
+
78
128
  private readonly nativeRef: RefObject<NativeRef | null>;
79
129
 
80
130
  private cachedGrabberOptions: TrueSheetProps['grabberOptions'] | undefined;
@@ -105,17 +155,21 @@ export class TrueSheet
105
155
 
106
156
  this.validateDetents();
107
157
 
108
- // Lazy load by default, except when initialDetentIndex is set (for auto-presentation)
158
+ // Lazy load by default, except when auto-presenting or when lazy is disabled
109
159
  const shouldRenderImmediately =
110
- props.initialDetentIndex !== undefined && props.initialDetentIndex >= 0;
160
+ (props.initialDetentIndex !== undefined && props.initialDetentIndex >= 0) ||
161
+ props.lazy === false;
111
162
 
112
163
  this.state = {
113
164
  shouldRenderNativeView: shouldRenderImmediately,
165
+ initialPresentReady: false,
166
+ scrollableHandle: null,
114
167
  };
115
168
 
116
169
  this.onMount = this.onMount.bind(this);
117
170
  this.onWillDismiss = this.onWillDismiss.bind(this);
118
171
  this.onDidDismiss = this.onDidDismiss.bind(this);
172
+ this.onDismissAttempt = this.onDismissAttempt.bind(this);
119
173
  this.onWillPresent = this.onWillPresent.bind(this);
120
174
  this.onDidPresent = this.onDidPresent.bind(this);
121
175
  this.onDetentChange = this.onDetentChange.bind(this);
@@ -129,6 +183,33 @@ export class TrueSheet
129
183
  this.onDidBlur = this.onDidBlur.bind(this);
130
184
  this.handleBackPress = this.handleBackPress.bind(this);
131
185
  this.onVisibilityChange = this.onVisibilityChange.bind(this);
186
+ this.onInitialPresentReady = this.onInitialPresentReady.bind(this);
187
+ }
188
+
189
+ private onInitialPresentReady(): void {
190
+ this.setState({ initialPresentReady: true });
191
+ }
192
+
193
+ // Resolves after commit (didMount/didUpdate) — the scrollable mounts with the
194
+ // sheet content, so its ref is only populated once the native tree renders.
195
+ private updateScrollableHandle(): void {
196
+ const { scrollableRef } = this.props;
197
+ let scrollableHandle: number | null = null;
198
+ if (scrollableRef?.current) {
199
+ try {
200
+ scrollableHandle = findNodeHandle(scrollableRef.current);
201
+ } catch (error) {
202
+ // A stale ref (e.g. a wrapper that never clears it on unmount) makes
203
+ // findNodeHandle throw. Treat it as no scrollable instead of crashing.
204
+ if (__DEV__) {
205
+ console.warn('TrueSheet: scrollableRef points to an unmounted component.', error);
206
+ }
207
+ }
208
+ }
209
+
210
+ if (scrollableHandle !== this.state.scrollableHandle) {
211
+ this.setState({ scrollableHandle });
212
+ }
132
213
  }
133
214
 
134
215
  private validateDetents(): void {
@@ -301,15 +382,18 @@ export class TrueSheet
301
382
  this.props.onWillDismiss?.(event);
302
383
  }
303
384
 
385
+ private onDismissAttempt(event: DismissAttemptEvent): void {
386
+ this.props.onDismissAttempt?.(event);
387
+ }
388
+
304
389
  private onDidDismiss(event: DidDismissEvent): void {
305
390
  this.isPresented = false;
306
391
  this.isSheetVisible = true;
307
392
  this.backHandlerSubscription?.remove();
308
393
  this.backHandlerSubscription = null;
309
394
 
310
- // Clean up native view after dismiss for lazy loading.
311
- // Skip unmount if a present is in progress to avoid race condition.
312
- if (!this.isPresenting) {
395
+ // Non-lazy content stays mounted; otherwise clean it up unless another presentation is active.
396
+ if (!this.isPresenting && this.props.lazy !== false) {
313
397
  this.setState({ shouldRenderNativeView: false });
314
398
  }
315
399
 
@@ -371,9 +455,12 @@ export class TrueSheet
371
455
  const nodeHandle = findNodeHandle(this.nativeRef.current);
372
456
  if (nodeHandle == null || nodeHandle === -1) return false;
373
457
 
374
- // When not dismissible, let back propagate (e.g. navigation goes back to the previous screen)
458
+ // When not dismissible, report the attempt. An attempt handler consumes the back
459
+ // press; otherwise let it propagate (e.g. navigation goes back to the previous screen)
375
460
  if (this.props.dismissible === false) {
376
- return this.props.onBackPress?.() ?? false;
461
+ const { onDismissAttempt, onBackPress } = this.props;
462
+ onDismissAttempt?.({ nativeEvent: null } as DismissAttemptEvent);
463
+ return onBackPress?.() ?? onDismissAttempt != null;
377
464
  }
378
465
 
379
466
  TrueSheetModule?.handleBackPress(nodeHandle);
@@ -434,15 +521,38 @@ export class TrueSheet
434
521
 
435
522
  componentDidMount(): void {
436
523
  this.registerInstance();
524
+ this.updateScrollableHandle();
437
525
  }
438
526
 
439
527
  componentDidUpdate(prevProps: TrueSheetProps): void {
440
528
  this.registerInstance();
529
+ this.updateScrollableHandle();
530
+
531
+ if (
532
+ prevProps.lazy !== false &&
533
+ this.props.lazy === false &&
534
+ !this.state.shouldRenderNativeView
535
+ ) {
536
+ this.setState({ shouldRenderNativeView: true });
537
+ }
441
538
 
442
539
  // Validate when detents prop changes
443
540
  if (prevProps.detents !== this.props.detents) {
444
541
  this.validateDetents();
445
542
  }
543
+
544
+ // initialDetentIndex set at runtime on a lazily-mounted sheet: render the
545
+ // native tree so InitialPresentGate can deliver the index (constructor
546
+ // only computes this for the initial prop).
547
+ const prevInitialDetentIndex = prevProps.initialDetentIndex ?? -1;
548
+ const initialDetentIndex = this.props.initialDetentIndex ?? -1;
549
+ if (
550
+ prevInitialDetentIndex < 0 &&
551
+ initialDetentIndex >= 0 &&
552
+ !this.state.shouldRenderNativeView
553
+ ) {
554
+ this.setState({ shouldRenderNativeView: true });
555
+ }
446
556
  }
447
557
 
448
558
  componentWillUnmount(): void {
@@ -456,6 +566,9 @@ export class TrueSheet
456
566
  const {
457
567
  detents = [0.5, 1],
458
568
  backgroundColor,
569
+ detentBackgrounds,
570
+ background,
571
+ backgroundStyle,
459
572
  dismissible = true,
460
573
  draggable = true,
461
574
  grabber = true,
@@ -466,12 +579,11 @@ export class TrueSheet
466
579
  initialDetentAnimated = true,
467
580
  dimmedDetentIndex,
468
581
  backgroundBlur,
469
- blurOptions,
470
582
  cornerRadius,
471
583
  maxContentHeight,
472
584
  maxContentWidth,
473
- anchor = 'center',
474
- anchorOffset,
585
+ placement = 'automatic',
586
+ placementOffset,
475
587
  scrollableOptions,
476
588
  footerOptions,
477
589
  presentation = 'page',
@@ -485,6 +597,7 @@ export class TrueSheet
485
597
  insetAdjustment = 'automatic',
486
598
  ...rest
487
599
  } = this.props;
600
+ delete rest.lazy;
488
601
 
489
602
  // Trim to max 3 detents and clamp fractions
490
603
  const resolvedDetents: number[] = detents.slice(0, 3).map((detent) => {
@@ -498,6 +611,12 @@ export class TrueSheet
498
611
  return Math.min(1, detent);
499
612
  });
500
613
 
614
+ // Hold initialDetentIndex back from native until the gate's effect flushes
615
+ // (see InitialPresentGate) — native presents when the prop lands.
616
+ const autoPresent = initialDetentIndex >= 0;
617
+ const gatedInitialDetentIndex =
618
+ autoPresent && !this.state.initialPresentReady ? -1 : initialDetentIndex;
619
+
501
620
  // Cache grabberOptions to avoid creating a new object every render
502
621
  if (grabberOptions !== this.cachedGrabberOptions) {
503
622
  this.cachedGrabberOptions = grabberOptions;
@@ -507,6 +626,43 @@ export class TrueSheet
507
626
  };
508
627
  }
509
628
 
629
+ const sheetContent = (
630
+ <>
631
+ <TrueSheetBackground background={background} backgroundStyle={backgroundStyle} />
632
+ {header && (
633
+ <TrueSheetHeaderViewNativeComponent
634
+ style={[
635
+ styles.header,
636
+ headerOptions?.position === 'absolute' && styles.absoluteHeader,
637
+ headerStyle,
638
+ ]}
639
+ >
640
+ {isValidElement(header) ? header : createElement(header)}
641
+ </TrueSheetHeaderViewNativeComponent>
642
+ )}
643
+ <TrueSheetContentViewNativeComponent style={style}>
644
+ {children}
645
+ </TrueSheetContentViewNativeComponent>
646
+ {footer && (
647
+ <TrueSheetFooterViewNativeComponent
648
+ autoBottomInset={insetAdjustment === 'automatic'}
649
+ style={[
650
+ styles.footer,
651
+ footerOptions?.position === 'absolute'
652
+ ? styles.absoluteFooter
653
+ : styles.relativeFooter,
654
+ footerStyle,
655
+ ]}
656
+ >
657
+ {isValidElement(footer) ? footer : createElement(footer)}
658
+ </TrueSheetFooterViewNativeComponent>
659
+ )}
660
+ {autoPresent && !this.state.initialPresentReady && (
661
+ <InitialPresentGate onReady={this.onInitialPresentReady} />
662
+ )}
663
+ </>
664
+ );
665
+
510
666
  return (
511
667
  <TrueSheetViewNativeComponent
512
668
  {...rest}
@@ -514,27 +670,35 @@ export class TrueSheet
514
670
  style={styles.sheetView}
515
671
  detents={resolvedDetents}
516
672
  backgroundBlur={backgroundBlur}
517
- blurOptions={blurOptions}
518
673
  backgroundColor={backgroundColor}
674
+ detentBackgrounds={
675
+ detentBackgrounds &&
676
+ Array.from(detentBackgrounds.slice(0, 3), (entry) => {
677
+ const normalized = normalizeDetentBackground(entry);
678
+ return normalized ? { ...normalized, color: processColor(normalized.color) } : {};
679
+ })
680
+ }
519
681
  cornerRadius={cornerRadius}
520
682
  grabber={grabber}
521
683
  grabberOptions={this.resolvedGrabberOptions}
522
684
  accessibilityOptions={accessibilityOptions}
523
685
  dimmed={dimmed}
524
686
  dimmedDetentIndex={dimmedDetentIndex}
525
- initialDetentIndex={initialDetentIndex}
687
+ initialDetentIndex={gatedInitialDetentIndex}
526
688
  initialDetentAnimated={initialDetentAnimated}
527
689
  dismissible={dismissible}
528
690
  draggable={draggable}
529
691
  maxContentHeight={maxContentHeight}
530
692
  maxContentWidth={maxContentWidth}
531
- anchor={anchor}
532
- anchorOffset={anchorOffset}
693
+ placement={placement}
694
+ placementOffset={placementOffset}
695
+ scrollableHandle={this.state.scrollableHandle ?? -1}
533
696
  scrollableOptions={scrollableOptions}
534
697
  headerOptions={headerOptions}
535
698
  footerOptions={{
536
699
  footerPosition: footerOptions?.position,
537
700
  keyboardOffset: footerOptions?.keyboardOffset,
701
+ avoidKeyboard: footerOptions?.avoidKeyboard,
538
702
  }}
539
703
  presentation={presentation}
540
704
  insetAdjustment={insetAdjustment}
@@ -543,6 +707,7 @@ export class TrueSheet
543
707
  onDidPresent={this.onDidPresent}
544
708
  onWillDismiss={this.onWillDismiss}
545
709
  onDidDismiss={this.onDidDismiss}
710
+ onDismissAttempt={this.onDismissAttempt}
546
711
  onDetentChange={this.onDetentChange}
547
712
  onDragBegin={this.onDragBegin}
548
713
  onDragChange={this.onDragChange}
@@ -555,10 +720,11 @@ export class TrueSheet
555
720
  onVisibilityChange={this.onVisibilityChange}
556
721
  // These must stay on this host view, not on the container. The container is
557
722
  // reparented into the native sheet, so this view is never a native ancestor of
558
- // the sheet content. Claiming the responder here stops the negotiation at the
559
- // sheet boundary without making an ancestor of the content the JS responder,
560
- // which would block scrolling inside the sheet.
561
- onStartShouldSetResponder={absorbUnclaimedTouches}
723
+ // the sheet content — this is the React-tree boundary between the sheet and
724
+ // the presenting screen, where both the responder negotiation and raw touch
725
+ // bubbling must stop.
726
+ onStartShouldSetResponder={stopResponderNegotiation}
727
+ onMoveShouldSetResponder={stopResponderNegotiation}
562
728
  onTouchStart={stopTouchPropagation}
563
729
  onTouchMove={stopTouchPropagation}
564
730
  onTouchEnd={stopTouchPropagation}
@@ -566,33 +732,22 @@ export class TrueSheet
566
732
  >
567
733
  {this.state.shouldRenderNativeView && (
568
734
  <TrueSheetContainerViewNativeComponent style={styles.container}>
569
- {header && (
570
- <TrueSheetHeaderViewNativeComponent
571
- style={[
572
- styles.header,
573
- headerOptions?.position === 'absolute' && styles.absoluteHeader,
574
- headerStyle,
575
- ]}
735
+ {AppContainer ? (
736
+ // Hosts the element inspector inside the sheet, like RN's Modal does —
737
+ // the app-level one sits behind the presented sheet.
738
+ // `display: contents` keeps AppContainer's wrapper views out of both
739
+ // Yoga layout and the native tree, so header/content/footer remain the
740
+ // container's direct native children, while the inspector overlay
741
+ // anchors to the container and lines up with measured frames.
742
+ <AppContainer
743
+ rootTag={this.context as RootTag}
744
+ rootViewStyle={styles.inspectorHost}
745
+ internal_excludeLogBox
576
746
  >
577
- {isValidElement(header) ? header : createElement(header)}
578
- </TrueSheetHeaderViewNativeComponent>
579
- )}
580
- <TrueSheetContentViewNativeComponent style={style}>
581
- {children}
582
- </TrueSheetContentViewNativeComponent>
583
- {footer && (
584
- <TrueSheetFooterViewNativeComponent
585
- autoBottomInset={insetAdjustment === 'automatic'}
586
- style={[
587
- styles.footer,
588
- footerOptions?.position === 'absolute'
589
- ? styles.absoluteFooter
590
- : styles.relativeFooter,
591
- footerStyle,
592
- ]}
593
- >
594
- {isValidElement(footer) ? footer : createElement(footer)}
595
- </TrueSheetFooterViewNativeComponent>
747
+ {sheetContent}
748
+ </AppContainer>
749
+ ) : (
750
+ sheetContent
596
751
  )}
597
752
  </TrueSheetContainerViewNativeComponent>
598
753
  )}
@@ -614,6 +769,9 @@ const styles = StyleSheet.create({
614
769
  container: {
615
770
  ...StyleSheet.absoluteFill,
616
771
  },
772
+ inspectorHost: {
773
+ display: 'contents',
774
+ },
617
775
  header: {
618
776
  pointerEvents: 'box-none',
619
777
  },