@lodev09/react-native-true-sheet 3.1.0-beta.5 → 3.1.0-beta.6

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.
@@ -157,15 +157,15 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
157
157
  override fun viewControllerWillPresent(index: Int, position: Float, detent: Float) {
158
158
  val surfaceId = UIManagerHelper.getSurfaceId(this)
159
159
  eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position, detent))
160
+
161
+ // Enable touch event dispatching to React Native
162
+ viewController.eventDispatcher = eventDispatcher
163
+ containerView?.footerView?.eventDispatcher = eventDispatcher
160
164
  }
161
165
 
162
166
  override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
163
167
  val surfaceId = UIManagerHelper.getSurfaceId(this)
164
168
  eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
165
-
166
- // Enable touch event dispatching to React Native
167
- viewController.eventDispatcher = eventDispatcher
168
- containerView?.footerView?.eventDispatcher = eventDispatcher
169
169
  }
170
170
 
171
171
  override fun viewControllerWillDismiss() {
@@ -66,6 +66,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
66
66
  private const val GRABBER_TAG = "TrueSheetGrabber"
67
67
  private const val DEFAULT_MAX_WIDTH = 640 // dp
68
68
  private const val DEFAULT_CORNER_RADIUS = 16 // dp
69
+
70
+ // Animation durations from res/anim/true_sheet_slide_in.xml and true_sheet_slide_out.xml
71
+ private const val PRESENT_ANIMATION_DURATION = 200L
72
+ private const val DISMISS_ANIMATION_DURATION = 150L
69
73
  }
70
74
 
71
75
  // ====================================================================
@@ -115,6 +119,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
115
119
  private val resolvedDetentPositions = mutableListOf<Int>()
116
120
 
117
121
  private var isDragging = false
122
+ private var isDismissing = false
118
123
  private var isReconfiguring = false
119
124
  private var windowAnimation: Int = 0
120
125
  private var lastEmittedPositionPx: Int = -1
@@ -272,6 +277,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
272
277
 
273
278
  dialog = null
274
279
  isDragging = false
280
+ isDismissing = false
275
281
  isPresented = false
276
282
  isDialogVisible = false
277
283
  lastEmittedPositionPx = -1
@@ -286,43 +292,51 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
286
292
  setupGrabber()
287
293
 
288
294
  sheetContainer?.post {
289
- val detentInfo = getDetentInfoForIndex(currentDetentIndex)
290
- val detent = getDetentValueForIndex(detentInfo.index)
291
295
  val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
292
296
 
293
- delegate?.viewControllerDidPresent(detentInfo.index, detentInfo.position, detent)
294
-
295
297
  // Store resolved position for initial detent
296
- storeResolvedPosition(detentInfo.index)
298
+ storeResolvedPosition(currentDetentIndex)
297
299
  emitChangePositionDelegate(positionPx, realtime = false)
298
300
 
301
+ positionFooter()
302
+ }
303
+
304
+ // Emit didPresent/didFocus after present animation completes
305
+ sheetContainer?.postDelayed({
306
+ val detentInfo = getDetentInfoForIndex(currentDetentIndex)
307
+ val detent = getDetentValueForIndex(detentInfo.index)
308
+
309
+ delegate?.viewControllerDidPresent(detentInfo.index, detentInfo.position, detent)
310
+
299
311
  // Notify parent sheet that it has lost focus (after this sheet appeared)
300
312
  parentSheetView?.viewControllerDidBlur()
301
313
 
314
+ // Emit didFocus with didPresent
315
+ delegate?.viewControllerDidFocus()
316
+
302
317
  presentPromise?.invoke()
303
318
  presentPromise = null
304
-
305
- positionFooter()
306
- }
319
+ }, PRESENT_ANIMATION_DURATION)
307
320
  }
308
321
 
309
322
  dialog.setOnCancelListener {
310
- delegate?.viewControllerWillDismiss()
323
+ // Skip if already handled by STATE_HIDDEN (programmatic dismiss)
324
+ if (isDismissing) return@setOnCancelListener
311
325
 
312
- // Notify parent sheet that it is about to regain focus
313
- parentSheetView?.viewControllerWillFocus()
314
- }
326
+ // User-initiated dismiss (back button, tap outside)
327
+ isDismissing = true
328
+ emitWillDismissEvents()
315
329
 
