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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +28 -5
  2. package/android/build.gradle +23 -1
  3. package/android/proguard-rules.pro +3 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +38 -109
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +127 -0
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +29 -3
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +234 -66
  14. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +491 -186
  15. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +64 -10
  16. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -14
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +135 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +23 -23
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +130 -6
  20. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  21. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +14 -17
  22. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
  23. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +148 -0
  24. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +33 -40
  25. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  26. package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
  27. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  28. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  29. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  30. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  31. package/android/src/main/jni/TrueSheetStateUpdater.cpp +37 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +114 -30
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +14 -3
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +5 -6
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +10 -6
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  46. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  47. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  48. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  49. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  50. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  51. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  52. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  53. package/ios/TrueSheetContainerView.h +23 -7
  54. package/ios/TrueSheetContainerView.mm +45 -25
  55. package/ios/TrueSheetContentView.h +44 -15
  56. package/ios/TrueSheetContentView.mm +221 -190
  57. package/ios/TrueSheetFooterView.mm +133 -28
  58. package/ios/TrueSheetModule.h +2 -7
  59. package/ios/TrueSheetModule.mm +18 -18
  60. package/ios/TrueSheetOverlayView.h +42 -0
  61. package/ios/TrueSheetOverlayView.mm +175 -0
  62. package/ios/TrueSheetView.h +2 -0
  63. package/ios/TrueSheetView.mm +268 -52
  64. package/ios/TrueSheetViewController.h +36 -3
  65. package/ios/TrueSheetViewController.mm +797 -102
  66. package/ios/core/RNScreensEventObserver.mm +31 -28
  67. package/ios/core/TrueSheetBlurView.h +0 -2
  68. package/ios/core/TrueSheetBlurView.mm +3 -36
  69. package/ios/core/TrueSheetDetentCalculator.h +18 -1
  70. package/ios/core/TrueSheetDetentCalculator.mm +44 -9
  71. package/ios/core/TrueSheetDimView.h +27 -0
  72. package/ios/core/TrueSheetDimView.mm +41 -0
  73. package/ios/core/TrueSheetGrabberView.mm +13 -3
  74. package/ios/core/TrueSheetKeyboardObserver.h +7 -0
  75. package/ios/core/TrueSheetKeyboardObserver.mm +107 -15
  76. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  77. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  78. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  79. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  80. package/ios/tvos/TrueSheetStubs.mm +12 -0
  81. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  82. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  83. package/lib/module/TrueSheet.js +222 -54
  84. package/lib/module/TrueSheet.js.map +1 -1
  85. package/lib/module/TrueSheet.web.js +364 -275
  86. package/lib/module/TrueSheet.web.js.map +1 -1
  87. package/lib/module/TrueSheetBackground.js +15 -0
  88. package/lib/module/TrueSheetBackground.js.map +1 -0
  89. package/lib/module/TrueSheetOverlay.js +30 -0
  90. package/lib/module/TrueSheetOverlay.js.map +1 -0
  91. package/lib/module/TrueSheetOverlay.web.js +35 -0
  92. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  93. package/lib/module/TrueSheetPeek.web.js +20 -15
  94. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  95. package/lib/module/TrueSheetProvider.web.js +13 -9
  96. package/lib/module/TrueSheetProvider.web.js.map +1 -1
  97. package/lib/module/detentBackgrounds.js +21 -0
  98. package/lib/module/detentBackgrounds.js.map +1 -0
  99. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  100. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  101. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  102. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +89 -73
  103. package/lib/module/index.js +1 -0
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/mocks/index.js +18 -2
  106. package/lib/module/mocks/index.js.map +1 -1
  107. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  108. package/lib/module/navigation/base-types.js +2 -0
  109. package/lib/module/navigation/base-types.js.map +1 -0
  110. package/lib/module/navigation/createTrueSheetRouter.js +158 -100
  111. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  112. package/lib/module/navigation/expo-router/base-types.js +2 -0
  113. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  114. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +71 -0
  115. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
  116. package/lib/module/navigation/expo-router/index.js +18 -7
  117. package/lib/module/navigation/expo-router/index.js.map +1 -1
  118. package/lib/module/navigation/expo-router/types.js +4 -0
  119. package/lib/module/navigation/expo-router/types.js.map +1 -0
  120. package/lib/module/navigation/index.js.map +1 -1
  121. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  122. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  123. package/lib/module/scrollable.js +28 -0
  124. package/lib/module/scrollable.js.map +1 -0
  125. package/lib/module/web/constants.js +1 -1
  126. package/lib/module/web/constants.js.map +1 -1
  127. package/lib/module/web/keyboard.js +69 -0
  128. package/lib/module/web/keyboard.js.map +1 -0
  129. package/lib/module/web/layout.js +168 -0
  130. package/lib/module/web/layout.js.map +1 -0
  131. package/lib/module/web/vaul/constants.js +9 -0
  132. package/lib/module/web/vaul/constants.js.map +1 -1
  133. package/lib/module/web/vaul/context.js +4 -0
  134. package/lib/module/web/vaul/context.js.map +1 -1
  135. package/lib/module/web/vaul/helpers.js +1 -0
  136. package/lib/module/web/vaul/helpers.js.map +1 -1
  137. package/lib/module/web/vaul/index.js +83 -21
  138. package/lib/module/web/vaul/index.js.map +1 -1
  139. package/lib/module/web/vaul/use-snap-points.js +57 -28
  140. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  141. package/lib/typescript/src/TrueSheet.d.ts +18 -1
  142. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  143. package/lib/typescript/src/TrueSheet.types.d.ts +209 -35
  144. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  145. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  146. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  147. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  148. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  149. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  150. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  151. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  152. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  153. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  154. package/lib/typescript/src/TrueSheetProvider.web.d.ts +5 -1
  155. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
  156. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  157. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  158. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -3
  159. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  160. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  161. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  162. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +76 -65
  163. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  164. package/lib/typescript/src/index.d.ts +1 -0
  165. package/lib/typescript/src/index.d.ts.map +1 -1
  166. package/lib/typescript/src/mocks/index.d.ts +4 -0
  167. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  168. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  169. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  170. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  171. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  172. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +36 -10
  173. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  174. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  175. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  176. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +33 -0
  177. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
  178. package/lib/typescript/src/navigation/expo-router/index.d.ts +9 -6
  179. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  180. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  181. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  182. package/lib/typescript/src/navigation/index.d.ts +3 -4
  183. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  184. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  185. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  186. package/lib/typescript/src/navigation/types.d.ts +43 -6
  187. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  188. package/lib/typescript/src/scrollable.d.ts +2 -0
  189. package/lib/typescript/src/scrollable.d.ts.map +1 -0
  190. package/lib/typescript/src/web/constants.d.ts +1 -1
  191. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  192. package/lib/typescript/src/web/keyboard.d.ts +15 -0
  193. package/lib/typescript/src/web/keyboard.d.ts.map +1 -0
  194. package/lib/typescript/src/web/layout.d.ts +20 -0
  195. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  196. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  197. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  198. package/lib/typescript/src/web/vaul/context.d.ts +5 -1
  199. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  200. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  201. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  202. package/lib/typescript/src/web/vaul/index.d.ts +20 -1
  203. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  204. package/lib/typescript/src/web/vaul/types.d.ts +9 -0
  205. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -1
  206. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +6 -4
  207. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  208. package/package.json +18 -8
  209. package/react-native.config.js +1 -0
  210. package/src/TrueSheet.tsx +257 -62
  211. package/src/TrueSheet.types.ts +225 -35
  212. package/src/TrueSheet.web.tsx +445 -333
  213. package/src/TrueSheetBackground.tsx +17 -0
  214. package/src/TrueSheetOverlay.tsx +24 -0
  215. package/src/TrueSheetOverlay.web.tsx +33 -0
  216. package/src/TrueSheetPeek.web.tsx +25 -23
  217. package/src/TrueSheetProvider.web.tsx +22 -13
  218. package/src/detentBackgrounds.ts +24 -0
  219. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  220. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  221. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  222. package/src/fabric/TrueSheetViewNativeComponent.ts +89 -73
  223. package/src/index.ts +1 -0
  224. package/src/mocks/index.ts +17 -2
  225. package/src/mocks/navigationExpoRouter.ts +6 -5
  226. package/src/navigation/base-types.ts +20 -0
  227. package/src/navigation/createTrueSheetRouter.ts +220 -144
  228. package/src/navigation/expo-router/base-types.ts +18 -0
  229. package/src/navigation/expo-router/extendTrueSheetRouter.ts +99 -0
  230. package/src/navigation/expo-router/index.ts +41 -30
  231. package/src/navigation/expo-router/types.ts +116 -0
  232. package/src/navigation/index.ts +6 -7
  233. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  234. package/src/navigation/types.ts +81 -23
  235. package/src/scrollable.ts +25 -0
  236. package/src/web/constants.ts +1 -1
  237. package/src/web/keyboard.ts +79 -0
  238. package/src/web/layout.ts +196 -0
  239. package/src/web/vaul/constants.ts +9 -0
  240. package/src/web/vaul/context.ts +7 -1
  241. package/src/web/vaul/helpers.ts +5 -0
  242. package/src/web/vaul/index.tsx +113 -16
  243. package/src/web/vaul/types.ts +10 -0
  244. package/src/web/vaul/use-snap-points.ts +86 -33
