@lodev09/react-native-true-sheet 4.0.0-beta.14 → 4.0.0-beta.15
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/TrueSheetContainerView.kt +28 -6
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +99 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +12 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +18 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +40 -39
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +18 -8
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +14 -8
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +8 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +32 -5
- package/ios/TrueSheetContainerView.h +17 -4
- package/ios/TrueSheetContainerView.mm +23 -6
- package/ios/TrueSheetContentView.h +13 -1
- package/ios/TrueSheetContentView.mm +93 -35
- package/ios/TrueSheetFooterView.mm +19 -5
- package/ios/TrueSheetView.mm +29 -28
- package/ios/TrueSheetViewController.h +21 -1
- package/ios/TrueSheetViewController.mm +54 -16
- package/ios/core/TrueSheetDetentCalculator.h +6 -1
- package/ios/core/TrueSheetDetentCalculator.mm +9 -4
- package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
- package/lib/module/TrueSheet.js +8 -5
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +135 -32
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetPeek.web.js +20 -15
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -3
- package/lib/module/navigation/expo-router/index.js +5 -4
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/module/web/constants.js +1 -1
- package/lib/module/web/constants.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +64 -25
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +5 -3
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/index.d.ts +5 -3
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +1 -1
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/web/constants.d.ts +1 -1
- package/lib/typescript/src/web/constants.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/TrueSheet.tsx +7 -4
- package/src/TrueSheet.types.ts +67 -25
- package/src/TrueSheet.web.tsx +154 -30
- package/src/TrueSheetPeek.web.tsx +25 -23
- package/src/fabric/TrueSheetViewNativeComponent.ts +5 -3
- package/src/navigation/expo-router/index.ts +8 -11
- package/src/navigation/types.ts +3 -2
- package/src/web/constants.ts +1 -1
|
@@ -5,7 +5,6 @@ import android.view.View
|
|
|
5
5
|
import android.view.ViewGroup
|
|
6
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
7
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
8
|
-
import com.facebook.react.util.RNLog
|
|
9
8
|
import com.facebook.react.views.view.ReactViewGroup
|
|
10
9
|
|
|
11
10
|
interface TrueSheetContainerViewDelegate {
|
|
@@ -15,6 +14,7 @@ interface TrueSheetContainerViewDelegate {
|
|
|
15
14
|
fun containerViewScrollViewDidChange()
|
|
16
15
|
fun containerViewHeaderDidChangeSize(width: Int, height: Int)
|
|
17
16
|
fun containerViewFooterDidChangeSize(width: Int, height: Int)
|
|
17
|
+
fun containerViewFooterDidLayout()
|
|
18
18
|
fun containerViewPeekDidChangeSize(width: Int, height: Int)
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -62,6 +62,18 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
var absoluteFooter: Boolean = false
|
|
65
|
+
set(value) {
|
|
66
|
+
if (field == value) return
|
|
67
|
+
field = value
|
|
68
|
+
contentView?.updateContentInset()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override var footerAvoidsKeyboard: Boolean = true
|
|
72
|
+
set(value) {
|
|
73
|
+
if (field == value) return
|
|
74
|
+
field = value
|
|
75
|
+
contentView?.updateContentInset()
|
|
76
|
+
}
|
|
65
77
|
|
|
66
78
|
/**
|
|
67
79
|
* Bottom safe-area inset the footer absorbs as padding — the footer
|
|
@@ -189,10 +201,9 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
189
201
|
fun attachPeekView(view: TrueSheetPeekView) {
|
|
190
202
|
if (peekView === view) return
|
|
191
203
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
204
|
+
// Last-attached wins. During a screen transition the incoming peek mounts
|
|
205
|
+
// before the outgoing one detaches, and the outgoing detach is a no-op.
|
|
206
|
+
peekView?.delegate = null
|
|
196
207
|
|
|
197
208
|
peekView = view
|
|
198
209
|
view.delegate = this
|
|
@@ -217,6 +228,12 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
217
228
|
override val footerKeyboardOcclusion: Int
|
|
218
229
|
get() = if (absoluteFooter) footerView?.keyboardOcclusionHeight ?: 0 else -(footerView?.height ?: 0)
|
|
219
230
|
|
|
231
|
+
override val absoluteFooterHeight: Int
|
|
232
|
+
get() = if (absoluteFooter) footerView?.height ?: 0 else 0
|
|
233
|
+
|
|
234
|
+
override val absoluteFooterTop: Int
|
|
235
|
+
get() = footerView?.y?.toInt() ?: height
|
|
236
|
+
|
|
220
237
|
override val hasRelativeFooter: Boolean
|
|
221
238
|
get() = footerView != null && !absoluteFooter
|
|
222
239
|
|
|
@@ -236,10 +253,15 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
236
253
|
override fun footerViewDidChangeSize(width: Int, height: Int) {
|
|
237
254
|
footerHeight = height
|
|
238
255
|
delegate?.containerViewFooterDidChangeSize(width, height)
|
|
239
|
-
// A relative footer takes over the bottom inset — drop the scrollable's
|
|
256
|
+
// A relative footer takes over the bottom inset — drop the scrollable's;
|
|
257
|
+
// an absolute one may pad the scrollable by its height
|
|
240
258
|
contentView?.updateContentInset()
|
|
241
259
|
}
|
|
242
260
|
|
|
261
|
+
override fun footerViewDidLayout() {
|
|
262
|
+
delegate?.containerViewFooterDidLayout()
|
|
263
|
+
}
|
|
264
|
+
|
|
243
265
|
override fun peekViewDidChangeSize(width: Int, height: Int) {
|
|
244
266
|
delegate?.containerViewPeekDidChangeSize(width, height)
|
|
245
267
|
}
|
|
@@ -18,11 +18,32 @@ import com.lodev09.truesheet.utils.ScreenUtils
|
|
|
18
18
|
import com.lodev09.truesheet.utils.isDescendantOf
|
|
19
19
|
import com.lodev09.truesheet.utils.smoothScrollTo
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* What the sheet insets the bottom of the scroll content for — the safe area
|
|
23
|
+
* while it can scroll, and/or an absolute footer floating over it.
|
|
24
|
+
*/
|
|
25
|
+
enum class TrueSheetContentInsetAdjustment(val safeArea: Boolean, val footer: Boolean) {
|
|
26
|
+
AUTOMATIC(safeArea = true, footer = true),
|
|
27
|
+
SAFE_AREA(safeArea = true, footer = false),
|
|
28
|
+
FOOTER(safeArea = false, footer = true),
|
|
29
|
+
NEVER(safeArea = false, footer = false);
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
fun fromString(value: String?): TrueSheetContentInsetAdjustment =
|
|
33
|
+
when (value) {
|
|
34
|
+
"safe-area" -> SAFE_AREA
|
|
35
|
+
"footer" -> FOOTER
|
|
36
|
+
"never" -> NEVER
|
|
37
|
+
else -> AUTOMATIC
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
data class ScrollableOptions(
|
|
22
43
|
val keyboardScrollOffset: Float = 0f,
|
|
23
44
|
val keyboardOffset: Float = 0f,
|
|
24
45
|
val scrollingExpandsSheet: Boolean = true,
|
|
25
|
-
val contentInsetAdjustment:
|
|
46
|
+
val contentInsetAdjustment: TrueSheetContentInsetAdjustment = TrueSheetContentInsetAdjustment.AUTOMATIC
|
|
26
47
|
)
|
|
27
48
|
|
|
28
49
|
/**
|
|
@@ -41,6 +62,21 @@ interface TrueSheetContentViewDelegate {
|
|
|
41
62
|
*/
|
|
42
63
|
val footerKeyboardOcclusion: Int
|
|
43
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Layout height of an absolute footer floating over the content, including
|
|
67
|
+
* its absorbed safe-area inset — 0 for a relative footer.
|
|
68
|
+
*/
|
|
69
|
+
val absoluteFooterHeight: Int
|
|
70
|
+
|
|
71
|
+
// Includes the controller's translation during sheet transitions.
|
|
72
|
+
val absoluteFooterTop: Int
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* false when an absolute footer stays behind the keyboard instead of rising
|
|
76
|
+
* above it.
|
|
77
|
+
*/
|
|
78
|
+
val footerAvoidsKeyboard: Boolean
|
|
79
|
+
|
|
44
80
|
/**
|
|
45
81
|
* A relative footer sits below the content and absorbs the bottom safe-area
|
|
46
82
|
* inset — the scrollable doesn't reach the sheet's bottom edge, so no
|
|
@@ -122,6 +158,13 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
122
158
|
private var baseBottomInset = 0
|
|
123
159
|
private var keyboardBottomInset = 0
|
|
124
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Height of the absolute footer the scroll content is padded by (see
|
|
163
|
+
* ScrollableOptions.contentInsetAdjustment), applied on top of the
|
|
164
|
+
* safe-area or keyboard inset.
|
|
165
|
+
*/
|
|
166
|
+
private var footerBottomInset = 0
|
|
167
|
+
|
|
125
168
|
private val scrollableLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
|
|
126
169
|
updateContentInset()
|
|
127
170
|
}
|
|
@@ -203,12 +246,12 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
203
246
|
/**
|
|
204
247
|
* Re-applies the base content inset — the bottom safe-area inset while the
|
|
205
248
|
* content can scroll, no relative footer absorbs it, and
|
|
206
|
-
* `
|
|
249
|
+
* `contentInsetAdjustment` includes the safe area.
|
|
207
250
|
*/
|
|
208
251
|
fun updateContentInset() {
|
|
209
252
|
val scrollView = detectedScrollView ?: return
|
|
210
253
|
|
|
211
|
-
val enabled = scrollableOptions?.contentInsetAdjustment ?: true
|
|
254
|
+
val enabled = scrollableOptions?.contentInsetAdjustment?.safeArea ?: true
|
|
212
255
|
baseBottomInset = if (enabled && delegate?.hasRelativeFooter != true && canScrollContent(scrollView)) {
|
|
213
256
|
bottomInset
|
|
214
257
|
} else {
|
|
@@ -226,7 +269,44 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
226
269
|
|
|
227
270
|
// The keyboard covers the safe-area region, so the larger inset wins over a sum
|
|
228
271
|
private fun applyBottomInset() {
|
|
229
|
-
|
|
272
|
+
val safeAreaInset = maxOf(baseBottomInset, keyboardBottomInset)
|
|
273
|
+
footerBottomInset = footerInsetAbove(safeAreaInset)
|
|
274
|
+
setScrollContentInset(safeAreaInset + footerBottomInset)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// A pinned footer uses its positioned top so overlap follows the sheet's
|
|
278
|
+
// animation, even before the container reaches its final height. A rising
|
|
279
|
+
// footer uses its layout top because the keyboard inset carries its rise.
|
|
280
|
+
// The safe-area inset baked into the footer is already
|
|
281
|
+
// covered by the safe-area or keyboard inset when one applies — only the
|
|
282
|
+
// footer's occlusion above it stacks on top. A pinned footer adds only the
|
|
283
|
+
// overlap not already covered by the safe-area or keyboard inset.
|
|
284
|
+
private fun footerInsetAbove(safeAreaInset: Int): Int {
|
|
285
|
+
if (scrollableOptions?.contentInsetAdjustment?.footer == false) return 0
|
|
286
|
+
val scrollView = detectedScrollView ?: return 0
|
|
287
|
+
val container = parent as? View ?: return 0
|
|
288
|
+
val footerHeight = delegate?.absoluteFooterHeight ?: 0
|
|
289
|
+
if (footerHeight <= 0) return 0
|
|
290
|
+
|
|
291
|
+
var scrollBottom = scrollView.bottom
|
|
292
|
+
var view: View = scrollView.parent as? View ?: return 0
|
|
293
|
+
while (view !== container) {
|
|
294
|
+
scrollBottom += view.top
|
|
295
|
+
view = view.parent as? View ?: return 0
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
val footerTop = if (delegate?.footerAvoidsKeyboard == false) {
|
|
299
|
+
delegate?.absoluteFooterTop ?: return 0
|
|
300
|
+
} else {
|
|
301
|
+
container.height - footerHeight
|
|
302
|
+
}
|
|
303
|
+
var inset = scrollBottom - footerTop
|
|
304
|
+
if (delegate?.footerAvoidsKeyboard == false) {
|
|
305
|
+
inset -= safeAreaInset
|
|
306
|
+
} else if (safeAreaInset > 0) {
|
|
307
|
+
inset -= footerHeight - maxOf(0, delegate?.footerKeyboardOcclusion ?: 0)
|
|
308
|
+
}
|
|
309
|
+
return maxOf(0, inset)
|
|
230
310
|
}
|
|
231
311
|
|
|
232
312
|
private fun setScrollContentInset(inset: Int) {
|
|
@@ -247,6 +327,7 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
247
327
|
detectedScrollView = null
|
|
248
328
|
baseBottomInset = 0
|
|
249
329
|
keyboardBottomInset = 0
|
|
330
|
+
footerBottomInset = 0
|
|
250
331
|
appliedKeyboardOffset = 0
|
|
251
332
|
}
|
|
252
333
|
|
|
@@ -344,6 +425,10 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
344
425
|
val baseInset = maxOf(0, location[1] + scrollView.height - keyboardTop)
|
|
345
426
|
val adjustedInset = maxOf(0, baseInset + keyboardOffset.toInt())
|
|
346
427
|
appliedKeyboardOffset = adjustedInset - baseInset
|
|
428
|
+
if (delegate?.footerAvoidsKeyboard == false) {
|
|
429
|
+
appliedKeyboardOffset += footerInsetAbove(maxOf(baseBottomInset, adjustedInset)) -
|
|
430
|
+
footerInsetAbove(maxOf(baseBottomInset, baseInset))
|
|
431
|
+
}
|
|
347
432
|
adjustedInset
|
|
348
433
|
} else {
|
|
349
434
|
appliedKeyboardOffset = 0
|
|
@@ -394,11 +479,18 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
394
479
|
}
|
|
395
480
|
|
|
396
481
|
// An absolute footer floats over the viewport's bottom edge — extend the
|
|
397
|
-
// caret target so it clears the footer, not just the keyboard.
|
|
398
|
-
|
|
482
|
+
// caret target so it clears the footer, not just the keyboard. Not needed
|
|
483
|
+
// while the footer inset is applied (the visible range already ends above
|
|
484
|
+
// it) or when the footer stays behind the keyboard.
|
|
485
|
+
val footerOcclusion = if (footerBottomInset > 0 || delegate?.footerAvoidsKeyboard == false) {
|
|
486
|
+
0
|
|
487
|
+
} else {
|
|
488
|
+
maxOf(0, delegate?.footerKeyboardOcclusion ?: 0)
|
|
489
|
+
}
|
|
399
490
|
|
|
400
491
|
val offset = keyboardScrollOffset.toInt()
|
|
401
|
-
val
|
|
492
|
+
val appliedInset = maxOf(baseBottomInset, keyboardBottomInset) + footerBottomInset
|
|
493
|
+
val visibleHeight = scrollView.height - scrollView.paddingBottom - appliedInset + appliedKeyboardOffset
|
|
402
494
|
val visibleTop = scrollView.scrollY
|
|
403
495
|
val visibleBottom = scrollView.scrollY + visibleHeight
|
|
404
496
|
|
|
@@ -22,6 +22,7 @@ import com.lodev09.truesheet.utils.TouchEventDeduper
|
|
|
22
22
|
*/
|
|
23
23
|
interface TrueSheetFooterViewDelegate {
|
|
24
24
|
fun footerViewDidChangeSize(width: Int, height: Int)
|
|
25
|
+
fun footerViewDidLayout()
|
|
25
26
|
val eventDispatcher: EventDispatcher?
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -97,6 +98,17 @@ class TrueSheetFooterView(private val reactContext: ThemedReactContext) :
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
override fun onLayout(
|
|
102
|
+
changed: Boolean,
|
|
103
|
+
left: Int,
|
|
104
|
+
top: Int,
|
|
105
|
+
right: Int,
|
|
106
|
+
bottom: Int
|
|
107
|
+
) {
|
|
108
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
109
|
+
delegate?.footerViewDidLayout()
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
// ==================== RootView Implementation ====================
|
|
101
113
|
|
|
102
114
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
@@ -224,15 +224,15 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
224
224
|
viewController.maxContentWidth = width
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
fun
|
|
228
|
-
val value =
|
|
229
|
-
if (viewController.
|
|
230
|
-
viewController.
|
|
227
|
+
fun setPlacement(placement: String?) {
|
|
228
|
+
val value = TrueSheetPlacement.fromString(placement)
|
|
229
|
+
if (viewController.placement == value) return
|
|
230
|
+
viewController.placement = value
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
fun
|
|
234
|
-
if (viewController.
|
|
235
|
-
viewController.
|
|
233
|
+
fun setPlacementOffset(offset: Int) {
|
|
234
|
+
if (viewController.placementOffset == offset) return
|
|
235
|
+
viewController.placementOffset = offset
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
fun setDimmed(dimmed: Boolean) {
|
|
@@ -323,6 +323,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
323
323
|
viewController.positionFooter()
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
fun setFooterAvoidsKeyboard(avoids: Boolean) {
|
|
327
|
+
viewController.footerAvoidsKeyboard = avoids
|
|
328
|
+
viewController.containerView?.footerAvoidsKeyboard = avoids
|
|
329
|
+
viewController.positionFooter()
|
|
330
|
+
}
|
|
331
|
+
|
|
326
332
|
private fun setupScrollable() {
|
|
327
333
|
viewController.containerView?.let {
|
|
328
334
|
it.scrollableOptions = viewController.scrollableOptions
|
|
@@ -330,6 +336,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
330
336
|
it.scrollableBottomInset = viewController.contentBottomInset
|
|
331
337
|
it.footerBottomInset = viewController.contentBottomInset
|
|
332
338
|
it.absoluteFooter = viewController.absoluteFooter
|
|
339
|
+
it.footerAvoidsKeyboard = viewController.footerAvoidsKeyboard
|
|
333
340
|
it.setupScrollable()
|
|
334
341
|
}
|
|
335
342
|
|
|
@@ -697,6 +704,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
697
704
|
viewController.positionFooter()
|
|
698
705
|
}
|
|
699
706
|
|
|
707
|
+
override fun containerViewFooterDidLayout() {
|
|
708
|
+
viewController.positionFooter()
|
|
709
|
+
}
|
|
710
|
+
|
|
700
711
|
override fun containerViewPeekDidChangeSize(width: Int, height: Int) {
|
|
701
712
|
// Peek content height affects peek detents
|
|
702
713
|
updateSheetIfNeeded()
|
|
@@ -80,17 +80,19 @@ interface TrueSheetViewControllerDelegate {
|
|
|
80
80
|
* enabling touch pass-through to underlying views. Handles detent configuration, drag interactions,
|
|
81
81
|
* keyboard avoidance, dimmed backgrounds, back button, and lifecycle events for stacked sheets.
|
|
82
82
|
*/
|
|
83
|
-
enum class
|
|
84
|
-
|
|
83
|
+
enum class TrueSheetPlacement {
|
|
84
|
+
AUTOMATIC,
|
|
85
|
+
LEADING,
|
|
85
86
|
CENTER,
|
|
86
|
-
|
|
87
|
+
TRAILING;
|
|
87
88
|
|
|
88
89
|
companion object {
|
|
89
|
-
fun fromString(value: String?):
|
|
90
|
+
fun fromString(value: String?): TrueSheetPlacement =
|
|
90
91
|
when (value) {
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
92
|
+
"leading" -> LEADING
|
|
93
|
+
"center" -> CENTER
|
|
94
|
+
"trailing" -> TRAILING
|
|
95
|
+
else -> AUTOMATIC
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
}
|
|
@@ -120,7 +122,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
120
122
|
companion object {
|
|
121
123
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
122
124
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
123
|
-
private const val
|
|
125
|
+
private const val DEFAULT_PLACEMENT_OFFSET = 16 // dp
|
|
124
126
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
125
127
|
private const val DISMISS_DURATION = 200L
|
|
126
128
|
private const val SCREEN_FADE_DURATION = 150L
|
|
@@ -221,8 +223,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
221
223
|
// Detent Configuration
|
|
222
224
|
override var maxContentHeight: Int? = null
|
|
223
225
|
override var maxContentWidth: Int? = null
|
|
224
|
-
override var
|
|
225
|
-
override var
|
|
226
|
+
override var placement: TrueSheetPlacement = TrueSheetPlacement.AUTOMATIC
|
|
227
|
+
override var placementOffset: Int = DEFAULT_PLACEMENT_OFFSET.dpToPx().toInt()
|
|
226
228
|
override var detents: MutableList<Double> = mutableListOf(0.5, 1.0)
|
|
227
229
|
|
|
228
230
|
// Appearance Configuration
|
|
@@ -330,6 +332,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
330
332
|
|
|
331
333
|
override var absoluteFooter: Boolean = false
|
|
332
334
|
|
|
335
|
+
override val footerInsetAdjustment: Boolean
|
|
336
|
+
get() = absoluteFooter && scrollableHandle > 0 && scrollableOptions?.contentInsetAdjustment?.footer != false
|
|
337
|
+
|
|
333
338
|
override val peekContentHeight: Int
|
|
334
339
|
get() = containerView?.peekContentHeight ?: cachedPeekContentHeight
|
|
335
340
|
|
|
@@ -496,9 +501,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
496
501
|
override fun coordinatorLayoutDidLayout(changed: Boolean) {
|
|
497
502
|
if (changed) invalidateScreenMetrics()
|
|
498
503
|
|
|
499
|
-
// Reposition footer when layout changes
|
|
500
504
|
if (isPresented && changed) {
|
|
501
|
-
|
|
505
|
+
setupSheetDetentsForSizeChange()
|
|
502
506
|
}
|
|
503
507
|
}
|
|
504
508
|
|
|
@@ -581,19 +585,19 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
581
585
|
delegate?.viewControllerDidAttemptDismiss()
|
|
582
586
|
}
|
|
583
587
|
} else {
|
|
584
|
-
|
|
588
|
+
resize(nextIndex)
|
|
585
589
|
}
|
|
586
590
|
}
|
|
587
591
|
|
|
588
592
|
override fun bottomSheetViewDidAccessibilityIncrement() {
|
|
589
593
|
if (currentDetentIndex < detents.size - 1) {
|
|
590
|
-
|
|
594
|
+
resize(currentDetentIndex + 1)
|
|
591
595
|
}
|
|
592
596
|
}
|
|
593
597
|
|
|
594
598
|
override fun bottomSheetViewDidAccessibilityDecrement() {
|
|
595
599
|
if (currentDetentIndex > 0) {
|
|
596
|
-
|
|
600
|
+
resize(currentDetentIndex - 1)
|
|
597
601
|
} else if (dismissible) {
|
|
598
602
|
dismiss(animated = true)
|
|
599
603
|
} else {
|
|
@@ -646,6 +650,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
646
650
|
|
|
647
651
|
val behavior = behavior ?: return
|
|
648
652
|
|
|
653
|
+
if (interactionState is InteractionState.Dragging || pendingDetentIndex >= 0) {
|
|
654
|
+
updateStateDimensionsForPosition(sheetView.top)
|
|
655
|
+
}
|
|
656
|
+
|
|
649
657
|
when (behavior.state) {
|
|
650
658
|
BottomSheetBehavior.STATE_DRAGGING -> handleDragChange(sheetView)
|
|
651
659
|
|
|
@@ -752,7 +760,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
752
760
|
// the detents (and the container) to it.
|
|
753
761
|
commitDetentAndDismissKeyboard(index, sheetView)
|
|
754
762
|
} else {
|
|
755
|
-
// Settled — resize the container to the settled detent (applies deferred shrinks)
|
|
756
763
|
updateStateDimensions()
|
|
757
764
|
}
|
|
758
765
|
|
|
@@ -880,7 +887,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
880
887
|
}
|
|
881
888
|
|
|
882
889
|
pendingDetentIndex = detentIndex
|
|
883
|
-
updateStateDimensions(deferShrink = true)
|
|
884
890
|
setupDimmedBackground()
|
|
885
891
|
setStateForDetentIndex(detentIndex)
|
|
886
892
|
resizePromise?.invoke()
|
|
@@ -1066,11 +1072,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1066
1072
|
}
|
|
1067
1073
|
|
|
1068
1074
|
fun setupSheetDetentsForSizeChange() {
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
|
|
1072
|
-
// change mid-drag (e.g. items removed from a list) isn't lost.
|
|
1073
|
-
if (interactionState is InteractionState.Dragging) {
|
|
1075
|
+
// Reconfiguring while the container tracks the sheet can interrupt its
|
|
1076
|
+
// motion. Apply content size changes once the drag or resize settles.
|
|
1077
|
+
if (interactionState is InteractionState.Dragging || isSettling) {
|
|
1074
1078
|
hasPendingSizeChange = true
|
|
1075
1079
|
return
|
|
1076
1080
|
}
|
|
@@ -1127,7 +1131,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1127
1131
|
|
|
1128
1132
|
fun updateDimAmount(sheetTop: Int? = null, animated: Boolean = false) {
|
|
1129
1133
|
if (!dimmed) return
|
|
1130
|
-
if (contentHeight == 0) return
|
|
1131
1134
|
|
|
1132
1135
|
// While keyboard is active or transitioning, use the target detent position for dim
|
|
1133
1136
|
val top = if (isKeyboardActive) {
|
|
@@ -1155,6 +1158,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1155
1158
|
|
|
1156
1159
|
var footerKeyboardOffset: Int = 0
|
|
1157
1160
|
|
|
1161
|
+
/**
|
|
1162
|
+
* false keeps an absolute footer pinned to the sheet's bottom edge behind
|
|
1163
|
+
* the keyboard instead of rising above it.
|
|
1164
|
+
*/
|
|
1165
|
+
override var footerAvoidsKeyboard: Boolean = true
|
|
1166
|
+
|
|
1158
1167
|
fun positionFooter() {
|
|
1159
1168
|
if (!isPresented) return
|
|
1160
1169
|
val footerView = containerView?.footerView ?: return
|
|
@@ -1171,7 +1180,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1171
1180
|
// The footer's absorbed safe-area inset rides below the keyboard's top
|
|
1172
1181
|
// edge — its content stays flush with the keyboard without dropping the
|
|
1173
1182
|
// inset padding from the layout, which would jump at the transition edges
|
|
1174
|
-
val keyboardShift = if (currentKeyboardInset > 0) {
|
|
1183
|
+
val keyboardShift = if (currentKeyboardInset > 0 && footerAvoidsKeyboard) {
|
|
1175
1184
|
maxOf(0, currentKeyboardInset + footerKeyboardOffset - footerView.bottomInset)
|
|
1176
1185
|
} else {
|
|
1177
1186
|
0
|
|
@@ -1189,6 +1198,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1189
1198
|
// Clamp to prevent footer going above safe area
|
|
1190
1199
|
val maxAllowedY = (sheetHeight - topInset - footerHeight).toFloat()
|
|
1191
1200
|
footerView.y = minOf(footerY, maxAllowedY)
|
|
1201
|
+
if (!footerAvoidsKeyboard) {
|
|
1202
|
+
containerView?.contentView?.updateContentInset()
|
|
1203
|
+
}
|
|
1192
1204
|
}
|
|
1193
1205
|
|
|
1194
1206
|
// =============================================================================
|
|
@@ -1253,11 +1265,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1253
1265
|
// Skip reconfigure during interactive keyboard dismiss (e.g. keyboardDismissMode="on-drag")
|
|
1254
1266
|
// to prevent the sheet from jumping. keyboardDidHide will reconfigure after.
|
|
1255
1267
|
if (restoring || isKeyboardDismissProgrammatic) {
|
|
1256
|
-
setupSheetDetents()
|
|
1257
1268
|
if (restoring) {
|
|
1258
1269
|
currentDetentIndex = detentIndexBeforeKeyboard
|
|
1259
|
-
setStateForDetentIndex(currentDetentIndex)
|
|
1260
1270
|
}
|
|
1271
|
+
setupSheetDetents()
|
|
1261
1272
|
}
|
|
1262
1273
|
|
|
1263
1274
|
// Same-frame reposition — the reconfigure above shrinks the container
|
|
@@ -1374,8 +1385,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1374
1385
|
private fun handleDragChange(sheetView: View) {
|
|
1375
1386
|
if (interactionState !is InteractionState.Dragging) return
|
|
1376
1387
|
|
|
1377
|
-
updateStateDimensionsForDrag(sheetView.top)
|
|
1378
|
-
|
|
1379
1388
|
val position = getPositionDpForView(sheetView)
|
|
1380
1389
|
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
1381
1390
|
delegate?.viewControllerDidDragChange(currentDetentIndex, position, detent)
|
|
@@ -1446,13 +1455,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1446
1455
|
/**
|
|
1447
1456
|
* Updates the Fabric state with the current/target detent size so Yoga sizes
|
|
1448
1457
|
* the container to the sheet's visible height.
|
|
1449
|
-
*
|
|
1450
|
-
* Unlike iOS, resize animations run over multiple frames (ViewDragHelper), so
|
|
1451
|
-
* shrinking is deferred to settle ([deferShrink]) when a resize animation is
|
|
1452
|
-
* about to run — growing applies immediately so content is laid out before
|
|
1453
|
-
* the sheet reveals it.
|
|
1454
1458
|
*/
|
|
1455
|
-
private fun updateStateDimensions(
|
|
1459
|
+
private fun updateStateDimensions() {
|
|
1456
1460
|
if (detents.isEmpty()) return
|
|
1457
1461
|
|
|
1458
1462
|
val targetIndex = (if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex)
|
|
@@ -1461,17 +1465,14 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1461
1465
|
val newHeight = minOf(detentCalculator.getDetentHeight(detents[targetIndex]), maxAvailableHeight)
|
|
1462
1466
|
val newWidth = getStateWidth()
|
|
1463
1467
|
|
|
1464
|
-
if (deferShrink && newWidth == lastStateWidth && newHeight < lastStateHeight) return
|
|
1465
|
-
|
|
1466
1468
|
setStateDimensions(newWidth, newHeight)
|
|
1467
1469
|
}
|
|
1468
1470
|
|
|
1469
1471
|
/**
|
|
1470
|
-
* Tracks the sheet's visible height during
|
|
1471
|
-
* animation) so the container resizes with the finger, like iOS. Clamped to the
|
|
1472
|
+
* Tracks the sheet's visible height during drags and resize animations. Clamped to the
|
|
1472
1473
|
* smallest detent — dragging below it slides the sheet out without resizing.
|
|
1473
1474
|
*/
|
|
1474
|
-
private fun
|
|
1475
|
+
private fun updateStateDimensionsForPosition(sheetTop: Int) {
|
|
1475
1476
|
if (detents.isEmpty()) return
|
|
1476
1477
|
|
|
1477
1478
|
val maxAvailableHeight = realScreenHeight - topInset
|
|
@@ -213,14 +213,14 @@ class TrueSheetViewManager :
|
|
|
213
213
|
view.setMaxContentWidth(if (width > 0) width.dpToPx().toInt() else null)
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
@ReactProp(name = "
|
|
217
|
-
override fun
|
|
218
|
-
view.
|
|
216
|
+
@ReactProp(name = "placement")
|
|
217
|
+
override fun setPlacement(view: TrueSheetView, placement: String?) {
|
|
218
|
+
view.setPlacement(placement)
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
@ReactProp(name = "
|
|
222
|
-
override fun
|
|
223
|
-
view.
|
|
221
|
+
@ReactProp(name = "placementOffset", defaultDouble = 0.0)
|
|
222
|
+
override fun setPlacementOffset(view: TrueSheetView, offset: Double) {
|
|
223
|
+
view.setPlacementOffset(if (offset > 0) offset.dpToPx().toInt() else 0)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
@ReactProp(name = "backgroundBlur")
|
|
@@ -233,6 +233,11 @@ class TrueSheetViewManager :
|
|
|
233
233
|
// iOS-specific prop - no-op on Android
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
@ReactProp(name = "glass", defaultBoolean = true)
|
|
237
|
+
override fun setGlass(view: TrueSheetView, glass: Boolean) {
|
|
238
|
+
// iOS-specific prop - no-op on Android
|
|
239
|
+
}
|
|
240
|
+
|
|
236
241
|
@ReactProp(name = "insetAdjustment")
|
|
237
242
|
override fun setInsetAdjustment(view: TrueSheetView, insetAdjustment: String?) {
|
|
238
243
|
view.setInsetAdjustment(insetAdjustment ?: "automatic")
|
|
@@ -264,8 +269,9 @@ class TrueSheetViewManager :
|
|
|
264
269
|
keyboardScrollOffset = if (options.hasKey("keyboardScrollOffset")) options.getDouble("keyboardScrollOffset").toFloat() else 0f,
|
|
265
270
|
keyboardOffset = if (options.hasKey("keyboardOffset")) options.getDouble("keyboardOffset").toFloat() else 0f,
|
|
266
271
|
scrollingExpandsSheet = if (options.hasKey("scrollingExpandsSheet")) options.getBoolean("scrollingExpandsSheet") else true,
|
|
267
|
-
contentInsetAdjustment =
|
|
268
|
-
options.
|
|
272
|
+
contentInsetAdjustment = TrueSheetContentInsetAdjustment.fromString(
|
|
273
|
+
if (options.hasKey("contentInsetAdjustment")) options.getString("contentInsetAdjustment") else null
|
|
274
|
+
)
|
|
269
275
|
)
|
|
270
276
|
view.setScrollableOptions(scrollableOptions)
|
|
271
277
|
}
|
|
@@ -284,6 +290,10 @@ class TrueSheetViewManager :
|
|
|
284
290
|
val keyboardOffset =
|
|
285
291
|
if (options != null && options.hasKey("keyboardOffset")) options.getDouble("keyboardOffset").toFloat() else 0f
|
|
286
292
|
view.setFooterKeyboardOffset(keyboardOffset)
|
|
293
|
+
|
|
294
|
+
val avoidKeyboard =
|
|
295
|
+
if (options != null && options.hasKey("avoidKeyboard")) options.getBoolean("avoidKeyboard") else true
|
|
296
|
+
view.setFooterAvoidsKeyboard(avoidKeyboard)
|
|
287
297
|
}
|
|
288
298
|
|
|
289
299
|
companion object {
|
|
@@ -18,7 +18,7 @@ import androidx.core.view.ViewCompat
|
|
|
18
18
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
19
19
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
20
20
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
21
|
-
import com.lodev09.truesheet.
|
|
21
|
+
import com.lodev09.truesheet.TrueSheetPlacement
|
|
22
22
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
23
23
|
|
|
24
24
|
interface TrueSheetBottomSheetViewDelegate {
|
|
@@ -27,8 +27,8 @@ interface TrueSheetBottomSheetViewDelegate {
|
|
|
27
27
|
val sheetElevation: Float
|
|
28
28
|
val sheetBackgroundColor: Int?
|
|
29
29
|
val maxContentWidth: Int?
|
|
30
|
-
val
|
|
31
|
-
val
|
|
30
|
+
val placement: TrueSheetPlacement
|
|
31
|
+
val placementOffset: Int
|
|
32
32
|
val grabber: Boolean
|
|
33
33
|
val grabberOptions: GrabberOptions?
|
|
34
34
|
val accessibilityOptions: AccessibilityOptions?
|
|
@@ -94,13 +94,19 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
|
|
|
94
94
|
// =============================================================================
|
|
95
95
|
|
|
96
96
|
private fun resolveAnchor(): Pair<Int, Int> {
|
|
97
|
-
val
|
|
98
|
-
val gravity = when (
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
val placement = if (ScreenUtils.isPortraitPhone(reactContext)) null else delegate?.placement
|
|
98
|
+
val gravity = when (placement) {
|
|
99
|
+
TrueSheetPlacement.LEADING -> Gravity.START
|
|
100
|
+
TrueSheetPlacement.TRAILING -> Gravity.END
|
|
101
101
|
else -> Gravity.CENTER_HORIZONTAL
|
|
102
102
|
} or Gravity.BOTTOM
|
|
103
|
-
val margin = if (
|
|
103
|
+
val margin = if (placement == TrueSheetPlacement.LEADING ||
|
|
104
|
+
placement == TrueSheetPlacement.TRAILING
|
|
105
|
+
) {
|
|
106
|
+
delegate?.placementOffset ?: 0
|
|
107
|
+
} else {
|
|
108
|
+
0
|
|
109
|
+
}
|
|
104
110
|
return Pair(gravity, margin)
|
|
105
111
|
}
|
|
106
112
|
|