316
- dialog.setOnDismissListener {
317
- val hadParent = parentSheetView != null
330
+ // Emit off-screen position since onSlide isn't triggered for user-initiated dismiss
331
+ emitChangePositionDelegate(screenHeight, realtime = false)
318
332
 
319
- // Notify parent sheet that it has regained focus
320
- parentSheetView?.viewControllerDidFocus()
321
- parentSheetView = null
333
+ // Emit didBlur/didDismiss after dismiss animation completes
334
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
335
+ emitDidDismissEvents()
336
+ }, DISMISS_ANIMATION_DURATION)
337
+ }
322
338
 
323
- dismissPromise?.invoke()
324
- dismissPromise = null
325
- delegate?.viewControllerDidDismiss(hadParent)
339
+ dialog.setOnDismissListener {
326
340
  cleanupDialog()
327
341
  }
328
342
  }
@@ -348,7 +362,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
348
362
 
349
363
  override fun onStateChanged(sheetView: View, newState: Int) {
350
364
  if (newState == BottomSheetBehavior.STATE_HIDDEN) {
351
- dismiss()
365
+ // Mark as dismissing so setOnCancelListener skips emitting events
366
+ isDismissing = true
367
+ emitDidDismissEvents()
368
+ dialog.dismiss()
352
369
  return
353
370
  }
354
371
 
@@ -433,6 +450,34 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
433
450
  rnScreensObserver = null
434
451
  }
435
452
 
453
+ // ====================================================================
454
+ // MARK: - Dismiss Event Helpers
455
+ // ====================================================================
456
+
457
+ /**
458
+ * Emits willBlur and willDismiss events, and notifies parent sheet of upcoming focus change.
459
+ */
460
+ private fun emitWillDismissEvents() {
461
+ delegate?.viewControllerWillBlur()
462
+ delegate?.viewControllerWillDismiss()
463
+ parentSheetView?.viewControllerWillFocus()
464
+ }
465
+
466
+ /**
467
+ * Emits didBlur and didDismiss events, notifies parent sheet, and invokes dismiss promise.
468
+ */
469
+ private fun emitDidDismissEvents() {
470
+ val hadParent = parentSheetView != null
471
+ parentSheetView?.viewControllerDidFocus()
472
+ parentSheetView = null
473
+
474
+ delegate?.viewControllerDidBlur()
475
+ delegate?.viewControllerDidDismiss(hadParent)
476
+
477
+ dismissPromise?.invoke()
478
+ dismissPromise = null
479
+ }
480
+
436
481
  // ====================================================================
437
482
  // MARK: - Dialog Visibility (for stacking)
438
483
  // ====================================================================
@@ -518,6 +563,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
518
563
 
519
564
  delegate?.viewControllerWillPresent(detentInfo.index, detentInfo.position, detent)
520
565
 
566
+ // Emit willFocus with willPresent
567
+ delegate?.viewControllerWillFocus()
568
+
521
569
  if (!animated) {
522
570
  dialog.window?.setWindowAnimations(0)
523
571
  }
@@ -527,15 +575,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
527
575
  }
528
576
 
529
577
  fun dismiss(animated: Boolean = true) {
578
+ emitWillDismissEvents()
579
+
530
580
  this.post {
531
581
  // Emit off-screen position (detent = 0 since sheet is fully hidden)
532
582
  emitChangePositionDelegate(screenHeight, realtime = false)
533
583
  }
534
584
 
535
- dialog?.apply {
536
- if (!animated) window?.setWindowAnimations(0)
537
- post { dismiss() }
585
+ if (!animated) {
586
+ dialog?.window?.setWindowAnimations(0)
538
587
  }
588
+
589
+ // Temporarily enable hideable to allow STATE_HIDDEN transition
590
+ behavior?.isHideable = true
591
+ behavior?.state = BottomSheetBehavior.STATE_HIDDEN
539
592
  }
540
593
 
541
594
  // ====================================================================
@@ -159,15 +159,20 @@
159
159
  }
160
160
  }
161
161
 
162
- if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
163
- dispatch_async(dispatch_get_main_queue(), ^{
162
+ dispatch_async(dispatch_get_main_queue(), ^{
163
+ if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
164
164
  NSInteger index = self.currentDetentIndex;
165
165
  CGFloat position = self.currentPosition;
166
166
  CGFloat detent = [self detentValueForIndex:index];
167
167
 
168
168
  [self.delegate viewControllerWillPresentAtIndex:index position:position detent:detent];
169
- });
170
- }
169
+ }
170
+
171
+ // Emit willFocus with willPresent
172
+ if ([self.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
173
+ [self.delegate viewControllerWillFocus];
174
+ }
175
+ });
171
176
  }