package/src/TrueSheet.tsx CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  createRef,
5
5
  type ReactNode,
6
6
  type ComponentRef,
7
+ type ComponentType,
7
8
  isValidElement,
8
9
  createElement,
9
10
  useEffect,
@@ -22,6 +23,7 @@ import type {
22
23
  PositionChangeEvent,
23
24
  DidDismissEvent,
24
25
  WillDismissEvent,
26
+ DismissAttemptEvent,
25
27
  MountEvent,
26
28
  WillFocusEvent,
27
29
  DidFocusEvent,
@@ -35,17 +37,39 @@ import TrueSheetHeaderViewNativeComponent from './fabric/TrueSheetHeaderViewNati
35
37
  import TrueSheetFooterViewNativeComponent from './fabric/TrueSheetFooterViewNativeComponent';
36
38
 
37
39
  import TrueSheetModule from './specs/NativeTrueSheetModule';
40
+ import { TrueSheetBackground } from './TrueSheetBackground';
41
+ import { normalizeDetentBackground } from './detentBackgrounds';
42
+ import { resolveScrollable } from './scrollable';
38
43
 
39
44
  import {
40
45
  Platform,
41
46
  StyleSheet,
42
47
  BackHandler,
48
+ RootTagContext,
43
49
  findNodeHandle,
44
50
  processColor,
45
51
  type NativeEventSubscription,
46
52
  type GestureResponderEvent,
53
+ type RootTag,
54
+ type StyleProp,
55
+ type ViewStyle,
47
56
  } from 'react-native';
48
57
 
58
+ interface AppContainerProps {
59
+ rootTag: RootTag;
60
+ rootViewStyle?: StyleProp<ViewStyle>;
61
+ internal_excludeLogBox?: boolean;
62
+ children?: ReactNode;
63
+ }
64
+
65
+ // RN has no public API for hosting the element inspector inside a presented view;
66
+ // this is the internal RN's Modal uses. Dev-only so prod bundles never resolve it.
67
+ // The template literal keeps the RN babel preset's deep-import warning off while
68
+ // Metro still resolves the module statically.
69
+ const AppContainer: ComponentType<AppContainerProps> | null = __DEV__
70
+ ? require(`react-native/Libraries/ReactNative/AppContainer`).default
71
+ : null;
72
+
49
73
  const LINKING_ERROR =
50
74
  `The package '@lodev09/react-native-true-sheet' doesn't seem to be linked. Make sure: \n\n` +
51
75
  Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
@@ -59,16 +83,26 @@ if (!TrueSheetModule) {
59
83
 
60
84
  type NativeRef = ComponentRef<typeof TrueSheetViewNativeComponent>;
61
85
 
62
- // Claim touches that no descendant claimed so the responder negotiation
63
- // doesn't bubble up to touchables outside the sheet
64
- const absorbUnclaimedTouches = () => true;
65
-
66
86
  // Stop raw touch events from bubbling past the sheet to outside ancestors
67
87
  const stopTouchPropagation = (event: GestureResponderEvent) => event.stopPropagation();
68
88
 
89
+ // Stop the responder negotiation at the sheet boundary without claiming the
90
+ // responder. Views inside the sheet are asked before this one, so in-sheet
91
+ // touchables work normally; views outside are asked after, so stopping here
92
+ // keeps unclaimed touches from leaking to touchables behind the sheet.
93
+ // Claiming instead (returning true) would steal the responder from a pressed
94
+ // touchable whenever the negotiation re-runs mid-gesture — e.g. a second
95
+ // finger, or the duplicate touch stream from a nested react-native-screens
96
+ // screen that attaches its own touch handler inside the sheet.
97
+ const stopResponderNegotiation = (event: GestureResponderEvent) => {
98
+ event.stopPropagation();
99
+ return false;
100
+ };
101
+
69
102
  interface TrueSheetState {
70
103
  shouldRenderNativeView: boolean;
71
104
  initialPresentReady: boolean;
105
+ scrollableHandle: number | null;
72
106
  }
73
107
 
74
108
  // Gates auto-present (initialDetentIndex) by one commit. Rendered last in the
@@ -90,6 +124,8 @@ export class TrueSheet
90
124
  {
91
125
  displayName = 'TrueSheet';
92
126
 
127
+ static contextType = RootTagContext;
128
+
93
129
  private readonly nativeRef: RefObject<NativeRef | null>;
94
130
 
95
131
  private cachedGrabberOptions: TrueSheetProps['grabberOptions'] | undefined;
@@ -104,9 +140,19 @@ export class TrueSheet
104
140
  private static readonly instances: { [name: string]: TrueSheet } = {};
105
141
 
106
142
  /**
107
- * Resolver to be called when mount event is received
143
+ * All mounted sheets, named or not.
144
+ */
145
+ private static readonly mountedInstances = new Set<TrueSheet>();
146
+
147
+ /**
148
+ * Resolver to be called when mount event is received, or with `false` when the present is cancelled
149
+ */
150
+ private presentationResolver: ((mounted: boolean) => void) | null = null;
151
+
152
+ /**
153
+ * Settles once a scheduled lazy unmount has committed
108
154
  */
109
- private presentationResolver: (() => void) | null = null;
155
+ private nativeViewUnmount: Promise<void> | null = null;
110
156
 
111
157
  /**
112
158
  * Tracks if a present operation is in progress
@@ -120,18 +166,21 @@ export class TrueSheet
120
166
 
121
167
  this.validateDetents();
122
168
 
123
- // Lazy load by default, except when initialDetentIndex is set (for auto-presentation)
169
+ // Lazy load by default, except when auto-presenting or when lazy is disabled
124
170
  const shouldRenderImmediately =
125
- props.initialDetentIndex !== undefined && props.initialDetentIndex >= 0;
171
+ (props.initialDetentIndex !== undefined && props.initialDetentIndex >= 0) ||
172
+ props.lazy === false;
126
173
 
127
174
  this.state = {
128
175
  shouldRenderNativeView: shouldRenderImmediately,
129
176
  initialPresentReady: false,
177
+ scrollableHandle: null,
130
178
  };
131
179
 
132
180
  this.onMount = this.onMount.bind(this);
133
181
  this.onWillDismiss = this.onWillDismiss.bind(this);
134
182
  this.onDidDismiss = this.onDidDismiss.bind(this);
183
+ this.onDismissAttempt = this.onDismissAttempt.bind(this);
135
184
  this.onWillPresent = this.onWillPresent.bind(this);
136
185
  this.onDidPresent = this.onDidPresent.bind(this);
137
186
  this.onDetentChange = this.onDetentChange.bind(this);
@@ -152,6 +201,34 @@ export class TrueSheet
152
201
  this.setState({ initialPresentReady: true });
153
202
  }
154
203
 
204
+ // Resolves after commit (didMount/didUpdate) — the scrollable mounts with the
205
+ // sheet content, so its ref is only populated once the native tree renders.
206
+ private updateScrollableHandle(): void {
207
+ const { scrollableRef } = this.props;
208
+ let scrollableHandle: number | null = null;
209
+ if (scrollableRef?.current) {
210
+ try {
211
+ scrollableHandle = findNodeHandle(
212
+ resolveScrollable(scrollableRef.current) as Parameters<typeof findNodeHandle>[0]
213
+ );
214
+ } catch (error) {
215
+ // findNodeHandle throws on a stale ref (e.g. a wrapper that never clears
216
+ // it on unmount) or a handle with no usable scroll accessor. Treat it as
217
+ // no scrollable instead of crashing.
218
+ if (__DEV__) {
219
+ console.warn(
220
+ 'TrueSheet: scrollableRef could not be resolved to a native view. It may be stale or expose no scroll node.',
221
+ error
222
+ );
223
+ }
224
+ }
225
+ }
226
+
227
+ if (scrollableHandle !== this.state.scrollableHandle) {
228
+ this.setState({ scrollableHandle });
229
+ }
230
+ }
231
+
155
232
  private validateDetents(): void {
156
233
  const { detents, initialDetentIndex } = this.props;
157
234
 
@@ -281,6 +358,7 @@ export class TrueSheet
281
358
  * @returns Promise that resolves when all sheets are dismissed
282
359
  */
283
360
  public static async dismissAll(animated: boolean = true): Promise<void> {
361
+ TrueSheet.mountedInstances.forEach((sheet) => sheet.cancelPendingPresent());
284
362
  return TrueSheetModule?.dismissAll(animated);
285
363
  }
286
364
 
@@ -322,25 +400,29 @@ export class TrueSheet
322
400
  this.props.onWillDismiss?.(event);
323
401
  }
324
402
 
403
+ private onDismissAttempt(event: DismissAttemptEvent): void {
404
+ this.props.onDismissAttempt?.(event);
405
+ }
406
+
325
407
  private onDidDismiss(event: DidDismissEvent): void {
326
408
  this.isPresented = false;
327
409
  this.isSheetVisible = true;
328
410
  this.backHandlerSubscription?.remove();
329
411
  this.backHandlerSubscription = null;
330
412
 
331
- // Clean up native view after dismiss for lazy loading.
332
- // Skip unmount if a present is in progress to avoid race condition.
333
- if (!this.isPresenting) {
334
- this.setState({ shouldRenderNativeView: false });
413
+ // Non-lazy content stays mounted; otherwise clean it up unless another presentation is active.
414
+ if (!this.isPresenting && this.props.lazy !== false) {
415
+ this.unmountNativeView();
335
416
  }
336
417
 
337
418
  this.props.onDidDismiss?.(event);
338
419
  }
339
420
 
340
421
  private onMount(event: MountEvent): void {
341
- // Resolve the mount promise if waiting
342
- if (this.presentationResolver) {
343
- this.presentationResolver();
422
+ // Resolve the mount promise if waiting. While an unmount is pending, the event is from
423
+ // the outgoing view — present() waits for the remount instead.
424
+ if (this.presentationResolver && !this.nativeViewUnmount) {
425
+ this.presentationResolver(true);
344
426
  this.presentationResolver = null;
345
427
  }
346
428
 
@@ -392,12 +474,17 @@ export class TrueSheet
392
474
  const nodeHandle = findNodeHandle(this.nativeRef.current);
393
475
  if (nodeHandle == null || nodeHandle === -1) return false;
394
476
 
395
- // When not dismissible, let back propagate (e.g. navigation goes back to the previous screen)
477
+ // When not dismissible, report the attempt. An attempt handler consumes the back
478
+ // press; otherwise let it propagate (e.g. navigation goes back to the previous screen)
396
479
  if (this.props.dismissible === false) {
397
- return this.props.onBackPress?.() ?? false;
480
+ const { onDismissAttempt, onBackPress } = this.props;
481
+ onDismissAttempt?.({ nativeEvent: null } as DismissAttemptEvent);
482
+ return onBackPress?.() ?? onDismissAttempt != null;
398
483
  }
399
484
 
400
- TrueSheetModule?.handleBackPress(nodeHandle);
485
+ // Rejects when the native view is gone but JS hasn't unmounted yet (e.g. the surface
486
+ // stopped while this back press was queued). Nothing is left to dismiss.
487
+ TrueSheetModule?.handleBackPress(nodeHandle).catch(() => {});
401
488
  return this.props.onBackPress?.() ?? true;
402
489
  }
403
490
 
@@ -414,20 +501,67 @@ export class TrueSheet
414
501
  );
415
502
  }
416
503
 
504
+ // Still waiting on the lazy mount — like native, a second present is a no-op
505
+ if (this.presentationResolver) {
506
+ console.warn('TrueSheet: sheet is already presented. Use resize() to change detent.');
507
+ return;
508
+ }
509
+
417
510
  this.isPresenting = true;
418
511
 
419
512
  // Lazy load: render native view if not already rendered
420
- if (!this.state.shouldRenderNativeView) {
421
- await new Promise<void>((resolve) => {
513
+ const unmount = this.nativeViewUnmount;
514
+ if (unmount || !this.state.shouldRenderNativeView) {
515
+ const mounted = await new Promise<boolean>((resolve) => {
422
516
  this.presentationResolver = resolve;
423
- this.setState({ shouldRenderNativeView: true });
517
+
518
+ const mount = () => {
519
+ if (this.presentationResolver === resolve) {
520
+ this.setState({ shouldRenderNativeView: true });
521
+ }
522
+ };
523
+
524
+ // An unmount from the last dismiss may not have committed yet — state still reads
525
+ // mounted, and presenting now would lose the sheet when it lands. Remount after it.
526
+ if (unmount) {
527
+ unmount.then(mount);
528
+ } else {
529
+ mount();
530
+ }
424
531
  });
532
+
533
+ // Dismissed before the native view mounted
534
+ if (!mounted) {
535
+ this.isPresenting = false;
536
+ return;
537
+ }
425
538
  }
426
539
 
427
540
  await TrueSheetModule?.presentByRef(this.handle, index, animated);
428
541
  this.isPresenting = false;
429
542
  }
430
543
 
544
+ /**
545
+ * Cancels a present still waiting on the lazy mount. Returns whether one was pending.
546
+ */
547
+ private cancelPendingPresent(): boolean {
548
+ if (!this.presentationResolver) return false;
549
+
550
+ this.presentationResolver(false);
551
+ this.presentationResolver = null;
552
+ this.unmountNativeView();
553
+ return true;
554
+ }
555
+
556
+ private unmountNativeView(): void {
557
+ this.nativeViewUnmount = new Promise((resolve) => {
558
+ this.setState({ shouldRenderNativeView: false }, () => {
559
+ this.nativeViewUnmount = null;
560
+ resolve();
561
+ });
562
+ });
563
+ }
564
+
431
565
  /**
432
566
  * Resize the sheet to a given detent index.
433
567
  * @param index - The detent index to resize to
@@ -441,6 +575,9 @@ export class TrueSheet
441
575
  * @param animated - Whether to animate the dismissal (default: true)
442
576
  */
443
577
  public async dismiss(animated: boolean = true): Promise<void> {
578
+ // Dismissing before the lazy mount finishes cancels the present — nothing was presented
579
+ if (this.cancelPendingPresent()) return;
580
+
444
581
  return TrueSheetModule?.dismissByRef(this.handle, animated);
445
582
  }
446
583
 
@@ -454,11 +591,22 @@ export class TrueSheet
454
591
  }
455
592
 
456
593
  componentDidMount(): void {
594
+ TrueSheet.mountedInstances.add(this);
457
595
  this.registerInstance();
596
+ this.updateScrollableHandle();
458
597
  }
459
598
 
460
599
  componentDidUpdate(prevProps: TrueSheetProps): void {
461
600
  this.registerInstance();
601
+ this.updateScrollableHandle();
602
+
603
+ if (
604
+ prevProps.lazy !== false &&
605
+ this.props.lazy === false &&
606
+ !this.state.shouldRenderNativeView
607
+ ) {
608
+ this.setState({ shouldRenderNativeView: true });
609
+ }
462
610
 
463
611
  // Validate when detents prop changes
464
612
  if (prevProps.detents !== this.props.detents) {
@@ -480,9 +628,12 @@ export class TrueSheet
480
628
  }
481
629
 
482
630
  componentWillUnmount(): void {
631
+ TrueSheet.mountedInstances.delete(this);
483
632
  this.unregisterInstance();
484
633
  this.backHandlerSubscription?.remove();
485
634
  this.backHandlerSubscription = null;
635
+ // Don't leave a present waiting on the lazy mount hanging
636
+ this.presentationResolver?.(false);
486
637
  this.presentationResolver = null;
487
638
  }
488
639
 
@@ -490,22 +641,27 @@ export class TrueSheet
490
641
  const {
491
642
  detents = [0.5, 1],
492
643
  backgroundColor,
644
+ detentBackgrounds,
645
+ background,
646
+ backgroundStyle,
493
647
  dismissible = true,
494
648
  draggable = true,
649
+ dismissThreshold = 'half',
495
650
  grabber = true,
496
651
  grabberOptions,
497
652
  accessibilityOptions,
498
653
  dimmed = true,
654
+ dimColor,
655
+ dimOpacity,
499
656
  initialDetentIndex = -1,
500
657
  initialDetentAnimated = true,
501
658
  dimmedDetentIndex,
502
659
  backgroundBlur,
503
- blurOptions,
504
660
  cornerRadius,
505
661
  maxContentHeight,
506
662
  maxContentWidth,
507
- anchor = 'center',
508
- anchorOffset,
663
+ placement = 'automatic',
664
+ placementOffset,
509
665
  scrollableOptions,
510
666
  footerOptions,
511
667
  presentation = 'page',
@@ -519,6 +675,7 @@ export class TrueSheet
519
675
  insetAdjustment = 'automatic',
520
676
  ...rest
521
677
  } = this.props;
678
+ delete rest.lazy;
522
679
 
523
680
  // Trim to max 3 detents and clamp fractions
524
681
  const resolvedDetents: number[] = detents.slice(0, 3).map((detent) => {
@@ -547,6 +704,43 @@ export class TrueSheet
547
704
  };
548
705
  }
549
706
 
707
+ const sheetContent = (
708
+ <>
709
+ <TrueSheetBackground background={background} backgroundStyle={backgroundStyle} />
710
+ {header && (
711
+ <TrueSheetHeaderViewNativeComponent
712
+ style={[
713
+ styles.header,
714
+ headerOptions?.position === 'absolute' && styles.absoluteHeader,
715
+ headerStyle,
716
+ ]}
717
+ >
718
+ {isValidElement(header) ? header : createElement(header)}
719
+ </TrueSheetHeaderViewNativeComponent>
720
+ )}
721
+ <TrueSheetContentViewNativeComponent style={style}>
722
+ {children}
723
+ </TrueSheetContentViewNativeComponent>
724
+ {footer && (
725
+ <TrueSheetFooterViewNativeComponent
726
+ autoBottomInset={insetAdjustment === 'automatic'}
727
+ style={[
728
+ styles.footer,
729
+ footerOptions?.position === 'absolute'
730
+ ? styles.absoluteFooter
731
+ : styles.relativeFooter,
732
+ footerStyle,
733
+ ]}
734
+ >
735
+ {isValidElement(footer) ? footer : createElement(footer)}
736
+ </TrueSheetFooterViewNativeComponent>
737
+ )}
738
+ {autoPresent && !this.state.initialPresentReady && (
739
+ <InitialPresentGate onReady={this.onInitialPresentReady} />
740
+ )}
741
+ </>
742
+ );
743
+
550
744
  return (
551
745
  <TrueSheetViewNativeComponent
552
746
  {...rest}
@@ -554,27 +748,38 @@ export class TrueSheet
554
748
  style={styles.sheetView}
555
749
  detents={resolvedDetents}
556
750
  backgroundBlur={backgroundBlur}
557
- blurOptions={blurOptions}
558
751
  backgroundColor={backgroundColor}
752
+ detentBackgrounds={
753
+ detentBackgrounds &&
754
+ Array.from(detentBackgrounds.slice(0, 3), (entry) => {
755
+ const normalized = normalizeDetentBackground(entry);
756
+ return normalized ? { ...normalized, color: processColor(normalized.color) } : {};
757
+ })
758
+ }
559
759
  cornerRadius={cornerRadius}
560
760
  grabber={grabber}
561
761
  grabberOptions={this.resolvedGrabberOptions}
562
762
  accessibilityOptions={accessibilityOptions}
563
763
  dimmed={dimmed}
764
+ dimColor={dimColor}
765
+ dimOpacity={dimOpacity}
564
766
  dimmedDetentIndex={dimmedDetentIndex}
565
767
  initialDetentIndex={gatedInitialDetentIndex}
566
768
  initialDetentAnimated={initialDetentAnimated}
567
769
  dismissible={dismissible}
568
770
  draggable={draggable}
771
+ dismissThreshold={dismissThreshold}
569
772
  maxContentHeight={maxContentHeight}
570
773
  maxContentWidth={maxContentWidth}
571
- anchor={anchor}
572
- anchorOffset={anchorOffset}
774
+ placement={placement}
775
+ placementOffset={placementOffset}
776
+ scrollableHandle={this.state.scrollableHandle ?? -1}
573
777
  scrollableOptions={scrollableOptions}
574
778
  headerOptions={headerOptions}
575
779
  footerOptions={{
576
780
  footerPosition: footerOptions?.position,
577
781
  keyboardOffset: footerOptions?.keyboardOffset,
782
+ avoidKeyboard: footerOptions?.avoidKeyboard,
578
783
  }}
579
784
  presentation={presentation}
580
785
  insetAdjustment={insetAdjustment}
@@ -583,6 +788,7 @@ export class TrueSheet
583
788
  onDidPresent={this.onDidPresent}
584
789
  onWillDismiss={this.onWillDismiss}
585
790
  onDidDismiss={this.onDidDismiss}
791
+ onDismissAttempt={this.onDismissAttempt}
586
792
  onDetentChange={this.onDetentChange}
587
793
  onDragBegin={this.onDragBegin}
588
794
  onDragChange={this.onDragChange}
@@ -595,10 +801,11 @@ export class TrueSheet
595
801
  onVisibilityChange={this.onVisibilityChange}
596
802
  // These must stay on this host view, not on the container. The container is
597
803
  // reparented into the native sheet, so this view is never a native ancestor of
598
- // the sheet content. Claiming the responder here stops the negotiation at the
599
- // sheet boundary without making an ancestor of the content the JS responder,
600
- // which would block scrolling inside the sheet.
601
- onStartShouldSetResponder={absorbUnclaimedTouches}
804
+ // the sheet content — this is the React-tree boundary between the sheet and
805
+ // the presenting screen, where both the responder negotiation and raw touch
806
+ // bubbling must stop.
807
+ onStartShouldSetResponder={stopResponderNegotiation}
808
+ onMoveShouldSetResponder={stopResponderNegotiation}
602
809
  onTouchStart={stopTouchPropagation}
603
810
  onTouchMove={stopTouchPropagation}
604
811
  onTouchEnd={stopTouchPropagation}
@@ -606,36 +813,22 @@ export class TrueSheet
606
813
  >
607
814
  {this.state.shouldRenderNativeView && (
608
815
  <TrueSheetContainerViewNativeComponent style={styles.container}>
609
- {header && (
610
- <TrueSheetHeaderViewNativeComponent
611
- style={[
612
- styles.header,
613
- headerOptions?.position === 'absolute' && styles.absoluteHeader,
614
- headerStyle,
615
- ]}
616
- >
617
- {isValidElement(header) ? header : createElement(header)}
618
- </TrueSheetHeaderViewNativeComponent>
619
- )}
620
- <TrueSheetContentViewNativeComponent style={style}>
621
- {children}
622
- </TrueSheetContentViewNativeComponent>
623
- {footer && (
624
- <TrueSheetFooterViewNativeComponent
625
- autoBottomInset={insetAdjustment === 'automatic'}
626
- style={[
627
- styles.footer,
628
- footerOptions?.position === 'absolute'
629
- ? styles.absoluteFooter
630
- : styles.relativeFooter,
631
- footerStyle,
632
- ]}
816
+ {AppContainer ? (
817
+ // Hosts the element inspector inside the sheet, like RN's Modal does —
818
+ // the app-level one sits behind the presented sheet.
819
+ // `display: contents` keeps AppContainer's wrapper views out of both
820
+ // Yoga layout and the native tree, so header/content/footer remain the
821
+ // container's direct native children, while the inspector overlay
822
+ // anchors to the container and lines up with measured frames.
823
+ <AppContainer
824
+ rootTag={this.context as RootTag}
825
+ rootViewStyle={styles.inspectorHost}
826
+ internal_excludeLogBox
633
827
  >
634
- {isValidElement(footer) ? footer : createElement(footer)}
635
- </TrueSheetFooterViewNativeComponent>
636
- )}
637
- {autoPresent && !this.state.initialPresentReady && (
638
- <InitialPresentGate onReady={this.onInitialPresentReady} />
828
+ {sheetContent}
829
+ </AppContainer>
830
+ ) : (
831
+ sheetContent
639
832
  )}
640
833
  </TrueSheetContainerViewNativeComponent>
641
834
  )}
@@ -650,13 +843,15 @@ TrueSheet satisfies TrueSheetStaticMethods;
650
843
  const styles = StyleSheet.create({
651
844
  sheetView: {
652
845
  ...StyleSheet.absoluteFill,
653
- zIndex: -9999,
654
- pointerEvents: 'box-none',
846
+ pointerEvents: 'none',
655
847
  },
656
848
  // Fill the sheet so flex layouts track the sheet's size per detent
657
849
  container: {
658
850
  ...StyleSheet.absoluteFill,
659
851
  },
852
+ inspectorHost: {
853
+ display: 'contents',
854
+ },
660
855
  header: {
661
856
  pointerEvents: 'box-none',
662
857
  },