@lodev09/react-native-true-sheet 4.0.0-beta.16 → 4.0.0-beta.18

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.
@@ -4,11 +4,9 @@ import android.annotation.SuppressLint
4
4
  import android.view.View
5
5
  import android.view.ViewGroup
6
6
  import com.facebook.react.uimanager.ThemedReactContext
7
- import com.facebook.react.uimanager.events.EventDispatcher
8
7
  import com.facebook.react.views.view.ReactViewGroup
9
8
 
10
9
  interface TrueSheetContainerViewDelegate {
11
- val eventDispatcher: EventDispatcher?
12
10
  fun containerViewContentDidChangeSize(width: Int, height: Int)
13
11
  fun containerViewContentDidScroll()
14
12
  fun containerViewScrollViewDidChange()
@@ -107,9 +105,6 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
107
105
  contentView?.bottomInset = value
108
106
  }
109
107
 
110
- override val eventDispatcher: EventDispatcher?
111
- get() = delegate?.eventDispatcher
112
-
113
108
  init {
114
109
  // Allow footer to position outside container bounds
115
110
  clipChildren = false
@@ -1,29 +1,18 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
- import android.view.MotionEvent
5
- import android.view.View
6
- import android.view.ViewParent
7
4
  import com.facebook.react.bridge.WritableNativeMap
8
- import com.facebook.react.uimanager.JSPointerDispatcher
9
- import com.facebook.react.uimanager.JSTouchDispatcher
10
5
  import com.facebook.react.uimanager.PixelUtil.pxToDp
11
- import com.facebook.react.uimanager.PointerEvents
12
- import com.facebook.react.uimanager.RootView
13
6
  import com.facebook.react.uimanager.StateWrapper
14
7
  import com.facebook.react.uimanager.ThemedReactContext
15
- import com.facebook.react.uimanager.events.EventDispatcher
16
8
  import com.facebook.react.views.view.ReactViewGroup
17
- import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
18
- import com.lodev09.truesheet.utils.TouchEventDeduper
19
9
 
20
10
  /**
21
- * Delegate interface for footer view size changes and event dispatching
11
+ * Delegate interface for footer view size changes
22
12
  */
23
13
  interface TrueSheetFooterViewDelegate {
24
14
  fun footerViewDidChangeSize(width: Int, height: Int)
25
15
  fun footerViewDidLayout()
26
- val eventDispatcher: EventDispatcher?
27
16
  }
28
17
 
29
18
  /**
@@ -31,19 +20,16 @@ interface TrueSheetFooterViewDelegate {
31
20
  * This is the second child of TrueSheetContainerView
32
21
  * Positioned absolutely at the bottom of the sheet
33
22
  *
34
- * Implements RootView to handle touch events when positioned outside parent bounds.
23
+ * Touches reach it through the controller's RootView like any other sheet
24
+ * content — the container tracks the sheet's visible height, so the footer
25
+ * always lays out inside the container's bounds.
35
26
  */
36
27
  @SuppressLint("ViewConstructor")
37
- class TrueSheetFooterView(private val reactContext: ThemedReactContext) :
38
- ReactViewGroup(reactContext),
39
- RootView {
28
+ class TrueSheetFooterView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
40
29
 
41
30
  var delegate: TrueSheetFooterViewDelegate? = null
42
31
  var stateWrapper: StateWrapper? = null
43
32
 
44
- private val eventDispatcher: EventDispatcher?
45
- get() = delegate?.eventDispatcher
46
-
47
33
  private var lastWidth = 0
48
34
  private var lastHeight = 0
49
35
 
@@ -80,14 +66,6 @@ class TrueSheetFooterView(private val reactContext: ThemedReactContext) :
80
66
  sw.updateState(newState)
81
67
  }
82
68
 
83
- private val jsTouchDispatcher = JSTouchDispatcher(this)
84
- private var jsPointerDispatcher: JSPointerDispatcher? = null
85
- private val touchDeduper = TouchEventDeduper()
86
-
87
- init {
88
- jsPointerDispatcher = JSPointerDispatcher(this)
89
- }
90
-
91
69
  override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
92
70
  super.onSizeChanged(w, h, oldw, oldh)
93
71
 
@@ -109,74 +87,6 @@ class TrueSheetFooterView(private val reactContext: ThemedReactContext) :
109
87
  delegate?.footerViewDidLayout()
110
88
  }
