@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
@@ -1,10 +1,13 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.view.WindowManager
4
+ import com.facebook.react.bridge.ColorPropConverter
4
5
  import com.facebook.react.bridge.ReadableArray
5
6
  import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.bridge.ReadableType
6
8
  import com.facebook.react.module.annotations.ReactModule
7
9
  import com.facebook.react.uimanager.PixelUtil.dpToPx
10
+ import com.facebook.react.uimanager.PointerEvents
8
11
  import com.facebook.react.uimanager.ReactStylesDiffMap
9
12
  import com.facebook.react.uimanager.StateWrapper
10
13
  import com.facebook.react.uimanager.ThemedReactContext
@@ -55,6 +58,20 @@ class TrueSheetViewManager :
55
58
 
56
59
  override fun getDelegate(): ViewManagerDelegate<TrueSheetView> = delegate
57
60
 
61
+ // The codegen delegate drops props outside the spec (pointerEvents comes from the
62
+ // host's style), so apply it here after the delegate pass.
63
+ override fun updateProperties(viewToUpdate: TrueSheetView, props: ReactStylesDiffMap) {
64
+ super.updateProperties(viewToUpdate, props)
65
+ if (props.hasKey("pointerEvents")) {
66
+ setPointerEvents(viewToUpdate, props.getString("pointerEvents"))
67
+ }
68
+ }
69
+
70
+ @ReactProp(name = "pointerEvents")
71
+ fun setPointerEvents(view: TrueSheetView, pointerEventsStr: String?) {
72
+ view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
73
+ }
74
+
58
75
  /**
59
76
  * Export custom direct event types for Fabric
60
77
  * Uses Kotlin native collections with decoupled event classes
@@ -66,6 +83,7 @@ class TrueSheetViewManager :
66
83
  DidPresentEvent.EVENT_NAME to hashMapOf("registrationName" to DidPresentEvent.REGISTRATION_NAME),
67
84
  WillDismissEvent.EVENT_NAME to hashMapOf("registrationName" to WillDismissEvent.REGISTRATION_NAME),
68
85
  DidDismissEvent.EVENT_NAME to hashMapOf("registrationName" to DidDismissEvent.REGISTRATION_NAME),
86
+ DismissAttemptEvent.EVENT_NAME to hashMapOf("registrationName" to DismissAttemptEvent.REGISTRATION_NAME),
69
87
  DetentChangeEvent.EVENT_NAME to hashMapOf("registrationName" to DetentChangeEvent.REGISTRATION_NAME),
70
88
  DragBeginEvent.EVENT_NAME to hashMapOf("registrationName" to DragBeginEvent.REGISTRATION_NAME),
71
89
  DragChangeEvent.EVENT_NAME to hashMapOf("registrationName" to DragChangeEvent.REGISTRATION_NAME),
@@ -98,6 +116,21 @@ class TrueSheetViewManager :
98
116
  view.setDetents(detents)
99
117
  }
100
118
 
119
+ @ReactProp(name = "detentBackgrounds")
120
+ override fun setDetentBackgrounds(view: TrueSheetView, value: ReadableArray?) {
121
+ view.setDetentBackgrounds(
122
+ (0 until (value?.size() ?: 0)).map { index ->
123
+ val entry = value?.getMap(index)
124
+ if (entry != null && entry.hasKey("color") && !entry.isNull("color")) {
125
+ val color = if (entry.getType("color") == ReadableType.Map) entry.getMap("color") else entry.getDouble("color")
126
+ ColorPropConverter.getColor(color, view.context)
127
+ } else {
128
+ null
129
+ }
130
+ }
131
+ )
132
+ }
133
+
101
134
  @ReactProp(name = "backgroundColor", customType = "Color")
102
135
  override fun setBackgroundColor(view: TrueSheetView, color: Int?) {
103
136
  view.setSheetBackgroundColor(color)
@@ -166,6 +199,11 @@ class TrueSheetViewManager :
166
199
  view.setDraggable(draggable)
167
200
  }
168
201
 
202
+ @ReactProp(name = "dismissThreshold")
203
+ override fun setDismissThreshold(view: TrueSheetView, value: String?) {
204
+ view.setDismissThreshold(value)
205
+ }
206
+
169
207
  @ReactProp(name = "dimmed", defaultBoolean = true)
170
208
  override fun setDimmed(view: TrueSheetView, dimmed: Boolean) {
171
209
  view.setDimmed(dimmed)
@@ -179,6 +217,7 @@ class TrueSheetViewManager :
179
217
  @ReactProp(name = "initialDetentIndex", defaultInt = -1)
180
218
  override fun setInitialDetentIndex(view: TrueSheetView, index: Int) {
181
219
  view.initialDetentIndex = index
220
+ view.presentInitialIfNeeded()
182
221
  }
183
222
 
184
223
  @ReactProp(name = "initialDetentAnimated", defaultBoolean = true)
@@ -196,14 +235,14 @@ class TrueSheetViewManager :
196
235
  view.setMaxContentWidth(if (width > 0) width.dpToPx().toInt() else null)
197
236
  }
198
237
 
199
- @ReactProp(name = "anchor")
200
- override fun setAnchor(view: TrueSheetView, anchor: String?) {
201
- view.setAnchor(anchor)
238
+ @ReactProp(name = "placement")
239
+ override fun setPlacement(view: TrueSheetView, placement: String?) {
240
+ view.setPlacement(placement)
202
241
  }
203
242
 
204
- @ReactProp(name = "anchorOffset", defaultDouble = 0.0)
205
- override fun setAnchorOffset(view: TrueSheetView, offset: Double) {
206
- view.setAnchorOffset(if (offset > 0) offset.dpToPx().toInt() else 0)
243
+ @ReactProp(name = "placementOffset", defaultDouble = 0.0)
244
+ override fun setPlacementOffset(view: TrueSheetView, offset: Double) {
245
+ view.setPlacementOffset(if (offset > 0) offset.dpToPx().toInt() else 0)
207
246
  }
208
247
 
209
248
  @ReactProp(name = "backgroundBlur")
@@ -211,11 +250,6 @@ class TrueSheetViewManager :
211
250
  // iOS-specific prop - no-op on Android
212
251
  }
213
252
 
214
- @ReactProp(name = "blurOptions")
215
- override fun setBlurOptions(view: TrueSheetView, options: ReadableMap?) {
216
- // iOS-specific prop - no-op on Android
217
- }
218
-
219
253
  @ReactProp(name = "insetAdjustment")
220
254
  override fun setInsetAdjustment(view: TrueSheetView, insetAdjustment: String?) {
221
255
  view.setInsetAdjustment(insetAdjustment ?: "automatic")
@@ -231,6 +265,11 @@ class TrueSheetViewManager :
231
265
  view.setSheetElevation(elevation.toFloat())
232
266
  }
233
267
 
268
+ @ReactProp(name = "scrollableHandle", defaultInt = -1)
269
+ override fun setScrollableHandle(view: TrueSheetView, handle: Int) {
270
+ view.setScrollableHandle(handle)
271
+ }
272
+
234
273
  @ReactProp(name = "scrollableOptions")
235
274
  override fun setScrollableOptions(view: TrueSheetView, options: ReadableMap?) {
236
275
  if (options == null) {
@@ -240,7 +279,11 @@ class TrueSheetViewManager :
240
279
 
241
280
  val scrollableOptions = ScrollableOptions(
242
281
  keyboardScrollOffset = if (options.hasKey("keyboardScrollOffset")) options.getDouble("keyboardScrollOffset").toFloat() else 0f,
243
- scrollingExpandsSheet = if (options.hasKey("scrollingExpandsSheet")) options.getBoolean("scrollingExpandsSheet") else true
282
+ keyboardOffset = if (options.hasKey("keyboardOffset")) options.getDouble("keyboardOffset").toFloat() else 0f,
283
+ scrollingExpandsSheet = if (options.hasKey("scrollingExpandsSheet")) options.getBoolean("scrollingExpandsSheet") else true,
284
+ contentInsetAdjustment = TrueSheetContentInsetAdjustment.fromString(
285
+ if (options.hasKey("contentInsetAdjustment")) options.getString("contentInsetAdjustment") else null
286
+ )
244
287
  )
245
288
  view.setScrollableOptions(scrollableOptions)
246
289
  }
@@ -259,6 +302,10 @@ class TrueSheetViewManager :
259
302
  val keyboardOffset =
260
303
  if (options != null && options.hasKey("keyboardOffset")) options.getDouble("keyboardOffset").toFloat() else 0f
261
304
  view.setFooterKeyboardOffset(keyboardOffset)
305
+
306
+ val avoidKeyboard =
307
+ if (options != null && options.hasKey("avoidKeyboard")) options.getBoolean("avoidKeyboard") else true
308
+ view.setFooterAvoidsKeyboard(avoidKeyboard)
262
309
  }
263
310
 
264
311
  companion object {
@@ -1,11 +1,73 @@
1
1
  package com.lodev09.truesheet.core
2
2
 
3
+ import android.view.MotionEvent
4
+ import android.view.VelocityTracker
3
5
  import android.view.View
4
6
  import androidx.coordinatorlayout.widget.CoordinatorLayout
7
+ import androidx.core.view.ViewCompat
8
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
5
9
  import com.google.android.material.bottomsheet.BottomSheetBehavior
10
+ import kotlin.math.abs
11
+
12
+ enum class TrueSheetDismissThreshold {
13
+ HALF,
14
+ SHORT;
15
+
16
+ companion object {
17
+ fun fromString(value: String?): TrueSheetDismissThreshold =
18
+ when (value) {
19
+ "short" -> SHORT
20
+ else -> HALF
21
+ }
22
+ }
23
+ }
6
24
 
7
25
  class TrueSheetBottomSheetBehavior<V : View> : BottomSheetBehavior<V>() {
8
26
  var scrollingExpandsSheet: Boolean = true
27
+ var dismissThreshold: TrueSheetDismissThreshold = TrueSheetDismissThreshold.HALF
28
+ var lowestSheetTop: (() -> Int)? = null
29
+
30
+ private var nestedDragged = false
31
+ private var nestedReleaseVelocity = 0f
32
+ private var touchPointerId = MotionEvent.INVALID_POINTER_ID
33
+ private var trackedEventTime = -1L
34
+ private var trackedAction = -1
35
+ private var velocityTracker: VelocityTracker? = null
36
+
37
+ init {
38
+ // Material hides once the release point, projected by velocity * friction, passes half
39
+ // the lowest detent. A higher friction lets short flicks dismiss, closer to iOS sheets.
40
+ hideFriction = HIDE_FRICTION
41
+ }
42
+
43
+ // With the short threshold, settle at or below the lowest detent like
44
+ // Compose Material3's sheet instead of Material's half-height rule.
45
+ private fun releaseState(child: V, dragged: Boolean, velocityY: Float): Int? {
46
+ if (dismissThreshold != TrueSheetDismissThreshold.SHORT || !isDraggable || !dragged) return null
47
+ val lowestTop = lowestSheetTop?.invoke() ?: return null
48
+ if (child.top < lowestTop) return null
49
+ val hides = if (abs(velocityY) > DISMISS_VELOCITY_DP.dpToPx()) {
50
+ velocityY > 0
51
+ } else {
52
+ child.top - lowestTop > DISMISS_DISTANCE_DP.dpToPx()
53
+ }
54
+ return if (hides && isHideable) STATE_HIDDEN else STATE_COLLAPSED
55
+ }
56
+
57
+ override fun onStartNestedScroll(
58
+ coordinatorLayout: CoordinatorLayout,
59
+ child: V,
60
+ directTargetChild: View,
61
+ target: View,
62
+ axes: Int,
63
+ type: Int
64
+ ): Boolean {
65
+ if (type == ViewCompat.TYPE_TOUCH) {
66
+ nestedDragged = false
67
+ nestedReleaseVelocity = 0f
68
+ }
69
+ return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
70
+ }
9
71
 
10
72
  override fun onNestedPreScroll(
11
73
  coordinatorLayout: CoordinatorLayout,
@@ -20,6 +82,13 @@ class TrueSheetBottomSheetBehavior<V : View> : BottomSheetBehavior<V>() {
20
82
  // Block expansion from scroll, but allow if sheet is already being dragged
21
83
  if (!scrollingExpandsSheet && dy > 0 && state != STATE_DRAGGING) return
22
84
  super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
85
+ if (type == ViewCompat.TYPE_TOUCH) {
86
+ if (state == STATE_EXPANDED) {
87
+ nestedDragged = false
88
+ } else if (consumed[1] != 0) {
89
+ nestedDragged = true
90
+ }
91
+ }
23
92
  }
24
93
 
25
94
  override fun onNestedPreFling(
@@ -29,8 +98,65 @@ class TrueSheetBottomSheetBehavior<V : View> : BottomSheetBehavior<V>() {
29
98
  velocityX: Float,
30
99
  velocityY: Float
31
100
  ): Boolean {
101
+ nestedReleaseVelocity = -velocityY
102
+ if (releaseState(child, nestedDragged, nestedReleaseVelocity) != null) return true
32
103
  // Don't consume flings — let the ScrollView decelerate naturally
33
104
  if (!scrollingExpandsSheet) return false
34
105
  return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
35
106
  }
107
+
108
+ override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
109
+ if (type == ViewCompat.TYPE_TOUCH) {
110
+ releaseState(child, nestedDragged, nestedReleaseVelocity)?.let {
111
+ nestedDragged = false
112
+ state = it
113
+ return
114
+ }
115
+ }
116
+ super.onStopNestedScroll(coordinatorLayout, child, target, type)
117
+ }
118
+
119
+ private fun trackTouch(event: MotionEvent) {
120
+ if (dismissThreshold != TrueSheetDismissThreshold.SHORT) return
121
+ if (event.eventTime == trackedEventTime && event.actionMasked == trackedAction) return
122
+ trackedEventTime = event.eventTime
123
+ trackedAction = event.actionMasked
124
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
125
+ velocityTracker?.recycle()
126
+ velocityTracker = VelocityTracker.obtain()
127
+ touchPointerId = event.getPointerId(0)
128
+ }
129
+ velocityTracker?.addMovement(event)
130
+ }
131
+
132
+ override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
133
+ trackTouch(event)
134
+ return super.onInterceptTouchEvent(parent, child, event)
135
+ }
136
+
137
+ override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
138
+ trackTouch(event)
139
+ val wasDragging = state == STATE_DRAGGING
140
+ val handled = super.onTouchEvent(parent, child, event)
141
+ if (event.actionMasked == MotionEvent.ACTION_UP) {
142
+ val velocityY = velocityTracker?.run {
143
+ computeCurrentVelocity(1000)
144
+ getYVelocity(touchPointerId)
145
+ } ?: 0f
146
+ releaseState(child, wasDragging, velocityY)?.let { state = it }
147
+ }
148
+ if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
149
+ velocityTracker?.recycle()
150
+ velocityTracker = null
151
+ }
152
+ return handled
153
+ }
154
+
155
+ companion object {
156
+ private const val HIDE_FRICTION = 0.3f
157
+
158
+ // Compose Material3 SheetState's velocity and positional thresholds
159
+ private const val DISMISS_VELOCITY_DP = 125f
160
+ private const val DISMISS_DISTANCE_DP = 56f
161
+ }
36
162
  }
@@ -18,7 +18,7 @@ import androidx.core.view.ViewCompat
18
18
  import com.facebook.react.uimanager.PixelUtil.dpToPx
19
19
  import com.facebook.react.uimanager.ThemedReactContext
20
20
  import com.google.android.material.bottomsheet.BottomSheetBehavior
21
- import com.lodev09.truesheet.TrueSheetAnchor
21
+ import com.lodev09.truesheet.TrueSheetPlacement
22
22
  import com.lodev09.truesheet.utils.ScreenUtils
23
23
 
24
24
  interface TrueSheetBottomSheetViewDelegate {
@@ -27,8 +27,8 @@ interface TrueSheetBottomSheetViewDelegate {
27
27
  val sheetElevation: Float
28
28
  val sheetBackgroundColor: Int?
29
29
  val maxContentWidth: Int?
30
- val anchor: TrueSheetAnchor
31
- val anchorOffset: Int
30
+ val placement: TrueSheetPlacement
31
+ val placementOffset: Int
32
32
  val grabber: Boolean
33
33
  val grabberOptions: GrabberOptions?
34
34
  val accessibilityOptions: AccessibilityOptions?
@@ -94,13 +94,19 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
94
94
  // =============================================================================
95
95
 
96
96
  private fun resolveAnchor(): Pair<Int, Int> {
97
- val anchor = if (ScreenUtils.isPortraitPhone(reactContext)) null else delegate?.anchor
98
- val gravity = when (anchor) {
99
- TrueSheetAnchor.LEFT -> Gravity.START
100
- TrueSheetAnchor.RIGHT -> Gravity.END
97
+ val placement = if (ScreenUtils.isPortraitPhone(reactContext)) null else delegate?.placement
98
+ val gravity = when (placement) {
99
+ TrueSheetPlacement.LEADING -> Gravity.START
100
+ TrueSheetPlacement.TRAILING -> Gravity.END
101
101
  else -> Gravity.CENTER_HORIZONTAL
102
102
  } or Gravity.BOTTOM
103
- val margin = if (anchor == TrueSheetAnchor.LEFT || anchor == TrueSheetAnchor.RIGHT) delegate?.anchorOffset ?: 0 else 0
103
+ val margin = if (placement == TrueSheetPlacement.LEADING ||
104
+ placement == TrueSheetPlacement.TRAILING
105
+ ) {
106
+ delegate?.placementOffset ?: 0
107
+ } else {
108
+ 0
109
+ }
104
110
  return Pair(gravity, margin)
105
111
  }
106
112
 
@@ -173,7 +179,14 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
173
179
  clipToOutline = true
174
180
  }
175
181
 
176
- private fun getDefaultBackgroundColor(): Int {
182
+ fun updateBackgroundColor(color: Int) {
183
+ (background as? ShapeDrawable)?.let {
184
+ it.paint.color = color
185
+ it.invalidateSelf()
186
+ }
187
+ }
188
+
189
+ fun getDefaultBackgroundColor(): Int {
177
190
  val isNight = (
178
191
  reactContext.resources.configuration.uiMode and
179
192
  Configuration.UI_MODE_NIGHT_MASK
@@ -10,9 +10,11 @@ import android.view.ViewGroup
10
10
  import android.widget.EditText
11
11
  import androidx.coordinatorlayout.widget.CoordinatorLayout
12
12
  import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
13
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
13
14
  import com.facebook.react.uimanager.PointerEvents
14
15
  import com.facebook.react.uimanager.ReactPointerEventsView
15
16
  import com.facebook.react.uimanager.TouchTargetHelper
17
+ import com.lodev09.truesheet.utils.KeyboardUtils
16
18
  import com.lodev09.truesheet.utils.isDescendantOf
17
19
 
18
20
  interface TrueSheetCoordinatorLayoutDelegate {
@@ -20,6 +22,12 @@ interface TrueSheetCoordinatorLayoutDelegate {
20
22
  fun coordinatorLayoutDidChangeConfiguration()
21
23
  fun findScrollView(): ViewGroup?
22
24
  fun findSheetView(): TrueSheetBottomSheetView?
25
+
26
+ /**
27
+ * A downward pull the sheet couldn't follow ended; `overdragPx` is the travel it
28
+ * didn't follow, `keyboardWasVisible` whether the keyboard was up when it began.
29
+ */
30
+ fun coordinatorLayoutDidPullDown(overdragPx: Int, keyboardWasVisible: Boolean)
23
31
  }
