@lodev09/react-native-true-sheet 3.0.0-beta.7 → 3.0.0-beta.8

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 (35) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +51 -49
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +10 -18
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +76 -20
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderView.kt +38 -0
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderViewManager.kt +21 -0
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +1 -0
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +81 -147
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +303 -409
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +2 -4
  10. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +116 -0
  11. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +33 -5
  12. package/ios/TrueSheetContainerView.h +20 -2
  13. package/ios/TrueSheetContainerView.mm +61 -14
  14. package/ios/TrueSheetContentView.h +4 -2
  15. package/ios/TrueSheetContentView.mm +29 -72
  16. package/ios/TrueSheetFooterView.mm +2 -2
  17. package/ios/TrueSheetHeaderView.h +29 -0
  18. package/ios/TrueSheetHeaderView.mm +60 -0
  19. package/ios/TrueSheetView.mm +178 -232
  20. package/ios/TrueSheetViewController.h +1 -2
  21. package/ios/TrueSheetViewController.mm +126 -236
  22. package/ios/utils/LayoutUtil.h +2 -1
  23. package/ios/utils/LayoutUtil.mm +14 -1
  24. package/lib/module/TrueSheet.js +10 -2
  25. package/lib/module/TrueSheet.js.map +1 -1
  26. package/lib/module/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
  27. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  28. package/lib/typescript/src/TrueSheet.types.d.ts +9 -9
  29. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  30. package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts +6 -0
  31. package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts.map +1 -0
  32. package/package.json +4 -1
  33. package/src/TrueSheet.tsx +10 -0
  34. package/src/TrueSheet.types.ts +10 -11
  35. package/src/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
@@ -25,8 +25,8 @@ import com.lodev09.truesheet.events.WillDismissEvent
25
25
  import com.lodev09.truesheet.events.WillPresentEvent
26
26
 
27
27
  /**
28
- * Main TrueSheet host view.
29
- * Manages the sheet dialog and container, and dispatches events to JavaScript.
28
+ * Main TrueSheet host view that manages the sheet dialog and dispatches events to JavaScript.
29
+ * This view is hidden (GONE) and delegates all rendering to TrueSheetViewController in a dialog window.
30
30
  */
31
31
  @SuppressLint("ViewConstructor")
32
32
  class TrueSheetView(private val reactContext: ThemedReactContext) :
@@ -35,14 +35,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
35
35
  TrueSheetViewControllerDelegate,
