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

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 (205) 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 +303 -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 +124 -50
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +369 -163
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +59 -12
  14. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +126 -0
  15. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
  16. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  20. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  21. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  22. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  23. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  24. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  25. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  26. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
  27. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  28. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  29. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  30. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  31. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  46. package/ios/TrueSheetContainerView.h +27 -7
  47. package/ios/TrueSheetContainerView.mm +31 -11
  48. package/ios/TrueSheetContentView.h +43 -15
  49. package/ios/TrueSheetContentView.mm +211 -189
  50. package/ios/TrueSheetFooterView.h +6 -0
  51. package/ios/TrueSheetFooterView.mm +93 -17
  52. package/ios/TrueSheetModule.h +2 -7
  53. package/ios/TrueSheetModule.mm +7 -16
  54. package/ios/TrueSheetOverlayView.h +32 -0
  55. package/ios/TrueSheetOverlayView.mm +161 -0
  56. package/ios/TrueSheetView.mm +275 -56
  57. package/ios/TrueSheetViewController.h +31 -3
  58. package/ios/TrueSheetViewController.mm +393 -94
  59. package/ios/core/TrueSheetBlurView.h +0 -2
  60. package/ios/core/TrueSheetBlurView.mm +3 -36
  61. package/ios/core/TrueSheetDetentCalculator.h +7 -1
  62. package/ios/core/TrueSheetDetentCalculator.mm +27 -4
  63. package/ios/core/TrueSheetGrabberView.mm +13 -3
  64. package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
  65. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  66. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  67. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  68. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  69. package/ios/tvos/TrueSheetStubs.mm +12 -0
  70. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  71. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  72. package/lib/module/TrueSheet.js +175 -39
  73. package/lib/module/TrueSheet.js.map +1 -1
  74. package/lib/module/TrueSheet.web.js +220 -243
  75. package/lib/module/TrueSheet.web.js.map +1 -1
  76. package/lib/module/TrueSheetBackground.js +15 -0
  77. package/lib/module/TrueSheetBackground.js.map +1 -0
  78. package/lib/module/TrueSheetOverlay.js +30 -0
  79. package/lib/module/TrueSheetOverlay.js.map +1 -0
  80. package/lib/module/TrueSheetOverlay.web.js +35 -0
  81. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  82. package/lib/module/TrueSheetPeek.web.js +20 -15
  83. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  84. package/lib/module/detentBackgrounds.js +21 -0
  85. package/lib/module/detentBackgrounds.js.map +1 -0
  86. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  87. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  88. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -10
  89. package/lib/module/index.js +1 -0
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/mocks/index.js +18 -2
  92. package/lib/module/mocks/index.js.map +1 -1
  93. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  94. package/lib/module/navigation/base-types.js +2 -0
  95. package/lib/module/navigation/base-types.js.map +1 -0
  96. package/lib/module/navigation/createTrueSheetRouter.js +0 -9
  97. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  98. package/lib/module/navigation/expo-router/base-types.js +2 -0
  99. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  100. package/lib/module/navigation/expo-router/index.js +10 -7
  101. package/lib/module/navigation/expo-router/index.js.map +1 -1
  102. package/lib/module/navigation/expo-router/types.js +4 -0
  103. package/lib/module/navigation/expo-router/types.js.map +1 -0
  104. package/lib/module/navigation/index.js.map +1 -1
  105. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  106. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  107. package/lib/module/web/constants.js +1 -1
  108. package/lib/module/web/constants.js.map +1 -1
  109. package/lib/module/web/layout.js +168 -0
  110. package/lib/module/web/layout.js.map +1 -0
  111. package/lib/module/web/vaul/constants.js +9 -0
  112. package/lib/module/web/vaul/constants.js.map +1 -1
  113. package/lib/module/web/vaul/context.js.map +1 -1
  114. package/lib/module/web/vaul/helpers.js +1 -0
  115. package/lib/module/web/vaul/helpers.js.map +1 -1
  116. package/lib/module/web/vaul/index.js +47 -7
  117. package/lib/module/web/vaul/index.js.map +1 -1
  118. package/lib/module/web/vaul/use-snap-points.js +21 -10
  119. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  120. package/lib/typescript/src/TrueSheet.d.ts +6 -0
  121. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  122. package/lib/typescript/src/TrueSheet.types.d.ts +157 -34
  123. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  124. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  125. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  126. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  127. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  128. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  129. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  130. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  131. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  132. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  133. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  134. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  135. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  136. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  137. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +12 -7
  138. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  139. package/lib/typescript/src/index.d.ts +1 -0
  140. package/lib/typescript/src/index.d.ts.map +1 -1
  141. package/lib/typescript/src/mocks/index.d.ts +4 -0
  142. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  143. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  144. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  145. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  146. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  147. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
  148. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  149. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  150. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  151. package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
  152. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  153. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  154. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  155. package/lib/typescript/src/navigation/index.d.ts +3 -4
  156. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  157. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  158. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  159. package/lib/typescript/src/navigation/types.d.ts +41 -5
  160. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  161. package/lib/typescript/src/web/constants.d.ts +1 -1
  162. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  163. package/lib/typescript/src/web/layout.d.ts +19 -0
  164. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  165. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  166. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  167. package/lib/typescript/src/web/vaul/context.d.ts +1 -0
  168. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  169. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  170. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  171. package/lib/typescript/src/web/vaul/index.d.ts +13 -1
  172. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  173. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +2 -1
  174. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  175. package/package.json +17 -8
  176. package/react-native.config.js +1 -0
  177. package/src/TrueSheet.tsx +208 -48
  178. package/src/TrueSheet.types.ts +166 -35
  179. package/src/TrueSheet.web.tsx +272 -290
  180. package/src/TrueSheetBackground.tsx +17 -0
  181. package/src/TrueSheetOverlay.tsx +24 -0
  182. package/src/TrueSheetOverlay.web.tsx +33 -0
  183. package/src/TrueSheetPeek.web.tsx +25 -23
  184. package/src/detentBackgrounds.ts +24 -0
  185. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  186. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  187. package/src/fabric/TrueSheetViewNativeComponent.ts +15 -10
  188. package/src/index.ts +1 -0
  189. package/src/mocks/index.ts +17 -2
  190. package/src/mocks/navigationExpoRouter.ts +6 -5
  191. package/src/navigation/base-types.ts +20 -0
  192. package/src/navigation/createTrueSheetRouter.ts +5 -33
  193. package/src/navigation/expo-router/base-types.ts +18 -0
  194. package/src/navigation/expo-router/index.ts +28 -30
  195. package/src/navigation/expo-router/types.ts +116 -0
  196. package/src/navigation/index.ts +6 -7
  197. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  198. package/src/navigation/types.ts +75 -22
  199. package/src/web/constants.ts +1 -1
  200. package/src/web/layout.ts +196 -0
  201. package/src/web/vaul/constants.ts +9 -0
  202. package/src/web/vaul/context.ts +1 -0
  203. package/src/web/vaul/helpers.ts +5 -0
  204. package/src/web/vaul/index.tsx +75 -5
  205. package/src/web/vaul/use-snap-points.ts +32 -15
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,8 +566,12 @@ 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,
574
+ dismissThreshold = 'half',
461
575
  grabber = true,