24
32
 
25
33
  /**
@@ -57,13 +65,26 @@ class TrueSheetCoordinatorLayout(context: Context) :
57
65
  private var streamGestureClaimed = false
58
66
 
59
67
  // Whether the sheet owns vertical drags for this stream: the touch target is inside
60
- // the sheet but outside the pinned scrollable (e.g. a header overlaying it), with
68
+ // the sheet but outside the detected scrollable (e.g. a header overlaying it), with
61
69
  // nothing vertically scrollable along its chain. Such targets consume the stream
62
70
  // natively (no nested scroll), and BottomSheetBehavior won't intercept because the
63
71
  // scrollable's bounds still contain the point (touchingScrollingChild) — intercept
64
72
  // manually.
65
73
  private var streamSheetDraggable = false
66
74
  private var streamDraggableEditText = false
75
+ private var streamScrollsIndependently = false
76
+
77
+ // Pull-down tracking. BottomSheetBehavior gives no callback when a downward drag
78
+ // is clamped by `isHideable = false` — the sheet stays put (or the scrollable
79
+ // overscrolls) and no state change fires. Accumulate the downward finger travel
80
+ // neither the sheet nor its scrollable followed and report it on release so the
81
+ // controller can surface a blocked dismiss attempt.
82
+ private var pullTracking = false
83
+ private var pullStartX = 0f
84
+ private var pullStartY = 0f
85
+ private var pullLastY = 0f
86
+ private var pullOverdrag = 0f
87
+ private var pullKeyboardVisible = false
67
88
 
68
89
  init {
69
90
  layoutParams = LayoutParams(
@@ -94,6 +115,49 @@ class TrueSheetCoordinatorLayout(context: Context) :
94
115
  override val pointerEvents: PointerEvents
95
116
  get() = PointerEvents.BOX_NONE
96
117
 
118
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
119
+ when (ev.actionMasked) {
120
+ MotionEvent.ACTION_DOWN -> {
121
+ val sheet = delegate?.findSheetView()
122
+ pullTracking = sheet != null && isTouchInside(sheet, ev)
123
+ pullStartX = ev.rawX
124
+ pullStartY = ev.rawY
125
+ pullLastY = ev.rawY
126
+ pullOverdrag = 0f
127
+ pullKeyboardVisible = pullTracking && KeyboardUtils.isKeyboardVisible(this)
128
+ }
129
+
130
+ MotionEvent.ACTION_MOVE -> if (pullTracking) {
131
+ val sheet = delegate?.findSheetView()
132
+ val topBefore = sheet?.top
133
+ val handled = super.dispatchTouchEvent(ev)
134
+ val step = ev.rawY - pullLastY
135
+ pullLastY = ev.rawY
136
+ // Finger moved down but the sheet stayed put and the scrollable is at
137
+ // its top — the pull was clamped
138
+ if (step > 0 && sheet != null && sheet.top == topBefore && delegate?.findScrollView()?.canScrollVertically(-1) != true) {
139
+ pullOverdrag += step
140
+ }
141
+ return handled
142
+ }
143
+
144
+ MotionEvent.ACTION_UP -> if (pullTracking) {
145
+ pullTracking = false
146
+ val dx = kotlin.math.abs(ev.rawX - pullStartX)
147
+ val dy = ev.rawY - pullStartY
148
+ // Let the behavior settle first so the controller sees the resting position.
149
+ val handled = super.dispatchTouchEvent(ev)
150
+ if (dy > dx && pullOverdrag > PULL_DOWN_THRESHOLD_DP.dpToPx()) {
151
+ delegate?.coordinatorLayoutDidPullDown(pullOverdrag.toInt(), pullKeyboardVisible)
152
+ }
153
+ return handled
154
+ }
155
+
156
+ MotionEvent.ACTION_CANCEL -> pullTracking = false
157
+ }
158
+ return super.dispatchTouchEvent(ev)
159
+ }
160
+
97
161
  /**
98
162
  * Clears stale `nestedScrollingChildRef` from BottomSheetBehavior.
99
163
  *
@@ -127,19 +191,20 @@ class TrueSheetCoordinatorLayout(context: Context) :
127
191
 
128
192
  /**
129
193
  * Resolves the stream's touch target (respecting zIndex and pointerEvents) and
130
- * decides whether the sheet may drag from it — inside the sheet, outside the pinned
194
+ * decides whether the sheet may drag from it — inside the sheet, outside the detected
131
195
  * scrollable, and nothing along the target's chain scrolls vertically.
132
196
  */
