@swmansion/react-native-bottom-sheet 0.10.0-next.2 → 0.10.0-next.4
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.
|
@@ -62,6 +62,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
62
62
|
private var pendingIndex: Int? = null
|
|
63
63
|
private var hasLaidOut = false
|
|
64
64
|
private var isPanning = false
|
|
65
|
+
private var panStartingIndex: Int? = null
|
|
65
66
|
|
|
66
67
|
// MARK: - Internal
|
|
67
68
|
|
|
@@ -84,6 +85,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
84
85
|
private var scrimColor = Color.TRANSPARENT
|
|
85
86
|
private var scrimProgress = 0f
|
|
86
87
|
private var suppressScrimForClosingTarget = false
|
|
88
|
+
private var scrimPinnedFull = false
|
|
87
89
|
private var maxDetentHeight = Float.NaN
|
|
88
90
|
private var contentHeightMarker: View? = null
|
|
89
91
|
|
|
@@ -293,6 +295,13 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
val previousMaxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
298
|
+
// Whether the scrim is currently fully opaque, i.e. the sheet is settled at
|
|
299
|
+
// or above the first non-zero detent. If so, a detent resize must not dip
|
|
300
|
+
// the scrim while the sheet re-anchors to the new geometry.
|
|
301
|
+
val wasScrimFull =
|
|
302
|
+
modal &&
|
|
303
|
+
firstNonZeroDetentHeight > 0f &&
|
|
304
|
+
currentSheetHeight() + 0.5f >= firstNonZeroDetentHeight
|
|
296
305
|
detentSpecs = resolvedDetents
|
|
297
306
|
if (width > 0 && height > 0 && detentSpecs.isNotEmpty()) {
|
|
298
307
|
layoutSheetContainer(width, height)
|
|
@@ -316,8 +325,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
316
325
|
// sheet surface keeps the same on-screen height across the resize.
|
|
317
326
|
val visibleHeight = previousMaxHeight - currentTy
|
|
318
327
|
sheetContainer.translationY = (newMaxHeight - visibleHeight).coerceIn(0f, newMaxHeight)
|
|
328
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
319
329
|
emitPosition()
|
|
320
|
-
snapToIndex(
|
|
330
|
+
snapToIndex(
|
|
331
|
+
targetIndex,
|
|
332
|
+
0f,
|
|
333
|
+
emitIndexChange = false,
|
|
334
|
+
emitSettle = shouldEmitSettle,
|
|
335
|
+
preserveScrimPin = true,
|
|
336
|
+
)
|
|
321
337
|
} else {
|
|
322
338
|
val currentVisibleHeight = previousMaxHeight - sheetContainer.translationY
|
|
323
339
|
val targetHeight = detentSpecs.getOrNull(targetIndex)?.height ?: 0f
|
|
@@ -331,8 +347,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
331
347
|
// up to the taller detent.
|
|
332
348
|
sheetContainer.translationY =
|
|
333
349
|
(newMaxHeight - currentVisibleHeight).coerceIn(0f, newMaxHeight)
|
|
350
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
334
351
|
emitPosition()
|
|
335
|
-
snapToIndex(
|
|
352
|
+
snapToIndex(
|
|
353
|
+
targetIndex,
|
|
354
|
+
0f,
|
|
355
|
+
emitIndexChange = false,
|
|
356
|
+
emitSettle = false,
|
|
357
|
+
preserveScrimPin = true,
|
|
358
|
+
)
|
|
336
359
|
}
|
|
337
360
|
}
|
|
338
361
|
}
|
|
@@ -393,20 +416,29 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
393
416
|
private val isTargetingClosedDetent: Boolean
|
|
394
417
|
get() = closedIndex?.let { targetIndex == it } == true
|
|
395
418
|
|
|
396
|
-
private
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
419
|
+
private fun snapCandidateIndices(includeIndex: Int? = null): List<Int> {
|
|
420
|
+
val indices = detentSpecs.indices.filter { !detentSpecs[it].programmatic }.toMutableList()
|
|
421
|
+
if (
|
|
422
|
+
includeIndex != null &&
|
|
423
|
+
includeIndex in detentSpecs.indices &&
|
|
424
|
+
detentSpecs[includeIndex].programmatic
|
|
425
|
+
) {
|
|
426
|
+
indices.add(includeIndex)
|
|
400
427
|
}
|
|
428
|
+
return indices.distinct().sortedBy { detentSpecs[it].height }
|
|
429
|
+
}
|
|
401
430
|
|
|
402
|
-
private
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
431
|
+
private fun draggableRange(includeIndex: Int? = null): ClosedFloatingPointRange<Float> {
|
|
432
|
+
val candidates = snapCandidateIndices(includeIndex)
|
|
433
|
+
if (candidates.isEmpty()) return 0f..0f
|
|
434
|
+
val translations = candidates.map(::translationY)
|
|
435
|
+
return (translations.minOrNull() ?: 0f)..(translations.maxOrNull() ?: 0f)
|
|
436
|
+
}
|
|
407
437
|
|
|
408
|
-
private
|
|
409
|
-
|
|
438
|
+
private fun isAtMaxDragCandidate(includeIndex: Int? = null): Boolean {
|
|
439
|
+
val range = draggableRange(includeIndex)
|
|
440
|
+
return sheetContainer.translationY <= range.start + 1f
|
|
441
|
+
}
|
|
410
442
|
|
|
411
443
|
private fun emitPosition() {
|
|
412
444
|
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
@@ -464,12 +496,16 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
464
496
|
velocity: Float,
|
|
465
497
|
emitIndexChange: Boolean = true,
|
|
466
498
|
emitSettle: Boolean = true,
|
|
499
|
+
preserveScrimPin: Boolean = false,
|
|
467
500
|
) {
|
|
468
501
|
if (index < 0 || index >= detentSpecs.size) return
|
|
469
502
|
targetIndex = index
|
|
470
503
|
if (!isTargetingClosedDetent) {
|
|
471
504
|
suppressScrimForClosingTarget = false
|
|
472
505
|
}
|
|
506
|
+
if (!preserveScrimPin) {
|
|
507
|
+
scrimPinnedFull = false
|
|
508
|
+
}
|
|
473
509
|
|
|
474
510
|
val targetTy = translationY(index)
|
|
475
511
|
val currentTy = sheetContainer.translationY
|
|
@@ -494,6 +530,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
494
530
|
activeAnimation = null
|
|
495
531
|
activeAnimationEmitsSettle = false
|
|
496
532
|
suppressScrimForClosingTarget = false
|
|
533
|
+
scrimPinnedFull = false
|
|
497
534
|
if (closedIndex == index) {
|
|
498
535
|
sheetContainer.translationY = translationY(index)
|
|
499
536
|
hideScrim()
|
|
@@ -510,24 +547,24 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
510
547
|
spring.start()
|
|
511
548
|
}
|
|
512
549
|
|
|
513
|
-
private fun bestSnapIndex(currentHeight: Float, velocity: Float): Int {
|
|
514
|
-
val
|
|
515
|
-
if (
|
|
550
|
+
private fun bestSnapIndex(currentHeight: Float, velocity: Float, includeIndex: Int? = null): Int {
|
|
551
|
+
val candidates = snapCandidateIndices(includeIndex)
|
|
552
|
+
if (candidates.isEmpty()) return targetIndex
|
|
516
553
|
|
|
517
554
|
val flickThreshold = 600f * density
|
|
518
555
|
|
|
519
556
|
if (velocity < -flickThreshold) {
|
|
520
|
-
return
|
|
521
|
-
?:
|
|
557
|
+
return candidates.firstOrNull { detentSpecs[it].height > currentHeight }
|
|
558
|
+
?: candidates.lastOrNull()
|
|
522
559
|
?: targetIndex
|
|
523
560
|
}
|
|
524
561
|
if (velocity > flickThreshold) {
|
|
525
|
-
return
|
|
526
|
-
?:
|
|
562
|
+
return candidates.lastOrNull { detentSpecs[it].height < currentHeight }
|
|
563
|
+
?: candidates.firstOrNull()
|
|
527
564
|
?: targetIndex
|
|
528
565
|
}
|
|
529
566
|
|
|
530
|
-
return
|
|
567
|
+
return candidates.minByOrNull { abs(detentSpecs[it].height - currentHeight) } ?: targetIndex
|
|
531
568
|
}
|
|
532
569
|
|
|
533
570
|
// MARK: - Touch handling
|
|
@@ -563,11 +600,12 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
563
600
|
val dx = x - initialTouchX
|
|
564
601
|
val dy = y - initialTouchY
|
|
565
602
|
|
|
566
|
-
|
|
603
|
+
val dragRange = draggableRange(targetIndex)
|
|
604
|
+
if (abs(dy) > touchSlop && abs(dy) > abs(dx) && dragRange.start < dragRange.endInclusive) {
|
|
567
605
|
if (disableScrollableNegotiation && findScrollableAtTouch() != null) {
|
|
568
606
|
return false
|
|
569
607
|
}
|
|
570
|
-
if (!
|
|
608
|
+
if (!isAtMaxDragCandidate(targetIndex)) {
|
|
571
609
|
lastTouchY = y
|
|
572
610
|
requestDisallowInterceptTouchEvent(false)
|
|
573
611
|
// Cancel in-flight JS touches. React Native's JSTouchDispatcher
|
|
@@ -640,7 +678,9 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
640
678
|
val dy = y - lastTouchY
|
|
641
679
|
lastTouchY = y
|
|
642
680
|
|
|
643
|
-
val
|
|
681
|
+
val dragRange = draggableRange(panStartingIndex)
|
|
682
|
+
val newTy =
|
|
683
|
+
(sheetContainer.translationY + dy).coerceIn(dragRange.start, dragRange.endInclusive)
|
|
644
684
|
sheetContainer.translationY = newTy
|
|
645
685
|
emitPosition()
|
|
646
686
|
return true
|
|
@@ -659,7 +699,8 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
659
699
|
velocityTracker = null
|
|
660
700
|
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
661
701
|
val currentHeight = maxHeight - sheetContainer.translationY
|
|
662
|
-
val index = bestSnapIndex(currentHeight, velocity)
|
|
702
|
+
val index = bestSnapIndex(currentHeight, velocity, panStartingIndex)
|
|
703
|
+
panStartingIndex = null
|
|
663
704
|
snapToIndex(index, velocity)
|
|
664
705
|
return true
|
|
665
706
|
}
|
|
@@ -679,6 +720,8 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
679
720
|
|
|
680
721
|
private fun beginPan(event: MotionEvent) {
|
|
681
722
|
isPanning = true
|
|
723
|
+
scrimPinnedFull = false
|
|
724
|
+
panStartingIndex = targetIndex
|
|
682
725
|
activePointerId = event.getPointerId(0)
|
|
683
726
|
lastTouchY = event.y
|
|
684
727
|
velocityTracker?.recycle()
|
|
@@ -771,6 +814,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
771
814
|
pendingIndex = null
|
|
772
815
|
hasLaidOut = false
|
|
773
816
|
isPanning = false
|
|
817
|
+
panStartingIndex = null
|
|
774
818
|
initialTouchY = 0f
|
|
775
819
|
initialTouchX = 0f
|
|
776
820
|
lastTouchY = 0f
|
|
@@ -802,6 +846,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
802
846
|
return
|
|
803
847
|
}
|
|
804
848
|
|
|
849
|
+
// While the sheet is fully open and only its content/detent geometry is
|
|
850
|
+
// resizing, the position momentarily lags the grown detent height. Keep the
|
|
851
|
+
// scrim at full opacity instead of dipping it until the re-anchor settles.
|
|
852
|
+
if (scrimPinnedFull) {
|
|
853
|
+
scrimProgress = 1f
|
|
854
|
+
invalidate()
|
|
855
|
+
return
|
|
856
|
+
}
|
|
857
|
+
|
|
805
858
|
val threshold = firstNonZeroDetentHeight
|
|
806
859
|
scrimProgress = if (threshold <= 0f) 0f else (position / threshold).coerceIn(0f, 1f)
|
|
807
860
|
invalidate()
|
|
@@ -55,10 +55,12 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
55
55
|
private var panGesture: UIPanGestureRecognizer!
|
|
56
56
|
private var activeAnimator: UIViewPropertyAnimator?
|
|
57
57
|
private var activeAnimatorEmitsSettle = false
|
|
58
|
+
private var scrimPinnedFull = false
|
|
58
59
|
private var displayLink: CADisplayLink?
|
|
59
60
|
private var pendingIndex: Int?
|
|
60
61
|
private var hasLaidOut = false
|
|
61
62
|
private var isPanning = false
|
|
63
|
+
private var panStartingIndex: Int?
|
|
62
64
|
private var isContentInteractionDisabled = false
|
|
63
65
|
private var contentHeightMarker: UIView?
|
|
64
66
|
private static var markerObservationContext = 0
|
|
@@ -237,6 +239,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
237
239
|
pendingIndex = nil
|
|
238
240
|
hasLaidOut = false
|
|
239
241
|
isPanning = false
|
|
242
|
+
panStartingIndex = nil
|
|
240
243
|
setContentInteractionEnabled(true)
|
|
241
244
|
stopObservingContentHeightMarker()
|
|
242
245
|
sheetContainer.transform = .identity
|
|
@@ -260,11 +263,27 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
260
263
|
return maxHeight - snapHeight
|
|
261
264
|
}
|
|
262
265
|
|
|
263
|
-
private
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
private func snapCandidateIndices(including index: Int? = nil) -> [Int] {
|
|
267
|
+
var indices = detentSpecs.indices.filter { !detentSpecs[$0].programmatic }
|
|
268
|
+
if
|
|
269
|
+
let index,
|
|
270
|
+
detentSpecs.indices.contains(index),
|
|
271
|
+
detentSpecs[index].programmatic
|
|
272
|
+
{
|
|
273
|
+
indices.append(index)
|
|
274
|
+
}
|
|
275
|
+
return Array(Set(indices)).sorted {
|
|
276
|
+
detentSpecs[$0].height < detentSpecs[$1].height
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private func draggableRange(including index: Int? = nil) -> (minTy: CGFloat, maxTy: CGFloat) {
|
|
281
|
+
let candidates = snapCandidateIndices(including: index)
|
|
282
|
+
guard !candidates.isEmpty else { return (minTy: 0, maxTy: 0) }
|
|
283
|
+
return (
|
|
284
|
+
minTy: candidates.map { translationY(for: $0) }.min() ?? 0,
|
|
285
|
+
maxTy: candidates.map { translationY(for: $0) }.max() ?? 0
|
|
286
|
+
)
|
|
268
287
|
}
|
|
269
288
|
|
|
270
289
|
private var closedIndex: Int? {
|
|
@@ -346,10 +365,14 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
346
365
|
_ index: Int,
|
|
347
366
|
velocity: CGFloat,
|
|
348
367
|
emitIndexChange: Bool = true,
|
|
349
|
-
emitSettle: Bool = true
|
|
368
|
+
emitSettle: Bool = true,
|
|
369
|
+
preserveScrimPin: Bool = false
|
|
350
370
|
) {
|
|
351
371
|
guard index >= 0, index < detentSpecs.count else { return }
|
|
352
372
|
targetIndex = index
|
|
373
|
+
if !preserveScrimPin {
|
|
374
|
+
scrimPinnedFull = false
|
|
375
|
+
}
|
|
353
376
|
|
|
354
377
|
let currentTy = sheetContainer.transform.ty
|
|
355
378
|
let targetTy = translationY(for: index)
|
|
@@ -373,6 +396,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
373
396
|
self.emitPosition()
|
|
374
397
|
self.activeAnimator = nil
|
|
375
398
|
self.activeAnimatorEmitsSettle = false
|
|
399
|
+
self.scrimPinnedFull = false
|
|
376
400
|
self.setContentInteractionEnabled(true)
|
|
377
401
|
self.updateInteractionState()
|
|
378
402
|
if emitIndexChange {
|
|
@@ -393,6 +417,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
393
417
|
switch gesture.state {
|
|
394
418
|
case .began:
|
|
395
419
|
isPanning = true
|
|
420
|
+
scrimPinnedFull = false
|
|
421
|
+
panStartingIndex = targetIndex
|
|
396
422
|
sheetContainer.endEditing(true)
|
|
397
423
|
setContentInteractionEnabled(false)
|
|
398
424
|
if let handler = surfaceTouchHandler {
|
|
@@ -411,8 +437,9 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
411
437
|
case .changed:
|
|
412
438
|
let delta = gesture.translation(in: self).y
|
|
413
439
|
gesture.setTranslation(.zero, in: self)
|
|
414
|
-
let
|
|
415
|
-
let
|
|
440
|
+
let range = draggableRange(including: panStartingIndex)
|
|
441
|
+
let minTy = range.minTy
|
|
442
|
+
let maxTy = range.maxTy
|
|
416
443
|
let newTy = max(minTy, min(maxTy, sheetContainer.transform.ty + delta))
|
|
417
444
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: newTy)
|
|
418
445
|
emitPosition()
|
|
@@ -421,7 +448,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
421
448
|
isPanning = false
|
|
422
449
|
let velocity = gesture.velocity(in: self).y
|
|
423
450
|
let currentHeight = maxHeight - sheetContainer.transform.ty
|
|
424
|
-
let index = bestSnapIndex(for: currentHeight, velocity: velocity)
|
|
451
|
+
let index = bestSnapIndex(for: currentHeight, velocity: velocity, including: panStartingIndex)
|
|
452
|
+
panStartingIndex = nil
|
|
425
453
|
snapToIndex(index, velocity: velocity)
|
|
426
454
|
|
|
427
455
|
case .cancelled:
|
|
@@ -429,11 +457,17 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
429
457
|
setContentInteractionEnabled(true)
|
|
430
458
|
let cancelVelocity = gesture.velocity(in: self).y
|
|
431
459
|
let cancelHeight = maxHeight - sheetContainer.transform.ty
|
|
432
|
-
let cancelIndex = bestSnapIndex(
|
|
460
|
+
let cancelIndex = bestSnapIndex(
|
|
461
|
+
for: cancelHeight,
|
|
462
|
+
velocity: cancelVelocity,
|
|
463
|
+
including: panStartingIndex
|
|
464
|
+
)
|
|
465
|
+
panStartingIndex = nil
|
|
433
466
|
snapToIndex(cancelIndex, velocity: cancelVelocity)
|
|
434
467
|
|
|
435
468
|
case .failed:
|
|
436
469
|
isPanning = false
|
|
470
|
+
panStartingIndex = nil
|
|
437
471
|
setContentInteractionEnabled(true)
|
|
438
472
|
|
|
439
473
|
default:
|
|
@@ -441,24 +475,28 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
441
475
|
}
|
|
442
476
|
}
|
|
443
477
|
|
|
444
|
-
private func bestSnapIndex(
|
|
445
|
-
|
|
446
|
-
|
|
478
|
+
private func bestSnapIndex(
|
|
479
|
+
for height: CGFloat,
|
|
480
|
+
velocity: CGFloat,
|
|
481
|
+
including index: Int? = nil
|
|
482
|
+
) -> Int {
|
|
483
|
+
let candidates = snapCandidateIndices(including: index)
|
|
484
|
+
guard !candidates.isEmpty else { return targetIndex }
|
|
447
485
|
|
|
448
486
|
let flickThreshold: CGFloat = 600
|
|
449
487
|
|
|
450
488
|
if velocity < -flickThreshold {
|
|
451
|
-
return
|
|
452
|
-
??
|
|
489
|
+
return candidates.first(where: { detentSpecs[$0].height > height })
|
|
490
|
+
?? candidates.last ?? targetIndex
|
|
453
491
|
}
|
|
454
492
|
if velocity > flickThreshold {
|
|
455
|
-
return
|
|
456
|
-
??
|
|
493
|
+
return candidates.last(where: { detentSpecs[$0].height < height })
|
|
494
|
+
?? candidates.first ?? targetIndex
|
|
457
495
|
}
|
|
458
496
|
|
|
459
|
-
return
|
|
460
|
-
abs($0.
|
|
461
|
-
})
|
|
497
|
+
return candidates.min(by: {
|
|
498
|
+
abs(detentSpecs[$0].height - height) < abs(detentSpecs[$1].height - height)
|
|
499
|
+
}) ?? targetIndex
|
|
462
500
|
}
|
|
463
501
|
|
|
464
502
|
private func isVerticallyScrollable(_ scrollView: UIScrollView) -> Bool {
|
|
@@ -498,8 +536,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
498
536
|
let velocity = panGesture.velocity(in: self)
|
|
499
537
|
guard abs(velocity.y) > abs(velocity.x) else { return false }
|
|
500
538
|
|
|
501
|
-
let
|
|
502
|
-
guard
|
|
539
|
+
let candidates = snapCandidateIndices(including: targetIndex)
|
|
540
|
+
guard candidates.count > 1 else { return false }
|
|
503
541
|
|
|
504
542
|
if disableScrollableNegotiation {
|
|
505
543
|
let locationInContainer = panGesture.location(in: sheetContainer)
|
|
@@ -508,9 +546,9 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
508
546
|
}
|
|
509
547
|
}
|
|
510
548
|
|
|
511
|
-
let
|
|
549
|
+
let maxCandidateHeight = candidates.map { detentSpecs[$0].height }.max() ?? 0
|
|
512
550
|
// Below max: allow drag in either direction to reach other detents.
|
|
513
|
-
guard
|
|
551
|
+
guard currentSheetHeight >= maxCandidateHeight - 0.5 else { return true }
|
|
514
552
|
// At max: only allow downward drag, and only when the scroll view (if any)
|
|
515
553
|
// is at its top edge — otherwise the scroll view should handle the gesture.
|
|
516
554
|
if velocity.y < 0 {
|
|
@@ -578,6 +616,12 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
578
616
|
}
|
|
579
617
|
|
|
580
618
|
let previousMaxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
619
|
+
// Whether the scrim is currently fully opaque, i.e. the sheet is settled at
|
|
620
|
+
// or above the first non-zero detent. If so, a detent resize must not dip
|
|
621
|
+
// the scrim while the sheet re-anchors to the new geometry.
|
|
622
|
+
let wasScrimFull = modal
|
|
623
|
+
&& firstNonZeroDetentHeight > 0
|
|
624
|
+
&& currentSheetHeight + 0.5 >= firstNonZeroDetentHeight
|
|
581
625
|
detentSpecs = resolvedDetents
|
|
582
626
|
|
|
583
627
|
guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
|
|
@@ -601,8 +645,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
601
645
|
let visibleHeight = previousMaxHeight - visualTy
|
|
602
646
|
let reanchoredTy = min(max(newMaxHeight - visibleHeight, 0), newMaxHeight)
|
|
603
647
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: reanchoredTy)
|
|
648
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
604
649
|
emitPosition()
|
|
605
|
-
snapToIndex(
|
|
650
|
+
snapToIndex(
|
|
651
|
+
targetIndex,
|
|
652
|
+
velocity: 0,
|
|
653
|
+
emitIndexChange: false,
|
|
654
|
+
emitSettle: shouldEmitSettle,
|
|
655
|
+
preserveScrimPin: true
|
|
656
|
+
)
|
|
606
657
|
} else {
|
|
607
658
|
let currentVisibleHeight = previousMaxHeight - currentTranslationY
|
|
608
659
|
let targetHeight = detent(at: targetIndex).height
|
|
@@ -616,8 +667,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
616
667
|
// up to the taller detent.
|
|
617
668
|
let startTy = min(max(newMaxHeight - currentVisibleHeight, 0), newMaxHeight)
|
|
618
669
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: startTy)
|
|
670
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
619
671
|
emitPosition()
|
|
620
|
-
snapToIndex(
|
|
672
|
+
snapToIndex(
|
|
673
|
+
targetIndex,
|
|
674
|
+
velocity: 0,
|
|
675
|
+
emitIndexChange: false,
|
|
676
|
+
emitSettle: false,
|
|
677
|
+
preserveScrimPin: true
|
|
678
|
+
)
|
|
621
679
|
}
|
|
622
680
|
}
|
|
623
681
|
}
|
|
@@ -755,6 +813,15 @@ private extension RNSBottomSheetHostingView {
|
|
|
755
813
|
return
|
|
756
814
|
}
|
|
757
815
|
|
|
816
|
+
// While the sheet is fully open and only its content/detent geometry is
|
|
817
|
+
// resizing, the position momentarily lags the grown detent height. Keep the
|
|
818
|
+
// scrim at full opacity instead of dipping it until the re-anchor settles.
|
|
819
|
+
if scrimPinnedFull {
|
|
820
|
+
scrimView.alpha = 1
|
|
821
|
+
scrimView.isHidden = false
|
|
822
|
+
return
|
|
823
|
+
}
|
|
824
|
+
|
|
758
825
|
let threshold = firstNonZeroDetentHeight
|
|
759
826
|
let progress: CGFloat
|
|
760
827
|
if threshold <= 0 {
|
package/package.json
CHANGED