@swmansion/react-native-bottom-sheet 0.10.0-next.3 → 0.10.0-next.5
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.
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetView.kt +39 -2
- package/ios/BottomSheetComponentView.mm +6 -0
- package/ios/BottomSheetContentView.h +1 -0
- package/ios/BottomSheetContentView.mm +5 -0
- package/ios/RNSBottomSheetHostingView.swift +61 -13
- package/package.json +1 -1
|
@@ -85,6 +85,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
85
85
|
private var scrimColor = Color.TRANSPARENT
|
|
86
86
|
private var scrimProgress = 0f
|
|
87
87
|
private var suppressScrimForClosingTarget = false
|
|
88
|
+
private var scrimPinnedFull = false
|
|
88
89
|
private var maxDetentHeight = Float.NaN
|
|
89
90
|
private var contentHeightMarker: View? = null
|
|
90
91
|
|
|
@@ -294,6 +295,13 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
294
295
|
}
|
|
295
296
|
|
|
296
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
|
|
297
305
|
detentSpecs = resolvedDetents
|
|
298
306
|
if (width > 0 && height > 0 && detentSpecs.isNotEmpty()) {
|
|
299
307
|
layoutSheetContainer(width, height)
|
|
@@ -317,8 +325,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
317
325
|
// sheet surface keeps the same on-screen height across the resize.
|
|
318
326
|
val visibleHeight = previousMaxHeight - currentTy
|
|
319
327
|
sheetContainer.translationY = (newMaxHeight - visibleHeight).coerceIn(0f, newMaxHeight)
|
|
328
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
320
329
|
emitPosition()
|
|
321
|
-
snapToIndex(
|
|
330
|
+
snapToIndex(
|
|
331
|
+
targetIndex,
|
|
332
|
+
0f,
|
|
333
|
+
emitIndexChange = false,
|
|
334
|
+
emitSettle = shouldEmitSettle,
|
|
335
|
+
preserveScrimPin = true,
|
|
336
|
+
)
|
|
322
337
|
} else {
|
|
323
338
|
val currentVisibleHeight = previousMaxHeight - sheetContainer.translationY
|
|
324
339
|
val targetHeight = detentSpecs.getOrNull(targetIndex)?.height ?: 0f
|
|
@@ -332,8 +347,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
332
347
|
// up to the taller detent.
|
|
333
348
|
sheetContainer.translationY =
|
|
334
349
|
(newMaxHeight - currentVisibleHeight).coerceIn(0f, newMaxHeight)
|
|
350
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
335
351
|
emitPosition()
|
|
336
|
-
snapToIndex(
|
|
352
|
+
snapToIndex(
|
|
353
|
+
targetIndex,
|
|
354
|
+
0f,
|
|
355
|
+
emitIndexChange = false,
|
|
356
|
+
emitSettle = false,
|
|
357
|
+
preserveScrimPin = true,
|
|
358
|
+
)
|
|
337
359
|
}
|
|
338
360
|
}
|
|
339
361
|
}
|
|
@@ -474,12 +496,16 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
474
496
|
velocity: Float,
|
|
475
497
|
emitIndexChange: Boolean = true,
|
|
476
498
|
emitSettle: Boolean = true,
|
|
499
|
+
preserveScrimPin: Boolean = false,
|
|
477
500
|
) {
|
|
478
501
|
if (index < 0 || index >= detentSpecs.size) return
|
|
479
502
|
targetIndex = index
|
|
480
503
|
if (!isTargetingClosedDetent) {
|
|
481
504
|
suppressScrimForClosingTarget = false
|
|
482
505
|
}
|
|
506
|
+
if (!preserveScrimPin) {
|
|
507
|
+
scrimPinnedFull = false
|
|
508
|
+
}
|
|
483
509
|
|
|
484
510
|
val targetTy = translationY(index)
|
|
485
511
|
val currentTy = sheetContainer.translationY
|
|
@@ -504,6 +530,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
504
530
|
activeAnimation = null
|
|
505
531
|
activeAnimationEmitsSettle = false
|
|
506
532
|
suppressScrimForClosingTarget = false
|
|
533
|
+
scrimPinnedFull = false
|
|
507
534
|
if (closedIndex == index) {
|
|
508
535
|
sheetContainer.translationY = translationY(index)
|
|
509
536
|
hideScrim()
|
|
@@ -693,6 +720,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
693
720
|
|
|
694
721
|
private fun beginPan(event: MotionEvent) {
|
|
695
722
|
isPanning = true
|
|
723
|
+
scrimPinnedFull = false
|
|
696
724
|
panStartingIndex = targetIndex
|
|
697
725
|
activePointerId = event.getPointerId(0)
|
|
698
726
|
lastTouchY = event.y
|
|
@@ -818,6 +846,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
818
846
|
return
|
|
819
847
|
}
|
|
820
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
|
+
|
|
821
858
|
val threshold = firstNonZeroDetentHeight
|
|
822
859
|
scrimProgress = if (threshold <= 0f) 0f else (position / threshold).coerceIn(0f, 1f)
|
|
823
860
|
invalidate()
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "../common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/BottomSheetStateHelper.h"
|
|
4
4
|
#import "../common/cpp/react/renderer/components/ReactNativeBottomSheetSpec/ComponentDescriptors.h"
|
|
5
5
|
|
|
6
|
+
#import <React/RCTAssert.h>
|
|
6
7
|
#import <React/RCTConversions.h>
|
|
7
8
|
#import <React/RCTFabricComponentsPlugins.h>
|
|
8
9
|
#import <react/renderer/components/ReactNativeBottomSheetSpec/EventEmitters.h>
|
|
@@ -138,6 +139,11 @@ using namespace facebook::react;
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
- (void)bottomSheetView:(BottomSheetContentView *)view didReportError:(NSString *)message
|
|
143
|
+
{
|
|
144
|
+
RCTFatal([NSError errorWithDomain:RCTErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : message}]);
|
|
145
|
+
}
|
|
146
|
+
|
|
141
147
|
- (void)prepareForRecycle
|
|
142
148
|
{
|
|
143
149
|
[super prepareForRecycle];
|
|
@@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
8
8
|
- (void)bottomSheetView:(BottomSheetContentView *)view didChangeIndex:(NSInteger)index;
|
|
9
9
|
- (void)bottomSheetView:(BottomSheetContentView *)view didSettle:(NSInteger)index;
|
|
10
10
|
- (void)bottomSheetView:(BottomSheetContentView *)view didChangePosition:(CGFloat)position;
|
|
11
|
+
- (void)bottomSheetView:(BottomSheetContentView *)view didReportError:(NSString *)message;
|
|
11
12
|
@end
|
|
12
13
|
|
|
13
14
|
@interface BottomSheetContentView : UIView
|
|
@@ -115,6 +115,11 @@
|
|
|
115
115
|
[self.delegate bottomSheetView:self didChangePosition:position];
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
- (void)bottomSheetHostingView:(RNSBottomSheetHostingView *)view didReportError:(NSString *)message
|
|
119
|
+
{
|
|
120
|
+
[self.delegate bottomSheetView:self didReportError:message];
|
|
121
|
+
}
|
|
122
|
+
|
|
118
123
|
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
|
119
124
|
{
|
|
120
125
|
CGPoint implPoint = [self convertPoint:point toView:_impl];
|
|
@@ -4,6 +4,7 @@ import UIKit
|
|
|
4
4
|
func bottomSheetHostingView(_ view: RNSBottomSheetHostingView, didChangeIndex index: Int)
|
|
5
5
|
func bottomSheetHostingView(_ view: RNSBottomSheetHostingView, didSettle index: Int)
|
|
6
6
|
func bottomSheetHostingView(_ view: RNSBottomSheetHostingView, didChangePosition position: CGFloat)
|
|
7
|
+
func bottomSheetHostingView(_ view: RNSBottomSheetHostingView, didReportError message: String)
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
private struct DetentSpec: Equatable {
|
|
@@ -55,10 +56,12 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
55
56
|
private var panGesture: UIPanGestureRecognizer!
|
|
56
57
|
private var activeAnimator: UIViewPropertyAnimator?
|
|
57
58
|
private var activeAnimatorEmitsSettle = false
|
|
59
|
+
private var scrimPinnedFull = false
|
|
58
60
|
private var displayLink: CADisplayLink?
|
|
59
61
|
private var pendingIndex: Int?
|
|
60
62
|
private var hasLaidOut = false
|
|
61
63
|
private var isPanning = false
|
|
64
|
+
private var lastReportedInvalidDetentMessage: String?
|
|
62
65
|
private var panStartingIndex: Int?
|
|
63
66
|
private var isContentInteractionDisabled = false
|
|
64
67
|
private var contentHeightMarker: UIView?
|
|
@@ -364,10 +367,14 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
364
367
|
_ index: Int,
|
|
365
368
|
velocity: CGFloat,
|
|
366
369
|
emitIndexChange: Bool = true,
|
|
367
|
-
emitSettle: Bool = true
|
|
370
|
+
emitSettle: Bool = true,
|
|
371
|
+
preserveScrimPin: Bool = false
|
|
368
372
|
) {
|
|
369
373
|
guard index >= 0, index < detentSpecs.count else { return }
|
|
370
374
|
targetIndex = index
|
|
375
|
+
if !preserveScrimPin {
|
|
376
|
+
scrimPinnedFull = false
|
|
377
|
+
}
|
|
371
378
|
|
|
372
379
|
let currentTy = sheetContainer.transform.ty
|
|
373
380
|
let targetTy = translationY(for: index)
|
|
@@ -391,6 +398,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
391
398
|
self.emitPosition()
|
|
392
399
|
self.activeAnimator = nil
|
|
393
400
|
self.activeAnimatorEmitsSettle = false
|
|
401
|
+
self.scrimPinnedFull = false
|
|
394
402
|
self.setContentInteractionEnabled(true)
|
|
395
403
|
self.updateInteractionState()
|
|
396
404
|
if emitIndexChange {
|
|
@@ -411,6 +419,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
411
419
|
switch gesture.state {
|
|
412
420
|
case .began:
|
|
413
421
|
isPanning = true
|
|
422
|
+
scrimPinnedFull = false
|
|
414
423
|
panStartingIndex = targetIndex
|
|
415
424
|
sheetContainer.endEditing(true)
|
|
416
425
|
setContentInteractionEnabled(false)
|
|
@@ -571,28 +580,35 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
571
580
|
detentSpecs.map(\.height).max()
|
|
572
581
|
}
|
|
573
582
|
|
|
574
|
-
private func resolveDetentSpecs() -> [DetentSpec] {
|
|
583
|
+
private func resolveDetentSpecs() -> [DetentSpec]? {
|
|
575
584
|
let maxHeight = resolvedMaxDetentHeight
|
|
576
585
|
let measuredContentHeight = maxHeight > 0 ? validContentHeight.map { min($0, maxHeight) } : nil
|
|
577
586
|
let contentHeight = measuredContentHeight ?? maxHeight
|
|
578
|
-
|
|
587
|
+
var resolvedDetents: [DetentSpec] = []
|
|
588
|
+
resolvedDetents.reserveCapacity(rawDetentSpecs.count)
|
|
589
|
+
|
|
590
|
+
for (index, spec) in rawDetentSpecs.enumerated() {
|
|
579
591
|
let height: CGFloat
|
|
580
592
|
switch spec.kind {
|
|
581
593
|
case .points:
|
|
582
594
|
if measuredContentHeight != nil, spec.value > contentHeight {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
595
|
+
let message =
|
|
596
|
+
"Invalid bottom sheet detent at index \(index): fixed detent \(spec.value) exceeds measured content height \(contentHeight)."
|
|
597
|
+
if lastReportedInvalidDetentMessage != message {
|
|
598
|
+
lastReportedInvalidDetentMessage = message
|
|
599
|
+
eventDelegate?.bottomSheetHostingView(self, didReportError: message)
|
|
600
|
+
}
|
|
601
|
+
return nil
|
|
589
602
|
}
|
|
590
603
|
height = spec.value
|
|
591
604
|
case .content:
|
|
592
605
|
height = contentHeight
|
|
593
606
|
}
|
|
594
|
-
|
|
607
|
+
resolvedDetents.append(DetentSpec(height: min(max(0, height), maxHeight), programmatic: spec.programmatic))
|
|
595
608
|
}
|
|
609
|
+
|
|
610
|
+
lastReportedInvalidDetentMessage = nil
|
|
611
|
+
return resolvedDetents
|
|
596
612
|
}
|
|
597
613
|
|
|
598
614
|
private func refreshDetentsFromLayout() {
|
|
@@ -602,13 +618,22 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
602
618
|
return
|
|
603
619
|
}
|
|
604
620
|
|
|
605
|
-
let resolvedDetents = resolveDetentSpecs()
|
|
621
|
+
guard let resolvedDetents = resolveDetentSpecs() else {
|
|
622
|
+
updateScrim()
|
|
623
|
+
return
|
|
624
|
+
}
|
|
606
625
|
guard resolvedDetents != detentSpecs else {
|
|
607
626
|
updateScrim()
|
|
608
627
|
return
|
|
609
628
|
}
|
|
610
629
|
|
|
611
630
|
let previousMaxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
631
|
+
// Whether the scrim is currently fully opaque, i.e. the sheet is settled at
|
|
632
|
+
// or above the first non-zero detent. If so, a detent resize must not dip
|
|
633
|
+
// the scrim while the sheet re-anchors to the new geometry.
|
|
634
|
+
let wasScrimFull = modal
|
|
635
|
+
&& firstNonZeroDetentHeight > 0
|
|
636
|
+
&& currentSheetHeight + 0.5 >= firstNonZeroDetentHeight
|
|
612
637
|
detentSpecs = resolvedDetents
|
|
613
638
|
|
|
614
639
|
guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
|
|
@@ -632,8 +657,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
632
657
|
let visibleHeight = previousMaxHeight - visualTy
|
|
633
658
|
let reanchoredTy = min(max(newMaxHeight - visibleHeight, 0), newMaxHeight)
|
|
634
659
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: reanchoredTy)
|
|
660
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
635
661
|
emitPosition()
|
|
636
|
-
snapToIndex(
|
|
662
|
+
snapToIndex(
|
|
663
|
+
targetIndex,
|
|
664
|
+
velocity: 0,
|
|
665
|
+
emitIndexChange: false,
|
|
666
|
+
emitSettle: shouldEmitSettle,
|
|
667
|
+
preserveScrimPin: true
|
|
668
|
+
)
|
|
637
669
|
} else {
|
|
638
670
|
let currentVisibleHeight = previousMaxHeight - currentTranslationY
|
|
639
671
|
let targetHeight = detent(at: targetIndex).height
|
|
@@ -647,8 +679,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
647
679
|
// up to the taller detent.
|
|
648
680
|
let startTy = min(max(newMaxHeight - currentVisibleHeight, 0), newMaxHeight)
|
|
649
681
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: startTy)
|
|
682
|
+
scrimPinnedFull = scrimPinnedFull || wasScrimFull
|
|
650
683
|
emitPosition()
|
|
651
|
-
snapToIndex(
|
|
684
|
+
snapToIndex(
|
|
685
|
+
targetIndex,
|
|
686
|
+
velocity: 0,
|
|
687
|
+
emitIndexChange: false,
|
|
688
|
+
emitSettle: false,
|
|
689
|
+
preserveScrimPin: true
|
|
690
|
+
)
|
|
652
691
|
}
|
|
653
692
|
}
|
|
654
693
|
}
|
|
@@ -786,6 +825,15 @@ private extension RNSBottomSheetHostingView {
|
|
|
786
825
|
return
|
|
787
826
|
}
|
|
788
827
|
|
|
828
|
+
// While the sheet is fully open and only its content/detent geometry is
|
|
829
|
+
// resizing, the position momentarily lags the grown detent height. Keep the
|
|
830
|
+
// scrim at full opacity instead of dipping it until the re-anchor settles.
|
|
831
|
+
if scrimPinnedFull {
|
|
832
|
+
scrimView.alpha = 1
|
|
833
|
+
scrimView.isHidden = false
|
|
834
|
+
return
|
|
835
|
+
}
|
|
836
|
+
|
|
789
837
|
let threshold = firstNonZeroDetentHeight
|
|
790
838
|
let progress: CGFloat
|
|
791
839
|
if threshold <= 0 {
|
package/package.json
CHANGED