133
197
  private fun updateStreamTargetFlags(ev: MotionEvent) {
134
198
  streamSheetDraggable = false
135
199
  streamDraggableEditText = false
200
+ streamScrollsIndependently = false
136
201
 
137
202
  val sheet = delegate?.findSheetView() ?: return
138
203
  val target = TouchTargetHelper.findTargetPathAndCoordinatesForTouch(ev.x, ev.y, this, FloatArray(2))
139
204
  .firstNotNullOfOrNull { it.getView() } ?: return
140
205
  if (target !== sheet && !target.isDescendantOf(sheet)) return
141
206
 
142
- // A pinned scrollable that can scroll (or hosts pull-to-refresh) owns its
207
+ // A detected scrollable that can scroll (or hosts pull-to-refresh) owns its
143
208
  // touches — nested scrolling drags the sheet. When it can't scroll (e.g.
144
209
  // the sheet is fully expanded and the content fits) it eats the gesture
145
210
  // instead, so fall through and let the sheet drag from it — an EditText
@@ -152,7 +217,10 @@ class TrueSheetCoordinatorLayout(context: Context) :
152
217
 
153
218
  var view: View? = target
154
219
  while (view != null && view !== this) {
155
- if (view.canScrollVertically(1) || view.canScrollVertically(-1)) return
220
+ if (view.canScrollVertically(1) || view.canScrollVertically(-1)) {
221
+ streamScrollsIndependently = !view.isNestedScrollingEnabled
222
+ return
223
+ }
156
224
  view = view.parent as? View
157
225
  }
158
226
 
@@ -160,6 +228,14 @@ class TrueSheetCoordinatorLayout(context: Context) :
160
228
  streamDraggableEditText = target is EditText
161
229
  }
