@lodev09/react-native-true-sheet 3.6.7 → 3.6.9
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/TrueSheetView.kt +40 -4
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +16 -28
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetKeyboardObserver.kt +18 -0
- package/ios/TrueSheetFooterView.mm +9 -2
- package/ios/TrueSheetView.mm +6 -5
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ package com.lodev09.truesheet
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.view.View
|
|
5
|
+
import android.view.ViewGroup
|
|
5
6
|
import android.view.ViewStructure
|
|
6
7
|
import android.view.accessibility.AccessibilityEvent
|
|
7
8
|
import androidx.annotation.UiThread
|
|
@@ -64,6 +65,9 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
64
65
|
// Debounce flag to coalesce rapid layout changes into a single sheet update
|
|
65
66
|
private var isSheetUpdatePending: Boolean = false
|
|
66
67
|
|
|
68
|
+
// Root container for the coordinator layout (activity or Modal dialog content view)
|
|
69
|
+
private var rootContainerView: ViewGroup? = null
|
|
70
|
+
|
|
67
71
|
// ==================== Initialization ====================
|
|
68
72
|
|
|
69
73
|
init {
|
|
@@ -131,6 +135,11 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
131
135
|
val child = getChildAt(index)
|
|
132
136
|
if (child is TrueSheetContainerView) {
|
|
133
137
|
child.delegate = null
|
|
138
|
+
|
|
139
|
+
// Dismiss the sheet when container is removed
|
|
140
|
+
if (viewController.isPresented) {
|
|
141
|
+
viewController.dismiss(animated = false)
|
|
142
|
+
}
|
|
134
143
|
}
|
|
135
144
|
viewController.removeView(child)
|
|
136
145
|
}
|
|
@@ -155,14 +164,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
155
164
|
fun onDropInstance() {
|
|
156
165
|
reactContext.removeLifecycleEventListener(this)
|
|
157
166
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
167
|
+
viewController.dismiss()
|
|
168
|
+
viewController.delegate = null
|
|
161
169
|
|
|
162
170
|
TrueSheetModule.unregisterView(id)
|
|
163
171
|
TrueSheetStackManager.removeSheet(this)
|
|
164
172
|
|
|
165
|
-
viewController.delegate = null
|
|
166
173
|
didInitiallyPresent = false
|
|
167
174
|
}
|
|
168
175
|
|
|
@@ -265,6 +272,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
265
272
|
@UiThread
|
|
266
273
|
fun present(detentIndex: Int, animated: Boolean = true, promiseCallback: () -> Unit) {
|
|
267
274
|
if (!viewController.isPresented) {
|
|
275
|
+
// Attach coordinator to the root container
|
|
276
|
+
rootContainerView = findRootContainerView()
|
|
277
|
+
viewController.coordinatorLayout?.let { rootContainerView?.addView(it) }
|
|
278
|
+
|
|
268
279
|
// Register with observer to track sheet stack hierarchy
|
|
269
280
|
viewController.parentSheetView = TrueSheetStackManager.onSheetWillPresent(this, detentIndex)
|
|
270
281
|
}
|
|
@@ -390,6 +401,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
390
401
|
}
|
|
391
402
|
|
|
392
403
|
override fun viewControllerDidDismiss(hadParent: Boolean) {
|
|
404
|
+
// Detach coordinator from the root container view
|
|
405
|
+
viewController.coordinatorLayout?.let { rootContainerView?.removeView(it) }
|
|
406
|
+
rootContainerView = null
|
|
407
|
+
|
|
393
408
|
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
394
409
|
eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
|
|
395
410
|
|
|
@@ -464,4 +479,25 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
464
479
|
// Footer changes don't affect detents, only reposition it
|
|
465
480
|
viewController.positionFooter()
|
|
466
481
|
}
|
|
482
|
+
|
|
483
|
+
// ==================== Private Helpers ====================
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Find the root container view for presenting the sheet.
|
|
487
|
+
* This traverses up the view hierarchy to find the content view (android.R.id.content)
|
|
488
|
+
* of whichever window this view is in - whether that's the activity's window or a
|
|
489
|
+
* Modal's dialog window.
|
|
490
|
+
*/
|
|
491
|
+
private fun findRootContainerView(): ViewGroup? {
|
|
492
|
+
var current: android.view.ViewParent? = parent
|
|
493
|
+
|
|
494
|
+
while (current != null) {
|
|
495
|
+
if (current is ViewGroup && current.id == android.R.id.content) {
|
|
496
|
+
return current
|
|
497
|
+
}
|
|
498
|
+
current = current.parent
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return reactContext.currentActivity?.findViewById(android.R.id.content)
|
|
502
|
+
}
|
|
467
503
|
}
|
|
@@ -110,7 +110,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
110
110
|
|
|
111
111
|
// CoordinatorLayout components (replaces DialogFragment)
|
|
112
112
|
internal var sheetView: TrueSheetBottomSheetView? = null
|
|
113
|
-
|
|
113
|
+
internal var coordinatorLayout: TrueSheetCoordinatorLayout? = null
|
|
114
114
|
private var dimView: TrueSheetDimView? = null
|
|
115
115
|
private var parentDimView: TrueSheetDimView? = null
|
|
116
116
|
|
|
@@ -139,7 +139,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
139
139
|
|
|
140
140
|
// Keyboard State
|
|
141
141
|
private var detentIndexBeforeKeyboard: Int = -1
|
|
142
|
-
private var isKeyboardTransitioning: Boolean = false
|
|
143
142
|
|
|
144
143
|
// Promises
|
|
145
144
|
var presentPromise: (() -> Unit)? = null
|
|
@@ -238,6 +237,14 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
238
237
|
private val currentKeyboardInset: Int
|
|
239
238
|
get() = keyboardObserver?.currentHeight ?: 0
|
|
240
239
|
|
|
240
|
+
private val isKeyboardTransitioning: Boolean
|
|
241
|
+
get() = keyboardObserver?.isTransitioning ?: false
|
|
242
|
+
|
|
243
|
+
private fun isFocusedViewWithinSheet(): Boolean {
|
|
244
|
+
val sheet = sheetView ?: return false
|
|
245
|
+
return keyboardObserver?.isFocusedViewWithinSheet(sheet) ?: false
|
|
246
|
+
}
|
|
247
|
+
|
|
241
248
|
val bottomInset: Int
|
|
242
249
|
get() = if (edgeToEdgeEnabled) ScreenUtils.getInsets(reactContext).bottom else 0
|
|
243
250
|
|
|
@@ -303,9 +310,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
303
310
|
cleanupBackCallback()
|
|
304
311
|
sheetView?.animate()?.cancel()
|
|
305
312
|
|
|
306
|
-
// Remove from activity
|
|
307
|
-
removeFromActivity()
|
|
308
|
-
|
|
309
313
|
// Cleanup dim views
|
|
310
314
|
dimView?.detach()
|
|
311
315
|
dimView = null
|
|
@@ -323,19 +327,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
323
327
|
isPresented = false
|
|
324
328
|
isSheetVisible = false
|
|
325
329
|
wasHiddenByModal = false
|
|
326
|
-
isKeyboardTransitioning = false
|
|
327
330
|
isPresentAnimating = false
|
|
328
331
|
lastEmittedPositionPx = -1
|
|
329
332
|
detentIndexBeforeKeyboard = -1
|
|
330
333
|
shouldAnimatePresent = true
|
|
331
334
|
}
|
|
332
335
|
|
|
333
|
-
private fun removeFromActivity() {
|
|
334
|
-
val coordinator = coordinatorLayout ?: return
|
|
335
|
-
val contentView = reactContext.currentActivity?.findViewById<ViewGroup>(android.R.id.content)
|
|
336
|
-
contentView?.removeView(coordinator)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
336
|
// =============================================================================
|
|
340
337
|
// MARK: - Back Button Handling
|
|
341
338
|
// =============================================================================
|
|
@@ -603,15 +600,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
603
600
|
// Setup sheet in coordinator layout
|
|
604
601
|
setupSheetInCoordinator(coordinator, sheet)
|
|
605
602
|
|
|
606
|
-
// Add coordinator to activity
|
|
607
|
-
val activity = reactContext.currentActivity ?: run {
|
|
608
|
-
RNLog.w(reactContext, "TrueSheet: No activity available for presentation.")
|
|
609
|
-
return
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
val contentView = activity.findViewById<ViewGroup>(android.R.id.content)
|
|
613
|
-
contentView?.addView(coordinator)
|
|
614
|
-
|
|
615
603
|
emitWillPresentEvents()
|
|
616
604
|
|
|
617
605
|
setupSheetDetents()
|
|
@@ -890,9 +878,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
890
878
|
// MARK: - Keyboard Handling
|
|
891
879
|
// =============================================================================
|
|
892
880
|
|
|
893
|
-
private fun shouldHandleKeyboard(): Boolean {
|
|
881
|
+
private fun shouldHandleKeyboard(checkFocus: Boolean = true): Boolean {
|
|
894
882
|
if (wasHiddenByModal) return false
|
|
895
|
-
return
|
|
883
|
+
if (!isTopmostSheet) return false
|
|
884
|
+
if (checkFocus && !isFocusedViewWithinSheet()) return false
|
|
885
|
+
return true
|
|
896
886
|
}
|
|
897
887
|
|
|
898
888
|
fun setupKeyboardObserver() {
|
|
@@ -904,7 +894,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
904
894
|
keyboardObserver = TrueSheetKeyboardObserver(coordinator, reactContext).apply {
|
|
905
895
|
delegate = object : TrueSheetKeyboardObserverDelegate {
|
|
906
896
|
override fun keyboardWillShow(height: Int) {
|
|
907
|
-
isKeyboardTransitioning = true
|
|
908
897
|
if (!shouldHandleKeyboard()) return
|
|
909
898
|
detentIndexBeforeKeyboard = currentDetentIndex
|
|
910
899
|
setupSheetDetents()
|
|
@@ -912,7 +901,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
912
901
|
}
|
|
913
902
|
|
|
914
903
|
override fun keyboardWillHide() {
|
|
915
|
-
if (!shouldHandleKeyboard()) return
|
|
904
|
+
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
905
|
+
|
|
916
906
|
setupSheetDetents()
|
|
917
907
|
if (!isDismissing && detentIndexBeforeKeyboard >= 0) {
|
|
918
908
|
setStateForDetentIndex(detentIndexBeforeKeyboard)
|
|
@@ -920,9 +910,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
920
910
|
}
|
|
921
911
|
}
|
|
922
912
|
|
|
923
|
-
override fun keyboardDidHide() {
|
|
924
|
-
isKeyboardTransitioning = false
|
|
925
|
-
}
|
|
913
|
+
override fun keyboardDidHide() {}
|
|
926
914
|
|
|
927
915
|
override fun keyboardDidChangeHeight(height: Int) {
|
|
928
916
|
if (!shouldHandleKeyboard()) return
|
|
@@ -31,6 +31,20 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
31
31
|
var targetHeight: Int = 0
|
|
32
32
|
private set
|
|
33
33
|
|
|
34
|
+
var isTransitioning: Boolean = false
|
|
35
|
+
private set
|
|
36
|
+
|
|
37
|
+
fun isFocusedViewWithinSheet(sheetView: View): Boolean {
|
|
38
|
+
val focusedView = reactContext.currentActivity?.currentFocus ?: return false
|
|
39
|
+
var current: View? = focusedView
|
|
40
|
+
while (current != null && current !== targetView) {
|
|
41
|
+
if (current === sheetView) return true
|
|
42
|
+
val parent = current.parent
|
|
43
|
+
current = if (parent is View) parent else null
|
|
44
|
+
}
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
private var isHiding: Boolean = false
|
|
35
49
|
private var globalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
|
|
36
50
|
private var activityRootView: View? = null
|
|
@@ -80,6 +94,7 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
80
94
|
endHeight = getKeyboardHeight()
|
|
81
95
|
targetHeight = endHeight
|
|
82
96
|
isHiding = endHeight < startHeight
|
|
97
|
+
isTransitioning = true
|
|
83
98
|
if (endHeight > startHeight) {
|
|
84
99
|
delegate?.keyboardWillShow(endHeight)
|
|
85
100
|
} else if (isHiding) {
|
|
@@ -102,6 +117,7 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
102
117
|
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
|
103
118
|
val finalHeight = getKeyboardHeight()
|
|
104
119
|
updateHeight(startHeight, finalHeight, 1f)
|
|
120
|
+
isTransitioning = false
|
|
105
121
|
if (isHiding) {
|
|
106
122
|
delegate?.keyboardDidHide()
|
|
107
123
|
isHiding = false
|
|
@@ -134,6 +150,7 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
134
150
|
targetHeight = newHeight
|
|
135
151
|
isHiding = newHeight < previousHeight
|
|
136
152
|
|
|
153
|
+
isTransitioning = true
|
|
137
154
|
if (newHeight > previousHeight) {
|
|
138
155
|
delegate?.keyboardWillShow(newHeight)
|
|
139
156
|
} else if (isHiding) {
|
|
@@ -142,6 +159,7 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
142
159
|
|
|
143
160
|
// On legacy API, keyboard has already animated - just update immediately
|
|
144
161
|
updateHeight(previousHeight, newHeight, 1f)
|
|
162
|
+
isTransitioning = false
|
|
145
163
|
|
|
146
164
|
if (isHiding && newHeight == 0) {
|
|
147
165
|
delegate?.keyboardDidHide()
|
|
@@ -22,6 +22,7 @@ using namespace facebook::react;
|
|
|
22
22
|
CGFloat _lastHeight;
|
|
23
23
|
BOOL _didInitialLayout;
|
|
24
24
|
NSLayoutConstraint *_bottomConstraint;
|
|
25
|
+
CGFloat _currentKeyboardOffset;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
@@ -39,6 +40,7 @@ using namespace facebook::react;
|
|
|
39
40
|
_lastHeight = 0;
|
|
40
41
|
_didInitialLayout = NO;
|
|
41
42
|
_bottomConstraint = nil;
|
|
43
|
+
_currentKeyboardOffset = 0;
|
|
42
44
|
}
|
|
43
45
|
return self;
|
|
44
46
|
}
|
|
@@ -59,8 +61,9 @@ using namespace facebook::react;
|
|
|
59
61
|
[self.leadingAnchor constraintEqualToAnchor:parentView.leadingAnchor].active = YES;
|
|
60
62
|
[self.trailingAnchor constraintEqualToAnchor:parentView.trailingAnchor].active = YES;
|
|
61
63
|
|
|
62
|
-
// Store bottom constraint for keyboard adjustment
|
|
63
|
-
_bottomConstraint = [self.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor
|
|
64
|
+
// Store bottom constraint for keyboard adjustment, preserving current keyboard offset
|
|
65
|
+
_bottomConstraint = [self.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor
|
|
66
|
+
constant:-_currentKeyboardOffset];
|
|
64
67
|
_bottomConstraint.active = YES;
|
|
65
68
|
|
|
66
69
|
// Apply height constraint
|
|
@@ -109,6 +112,7 @@ using namespace facebook::react;
|
|
|
109
112
|
_lastHeight = 0;
|
|
110
113
|
_didInitialLayout = NO;
|
|
111
114
|
_bottomConstraint = nil;
|
|
115
|
+
_currentKeyboardOffset = 0;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
#pragma mark - Keyboard Handling
|
|
@@ -163,6 +167,9 @@ using namespace facebook::react;
|
|
|
163
167
|
// Cap to ensure we don't go negative
|
|
164
168
|
CGFloat bottomOffset = MAX(0, keyboardHeight);
|
|
165
169
|
|
|
170
|
+
// Store the current keyboard offset so it persists through constraint recreation
|
|
171
|
+
_currentKeyboardOffset = bottomOffset;
|
|
172
|
+
|
|
166
173
|
[UIView animateWithDuration:duration
|
|
167
174
|
delay:0
|
|
168
175
|
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -96,7 +96,12 @@ using namespace facebook::react;
|
|
|
96
96
|
|
|
97
97
|
- (void)dealloc {
|
|
98
98
|
if (_controller && _controller.presentingViewController) {
|
|
99
|
-
|
|
99
|
+
// Find the root presenting controller to dismiss the entire stack
|
|
100
|
+
UIViewController *root = _controller.presentingViewController;
|
|
101
|
+
while (root.presentingViewController != nil) {
|
|
102
|
+
root = root.presentingViewController;
|
|
103
|
+
}
|
|
104
|
+
[root dismissViewControllerAnimated:YES completion:nil];
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
_controller.delegate = nil;
|
|
@@ -258,10 +263,6 @@ using namespace facebook::react;
|
|
|
258
263
|
- (void)prepareForRecycle {
|
|
259
264
|
[super prepareForRecycle];
|
|
260
265
|
|
|
261
|
-
if (_controller && _controller.presentingViewController) {
|
|
262
|
-
[_controller dismissViewControllerAnimated:YES completion:nil];
|
|
263
|
-
}
|
|
264
|
-
|
|
265
266
|
[TrueSheetModule unregisterViewWithTag:@(self.tag)];
|
|
266
267
|
|
|
267
268
|
_lastStateSize = CGSizeZero;
|
package/package.json
CHANGED