36
36
  TrueSheetContainerViewDelegate {
37
37
 
38
- /**
39
- * The TrueSheetViewController instance that acts as both root view and controller
40
- */
41
38
  private val viewController: TrueSheetViewController = TrueSheetViewController(reactContext)
42
39
 
43
- /**
44
- * Gets the container view (first child of view controller)
45
- */
46
40
  private val containerView: TrueSheetContainerView?
47
41
  get() = viewController.getChildAt(0) as? TrueSheetContainerView
48
42
 
@@ -52,24 +46,27 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
52
46
  var initialDetentAnimated: Boolean = true
53
47
 
54
48
  var stateWrapper: StateWrapper? = null
49
+ set(value) {
50
+ // Immediately update state with screen width during first state update
51
+ // This ensures we have initial width for content layout before presenting
52
+ if (field == null && value != null) {
53
+ updateState(viewController.screenWidth, 0)
54
+ }
55
+ field = value
56
+ }
55
57
 
56
58
  // Track last dimensions to avoid unnecessary state updates
57
59
  private var lastContainerWidth: Int = 0
58
60
  private var lastContainerHeight: Int = 0
59
61
 
60
- /**
61
- * Tracks if initial presentation has been handled
62
- */
63
- private var hasHandledInitialPresentation = false
62
+ // Flag to prevent multiple pending sheet updates
63
+ private var isSheetUpdatePending: Boolean = false
64
64
 
65
65
  init {
66
66
  reactContext.addLifecycleEventListener(this)
67
-
68
- // Set delegates
69
67
  viewController.delegate = this
70
68
 
71
- // Hide the host view from layout and touch handling
72
- // The actual content is shown in a dialog window
69
+ // Hide the host view - actual content is rendered in the dialog window
73
70
  visibility = GONE
74
71
  }
75
72
 
@@ -89,98 +86,66 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
89
86
 
90
87
  override fun setId(id: Int) {
91
88
  super.setId(id)
92
-
93
89
  viewController.id = id
94
90
  TrueSheetModule.registerView(this, id)
95
91
  }
96
92
 
97
93
  override fun onDetachedFromWindow() {
98
94
  super.onDetachedFromWindow()
99
- onDropInstance()
100
95
 
101
- TrueSheetModule.unregisterView(id)
96
+ // Don't unregister if we have active modals - we need the view for recovery
97
+ if (viewController.hasActiveModals()) {
98
+ return
99
+ }
100
+
101
+ onDropInstance()
102
102
  }
103
103
 
104
104
  /**
105
- * showOrUpdate will display the Dialog. It is called by the manager once all properties are set
106
- * because we need to know all of them before creating the Dialog. It is also smart during updates
107
- * if the changed properties can be applied directly to the Dialog or require the recreation of a
108
- * new Dialog.
105
+ * Called by the manager after all properties are set.
106
+ * Reconfigures the sheet if it's currently presented.
109
107
  */
110
- fun showOrUpdate() {
111
- // Only handle initial presentation once on mount
112
- if (!hasHandledInitialPresentation && initialDetentIndex >= 0) {
113
- hasHandledInitialPresentation = true
114
-
115
- // Create dialog if not created yet
116
- if (!viewController.isPresented) {
117
- viewController.createDialog()
118
- }
119
-
120
- post {
121
- present(initialDetentIndex, initialDetentAnimated) { }
122
- }
123
- } else if (viewController.isPresented) {
124
- viewController.setupSheetDetents()
108
+ fun finalizeUpdates() {
109
+ if (viewController.isPresented) {
110
+ viewController.setupBackground()
111
+ viewController.setupGrabber()
112
+ updateSheetIfNeeded()
125
113
  viewController.setStateForDetentIndex(viewController.currentDetentIndex)
126
- viewController.positionFooter()
127
114
  }
128
115
  }
129
116
 
130
117
  // ==================== View Management ====================
131
118
 
132
119
  override fun addView(child: View?, index: Int) {
133
- // Add the child to our ViewController
134
- // This is the TrueSheetContainerView
135
120
  viewController.addView(child, index)
136
121
 
137
- // Create dialog and dispatch mount event when TrueSheetContainerView is added
138
122
  if (child is TrueSheetContainerView) {
139
- // Set up container delegate to listen for content size changes
140
123
  child.delegate = this
124
+ viewController.createDialog()
141
125
 
142
- // Get initial content height from container
143
- val contentHeight = child.contentHeight
144
- if (contentHeight > 0) {
145
- viewController.contentHeight = contentHeight
126
+ // Present at initial detent after layout pass when content height is available
127
+ if (initialDetentIndex >= 0) {
128
+ post { present(initialDetentIndex, initialDetentAnimated) { } }
146
129
  }
147
130
 
148
- // Create the dialog now that the container is mounted
149
- viewController.createDialog()
150
-
151
131
  val surfaceId = UIManagerHelper.getSurfaceId(this)
152
- eventDispatcher?.dispatchEvent(
153
- MountEvent(surfaceId, id)
154
- )
132
+ eventDispatcher?.dispatchEvent(MountEvent(surfaceId, id))
155
133
  }
156
134
  }
157
135
 
158
136
  override fun getChildCount(): Int = viewController.childCount
159
137
  override fun getChildAt(index: Int): View? = viewController.getChildAt(index)
160
138
 
161
- override fun removeView(child: View?) {
162
- if (child != null) {
163
- // Clean up container delegate
164
- if (child is TrueSheetContainerView) {
165
- child.delegate = null
166
- }
167
-
168
- viewController.removeView(child)
169
- }
170
- }
171
-
172
139
  override fun removeViewAt(index: Int) {
173
140
  val child = getChildAt(index)
141
+ if (child is TrueSheetContainerView) {
142
+ child.delegate = null
143
+ }
174
144
  viewController.removeView(child)
175
145
  }
176
146
 
177
- override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {
178
- // Explicitly override this to prevent accessibility events being passed down to children
179
- // Those will be handled by the mHostView which lives in the dialog
180
- }
181
-
182
- // Explicitly override this to prevent accessibility events being passed down to children
183
- // Those will be handled by the mHostView which lives in the dialog
147
+ // Accessibility events are handled by the dialog's host view
148
+ override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
184
149
  override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
185
150
 
186
151
  fun onDropInstance() {
@@ -194,7 +159,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
194
159
  }
195
160
 
196
161
  override fun onHostResume() {
197
- showOrUpdate()
162
+ finalizeUpdates()
198
163
  }
199
164
 
200
165
  override fun onHostPause() {
@@ -208,71 +173,55 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
208
173
 
209
174
  override fun viewControllerWillPresent(index: Int, position: Float) {
210
175
  val surfaceId = UIManagerHelper.getSurfaceId(this)
211
- eventDispatcher?.dispatchEvent(
212
- WillPresentEvent(surfaceId, id, index, position)
213
- )
176
+ eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position))
214
177
  }
215
178
 
216
179
  override fun viewControllerDidPresent(index: Int, position: Float) {
217
180
  val surfaceId = UIManagerHelper.getSurfaceId(this)
218
- eventDispatcher?.dispatchEvent(
219
- DidPresentEvent(surfaceId, id, index, position)
220
- )
181
+ eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position))
221
182
 
222
- // Set our touch event dispatcher on the view controller
183
+ // Enable touch event dispatching to React Native
223
184
  viewController.eventDispatcher = eventDispatcher
185
+ containerView?.footerView?.eventDispatcher = eventDispatcher
224
186
  }
225
187
 
226
188
  override fun viewControllerWillDismiss() {
227
189
  val surfaceId = UIManagerHelper.getSurfaceId(this)
228
- eventDispatcher?.dispatchEvent(
229
- WillDismissEvent(surfaceId, id)
230
- )
190
+ eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
231
191
 
232
- // Clear our touch event dispatcher on the view controller
192
+ // Disable touch event dispatching
233
193
  viewController.eventDispatcher = null
194
+ containerView?.footerView?.eventDispatcher = null
234
195
  }
235
196
 
236
197
  override fun viewControllerDidDismiss() {
237
198
  val surfaceId = UIManagerHelper.getSurfaceId(this)
238
- eventDispatcher?.dispatchEvent(
239
- DidDismissEvent(surfaceId, id)
240
- )
199
+ eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
241
200
  }
242
201
 
243
202
  override fun viewControllerDidChangeDetent(index: Int, position: Float) {
244
203
  val surfaceId = UIManagerHelper.getSurfaceId(this)
245
- eventDispatcher?.dispatchEvent(
246
- DetentChangeEvent(surfaceId, id, index, position)
247
- )
204
+ eventDispatcher?.dispatchEvent(DetentChangeEvent(surfaceId, id, index, position))
248
205
  }
249
206
 
250
207
  override fun viewControllerDidDragBegin(index: Int, position: Float) {
251
208
  val surfaceId = UIManagerHelper.getSurfaceId(this)
252
- eventDispatcher?.dispatchEvent(
253
- DragBeginEvent(surfaceId, id, index, position)
254
- )
209
+ eventDispatcher?.dispatchEvent(DragBeginEvent(surfaceId, id, index, position))
255
210
  }
256
211
 
257
212
  override fun viewControllerDidDragChange(index: Int, position: Float) {
258
213
  val surfaceId = UIManagerHelper.getSurfaceId(this)
259
- eventDispatcher?.dispatchEvent(
260
- DragChangeEvent(surfaceId, id, index, position)
261
- )
214
+ eventDispatcher?.dispatchEvent(DragChangeEvent(surfaceId, id, index, position))
262
215
  }
263
216
 
264
217
  override fun viewControllerDidDragEnd(index: Int, position: Float) {
265
218
  val surfaceId = UIManagerHelper.getSurfaceId(this)
266
- eventDispatcher?.dispatchEvent(
267
- DragEndEvent(surfaceId, id, index, position)
268
- )
219
+ eventDispatcher?.dispatchEvent(DragEndEvent(surfaceId, id, index, position))
269
220
  }
270
221
 
271
222
  override fun viewControllerDidChangePosition(index: Int, position: Float, transitioning: Boolean) {
272
223
  val surfaceId = UIManagerHelper.getSurfaceId(this)
273
- eventDispatcher?.dispatchEvent(
274
- PositionChangeEvent(surfaceId, id, index, position, transitioning)
275
- )
224
+ eventDispatcher?.dispatchEvent(PositionChangeEvent(surfaceId, id, index, position, transitioning))
276
225
  }
277
226
 
278
227
  override fun viewControllerDidChangeSize(width: Int, height: Int) {
@@ -303,15 +252,13 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
303
252
  }
304
253
 
305
254
  fun setCornerRadius(radius: Float) {
306
- if (viewController.cornerRadius == radius) return
307
- viewController.cornerRadius = radius
308
- viewController.setupBackground()
255
+ if (viewController.sheetCornerRadius == radius) return
256
+ viewController.sheetCornerRadius = radius
309
257
  }
310
258
 
311
259
  fun setSheetBackgroundColor(color: Int) {
312
260
  if (viewController.sheetBackgroundColor == color) return
313
261
  viewController.sheetBackgroundColor = color
314
- viewController.setupBackground()
315
262
  }
316
263
 
317
264
  fun setSoftInputMode(mode: Int) {
@@ -322,7 +269,9 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
322
269
  viewController.dismissible = dismissible
323
270
  }
324
271
 
325
- fun setGrabber(grabber: Boolean) {}
272
+ fun setGrabber(grabber: Boolean) {
273
+ viewController.grabber = grabber
274
+ }
326
275
 
327
276
  fun setDetents(newDetents: MutableList<Double>) {
328
277
  viewController.detents = newDetents
@@ -337,77 +286,62 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
337
286
  // ==================== State Management ====================
338
287
 
339
288
  /**
340
- * Update state with container dimensions.
341
- * Called when the dialog size changes.
289
+ * Updates the Fabric state with container dimensions for Yoga layout.
342
290
  */
343
291
  fun updateState(width: Int, height: Int) {
344
- // Skip if dimensions haven't changed
345
- if (width == lastContainerWidth && height == lastContainerHeight) {
346
- return
347
- }
292
+ if (width == lastContainerWidth && height == lastContainerHeight) return
348
293
 
349
- // Store new dimensions
350
294
  lastContainerWidth = width
351
295
  lastContainerHeight = height
352
296
 
353
297
  val sw = stateWrapper ?: return
354
-
355
- val realWidth = width.toFloat().pxToDp()
356
- val realHeight = height.toFloat().pxToDp()
357
-
358
298
  val newStateData = WritableNativeMap()
359
- newStateData.putDouble("containerWidth", realWidth.toDouble())
360
- newStateData.putDouble("containerHeight", realHeight.toDouble())
299
+ newStateData.putDouble("containerWidth", width.toFloat().pxToDp().toDouble())
300
+ newStateData.putDouble("containerHeight", height.toFloat().pxToDp().toDouble())
361
301
  sw.updateState(newStateData)
362
302
  }
363
303
 
364
- /**
365
- * Presents the sheet at the given detent index.
366
- *
367
- * @param detentIndex The detent index to present at
368
- * @param animated Whether to animate the presentation
369
- * @param promiseCallback Callback invoked when presentation completes
370
- */
371
304
  @UiThread
372
305
  fun present(detentIndex: Int, animated: Boolean = true, promiseCallback: () -> Unit) {
373
306
  viewController.presentPromise = promiseCallback
374
307
  viewController.present(detentIndex, animated)
375
308
  }
376
309
 
377
- /**
378
- * Dismisses the sheet.
379
- *
380
- * @param promiseCallback Callback invoked when dismissal completes
381
- */
382
310
  @UiThread
383
311
  fun dismiss(promiseCallback: () -> Unit) {
384
312
  viewController.dismissPromise = promiseCallback
385
313
  viewController.dismiss()
386
314
  }
387
315
 
388
- // ==================== TrueSheetContainerViewDelegate Implementation ====================
316
+ /**
317
+ * Debounced sheet update to handle rapid content/header size changes.
318
+ * Uses post to ensure all layout passes complete before reconfiguring.
319
+ */
320
+ fun updateSheetIfNeeded() {
321
+ if (!viewController.isPresented || isSheetUpdatePending) return
389
322
 
390
- override fun containerViewContentDidChangeSize(width: Int, height: Int) {
391
- // Clamp content height to container height to prevent unbounded growth with scrollable content
392
- val containerHeight = viewController.maxScreenHeight
393
- val contentHeight = if (containerHeight > 0) minOf(height, containerHeight) else height
323
+ isSheetUpdatePending = true
324
+ viewController.post {
325
+ isSheetUpdatePending = false
326
+ viewController.setupSheetDetents()
327
+ viewController.positionFooter()
328
+ }
329
+ }
394
330
 
395
- viewController.contentHeight = contentHeight
331
+ // ==================== TrueSheetContainerViewDelegate Implementation ====================
396
332
 
397
- // Update detents if sheet is already presented
398
- if (viewController.isPresented) {
399
- // Reconfigure sheet detents with new content height
400
- viewController.setupSheetDetents()
333
+ override fun containerViewContentDidChangeSize(width: Int, height: Int) {
334
+ updateSheetIfNeeded()
335
+ }
401
336
 
402
- // Use post to ensure layout is complete before positioning footer
403
- viewController.post {
404
- viewController.positionFooter()
405
- }
406
- }
337
+ override fun containerViewHeaderDidChangeSize(width: Int, height: Int) {
338
+ updateSheetIfNeeded()
339
+ // Update state for scroll view behavior to work correctly with header
340
+ val sheetHeight = TrueSheetViewController.getEffectiveSheetHeight(viewController.height, height)
341
+ updateState(viewController.width, sheetHeight)
407
342
  }
408
343
 
409
344
  override fun containerViewFooterDidChangeSize(width: Int, height: Int) {
410
- // Reposition footer when its size changes
411
345
  if (viewController.isPresented) {
412
346
  viewController.positionFooter()
413
347
  }