111
89
 
112
- // ==================== RootView Implementation ====================
113
-
114
- override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
115
- eventDispatcher?.let { dispatcher ->
116
- if (touchDeduper.shouldDispatch(event)) {
117
- jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
118
- }
119
- jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
120
- }
121
- return super.onInterceptTouchEvent(event)
122
- }
123
-
124
- override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
125
- // Mirrors ReactSurfaceView: keep receiving onInterceptTouchEvent so
126
- // jsTouchDispatcher sees the gesture end, but forward the request up the tree.
127
- parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
128
- }
129
-
130
- override fun onTouchEvent(event: MotionEvent): Boolean {
131
- if (pointerEvents == PointerEvents.NONE || pointerEvents == PointerEvents.BOX_NONE) {
132
- return false
133
- }
134
-
135
- eventDispatcher?.let { dispatcher ->
136
- if (touchDeduper.shouldDispatch(event)) {
137
- jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
138
- }
139
- jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
140
- }
141
- super.onTouchEvent(event)
142
- return true
143
- }
144
-
145
- override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
146
- eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, true) }
147
- return super.onHoverEvent(event)
148
- }
149
-
150
- override fun onHoverEvent(event: MotionEvent): Boolean {
151
- eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, false) }
152
- return super.onHoverEvent(event)
153
- }
154
-
155
- override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
156
- findCoordinatorLayout()?.childDidClaimNativeGesture()
157
- eventDispatcher?.let { dispatcher ->
158
- jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher)
159
- jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, dispatcher)
160
- }
161
- }
162
-
163
- private fun findCoordinatorLayout(): TrueSheetCoordinatorLayout? {
164
- var current: ViewParent? = parent
165
- while (current != null && current !is TrueSheetCoordinatorLayout) {
166
- current = current.parent
167
- }
168
- return current as? TrueSheetCoordinatorLayout
169
- }
170
-
171
- override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
172
- eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
173
- jsPointerDispatcher?.onChildEndedNativeGesture()
174
- }
175
-
176
- override fun handleException(t: Throwable) {
177
- reactContext.reactApplicationContext.handleException(RuntimeException(t))
178
- }
179
-
180
90
  companion object {
181
91
  const val TAG_NAME = "TrueSheet"
182
92
  }
@@ -209,12 +209,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
209
209
  // Touch Dispatchers
210
210
  private val jsTouchDispatcher = JSTouchDispatcher(this)
211
211
  private val jsPointerDispatcher = JSPointerDispatcher(this)
212
-
213
- // True when the active touch stream began inside the footer. Latched on
214
- // ACTION_DOWN so subsequent MOVE events keep routing to the footer even if
215
- // the finger drifts outside the footer's rect mid-gesture.
216
- private var footerOwnsTouchStream = false
217
-
218
212
  private val touchDeduper = TouchEventDeduper()
219
213
 
220
214
  private val eventDispatcher
@@ -714,6 +708,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
714
708
  if (isPresentAnimating) {
715
709
  isPresentAnimating = false
716
710
  finishPresent()
711
+ flushPendingSizeChange()
717
712
  return
718
713
  }
719
714
 
@@ -1042,13 +1037,25 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
1042
1037
  animate = isPresented
1043
1038
  )
1044
1039
 
1045
- updateStateDimensions()
1046
-
1047
1040
  if (isPresented && applyState) {
1048
1041
  // Prefer the pending target while a resize animation is in flight so a
1049
1042
  // layout-driven reconfigure doesn't revert to the stale currentDetentIndex.
1050
1043
  val targetIndex = if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex
1044
+
1045
+ // Track the settle like resize() — the container (and the footer pinned
1046
+ // to its bottom) follows the sheet's visible height instead of jumping
1047
+ // to the target and riding the animation
1048
+ pendingDetentIndex = targetIndex
1049
+ sheetView?.let { updateStateDimensionsForPosition(it.top) }
1051
1050
  setStateForDetentIndex(targetIndex)
1051
+
1052
+ // Already at the target — no slide frames will follow
1053
+ if (!isSettling) {
1054
+ pendingDetentIndex = -1
1055
+ updateStateDimensions()
1056
+ }
1057
+ } else {
1058
+ updateStateDimensions()
1052
1059
  }