462
576
  grabberOptions,
463
577
  accessibilityOptions,
@@ -466,12 +580,11 @@ export class TrueSheet
466
580
  initialDetentAnimated = true,
467
581
  dimmedDetentIndex,
468
582
  backgroundBlur,
469
- blurOptions,
470
583
  cornerRadius,
471
584
  maxContentHeight,
472
585
  maxContentWidth,
473
- anchor = 'center',
474
- anchorOffset,
586
+ placement = 'automatic',
587
+ placementOffset,
475
588
  scrollableOptions,
476
589
  footerOptions,
477
590
  presentation = 'page',
@@ -485,6 +598,7 @@ export class TrueSheet
485
598
  insetAdjustment = 'automatic',
486
599
  ...rest
487
600
  } = this.props;
601
+ delete rest.lazy;
488
602
 
489
603
  // Trim to max 3 detents and clamp fractions
490
604
  const resolvedDetents: number[] = detents.slice(0, 3).map((detent) => {
@@ -498,6 +612,12 @@ export class TrueSheet
498
612
  return Math.min(1, detent);
499
613
  });
500
614
 
615
+ // Hold initialDetentIndex back from native until the gate's effect flushes
616
+ // (see InitialPresentGate) — native presents when the prop lands.
617
+ const autoPresent = initialDetentIndex >= 0;
618
+ const gatedInitialDetentIndex =
619
+ autoPresent && !this.state.initialPresentReady ? -1 : initialDetentIndex;
620
+
501
621
  // Cache grabberOptions to avoid creating a new object every render