172
177
 
173
178
  [self setupTransitionTracker];
@@ -184,13 +189,19 @@
184
189
  }
185
190
  }
186
191
 
187
- if ([self.delegate respondsToSelector:@selector(viewControllerDidPresentAtIndex:position:detent:)]) {
188
- dispatch_async(dispatch_get_main_queue(), ^{
192
+ dispatch_async(dispatch_get_main_queue(), ^{
193
+ if ([self.delegate respondsToSelector:@selector(viewControllerDidPresentAtIndex:position:detent:)]) {
189
194
  NSInteger index = [self currentDetentIndex];
190
195
  CGFloat detent = [self detentValueForIndex:index];
191
196
  [self.delegate viewControllerDidPresentAtIndex:index position:self.currentPosition detent:detent];
192
- });
193
- }
197
+ }
198
+
199
+ // Emit didFocus with didPresent
200
+ if ([self.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
201
+ [self.delegate viewControllerDidFocus];
202
+ }
203
+ });
204
+
194
205
  [self setupGestureRecognizer];
195
206
  _isPresented = YES;
196
207
  }
@@ -204,9 +215,16 @@
204
215
  [super viewWillDisappear:animated];
205
216
 
206
217
  if (self.isDismissing) {
207
- if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
208
- [self.delegate viewControllerWillDismiss];
209
- }
218
+ dispatch_async(dispatch_get_main_queue(), ^{
219
+ // Emit willBlur with willDismiss
220
+ if ([self.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
221
+ [self.delegate viewControllerWillBlur];
222
+ }
223
+
224
+ if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
225
+ [self.delegate viewControllerWillDismiss];
226
+ }
227
+ });
210
228
 
211
229
  // Notify the parent sheet (if any) that it is about to regain focus
212
230
  if (_parentSheetController) {
@@ -235,6 +253,11 @@
235
253
  _parentSheetController = nil;
236
254
  }
237
255
 
256
+ // Emit didBlur with didDismiss
257
+ if ([self.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
258
+ [self.delegate viewControllerDidBlur];
259
+ }
260
+
238
261
  if ([self.delegate respondsToSelector:@selector(viewControllerDidDismiss)]) {
239
262
  [self.delegate viewControllerDidDismiss];
240
263
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodev09/react-native-true-sheet",
3
- "version": "3.1.0-beta.5",
3
+ "version": "3.1.0-beta.6",
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",
@@ -80,7 +80,7 @@
80
80
  "@eslint/js": "^9.35.0",
81
81
  "@evilmartians/lefthook": "^2.0.4",
82
82
  "@react-native/babel-preset": "^0.82.1",
83
- "@react-native/eslint-config": "^0.81.1",
83
+ "@react-native/eslint-config": "^0.82.1",
84
84
  "@release-it/conventional-changelog": "^10.0.1",
85
85
  "@testing-library/react-native": "^13.3.3",
86
86
  "@types/babel__core": "^7",
@@ -6,13 +6,13 @@ export class TrueSheet extends React.Component {
6
6
  static instances = {};
7
7
 
8
8
  // Static methods
9
- static dismiss = jest.fn((name) => Promise.resolve());
10
- static present = jest.fn((name, index = 0) => Promise.resolve());
9
+ static dismiss = jest.fn((name, animated = true) => Promise.resolve());
10
+ static present = jest.fn((name, index = 0, animated = true) => Promise.resolve());
11
11
  static resize = jest.fn((name, index) => Promise.resolve());
12
12
 
13
13
  // Instance methods
14
- dismiss = jest.fn(() => Promise.resolve());
15
- present = jest.fn((index = 0) => Promise.resolve());
14
+ dismiss = jest.fn((animated = true) => Promise.resolve());
15
+ present = jest.fn((index = 0, animated = true) => Promise.resolve());
16
16
  resize = jest.fn((index) => Promise.resolve());
17
17
 
18
18
  componentDidMount() {
@@ -30,9 +30,10 @@ export class TrueSheet extends React.Component {
30
30
  }
31
31
 
32
32
  render() {
33
- const { children, footer, style, ...rest } = this.props;
33
+ const { children, header, footer, style, ...rest } = this.props;
34
34
  return (
35
35
  <View style={style} {...rest}>
36
+ {header}
36
37
  {children}
37
38
  {footer}
38
39
  </View>