@lodev09/react-native-true-sheet 4.0.0-beta.2 → 4.0.0-beta.21
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/README.md +25 -4
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +21 -110
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +116 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +124 -50
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +369 -163
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +59 -12
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +126 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
- package/ios/TrueSheetContainerView.h +27 -7
- package/ios/TrueSheetContainerView.mm +31 -11
- package/ios/TrueSheetContentView.h +43 -15
- package/ios/TrueSheetContentView.mm +211 -189
- package/ios/TrueSheetFooterView.h +6 -0
- package/ios/TrueSheetFooterView.mm +93 -17
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +7 -16
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetView.mm +275 -56
- package/ios/TrueSheetViewController.h +31 -3
- package/ios/TrueSheetViewController.mm +393 -94
- package/ios/core/TrueSheetBlurView.h +0 -2
- package/ios/core/TrueSheetBlurView.mm +3 -36
- package/ios/core/TrueSheetDetentCalculator.h +7 -1
- package/ios/core/TrueSheetDetentCalculator.mm +27 -4
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetLifecycleEvents.h +2 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +175 -39
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +220 -243
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetBackground.js +15 -0
- package/lib/module/TrueSheetBackground.js.map +1 -0
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +20 -15
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/detentBackgrounds.js +21 -0
- package/lib/module/detentBackgrounds.js.map +1 -0
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -10
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +18 -2
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/createTrueSheetRouter.js +0 -9
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/base-types.js +2 -0
- package/lib/module/navigation/expo-router/base-types.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +10 -7
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/module/navigation/expo-router/types.js +4 -0
- package/lib/module/navigation/expo-router/types.js.map +1 -0
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/web/constants.js +1 -1
- package/lib/module/web/constants.js.map +1 -1
- package/lib/module/web/layout.js +168 -0
- package/lib/module/web/layout.js.map +1 -0
- package/lib/module/web/vaul/constants.js +9 -0
- package/lib/module/web/vaul/constants.js.map +1 -1
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/helpers.js +1 -0
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +47 -7
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/module/web/vaul/use-snap-points.js +21 -10
- package/lib/module/web/vaul/use-snap-points.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +6 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +157 -34
- 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/TrueSheetBackground.d.ts +3 -0
- package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- 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/detentBackgrounds.d.ts +10 -0
- package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +12 -7
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/base-types.d.ts +9 -0
- package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +3 -4
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +41 -5
- 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/lib/typescript/src/web/layout.d.ts +19 -0
- package/lib/typescript/src/web/layout.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +13 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts +2 -1
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
- package/package.json +17 -8
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +208 -48
- package/src/TrueSheet.types.ts +166 -35
- package/src/TrueSheet.web.tsx +272 -290
- package/src/TrueSheetBackground.tsx +17 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +25 -23
- package/src/detentBackgrounds.ts +24 -0
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +15 -10
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +17 -2
- package/src/mocks/navigationExpoRouter.ts +6 -5
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +5 -33
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/index.ts +28 -30
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +6 -7
- package/src/navigation/screen/useSheetScreenState.ts +2 -0
- package/src/navigation/types.ts +75 -22
- package/src/web/constants.ts +1 -1
- package/src/web/layout.ts +196 -0
- package/src/web/vaul/constants.ts +9 -0
- package/src/web/vaul/context.ts +1 -0
- package/src/web/vaul/helpers.ts +5 -0
- package/src/web/vaul/index.tsx +75 -5
- package/src/web/vaul/use-snap-points.ts +32 -15
|
@@ -18,11 +18,27 @@ interface TrueSheetDetentCalculatorDelegate {
|
|
|
18
18
|
val absoluteHeader: Boolean
|
|
19
19
|
val footerHeight: Int
|
|
20
20
|
val absoluteFooter: Boolean
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True while the plugged scrollable is padded by the absolute footer's
|
|
24
|
+
* height (see ScrollableOptions.footerInsetAdjustment) — the footer then
|
|
25
|
+
* counts toward the auto detent like a relative one, so the content ends
|
|
26
|
+
* above it.
|
|
27
|
+
*/
|
|
28
|
+
val footerInsetAdjustment: Boolean
|
|
29
|
+
val footerAvoidsKeyboard: Boolean
|
|
21
30
|
val peekContentHeight: Int
|
|
22
31
|
val contentBottomInset: Int
|
|
23
32
|
val maxContentHeight: Int?
|
|
24
33
|
val keyboardInset: Int
|
|
25
34
|
val topInset: Int
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True while the keyboard holds the sheet up: the collapsed stop then sits at
|
|
38
|
+
* the keyboard-free first detent (the "floor") so a drag-down dismisses the
|
|
39
|
+
* keyboard before it can dismiss the sheet.
|
|
40
|
+
*/
|
|
41
|
+
val hasKeyboardFloor: Boolean
|
|
26
42
|
}
|
|
27
43
|
|
|
28
44
|
/**
|
|
@@ -44,6 +60,15 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
44
60
|
private val maxContentHeight: Int? get() = delegate?.maxContentHeight
|
|
45
61
|
private val keyboardInset: Int get() = delegate?.keyboardInset ?: 0
|
|
46
62
|
private val topInset: Int get() = delegate?.topInset ?: 0
|
|
63
|
+
private val hasKeyboardFloor: Boolean get() = delegate?.hasKeyboardFloor == true
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* An absolute footer floats over the content and contributes no height to
|
|
67
|
+
* the auto detent — unless it pads the scrollable, then the content ends
|
|
68
|
+
* above it like a relative footer.
|
|
69
|
+
*/
|
|
70
|
+
private val footerInAuto: Boolean
|
|
71
|
+
get() = delegate?.absoluteFooter == false || delegate?.footerInsetAdjustment == true
|
|
47
72
|
|
|
48
73
|
/**
|
|
49
74
|
* Height for auto (-1.0) detents: content + header + footer height.
|
|
@@ -52,14 +77,14 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
52
77
|
private val autoDetentHeight: Int
|
|
53
78
|
get() = contentHeight +
|
|
54
79
|
(if (delegate?.absoluteHeader == true) 0 else headerHeight) +
|
|
55
|
-
(if (
|
|
80
|
+
(if (footerInAuto) footerHeight else 0)
|
|
56
81
|
|
|
57
82
|
/**
|
|
58
|
-
* Bottom inset for auto detents. A
|
|
59
|
-
* edge, so it absorbs the inset instead of adding it
|
|
83
|
+
* Bottom inset for auto detents. A footer counted in the auto height owns
|
|
84
|
+
* the sheet's bottom edge, so it absorbs the inset instead of adding it.
|
|
60
85
|
*/
|
|
61
86
|
private val autoDetentBottomInset: Int
|
|
62
|
-
get() = if (footerHeight > 0 &&
|
|
87
|
+
get() = if (footerHeight > 0 && footerInAuto) 0 else contentBottomInset
|
|
63
88
|
|
|
64
89
|
/**
|
|
65
90
|
* Bottom inset for peek detents. An absolute footer counts toward the peek
|
|
@@ -97,11 +122,37 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
97
122
|
(detent * screenHeight).toInt() + contentBottomInset
|
|
98
123
|
}
|
|
99
124
|
|
|
100
|
-
val
|
|
125
|
+
val keyboardHeight = if (includeKeyboard) keyboardInset else 0
|
|
126
|
+
val footerKeyboardOverlap = if (detent == -1.0 &&
|
|
127
|
+
delegate?.absoluteFooter == true &&
|
|
128
|
+
footerInAuto &&
|
|
129
|
+
delegate?.footerAvoidsKeyboard == false
|
|
130
|
+
) {
|
|
131
|
+
minOf(keyboardHeight, maxOf(0, footerHeight - contentBottomInset))
|
|
132
|
+
} else {
|
|
133
|
+
0
|
|
134
|
+
}
|
|
135
|
+
val height = baseHeight + keyboardHeight - footerKeyboardOverlap
|
|
101
136
|
val maxAllowedHeight = screenHeight + contentBottomInset
|
|
102
137
|
return maxContentHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
|
|
103
138
|
}
|
|
104
139
|
|
|
140
|
+
fun getBackgroundIndex(sheetTop: Int, displayedIndex: Int): Int {
|
|
141
|
+
if (detents.isEmpty()) return -1
|
|
142
|
+
|
|
143
|
+
val height = getVisibleSheetHeight(sheetTop)
|
|
144
|
+
var nextIndex = displayedIndex.coerceIn(0, detents.size - 1)
|
|
145
|
+
var distance = kotlin.math.abs(height - getVisibleSheetHeight(getSheetTopForDetentIndex(nextIndex)))
|
|
146
|
+
for (index in detents.indices) {
|
|
147
|
+
val nextDistance = kotlin.math.abs(height - getVisibleSheetHeight(getSheetTopForDetentIndex(index)))
|
|
148
|
+
if (nextDistance < distance) {
|
|
149
|
+
nextIndex = index
|
|
150
|
+
distance = nextDistance
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return nextIndex
|
|
154
|
+
}
|
|
155
|
+
|
|
105
156
|
/**
|
|
106
157
|
* Get the expected sheet top position for a detent index.
|
|
107
158
|
*/
|
|
@@ -110,12 +161,31 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
110
161
|
RNLog.w(reactContext, "TrueSheet: Detent index ($index) is out of bounds (0..${detents.size - 1})")
|
|
111
162
|
return realScreenHeight
|
|
112
163
|
}
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
// With a keyboard floor only the last detent stays keyboard-shifted — it's
|
|
165
|
+
// the keyboard-expanded resting position; the stops below it are keyboard-free
|
|
166
|
+
val isKeyboardFreeStop = hasKeyboardFloor && index != detents.size - 1
|
|
167
|
+
return getSheetTop(detents[index], includeKeyboard = !isKeyboardFreeStop)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Top of the lowest stop the sheet can rest at: the keyboard floor while it's
|
|
172
|
+
* active, else the first detent.
|
|
173
|
+
*/
|
|
174
|
+
fun getLowestSheetTop(): Int = if (hasKeyboardFloor) getSheetTop(detents[0], includeKeyboard = false) else getSheetTopForDetentIndex(0)
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Top of the half stop while the keyboard floor is active: the keyboard-free
|
|
178
|
+
* second detent, or the floor itself for a single detent.
|
|
179
|
+
*/
|
|
180
|
+
fun getKeyboardHalfStopTop(): Int = getSheetTop(detents[minOf(1, detents.size - 1)], includeKeyboard = false)
|
|
181
|
+
|
|
182
|
+
// Clamp to the space the sheet can actually occupy — matching
|
|
183
|
+
// setupSheetDetents. A keyboard-inflated detent height can exceed it,
|
|
184
|
+
// placing the expected top above the screen while the real sheet stops
|
|
185
|
+
// at the top inset.
|
|
186
|
+
private fun getSheetTop(detent: Double, includeKeyboard: Boolean = true): Int {
|
|
117
187
|
val maxAvailableHeight = realScreenHeight - topInset
|
|
118
|
-
return realScreenHeight - minOf(getDetentHeight(
|
|
188
|
+
return realScreenHeight - minOf(getDetentHeight(detent, includeKeyboard), maxAvailableHeight)
|
|
119
189
|
}
|
|
120
190
|
|
|
121
191
|
/**
|
|
@@ -152,6 +222,9 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
152
222
|
*/
|
|
153
223
|
fun getStateForDetentIndex(index: Int): Int {
|
|
154
224
|
val stateMap = getDetentStateMap() ?: return BottomSheetBehavior.STATE_HIDDEN
|
|
225
|
+
// With a keyboard floor the keyboard-expanded last detent lives at the
|
|
226
|
+
// expanded stop — the half stop (which may share its index) is keyboard-free
|
|
227
|
+
if (hasKeyboardFloor && index == detents.size - 1) return BottomSheetBehavior.STATE_EXPANDED
|
|
155
228
|
return stateMap.entries.find { it.value == index }?.key ?: BottomSheetBehavior.STATE_HIDDEN
|
|
156
229
|
}
|
|
157
230
|
|
|
@@ -169,8 +242,12 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
169
242
|
*/
|
|
170
243
|
private fun getDetentStateMap(): Map<Int, Int>? =
|
|
171
244
|
when (detents.size) {
|
|
245
|
+
// With one detent halfExpandedRatio matches the peek height, so the
|
|
246
|
+
// behavior can settle HALF_EXPANDED at the same position (e.g. an upward
|
|
247
|
+
// drag release) — map it too, or the settle is unmappable
|
|
172
248
|
1 -> mapOf(
|
|
173
249
|
BottomSheetBehavior.STATE_COLLAPSED to 0,
|
|
250
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED to 0,
|
|
174
251
|
BottomSheetBehavior.STATE_EXPANDED to 0
|
|
175
252
|
)
|
|
176
253
|
|
|
@@ -197,16 +274,18 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
197
274
|
* Find which segment the position falls into for interpolation.
|
|
198
275
|
* @return Triple(fromIndex, toIndex, progress) where progress is 0-1, or null if no detents
|
|
199
276
|
*/
|
|
200
|
-
fun findSegmentForPosition(positionPx: Int): Triple<Int, Int, Float>? {
|
|
277
|
+
private fun findSegmentForPosition(positionPx: Int): Triple<Int, Int, Float>? {
|
|
201
278
|
val count = detents.size
|
|
202
279
|
if (count == 0) return null
|
|
203
280
|
|
|
204
|
-
|
|
281
|
+
// A single keyboard-shifted detent can rest down at its keyboard floor —
|
|
282
|
+
// that range is still index 0, not a dismissal
|
|
283
|
+
val floorPos = getLowestSheetTop()
|
|
205
284
|
|
|
206
|
-
// Position is below
|
|
207
|
-
if (positionPx >
|
|
208
|
-
val range = realScreenHeight -
|
|
209
|
-
val progress = if (range > 0) (positionPx -
|
|
285
|
+
// Position is below the lowest stop (sheet is being dragged down to dismiss)
|
|
286
|
+
if (positionPx > floorPos) {
|
|
287
|
+
val range = realScreenHeight - floorPos
|
|
288
|
+
val progress = if (range > 0) (positionPx - floorPos).toFloat() / range else 0f
|
|
210
289
|
return Triple(-1, 0, progress)
|
|
211
290
|
}
|
|
212
291
|
|
|
@@ -238,37 +317,23 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
|
238
317
|
}
|
|
239
318
|
|
|
240
319
|
/**
|
|
241
|
-
* Returns continuous index (e.g., 0.5 = halfway between detent
|
|
320
|
+
* Returns interpolated continuous index (e.g., 0.5 = halfway between detent
|
|
321
|
+
* 0 and 1) and screen fraction for a position, sharing a single segment
|
|
322
|
+
* lookup — this runs on every position frame.
|
|
242
323
|
*/
|
|
243
|
-
fun
|
|
244
|
-
|
|
245
|
-
if (count == 0) return -1f
|
|
246
|
-
|
|
247
|
-
val segment = findSegmentForPosition(positionPx) ?: return 0f
|
|
248
|
-
val (fromIndex, _, progress) = segment
|
|
249
|
-
|
|
250
|
-
if (fromIndex == -1) return -progress
|
|
251
|
-
return fromIndex + progress
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Returns interpolated screen fraction for position.
|
|
256
|
-
*/
|
|
257
|
-
fun getInterpolatedDetentForPosition(positionPx: Int): Float {
|
|
258
|
-
val count = detents.size
|
|
259
|
-
if (count == 0) return 0f
|
|
324
|
+
fun getInterpolatedPositionInfo(positionPx: Int): Pair<Float, Float> {
|
|
325
|
+
if (detents.isEmpty()) return Pair(-1f, 0f)
|
|
260
326
|
|
|
261
|
-
val
|
|
262
|
-
|
|
327
|
+
val (fromIndex, toIndex, progress) = findSegmentForPosition(positionPx)
|
|
328
|
+
?: return Pair(0f, getDetentValueForIndex(0))
|
|
263
329
|
|
|
264
330
|
if (fromIndex == -1) {
|
|
265
|
-
|
|
266
|
-
return maxOf(0f, firstDetent * (1 - progress))
|
|
331
|
+
return Pair(-progress, maxOf(0f, getDetentValueForIndex(0) * (1 - progress)))
|
|
267
332
|
}
|
|
268
333
|
|
|
269
334
|
val fromDetent = getDetentValueForIndex(fromIndex)
|
|
270
335
|
val toDetent = getDetentValueForIndex(toIndex)
|
|
271
|
-
return fromDetent + progress * (toDetent - fromDetent)
|
|
336
|
+
return Pair(fromIndex + progress, fromDetent + progress * (toDetent - fromDetent))
|
|
272
337
|
}
|
|
273
338
|
|
|
274
339
|
companion object {
|
|
@@ -139,6 +139,9 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
|
139
139
|
|
|
140
140
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
141
141
|
if (blockGestures) {
|
|
142
|
+
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
143
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
144
|
+
}
|
|
142
145
|
// When dimmed, consume touch and trigger click on ACTION_UP
|
|
143
146
|
if (event.action == MotionEvent.ACTION_UP) {
|
|
144
147
|
performClick()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.view.MotionEvent
|
|
5
|
+
import android.view.View
|
|
6
|
+
import android.widget.FrameLayout
|
|
7
|
+
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
8
|
+
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
9
|
+
import com.facebook.react.uimanager.RootView
|
|
10
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
11
|
+
import com.facebook.react.uimanager.TouchTargetHelper
|
|
12
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
13
|
+
import com.lodev09.truesheet.utils.TouchEventDeduper
|
|
14
|
+
|
|
15
|
+
interface TrueSheetOverlayRootViewDelegate {
|
|
16
|
+
val eventDispatcher: EventDispatcher?
|
|
17
|
+
fun overlayRootViewDidChangeSize(width: Int, height: Int)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Window-level view hosting an overlay's children, attached to the activity's
|
|
22
|
+
* content view above the sheet coordinators.
|
|
23
|
+
*
|
|
24
|
+
* Implements RootView to dispatch touches to JS. Touches that miss every child
|
|
25
|
+
* fall through to the views beneath.
|
|
26
|
+
*/
|
|
27
|
+
@SuppressLint("ViewConstructor")
|
|
28
|
+
class TrueSheetOverlayRootView(private val reactContext: ThemedReactContext) :
|
|
29
|
+
FrameLayout(reactContext),
|
|
30
|
+
RootView {
|
|
31
|
+
|
|
32
|
+
var delegate: TrueSheetOverlayRootViewDelegate? = null
|
|
33
|
+
|
|
34
|
+
private val eventDispatcher: EventDispatcher?
|
|
35
|
+
get() = delegate?.eventDispatcher
|
|
36
|
+
|
|
37
|
+
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
38
|
+
private val jsPointerDispatcher = JSPointerDispatcher(this)
|
|
39
|
+
private val touchDeduper = TouchEventDeduper()
|
|
40
|
+
|
|
41
|
+
// Whether the current touch stream landed on a child
|
|
42
|
+
private var isHandlingStream = false
|
|
43
|
+
|
|
44
|
+
init {
|
|
45
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
46
|
+
|
|
47
|
+
// Draws and receives touches above the sheet coordinators regardless of add order
|
|
48
|
+
translationZ = OVERLAY_Z
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Children are positioned by Fabric — a FrameLayout pass would stretch them to match_parent
|
|
52
|
+
override fun onLayout(
|
|
53
|
+
changed: Boolean,
|
|
54
|
+
left: Int,
|
|
55
|
+
top: Int,
|
|
56
|
+
right: Int,
|
|
57
|
+
bottom: Int
|
|
58
|
+
) {
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
62
|
+
super.onSizeChanged(w, h, oldw, oldh)
|
|
63
|
+
delegate?.overlayRootViewDidChangeSize(w, h)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
67
|
+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
68
|
+
// The path ends with this root, so a touch that misses every child resolves to it
|
|
69
|
+
val target = TouchTargetHelper
|
|
70
|
+
.findTargetPathAndCoordinatesForTouch(ev.x, ev.y, this, FloatArray(2))
|
|
71
|
+
.firstNotNullOfOrNull { it.getView() }
|
|
72
|
+
isHandlingStream = target != null && target !== this
|
|
73
|
+
}
|
|
74
|
+
if (!isHandlingStream) return false
|
|
75
|
+
return super.dispatchTouchEvent(ev)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ==================== RootView Implementation ====================
|
|
79
|
+
|
|
80
|
+
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
81
|
+
eventDispatcher?.let { dispatcher ->
|
|
82
|
+
if (touchDeduper.shouldDispatch(event)) {
|
|
83
|
+
jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
84
|
+
}
|
|
85
|
+
jsPointerDispatcher.handleMotionEvent(event, dispatcher, true)
|
|
86
|
+
}
|
|
87
|
+
return super.onInterceptTouchEvent(event)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
|
91
|
+
// Mirrors ReactSurfaceView: keep receiving onInterceptTouchEvent so
|
|
92
|
+
// jsTouchDispatcher sees the gesture end, but forward the request up the tree.
|
|
93
|
+
parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
97
|
+
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
98
|
+
eventDispatcher?.let { dispatcher ->
|
|
99
|
+
if (touchDeduper.shouldDispatch(event)) {
|
|
100
|
+
jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
101
|
+
}
|
|
102
|
+
jsPointerDispatcher.handleMotionEvent(event, dispatcher, false)
|
|
103
|
+
}
|
|
104
|
+
super.onTouchEvent(event)
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
|
|
109
|
+
eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, true) }
|
|
110
|
+
return super.onHoverEvent(event)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
override fun onHoverEvent(event: MotionEvent): Boolean {
|
|
114
|
+
eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, false) }
|
|
115
|
+
return super.onHoverEvent(event)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
|
119
|
+
eventDispatcher?.let { dispatcher ->
|
|
120
|
+
jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher)
|
|
121
|
+
jsPointerDispatcher.onChildStartedNativeGesture(childView, ev, dispatcher)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
|
|
126
|
+
eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
|
|
127
|
+
jsPointerDispatcher.onChildEndedNativeGesture()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
override fun handleException(t: Throwable) {
|
|
131
|
+
reactContext.reactApplicationContext.handleException(RuntimeException(t))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
companion object {
|
|
135
|
+
private const val OVERLAY_Z = 1000f
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -93,6 +93,21 @@ class DidDismissEvent(surfaceId: Int, viewId: Int) : Event<DidDismissEvent>(surf
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Fired when the user tries to dismiss a non-dismissible sheet
|
|
98
|
+
*/
|
|
99
|
+
class DismissAttemptEvent(surfaceId: Int, viewId: Int) : Event<DismissAttemptEvent>(surfaceId, viewId) {
|
|
100
|
+
|
|
101
|
+
override fun getEventName(): String = EVENT_NAME
|
|
102
|
+
|
|
103
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
104
|
+
|
|
105
|
+
companion object {
|
|
106
|
+
const val EVENT_NAME = "topDismissAttempt"
|
|
107
|
+
const val REGISTRATION_NAME = "onDismissAttempt"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
/**
|
|
97
112
|
* Fired when the sheet visibility changes due to screen transitions
|
|
98
113
|
*/
|
|
@@ -3,6 +3,7 @@ package com.lodev09.truesheet.utils
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.os.Build
|
|
5
5
|
import android.view.View
|
|
6
|
+
import android.view.WindowInsets
|
|
6
7
|
import android.view.inputmethod.InputMethodManager
|
|
7
8
|
import androidx.core.view.ViewCompat
|
|
8
9
|
import androidx.core.view.WindowInsetsAnimationCompat
|
|
@@ -19,7 +20,7 @@ object KeyboardUtils {
|
|
|
19
20
|
return isKeyboardVisible(rootView)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
fun isKeyboardVisible(view: View): Boolean {
|
|
23
24
|
val insets = ViewCompat.getRootWindowInsets(view) ?: return false
|
|
24
25
|
return insets.isVisible(WindowInsetsCompat.Type.ime())
|
|
25
26
|
}
|
|
@@ -36,10 +37,22 @@ object KeyboardUtils {
|
|
|
36
37
|
* Dismisses the soft keyboard with an optional callback when the animation completes.
|
|
37
38
|
*/
|
|
38
39
|
fun dismiss(view: View, onComplete: (() -> Unit)?) {
|
|
40
|
+
if (!isKeyboardVisible(view)) {
|
|
41
|
+
onComplete?.invoke()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
40
46
|
val focusedView = view.rootView.findFocus()
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
// A keyboard can stay up with nothing focused, e.g. a blur that lands after focus was restored,
|
|
49
|
+
// so hide it through the window instead of treating it as already hidden.
|
|
50
|
+
if (focusedView == null) {
|
|
51
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
52
|
+
view.windowInsetsController?.hide(WindowInsets.Type.ime())
|
|
53
|
+
} else {
|
|
54
|
+
imm?.hideSoftInputFromWindow(view.windowToken, 0)
|
|
55
|
+
}
|
|
43
56
|
onComplete?.invoke()
|
|
44
57
|
return
|
|
45
58
|
}
|
|
@@ -104,18 +104,6 @@ object ScreenUtils {
|
|
|
104
104
|
*/
|
|
105
105
|
fun getScreenHeight(reactContext: ReactContext): Int = reactContext.resources.displayMetrics.heightPixels
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* Get the location of a view in screen coordinates
|
|
109
|
-
*
|
|
110
|
-
* @param view The view to get screen location for
|
|
111
|
-
* @return IntArray with [x, y] coordinates in screen space
|
|
112
|
-
*/
|
|
113
|
-
fun getScreenLocation(view: View): IntArray {
|
|
114
|
-
val location = IntArray(2)
|
|
115
|
-
view.getLocationOnScreen(location)
|
|
116
|
-
return location
|
|
117
|
-
}
|
|
118
|
-
|
|
119
107
|
/**
|
|
120
108
|
* Returns true if the device is a phone in portrait orientation.
|
|
121
109
|
*/
|
|
@@ -2,8 +2,10 @@ package com.lodev09.truesheet.utils
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import android.view.ViewGroup
|
|
5
|
+
import android.view.ViewParent
|
|
5
6
|
import android.widget.ScrollView
|
|
6
7
|
import androidx.core.widget.NestedScrollView
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
9
|
|
|
8
10
|
fun View.isDescendantOf(ancestor: View): Boolean {
|
|
9
11
|
if (!isAttachedToWindow) return false
|
|
@@ -15,16 +17,26 @@ fun View.isDescendantOf(ancestor: View): Boolean {
|
|
|
15
17
|
return false
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
fun ViewGroup.smoothScrollBy(dx: Int, dy: Int) {
|
|
19
|
-
when (this) {
|
|
20
|
-
is ScrollView -> smoothScrollBy(dx, dy)
|
|
21
|
-
is NestedScrollView -> smoothScrollBy(dx, dy)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
fun ViewGroup.smoothScrollTo(x: Int, y: Int) {
|
|
26
21
|
when (this) {
|
|
27
22
|
is ScrollView -> smoothScrollTo(x, y)
|
|
28
23
|
is NestedScrollView -> smoothScrollTo(x, y)
|
|
29
24
|
}
|
|
30
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The content view hosting this view — the activity's, or a Modal dialog's.
|
|
29
|
+
* Window-level views (sheet coordinators, overlays) attach here.
|
|
30
|
+
*/
|
|
31
|
+
fun View.findRootContainerView(reactContext: ThemedReactContext): ViewGroup? {
|
|
32
|
+
var current: ViewParent? = parent
|
|
33
|
+
|
|
34
|
+
while (current != null) {
|
|
35
|
+
if (current is ViewGroup && current.id == android.R.id.content) {
|
|
36
|
+
return current
|
|
37
|
+
}
|
|
38
|
+
current = current.parent
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return reactContext.currentActivity?.findViewById(android.R.id.content)
|
|
42
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <jsi/jsi.h>
|
|
6
6
|
#include <react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h>
|
|
7
7
|
#include <react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h>
|
|
8
|
+
#include <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h>
|
|
8
9
|
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
|
|
9
10
|
|
|
10
11
|
namespace facebook {
|
package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h
CHANGED
|
@@ -11,14 +11,6 @@ namespace facebook::react {
|
|
|
11
11
|
class TrueSheetContentViewComponentDescriptor final
|
|
12
12
|
: public ConcreteComponentDescriptor<TrueSheetContentViewShadowNode> {
|
|
13
13
|
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
|
14
|
-
|
|
15
|
-
void adopt(ShadowNode &shadowNode) const override {
|
|
16
|
-
auto &concreteShadowNode =
|
|
17
|
-
static_cast<TrueSheetContentViewShadowNode &>(shadowNode);
|
|
18
|
-
concreteShadowNode.adjustLayoutWithState();
|
|
19
|
-
|
|
20
|
-
ConcreteComponentDescriptor::adopt(shadowNode);
|
|
21
|
-
}
|
|
22
14
|
};
|
|
23
15
|
|
|
24
|
-
}
|
|
16
|
+
} // namespace facebook::react
|