@swmansion/react-native-bottom-sheet 0.10.0-next.2 → 0.10.0-next.3
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
|
|
|
@@ -393,20 +394,29 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
393
394
|
private val isTargetingClosedDetent: Boolean
|
|
394
395
|
get() = closedIndex?.let { targetIndex == it } == true
|
|
395
396
|
|
|
396
|
-
private
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
397
|
+
private fun snapCandidateIndices(includeIndex: Int? = null): List<Int> {
|
|
398
|
+
val indices = detentSpecs.indices.filter { !detentSpecs[it].programmatic }.toMutableList()
|
|
399
|
+
if (
|
|
400
|
+
includeIndex != null &&
|
|
401
|
+
includeIndex in detentSpecs.indices &&
|
|
402
|
+
detentSpecs[includeIndex].programmatic
|
|
403
|
+
) {
|
|
404
|
+
indices.add(includeIndex)
|
|
400
405
|
}
|
|
406
|
+
return indices.distinct().sortedBy { detentSpecs[it].height }
|
|
407
|
+
}
|
|
401
408
|
|
|
402
|
-
private
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
409
|
+
private fun draggableRange(includeIndex: Int? = null): ClosedFloatingPointRange<Float> {
|
|
410
|
+
val candidates = snapCandidateIndices(includeIndex)
|
|
411
|
+
if (candidates.isEmpty()) return 0f..0f
|
|
412
|
+
val translations = candidates.map(::translationY)
|
|
413
|
+
return (translations.minOrNull() ?: 0f)..(translations.maxOrNull() ?: 0f)
|
|
414
|
+
}
|
|
407
415
|
|
|
408
|
-
private
|
|
409
|
-
|
|
416
|
+
private fun isAtMaxDragCandidate(includeIndex: Int? = null): Boolean {
|
|
417
|
+
val range = draggableRange(includeIndex)
|
|
418
|
+
return sheetContainer.translationY <= range.start + 1f
|
|
419
|
+
}
|
|
410
420
|
|
|
411
421
|
private fun emitPosition() {
|
|
412
422
|
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
@@ -510,24 +520,24 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
510
520
|
spring.start()
|
|
511
521
|
}
|
|
512
522
|
|
|
513
|
-
private fun bestSnapIndex(currentHeight: Float, velocity: Float): Int {
|
|
514
|
-
val
|
|
515
|
-
if (
|
|
523
|
+
private fun bestSnapIndex(currentHeight: Float, velocity: Float, includeIndex: Int? = null): Int {
|
|
524
|
+
val candidates = snapCandidateIndices(includeIndex)
|
|
525
|
+
if (candidates.isEmpty()) return targetIndex
|
|
516
526
|
|
|
517
527
|
val flickThreshold = 600f * density
|
|
518
528
|
|
|
519
529
|
if (velocity < -flickThreshold) {
|
|
520
|
-
return
|
|
521
|
-
?:
|
|
530
|
+
return candidates.firstOrNull { detentSpecs[it].height > currentHeight }
|
|
531
|
+
?: candidates.lastOrNull()
|
|
522
532
|
?: targetIndex
|
|
523
533
|
}
|
|
524
534
|
if (velocity > flickThreshold) {
|
|
525
|
-
return
|
|
526
|
-
?:
|
|
535
|
+
return candidates.lastOrNull { detentSpecs[it].height < currentHeight }
|
|
536
|
+
?: candidates.firstOrNull()
|
|
527
537
|
?: targetIndex
|
|
528
538
|
}
|
|
529
539
|
|
|
530
|
-
return
|
|
540
|
+
return candidates.minByOrNull { abs(detentSpecs[it].height - currentHeight) } ?: targetIndex
|
|
531
541
|
}
|
|
532
542
|
|
|
533
543
|
// MARK: - Touch handling
|
|
@@ -563,11 +573,12 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
563
573
|
val dx = x - initialTouchX
|
|
564
574
|
val dy = y - initialTouchY
|
|
565
575
|
|
|
566
|
-
|
|
576
|
+
val dragRange = draggableRange(targetIndex)
|
|
577
|
+
if (abs(dy) > touchSlop && abs(dy) > abs(dx) && dragRange.start < dragRange.endInclusive) {
|
|
567
578
|
if (disableScrollableNegotiation && findScrollableAtTouch() != null) {
|
|
568
579
|
return false
|
|
569
580
|
}
|
|
570
|
-
if (!
|
|
581
|
+
if (!isAtMaxDragCandidate(targetIndex)) {
|
|
571
582
|
lastTouchY = y
|
|
572
583
|
requestDisallowInterceptTouchEvent(false)
|
|
573
584
|
// Cancel in-flight JS touches. React Native's JSTouchDispatcher
|
|
@@ -640,7 +651,9 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
640
651
|
val dy = y - lastTouchY
|
|
641
652
|
lastTouchY = y
|
|
642
653
|
|
|
643
|
-
val
|
|
654
|
+
val dragRange = draggableRange(panStartingIndex)
|
|
655
|
+
val newTy =
|
|
656
|
+
(sheetContainer.translationY + dy).coerceIn(dragRange.start, dragRange.endInclusive)
|
|
644
657
|
sheetContainer.translationY = newTy
|
|
645
658
|
emitPosition()
|
|
646
659
|
return true
|
|
@@ -659,7 +672,8 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
659
672
|
velocityTracker = null
|
|
660
673
|
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
661
674
|
val currentHeight = maxHeight - sheetContainer.translationY
|
|
662
|
-
val index = bestSnapIndex(currentHeight, velocity)
|
|
675
|
+
val index = bestSnapIndex(currentHeight, velocity, panStartingIndex)
|
|
676
|
+
panStartingIndex = null
|
|
663
677
|
snapToIndex(index, velocity)
|
|
664
678
|
return true
|
|
665
679
|
}
|
|
@@ -679,6 +693,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
679
693
|
|
|
680
694
|
private fun beginPan(event: MotionEvent) {
|
|
681
695
|
isPanning = true
|
|
696
|
+
panStartingIndex = targetIndex
|
|
682
697
|
activePointerId = event.getPointerId(0)
|
|
683
698
|
lastTouchY = event.y
|
|
684
699
|
velocityTracker?.recycle()
|
|
@@ -771,6 +786,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
771
786
|
pendingIndex = null
|
|
772
787
|
hasLaidOut = false
|
|
773
788
|
isPanning = false
|
|
789
|
+
panStartingIndex = null
|
|
774
790
|
initialTouchY = 0f
|
|
775
791
|
initialTouchX = 0f
|
|
776
792
|
lastTouchY = 0f
|
|
@@ -59,6 +59,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
59
59
|
private var pendingIndex: Int?
|
|
60
60
|
private var hasLaidOut = false
|
|
61
61
|
private var isPanning = false
|
|
62
|
+
private var panStartingIndex: Int?
|
|
62
63
|
private var isContentInteractionDisabled = false
|
|
63
64
|
private var contentHeightMarker: UIView?
|
|
64
65
|
private static var markerObservationContext = 0
|
|
@@ -237,6 +238,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
237
238
|
pendingIndex = nil
|
|
238
239
|
hasLaidOut = false
|
|
239
240
|
isPanning = false
|
|
241
|
+
panStartingIndex = nil
|
|
240
242
|
setContentInteractionEnabled(true)
|
|
241
243
|
stopObservingContentHeightMarker()
|
|
242
244
|
sheetContainer.transform = .identity
|
|
@@ -260,11 +262,27 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
260
262
|
return maxHeight - snapHeight
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
private
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
private func snapCandidateIndices(including index: Int? = nil) -> [Int] {
|
|
266
|
+
var indices = detentSpecs.indices.filter { !detentSpecs[$0].programmatic }
|
|
267
|
+
if
|
|
268
|
+
let index,
|
|
269
|
+
detentSpecs.indices.contains(index),
|
|
270
|
+
detentSpecs[index].programmatic
|
|
271
|
+
{
|
|
272
|
+
indices.append(index)
|
|
273
|
+
}
|
|
274
|
+
return Array(Set(indices)).sorted {
|
|
275
|
+
detentSpecs[$0].height < detentSpecs[$1].height
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private func draggableRange(including index: Int? = nil) -> (minTy: CGFloat, maxTy: CGFloat) {
|
|
280
|
+
let candidates = snapCandidateIndices(including: index)
|
|
281
|
+
guard !candidates.isEmpty else { return (minTy: 0, maxTy: 0) }
|
|
282
|
+
return (
|
|
283
|
+
minTy: candidates.map { translationY(for: $0) }.min() ?? 0,
|
|
284
|
+
maxTy: candidates.map { translationY(for: $0) }.max() ?? 0
|
|
285
|
+
)
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
private var closedIndex: Int? {
|
|
@@ -393,6 +411,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
393
411
|
switch gesture.state {
|
|
394
412
|
case .began:
|
|
395
413
|
isPanning = true
|
|
414
|
+
panStartingIndex = targetIndex
|
|
396
415
|
sheetContainer.endEditing(true)
|
|
397
416
|
setContentInteractionEnabled(false)
|
|
398
417
|
if let handler = surfaceTouchHandler {
|
|
@@ -411,8 +430,9 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
411
430
|
case .changed:
|
|
412
431
|
let delta = gesture.translation(in: self).y
|
|
413
432
|
gesture.setTranslation(.zero, in: self)
|
|
414
|
-
let
|
|
415
|
-
let
|
|
433
|
+
let range = draggableRange(including: panStartingIndex)
|
|
434
|
+
let minTy = range.minTy
|
|
435
|
+
let maxTy = range.maxTy
|
|
416
436
|
let newTy = max(minTy, min(maxTy, sheetContainer.transform.ty + delta))
|
|
417
437
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: newTy)
|
|
418
438
|
emitPosition()
|
|
@@ -421,7 +441,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
421
441
|
isPanning = false
|
|
422
442
|
let velocity = gesture.velocity(in: self).y
|
|
423
443
|
let currentHeight = maxHeight - sheetContainer.transform.ty
|
|
424
|
-
let index = bestSnapIndex(for: currentHeight, velocity: velocity)
|
|
444
|
+
let index = bestSnapIndex(for: currentHeight, velocity: velocity, including: panStartingIndex)
|
|
445
|
+
panStartingIndex = nil
|
|
425
446
|
snapToIndex(index, velocity: velocity)
|
|
426
447
|
|
|
427
448
|
case .cancelled:
|
|
@@ -429,11 +450,17 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
429
450
|
setContentInteractionEnabled(true)
|
|
430
451
|
let cancelVelocity = gesture.velocity(in: self).y
|
|
431
452
|
let cancelHeight = maxHeight - sheetContainer.transform.ty
|
|
432
|
-
let cancelIndex = bestSnapIndex(
|
|
453
|
+
let cancelIndex = bestSnapIndex(
|
|
454
|
+
for: cancelHeight,
|
|
455
|
+
velocity: cancelVelocity,
|
|
456
|
+
including: panStartingIndex
|
|
457
|
+
)
|
|
458
|
+
panStartingIndex = nil
|
|
433
459
|
snapToIndex(cancelIndex, velocity: cancelVelocity)
|
|
434
460
|
|
|
435
461
|
case .failed:
|
|
436
462
|
isPanning = false
|
|
463
|
+
panStartingIndex = nil
|
|
437
464
|
setContentInteractionEnabled(true)
|
|
438
465
|
|
|
439
466
|
default:
|
|
@@ -441,24 +468,28 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
441
468
|
}
|
|
442
469
|
}
|
|
443
470
|
|
|
444
|
-
private func bestSnapIndex(
|
|
445
|
-
|
|
446
|
-
|
|
471
|
+
private func bestSnapIndex(
|
|
472
|
+
for height: CGFloat,
|
|
473
|
+
velocity: CGFloat,
|
|
474
|
+
including index: Int? = nil
|
|
475
|
+
) -> Int {
|
|
476
|
+
let candidates = snapCandidateIndices(including: index)
|
|
477
|
+
guard !candidates.isEmpty else { return targetIndex }
|
|
447
478
|
|
|
448
479
|
let flickThreshold: CGFloat = 600
|
|
449
480
|
|
|
450
481
|
if velocity < -flickThreshold {
|
|
451
|
-
return
|
|
452
|
-
??
|
|
482
|
+
return candidates.first(where: { detentSpecs[$0].height > height })
|
|
483
|
+
?? candidates.last ?? targetIndex
|
|
453
484
|
}
|
|
454
485
|
if velocity > flickThreshold {
|
|
455
|
-
return
|
|
456
|
-
??
|
|
486
|
+
return candidates.last(where: { detentSpecs[$0].height < height })
|
|
487
|
+
?? candidates.first ?? targetIndex
|
|
457
488
|
}
|
|
458
489
|
|
|
459
|
-
return
|
|
460
|
-
abs($0.
|
|
461
|
-
})
|
|
490
|
+
return candidates.min(by: {
|
|
491
|
+
abs(detentSpecs[$0].height - height) < abs(detentSpecs[$1].height - height)
|
|
492
|
+
}) ?? targetIndex
|
|
462
493
|
}
|
|
463
494
|
|
|
464
495
|
private func isVerticallyScrollable(_ scrollView: UIScrollView) -> Bool {
|
|
@@ -498,8 +529,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
498
529
|
let velocity = panGesture.velocity(in: self)
|
|
499
530
|
guard abs(velocity.y) > abs(velocity.x) else { return false }
|
|
500
531
|
|
|
501
|
-
let
|
|
502
|
-
guard
|
|
532
|
+
let candidates = snapCandidateIndices(including: targetIndex)
|
|
533
|
+
guard candidates.count > 1 else { return false }
|
|
503
534
|
|
|
504
535
|
if disableScrollableNegotiation {
|
|
505
536
|
let locationInContainer = panGesture.location(in: sheetContainer)
|
|
@@ -508,9 +539,9 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
508
539
|
}
|
|
509
540
|
}
|
|
510
541
|
|
|
511
|
-
let
|
|
542
|
+
let maxCandidateHeight = candidates.map { detentSpecs[$0].height }.max() ?? 0
|
|
512
543
|
// Below max: allow drag in either direction to reach other detents.
|
|
513
|
-
guard
|
|
544
|
+
guard currentSheetHeight >= maxCandidateHeight - 0.5 else { return true }
|
|
514
545
|
// At max: only allow downward drag, and only when the scroll view (if any)
|
|
515
546
|
// is at its top edge — otherwise the scroll view should handle the gesture.
|
|
516
547
|
if velocity.y < 0 {
|
package/package.json
CHANGED