162
230
 
231
+ private fun isTouchInside(view: View, ev: MotionEvent): Boolean {
232
+ val loc = IntArray(2)
233
+ view.getLocationOnScreen(loc)
234
+ val x = ev.rawX.toInt()
235
+ val y = ev.rawY.toInt()
236
+ return x >= loc[0] && x <= loc[0] + view.width && y >= loc[1] && y <= loc[1] + view.height
237
+ }
238
+
163
239
  private fun findDescendantViewAt(view: View, rawX: Int, rawY: Int, predicate: (View) -> Boolean): View? {
164
240
  val loc = IntArray(2)
165
241
  view.getLocationOnScreen(loc)
@@ -174,7 +250,7 @@ class TrueSheetCoordinatorLayout(context: Context) :
174
250
  }
175
251
 
176
252
  /**
177
- * Called by descendant RootView implementations (controller, footer) when a child
253
+ * Called by the descendant RootView (controller) when a child
178
254
  * starts a native gesture — e.g. an RNGH handler activated or a scrollable began
179
255
  * scrolling. Yields sheet interception for the rest of the touch stream.
180
256
  */
@@ -226,6 +302,8 @@ class TrueSheetCoordinatorLayout(context: Context) :
226
302
  return false
227
303
  }
228
304
 
305
+ if (streamScrollsIndependently) return false
306
+
229
307
  if (streamGestureClaimed) {
230
308
  if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
231
309
  streamGestureClaimed = false
@@ -299,5 +377,6 @@ class TrueSheetCoordinatorLayout(context: Context) :
299
377
  companion object {
300
378
  private const val GESTURE_HANDLER_ROOT_VIEW_CLASS = "com.swmansion.gesturehandler.react.RNGestureHandlerRootView"
301
379
  private const val GESTURE_CLAIM_SLOP_FACTOR = 3
380
+ private const val PULL_DOWN_THRESHOLD_DP = 24f
302
381
  }
303
382
  }