@lodev09/react-native-true-sheet 3.9.0-beta.2 → 3.9.0-beta.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.
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +46 -2
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +2 -2
- package/ios/TrueSheetView.mm +8 -1
- package/ios/core/RNScreensEventObserver.h +0 -1
- package/ios/core/RNScreensEventObserver.mm +38 -6
- package/package.json +1 -1
|
@@ -528,6 +528,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
528
528
|
when (newState) {
|
|
529
529
|
BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)
|
|
530
530
|
|
|
531
|
+
BottomSheetBehavior.STATE_SETTLING -> handleSettling(sheetView)
|
|
532
|
+
|
|
531
533
|
BottomSheetBehavior.STATE_EXPANDED,
|
|
532
534
|
BottomSheetBehavior.STATE_COLLAPSED,
|
|
533
535
|
BottomSheetBehavior.STATE_HALF_EXPANDED -> handleStateSettled(sheetView, newState)
|
|
@@ -581,7 +583,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
581
583
|
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
582
584
|
delegate?.viewControllerDidDragEnd(detentInfo.index, detentInfo.position, detent)
|
|
583
585
|
|
|
584
|
-
if
|
|
586
|
+
// Skip detent change if keyboard inset is still active — detent mapping is unreliable.
|
|
587
|
+
// keyboardWillHide will recalculate detents and settle at the correct index.
|
|
588
|
+
if (detentInfo.index != currentDetentIndex && keyboardInset == 0) {
|
|
585
589
|
currentDetentIndex = detentInfo.index
|
|
586
590
|
setupDimmedBackground()
|
|
587
591
|
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
@@ -713,6 +717,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
713
717
|
return
|
|
714
718
|
}
|
|
715
719
|
|
|
720
|
+
// Commit the new index so keyboardWillHide restores to it instead of the stale one
|
|
721
|
+
if (detentIndexBeforeKeyboard >= 0) {
|
|
722
|
+
detentIndexBeforeKeyboard = detentIndex
|
|
723
|
+
}
|
|
724
|
+
|
|
716
725
|
setupDimmedBackground()
|
|
717
726
|
setStateForDetentIndex(detentIndex)
|
|
718
727
|
resizePromise?.invoke()
|
|
@@ -1015,7 +1024,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1015
1024
|
|
|
1016
1025
|
override fun keyboardWillHide() {
|
|
1017
1026
|
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
1018
|
-
|
|
1019
1027
|
setupSheetDetents()
|
|
1020
1028
|
if (!isBeingDismissed && detentIndexBeforeKeyboard >= 0) {
|
|
1021
1029
|
setStateForDetentIndex(detentIndexBeforeKeyboard)
|
|
@@ -1062,14 +1070,50 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1062
1070
|
detentCalculator.getPositionDp(detentCalculator.getVisibleSheetHeight(sheetView.top))
|
|
1063
1071
|
|
|
1064
1072
|
private fun handleDragBegin(sheetView: View) {
|
|
1073
|
+
detentIndexBeforeKeyboard = -1
|
|
1074
|
+
|
|
1065
1075
|
val position = getPositionDpForView(sheetView)
|
|
1066
1076
|
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
1067
1077
|
delegate?.viewControllerDidDragBegin(currentDetentIndex, position, detent)
|
|
1068
1078
|
interactionState = InteractionState.Dragging(startTop = sheetView.top)
|
|
1069
1079
|
}
|
|
1070
1080
|
|
|
1081
|
+
private fun handleSettling(sheetView: View) {
|
|
1082
|
+
if (interactionState !is InteractionState.Dragging) return
|
|
1083
|
+
if (keyboardInset <= 0) return
|
|
1084
|
+
|
|
1085
|
+
// After drag release, check if the sheet was dragged past the midpoint between the
|
|
1086
|
+
// keyboard-expanded position and a non-keyboard detent position. If so, dismiss
|
|
1087
|
+
// keyboard and commit to that detent.
|
|
1088
|
+
val maxAvailableHeight = realScreenHeight - topInset
|
|
1089
|
+
val keyboardHeight = minOf(detentCalculator.getDetentHeight(detents.last()), maxAvailableHeight)
|
|
1090
|
+
val keyboardTop = realScreenHeight - keyboardHeight
|
|
1091
|
+
|
|
1092
|
+
for (i in detents.indices) {
|
|
1093
|
+
val nonKeyboardHeight = minOf(detentCalculator.getDetentHeight(detents[i], includeKeyboard = false), maxAvailableHeight)
|
|
1094
|
+
val nonKeyboardTop = realScreenHeight - nonKeyboardHeight
|
|
1095
|
+
val midpoint = keyboardTop + (nonKeyboardTop - keyboardTop) / 2
|
|
1096
|
+
|
|
1097
|
+
if (sheetView.top >= midpoint) {
|
|
1098
|
+
// Target position matches the keyboard-expanded position — keep keyboard open
|
|
1099
|
+
if (nonKeyboardTop == keyboardTop) break
|
|
1100
|
+
|
|
1101
|
+
detentIndexBeforeKeyboard = -1
|
|
1102
|
+
currentDetentIndex = i
|
|
1103
|
+
dismissKeyboard()
|
|
1104
|
+
setupDimmedBackground()
|
|
1105
|
+
|
|
1106
|
+
val position = getPositionDpForView(sheetView)
|
|
1107
|
+
val detent = detentCalculator.getDetentValueForIndex(i)
|
|
1108
|
+
delegate?.viewControllerDidChangeDetent(i, position, detent)
|
|
1109
|
+
break
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1071
1114
|
private fun handleDragChange(sheetView: View) {
|
|
1072
1115
|
if (interactionState !is InteractionState.Dragging) return
|
|
1116
|
+
|
|
1073
1117
|
val position = getPositionDpForView(sheetView)
|
|
1074
1118
|
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
1075
1119
|
delegate?.viewControllerDidDragChange(currentDetentIndex, position, detent)
|
|
@@ -39,7 +39,7 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
39
39
|
* Calculate the height in pixels for a given detent value.
|
|
40
40
|
* @param detent The detent value: -1.0 for content-fit, or 0.0-1.0 for screen fraction
|
|
41
41
|
*/
|
|
42
|
-
fun getDetentHeight(detent: Double): Int {
|
|
42
|
+
fun getDetentHeight(detent: Double, includeKeyboard: Boolean = true): Int {
|
|
43
43
|
val baseHeight = if (detent == -1.0) {
|
|
44
44
|
contentHeight + headerHeight + contentBottomInset
|
|
45
45
|
} else {
|
|
@@ -49,7 +49,7 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
49
49
|
(detent * screenHeight).toInt() + contentBottomInset
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
val height = baseHeight + keyboardInset
|
|
52
|
+
val height = if (includeKeyboard) baseHeight + keyboardInset else baseHeight
|
|
53
53
|
val maxAllowedHeight = screenHeight + contentBottomInset
|
|
54
54
|
return maxContentHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
|
|
55
55
|
}
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -291,6 +291,8 @@ using namespace facebook::react;
|
|
|
291
291
|
stateData.containerHeight = static_cast<float>(size.height);
|
|
292
292
|
|
|
293
293
|
#if REACT_NATIVE_VERSION_MINOR >= 82
|
|
294
|
+
// TODO: RN 0.82+ processes state updates in the same layout pass (synchronous).
|
|
295
|
+
// Once stable, we can drop native layout constraints in favor of synchronous Yoga layout.
|
|
294
296
|
_state->updateState(std::move(stateData), facebook::react::EventQueue::UpdateMode::unstable_Immediate);
|
|
295
297
|
#else
|
|
296
298
|
_state->updateState(std::move(stateData));
|
|
@@ -669,7 +671,12 @@ using namespace facebook::react;
|
|
|
669
671
|
- (void)presenterScreenWillAppear {
|
|
670
672
|
if (_dismissedByNavigation && !_controller.isPresented && !_controller.isBeingPresented) {
|
|
671
673
|
_dismissedByNavigation = NO;
|
|
672
|
-
|
|
674
|
+
|
|
675
|
+
if (self.window) {
|
|
676
|
+
[self presentAtIndex:_controller.activeDetentIndex animated:YES completion:nil];
|
|
677
|
+
} else {
|
|
678
|
+
_pendingNavigationRepresent = YES;
|
|
679
|
+
}
|
|
673
680
|
}
|
|
674
681
|
}
|
|
675
682
|
|
|
@@ -25,6 +25,8 @@ using namespace facebook::react;
|
|
|
25
25
|
__weak UIViewController *_parentScreenController;
|
|
26
26
|
__weak UIWindow *_window;
|
|
27
27
|
BOOL _dismissedByNavigation;
|
|
28
|
+
BOOL _pendingInteractiveDismiss;
|
|
29
|
+
BOOL _pendingInteractiveAppear;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
- (instancetype)init {
|
|
@@ -60,19 +62,44 @@ using namespace facebook::react;
|
|
|
60
62
|
|
|
61
63
|
if (auto family = event.shadowNodeFamily.lock()) {
|
|
62
64
|
Tag screenTag = family->getTag();
|
|
65
|
+
if (![strongSelf->_screenTags containsObject:@(screenTag)]) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
BOOL interactive = [strongSelf isInteractiveTransition];
|
|
63
70
|
|
|
64
71
|
if (event.type == "topWillDisappear") {
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
if (interactive) {
|
|
73
|
+
strongSelf->_pendingInteractiveDismiss = YES;
|
|
74
|
+
} else if ([strongSelf shouldDismissForScreenTag:screenTag]) {
|
|
75
|
+
strongSelf->_dismissedByNavigation = YES;
|
|
76
|
+
[strongSelf.delegate presenterScreenWillDisappear];
|
|
77
|
+
}
|
|
78
|
+
} else if (event.type == "topDisappear") {
|
|
79
|
+
if (strongSelf->_pendingInteractiveDismiss) {
|
|
80
|
+
strongSelf->_pendingInteractiveDismiss = NO;
|
|
81
|
+
strongSelf->_dismissedByNavigation = YES;
|
|
82
|
+
[strongSelf.delegate presenterScreenWillDisappear];
|
|
70
83
|
}
|
|
71
84
|
} else if (event.type == "topWillAppear") {
|
|
72
|
-
if (
|
|
85
|
+
if (strongSelf->_dismissedByNavigation) {
|
|
86
|
+
if (interactive) {
|
|
87
|
+
strongSelf->_pendingInteractiveAppear = YES;
|
|
88
|
+
} else {
|
|
89
|
+
strongSelf->_dismissedByNavigation = NO;
|
|
90
|
+
[strongSelf.delegate presenterScreenWillAppear];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
strongSelf->_pendingInteractiveDismiss = NO;
|
|
94
|
+
} else if (event.type == "topAppear") {
|
|
95
|
+
if (strongSelf->_pendingInteractiveAppear) {
|
|
96
|
+
strongSelf->_pendingInteractiveAppear = NO;
|
|
73
97
|
strongSelf->_dismissedByNavigation = NO;
|
|
74
98
|
[strongSelf.delegate presenterScreenWillAppear];
|
|
75
99
|
}
|
|
100
|
+
} else if (event.type == "topGestureCancel") {
|
|
101
|
+
strongSelf->_pendingInteractiveDismiss = NO;
|
|
102
|
+
strongSelf->_pendingInteractiveAppear = NO;
|
|
76
103
|
}
|
|
77
104
|
}
|
|
78
105
|
return false;
|
|
@@ -121,6 +148,11 @@ using namespace facebook::react;
|
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
|
|
151
|
+
- (BOOL)isInteractiveTransition {
|
|
152
|
+
UINavigationController *navController = _presenterScreenController.navigationController;
|
|
153
|
+
return navController.transitionCoordinator.isInteractive;
|
|
154
|
+
}
|
|
155
|
+
|
|
124
156
|
- (BOOL)shouldDismissForScreenTag:(NSInteger)screenTag {
|
|
125
157
|
// For parent screens (not immediate presenter), check if the presenter screen is being removed
|
|
126
158
|
// This handles nested stack removal case
|
package/package.json
CHANGED