502
622
  if (grabberOptions !== this.cachedGrabberOptions) {
503
623
  this.cachedGrabberOptions = grabberOptions;
@@ -507,6 +627,43 @@ export class TrueSheet
507
627
  };
508
628
  }
509
629
 
630
+ const sheetContent = (
631
+ <>
632
+ <TrueSheetBackground background={background} backgroundStyle={backgroundStyle} />
633
+ {header && (
634
+ <TrueSheetHeaderViewNativeComponent
635
+ style={[
636
+ styles.header,
637
+ headerOptions?.position === 'absolute' && styles.absoluteHeader,
638
+ headerStyle,
639
+ ]}
640
+ >
641
+ {isValidElement(header) ? header : createElement(header)}
642
+ </TrueSheetHeaderViewNativeComponent>
643
+ )}
644
+ <TrueSheetContentViewNativeComponent style={style}>
645
+ {children}
646
+ </TrueSheetContentViewNativeComponent>
647
+ {footer && (
648
+ <TrueSheetFooterViewNativeComponent
649
+ autoBottomInset={insetAdjustment === 'automatic'}
650
+ style={[
651
+ styles.footer,
652
+ footerOptions?.position === 'absolute'
653
+ ? styles.absoluteFooter
654
+ : styles.relativeFooter,
655
+ footerStyle,
656
+ ]}
657
+ >
658
+ {isValidElement(footer) ? footer : createElement(footer)}
659
+ </TrueSheetFooterViewNativeComponent>
660
+ )}
661
+ {autoPresent && !this.state.initialPresentReady && (
662
+ <InitialPresentGate onReady={this.onInitialPresentReady} />
663
+ )}
664
+ </>
665
+ );
666
+
510
667
  return (
511
668
  <TrueSheetViewNativeComponent
512
669
  {...rest}
@@ -514,27 +671,36 @@ export class TrueSheet
514
671
  style={styles.sheetView}
515
672
  detents={resolvedDetents}
516
673
  backgroundBlur={backgroundBlur}
517
- blurOptions={blurOptions}
518
674
  backgroundColor={backgroundColor}
675
+ detentBackgrounds={
676
+ detentBackgrounds &&
677
+ Array.from(detentBackgrounds.slice(0, 3), (entry) => {
678
+ const normalized = normalizeDetentBackground(entry);
679
+ return normalized ? { ...normalized, color: processColor(normalized.color) } : {};
680
+ })
681
+ }
519
682
  cornerRadius={cornerRadius}
520
683
  grabber={grabber}
521
684
  grabberOptions={this.resolvedGrabberOptions}
522
685
  accessibilityOptions={accessibilityOptions}
523
686
  dimmed={dimmed}
524
687
  dimmedDetentIndex={dimmedDetentIndex}
525
- initialDetentIndex={initialDetentIndex}
688
+ initialDetentIndex={gatedInitialDetentIndex}
526
689
  initialDetentAnimated={initialDetentAnimated}
527
690
  dismissible={dismissible}
528
691
  draggable={draggable}
692
+ dismissThreshold={dismissThreshold}
529
693
  maxContentHeight={maxContentHeight}
530
694
  maxContentWidth={maxContentWidth}
531
- anchor={anchor}
532
- anchorOffset={anchorOffset}
695
+ placement={placement}
696
+ placementOffset={placementOffset}
697
+ scrollableHandle={this.state.scrollableHandle ?? -1}
533
698
  scrollableOptions={scrollableOptions}
534
699
  headerOptions={headerOptions}
535
700
  footerOptions={{
536
701
  footerPosition: footerOptions?.position,
537
702
  keyboardOffset: footerOptions?.keyboardOffset,
703
+ avoidKeyboard: footerOptions?.avoidKeyboard,
538
704
  }}
539
705
  presentation={presentation}
540
706
  insetAdjustment={insetAdjustment}
@@ -543,6 +709,7 @@ export class TrueSheet
543
709
  onDidPresent={this.onDidPresent}
544
710
  onWillDismiss={this.onWillDismiss}
545
711
  onDidDismiss={this.onDidDismiss}
712
+ onDismissAttempt={this.onDismissAttempt}
546
713
  onDetentChange={this.onDetentChange}
547
714
  onDragBegin={this.onDragBegin}
548
715
  onDragChange={this.onDragChange}
@@ -555,10 +722,11 @@ export class TrueSheet
555
722
  onVisibilityChange={this.onVisibilityChange}
556
723
  // These must stay on this host view, not on the container. The container is
557
724
  // 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}
725
+ // the sheet content — this is the React-tree boundary between the sheet and
726
+ // the presenting screen, where both the responder negotiation and raw touch
727
+ // bubbling must stop.
728
+ onStartShouldSetResponder={stopResponderNegotiation}
729
+ onMoveShouldSetResponder={stopResponderNegotiation}
562
730
  onTouchStart={stopTouchPropagation}
563
731
  onTouchMove={stopTouchPropagation}
564
732
  onTouchEnd={stopTouchPropagation}
@@ -566,33 +734,22 @@ export class TrueSheet
566
734
  >
567
735
  {this.state.shouldRenderNativeView && (
568
736
  <TrueSheetContainerViewNativeComponent style={styles.container}>
569
- {header && (
570
- <TrueSheetHeaderViewNativeComponent
571
- style={[
572
- styles.header,
573
- headerOptions?.position === 'absolute' && styles.absoluteHeader,
574
- headerStyle,
575
- ]}
737
+ {AppContainer ? (
738
+ // Hosts the element inspector inside the sheet, like RN's Modal does —
739
+ // the app-level one sits behind the presented sheet.
740
+ // `display: contents` keeps AppContainer's wrapper views out of both
741
+ // Yoga layout and the native tree, so header/content/footer remain the
742
+ // container's direct native children, while the inspector overlay
743
+ // anchors to the container and lines up with measured frames.
744
+ <AppContainer
745
+ rootTag={this.context as RootTag}
746
+ rootViewStyle={styles.inspectorHost}
747
+ internal_excludeLogBox
576
748
  >
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>
749
+ {sheetContent}
750
+ </AppContainer>
751
+ ) : (
752
+ sheetContent
596
753
  )}
597
754
  </TrueSheetContainerViewNativeComponent>
598
755
  )}
@@ -614,6 +771,9 @@ const styles = StyleSheet.create({
614
771
  container: {
615
772
  ...StyleSheet.absoluteFill,
616
773
  },
774
+ inspectorHost: {
775
+ display: 'contents',
776
+ },
617
777
  header: {
618
778
  pointerEvents: 'box-none',
619
779
  },