1053
1060
 
1054
1061
  interactionState = InteractionState.Idle
@@ -1190,9 +1197,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
1190
1197
  val sheetHeight = sheet.height
1191
1198
 
1192
1199
  // Pinned to the screen bottom (above the keyboard) while the sheet sits at
1193
- // or above its lowest stop; past it the footer rides down with the sheet
1194
- // instead, like iOS
1195
- val anchorTop = minOf(sheet.top, detentCalculator.getLowestSheetTop())
1200
+ // or above its lowest stop; dragged past it the footer rides down with the
1201
+ // sheet instead, like iOS. A resize settle always pins — the sheet may be
1202
+ // animating up from below a stop that just grew
1203
+ val anchorTop = if (pendingDetentIndex >= 0) sheet.top else minOf(sheet.top, detentCalculator.getLowestSheetTop())
1196
1204
  val footerY = (sheetHeight - anchorTop - footerHeight - keyboardShift).toFloat()
1197
1205
 
1198
1206
  // Clamp to prevent footer going above safe area
@@ -1566,41 +1574,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
1566
1574
  // MARK: - RootView Touch Handling
1567
1575
  // =============================================================================
1568
1576
 
1569
- override fun dispatchTouchEvent(event: MotionEvent): Boolean {
1570
- val footer = containerView?.footerView
1571
- val action = event.actionMasked
1572
-
1573
- if (footer != null && footer.isShown) {
1574
- if (action == MotionEvent.ACTION_DOWN) {
1575
- val loc = ScreenUtils.getScreenLocation(footer)
1576
- val x = event.rawX.toInt()
1577
- val y = event.rawY.toInt()
1578
- footerOwnsTouchStream = x >= loc[0] &&
1579
- x <= loc[0] + footer.width &&
1580
- y >= loc[1] &&
1581
- y <= loc[1] + footer.height
1582
- }
1583
-
1584
- if (footerOwnsTouchStream) {
1585
- val loc = ScreenUtils.getScreenLocation(footer)
1586
- val localEvent = MotionEvent.obtain(event)
1587
- localEvent.setLocation(event.rawX - loc[0], event.rawY - loc[1])
1588
- val handled = footer.dispatchTouchEvent(localEvent)
1589
- localEvent.recycle()
1590
-
1591
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1592
- footerOwnsTouchStream = false
1593
- }
1594
- if (handled) return true
1595
- }
1596
- }
1597
-
1598
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1599
- footerOwnsTouchStream = false
1600
- }
1601
- return super.dispatchTouchEvent(event)
1602
- }
1603
-
1604
1577
  override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
1605
1578
  eventDispatcher?.let {
1606
1579
  if (touchDeduper.shouldDispatch(event)) {
@@ -250,7 +250,7 @@ class TrueSheetCoordinatorLayout(context: Context) :
250
250
  }
251
251
 
252
252
  /**
253
- * Called by descendant RootView implementations (controller, footer) when a child
253
+ * Called by the descendant RootView (controller) when a child
254
254
  * starts a native gesture — e.g. an RNGH handler activated or a scrollable began
255
255
  * scrolling. Yields sheet interception for the rest of the touch stream.
256
256
  */
@@ -104,18 +104,6 @@ object ScreenUtils {
104
104
  */
105
105
  fun getScreenHeight(reactContext: ReactContext): Int = reactContext.resources.displayMetrics.heightPixels
106
106
 
107
- /**
108
- * Get the location of a view in screen coordinates
109
- *
110
- * @param view The view to get screen location for
111
- * @return IntArray with [x, y] coordinates in screen space
112
- */
113
- fun getScreenLocation(view: View): IntArray {
114
- val location = IntArray(2)
115
- view.getLocationOnScreen(location)
116
- return location
117
- }
118
-
119
107
  /**
120
108
  * Returns true if the device is a phone in portrait orientation.
121
109
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodev09/react-native-true-sheet",
3
- "version": "4.0.0-beta.16",
3
+ "version": "4.0.0-beta.18",
4
4
  "description": "The true native bottom sheet experience for your React Native Apps.",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",